Support for text-based savefiles
This commit is contained in:
parent
1f3124d20c
commit
e1dad71b8e
|
@ -54,6 +54,7 @@ build/*
|
||||||
*.mio0
|
*.mio0
|
||||||
*.z64
|
*.z64
|
||||||
*.map
|
*.map
|
||||||
|
*.sav
|
||||||
.assets-local.txt
|
.assets-local.txt
|
||||||
sm64_save_file.bin
|
sm64_save_file.bin
|
||||||
sm64config.txt
|
sm64config.txt
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include <ultra64.h>
|
#include <ultra64.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include "sm64.h"
|
#include "sm64.h"
|
||||||
#include "game_init.h"
|
#include "game_init.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
@ -11,6 +11,7 @@
|
||||||
#include "level_table.h"
|
#include "level_table.h"
|
||||||
#include "course_table.h"
|
#include "course_table.h"
|
||||||
#include "thread6.h"
|
#include "thread6.h"
|
||||||
|
#include "pc/ini.h"
|
||||||
|
|
||||||
#define MENU_DATA_MAGIC 0x4849
|
#define MENU_DATA_MAGIC 0x4849
|
||||||
#define SAVE_FILE_MAGIC 0x4441
|
#define SAVE_FILE_MAGIC 0x4441
|
||||||
|
@ -50,6 +51,69 @@ s8 gLevelToCourseNumTable[] = {
|
||||||
STATIC_ASSERT(ARRAY_COUNT(gLevelToCourseNumTable) == LEVEL_COUNT - 1,
|
STATIC_ASSERT(ARRAY_COUNT(gLevelToCourseNumTable) == LEVEL_COUNT - 1,
|
||||||
"change this array if you are adding levels");
|
"change this array if you are adding levels");
|
||||||
|
|
||||||
|
/* Flag key */
|
||||||
|
const char *sav_flags[NUM_FLAGS] = {
|
||||||
|
"file_exists", "wing_cap", "metal_cap", "vanish_cap", "key_1", "key_2",
|
||||||
|
"basement_door", "upstairs_door", "ddd_moved_back", "moat_drained",
|
||||||
|
"pps_door", "wf_door", "ccm_door", "jrb_door", "bitdw_door",
|
||||||
|
"bitfs_door", "", "", "", "", "50star_door"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Main course keys */
|
||||||
|
const char *sav_courses[NUM_COURSES] = {
|
||||||
|
"bob", "wf", "jrb", "ccm", "bbh", "hmc", "lll",
|
||||||
|
"ssl", "ddd", "sl", "wdw", "ttm", "thi", "ttc", "rr"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Bonus courses keys (including Castle Course) */
|
||||||
|
const char *sav_bonus_courses[NUM_BONUS_COURSES] = {
|
||||||
|
"hub", "bitdw", "bitfs", "bits", "pss", "cotmc",
|
||||||
|
"totwc", "vcutm", "wmotr", "sa",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Mario's cap type keys */
|
||||||
|
const char *cap_on_types[NUM_CAP_ON] = {
|
||||||
|
"ground", "klepto", "ukiki", "mrblizzard"
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Get current timestamp */
|
||||||
|
static void get_timestamp(char* buffer)
|
||||||
|
{
|
||||||
|
time_t timer;
|
||||||
|
struct tm* tm_info;
|
||||||
|
|
||||||
|
timer = time(NULL);
|
||||||
|
tm_info = localtime(&timer);
|
||||||
|
|
||||||
|
strftime(buffer, 26, "%Y-%m-%d %H:%M:%S", tm_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert 'binary' integer to decimal integer */
|
||||||
|
static u32 bin_to_int(u32 n)
|
||||||
|
{
|
||||||
|
s32 dec = 0, i = 0, rem;
|
||||||
|
while (n != 0) {
|
||||||
|
rem = n % 10;
|
||||||
|
n /= 10;
|
||||||
|
dec += rem * (1 << i);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
return dec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert decimal integer to 'binary' integer */
|
||||||
|
static u32 int_to_bin(u32 n)
|
||||||
|
{
|
||||||
|
s32 bin = 0, rem, i = 1;
|
||||||
|
while (n != 0) {
|
||||||
|
rem = n % 2;
|
||||||
|
n /= 2;
|
||||||
|
bin += rem * i;
|
||||||
|
i *= 10;
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
// This was probably used to set progress to 100% for debugging, but
|
// This was probably used to set progress to 100% for debugging, but
|
||||||
// it was removed from the release ROM.
|
// it was removed from the release ROM.
|
||||||
static void stub_save_file_1(void) {
|
static void stub_save_file_1(void) {
|
||||||
|
@ -178,6 +242,208 @@ static inline s32 write_eeprom_menudata(const u32 slot, const u32 num) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write SaveFile and MainMenuSaveData structs to a text-based savefile.
|
||||||
|
*/
|
||||||
|
static s32 write_text_save(s32 fileIndex)
|
||||||
|
{
|
||||||
|
FILE* file;
|
||||||
|
char *filename, *value;
|
||||||
|
struct SaveFile *savedata;
|
||||||
|
struct MainMenuSaveData *menudata;
|
||||||
|
|
||||||
|
u32 i, flags, coins, stars, starFlags;
|
||||||
|
|
||||||
|
/* Define savefile's name */
|
||||||
|
filename = (char*)malloc(14 * sizeof(char));
|
||||||
|
if (sprintf(filename, FILENAME_FORMAT, fileIndex) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = fopen(filename, "wt");
|
||||||
|
if (file == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write header */
|
||||||
|
fprintf(file, "# Super Mario 64 save file\n");
|
||||||
|
fprintf(file, "# Comment starts with #\n");
|
||||||
|
fprintf(file, "# True = 1, False = 0\n");
|
||||||
|
|
||||||
|
/* Write current timestamp */
|
||||||
|
value = (char*)malloc(26 * sizeof(char));
|
||||||
|
get_timestamp(value);
|
||||||
|
fprintf(file, "# %s\n", value);
|
||||||
|
|
||||||
|
/* Write MainMenuSaveData info */
|
||||||
|
menudata = &gSaveBuffer.menuData[0];
|
||||||
|
fprintf(file, "\n[menu]\n");
|
||||||
|
fprintf(file, "coin_score_age = %d\n", menudata->coinScoreAges[fileIndex]);
|
||||||
|
fprintf(file, "sound_mode = %u\n", menudata->soundMode);
|
||||||
|
|
||||||
|
/* Write all flags */
|
||||||
|
fprintf(file, "\n[flags]\n");
|
||||||
|
for (i = 1; i < NUM_FLAGS; i++) {
|
||||||
|
if (strcmp(sav_flags[i], "")) {
|
||||||
|
flags = save_file_get_flags();
|
||||||
|
flags = (flags & (1 << i)); /* Get a specific bit */
|
||||||
|
flags = (flags) ? 1 : 0; /* Determine if bit is set or not */
|
||||||
|
|
||||||
|
fprintf(file, "%s = %d\n", sav_flags[i], flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write coin count and star flags from each course (except Castle Grounds) */
|
||||||
|
fprintf(file, "\n[courses]\n");
|
||||||
|
for (i = 0; i < NUM_COURSES; i++) {
|
||||||
|
stars = save_file_get_star_flags(fileIndex, i);
|
||||||
|
coins = save_file_get_course_coin_score(fileIndex, i);
|
||||||
|
starFlags = int_to_bin(stars); /* 63 -> 111111 */
|
||||||
|
|
||||||
|
fprintf(file, "%s = \"%d, %07d\"\n", sav_courses[i], coins, starFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write star flags from each bonus cource (including Castle Grounds) */
|
||||||
|
fprintf(file, "\n[bonus]\n");
|
||||||
|
for (i = 0; i < NUM_BONUS_COURSES; i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
stars = save_file_get_star_flags(fileIndex, -1);
|
||||||
|
} else {
|
||||||
|
stars = save_file_get_star_flags(fileIndex, i+15);
|
||||||
|
}
|
||||||
|
starFlags = int_to_bin(stars);
|
||||||
|
|
||||||
|
fprintf(file, "%s = %d\n", sav_bonus_courses[i], starFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write who steal Mario's cap */
|
||||||
|
fprintf(file, "\n[cap]\n");
|
||||||
|
for (i = 0; i < NUM_CAP_ON; i++) {
|
||||||
|
flags = save_file_get_flags(); // Read all flags
|
||||||
|
flags = (flags & (1 << (i+16))); // Get `cap` flags
|
||||||
|
flags = (flags) ? 1 : 0; // Determine if bit is set or not
|
||||||
|
if (flags) {
|
||||||
|
fprintf(file, "type = %s\n", cap_on_types[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write in what course and area Mario losted its cap, and cap's position */
|
||||||
|
savedata = &gSaveBuffer.files[fileIndex][0];
|
||||||
|
fprintf(file, "level = %d\n", savedata->capLevel);
|
||||||
|
fprintf(file, "area = %d\n", savedata->capArea);
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read gSaveBuffer data from a text-based savefile.
|
||||||
|
*/
|
||||||
|
static s32 read_text_save(s32 fileIndex)
|
||||||
|
{
|
||||||
|
char *filename, *temp;
|
||||||
|
const char *value;
|
||||||
|
ini_t *savedata;
|
||||||
|
|
||||||
|
u32 i, flag, coins, stars, starFlags;
|
||||||
|
u32 capLevel, capArea;
|
||||||
|
Vec3s capPos;
|
||||||
|
|
||||||
|
/* Define savefile's name */
|
||||||
|
filename = (char*)malloc(14 * sizeof(char));
|
||||||
|
if (sprintf(filename, FILENAME_FORMAT, fileIndex) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try to open the file */
|
||||||
|
savedata = ini_load(filename);
|
||||||
|
if (savedata == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Good, file exists for gSaveBuffer */
|
||||||
|
gSaveBuffer.files[fileIndex][0].flags |= SAVE_FLAG_FILE_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read coin score age for selected file and sound mode */
|
||||||
|
ini_sget(savedata, "menu", "coin_score_age", "%d",
|
||||||
|
&gSaveBuffer.menuData[0].coinScoreAges[fileIndex]);
|
||||||
|
ini_sget(savedata, "menu", "sound_mode", "%u",
|
||||||
|
&gSaveBuffer.menuData[0].soundMode); // Can override 4 times!
|
||||||
|
|
||||||
|
/* Parse main flags */
|
||||||
|
for (i = 1; i < NUM_FLAGS; i++) {
|
||||||
|
value = ini_get(savedata, "flags", sav_flags[i]);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
flag = strtol(value, &temp, 10);
|
||||||
|
if (flag) {
|
||||||
|
flag = 1 << i; /* Look #define in header.. */
|
||||||
|
gSaveBuffer.files[fileIndex][0].flags |= flag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse coin and star values for each main course */
|
||||||
|
for (i = 0; i < NUM_COURSES; i++) {
|
||||||
|
value = ini_get(savedata, "courses", sav_courses[i]);
|
||||||
|
if (value) {
|
||||||
|
sscanf(value, "%d, %d", &coins, &stars);
|
||||||
|
starFlags = bin_to_int(stars); /* 111111 -> 63 */
|
||||||
|
|
||||||
|
save_file_set_star_flags(fileIndex, i, starFlags);
|
||||||
|
gSaveBuffer.files[fileIndex][0].courseCoinScores[i] = coins;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse star values for each bonus course */
|
||||||
|
for (i = 0; i < NUM_BONUS_COURSES; i++) {
|
||||||
|
value = ini_get(savedata, "bonus", sav_bonus_courses[i]);
|
||||||
|
if (value) {
|
||||||
|
sscanf(value, "%d", &stars);
|
||||||
|
starFlags = bin_to_int(stars);
|
||||||
|
|
||||||
|
if (strlen(value) == 5) {
|
||||||
|
/* Process Castle Grounds */
|
||||||
|
save_file_set_star_flags(fileIndex, -1, starFlags);
|
||||||
|
}
|
||||||
|
else if (strlen(value) == 2) {
|
||||||
|
/* Process Princess's Secret Slide */
|
||||||
|
save_file_set_star_flags(fileIndex, COURSE_PSS, starFlags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Process another shitty bonus course */
|
||||||
|
save_file_set_star_flags(fileIndex, i+15, starFlags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find, who steal Mario's cap ... */
|
||||||
|
for (i = 0; i < NUM_CAP_ON; i++) {
|
||||||
|
value = ini_get(savedata, "cap", "type");
|
||||||
|
if (value) {
|
||||||
|
if (!strcmp(value, cap_on_types[i])) {
|
||||||
|
flag = (1 << (16 + i));
|
||||||
|
gSaveBuffer.files[fileIndex][0].flags |= flag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... also it's position, area and level */
|
||||||
|
sscanf(ini_get(savedata, "cap", "level"), "%d", &capLevel);
|
||||||
|
sscanf(ini_get(savedata, "cap", "area"), "%d", &capArea);
|
||||||
|
|
||||||
|
gSaveBuffer.files[fileIndex][0].capLevel = capLevel;
|
||||||
|
gSaveBuffer.files[fileIndex][0].capArea = capArea;
|
||||||
|
|
||||||
|
/* Cleaning up after ourselves */
|
||||||
|
ini_free(savedata);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sum the bytes in data to data + size - 2. The last two bytes are ignored
|
* Sum the bytes in data to data + size - 2. The last two bytes are ignored
|
||||||
|
@ -242,7 +508,7 @@ static void save_main_menu_data(void) {
|
||||||
bcopy(&gSaveBuffer.menuData[0], &gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]));
|
bcopy(&gSaveBuffer.menuData[0], &gSaveBuffer.menuData[1], sizeof(gSaveBuffer.menuData[1]));
|
||||||
|
|
||||||
// Write to EEPROM
|
// Write to EEPROM
|
||||||
write_eeprom_menudata(0, 2);
|
write_eeprom_menudata(0, 2);
|
||||||
|
|
||||||
gMainMenuDataModified = FALSE;
|
gMainMenuDataModified = FALSE;
|
||||||
}
|
}
|
||||||
|
@ -350,7 +616,16 @@ static void save_file_bswap(struct SaveBuffer *buf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void save_file_do_save(s32 fileIndex) {
|
void save_file_do_save(s32 fileIndex) {
|
||||||
if (gSaveFileModified) {
|
if (gSaveFileModified)
|
||||||
|
#ifdef TEXTSAVES
|
||||||
|
{
|
||||||
|
// Write to text file
|
||||||
|
write_text_save(fileIndex);
|
||||||
|
gSaveFileModified = FALSE;
|
||||||
|
gMainMenuDataModified = FALSE;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
{
|
||||||
// Compute checksum
|
// Compute checksum
|
||||||
add_save_block_signature(&gSaveBuffer.files[fileIndex][0],
|
add_save_block_signature(&gSaveBuffer.files[fileIndex][0],
|
||||||
sizeof(gSaveBuffer.files[fileIndex][0]), SAVE_FILE_MAGIC);
|
sizeof(gSaveBuffer.files[fileIndex][0]), SAVE_FILE_MAGIC);
|
||||||
|
@ -361,11 +636,11 @@ void save_file_do_save(s32 fileIndex) {
|
||||||
|
|
||||||
// Write to EEPROM
|
// Write to EEPROM
|
||||||
write_eeprom_savefile(fileIndex, 0, 2);
|
write_eeprom_savefile(fileIndex, 0, 2);
|
||||||
|
|
||||||
gSaveFileModified = FALSE;
|
gSaveFileModified = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
save_main_menu_data();
|
save_main_menu_data();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void save_file_erase(s32 fileIndex) {
|
void save_file_erase(s32 fileIndex) {
|
||||||
|
@ -396,6 +671,14 @@ void save_file_load_all(void) {
|
||||||
gSaveFileModified = FALSE;
|
gSaveFileModified = FALSE;
|
||||||
|
|
||||||
bzero(&gSaveBuffer, sizeof(gSaveBuffer));
|
bzero(&gSaveBuffer, sizeof(gSaveBuffer));
|
||||||
|
|
||||||
|
#ifdef TEXTSAVES
|
||||||
|
for (file = 0; file < NUM_SAVE_FILES; file++) {
|
||||||
|
read_text_save(file);
|
||||||
|
}
|
||||||
|
gSaveFileModified = TRUE;
|
||||||
|
gMainMenuDataModified = TRUE;
|
||||||
|
#else
|
||||||
read_eeprom_data(&gSaveBuffer, sizeof(gSaveBuffer));
|
read_eeprom_data(&gSaveBuffer, sizeof(gSaveBuffer));
|
||||||
|
|
||||||
if (save_file_need_bswap(&gSaveBuffer))
|
if (save_file_need_bswap(&gSaveBuffer))
|
||||||
|
@ -432,7 +715,7 @@ void save_file_load_all(void) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
stub_save_file_1();
|
stub_save_file_1();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,4 +1022,4 @@ s32 check_warp_checkpoint(struct WarpNode *warpNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return isWarpCheckpointActive;
|
return isWarpCheckpointActive;
|
||||||
}
|
}
|
|
@ -160,4 +160,12 @@ void eu_set_language(u16 language);
|
||||||
u16 eu_get_language(void);
|
u16 eu_get_language(void);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef TEXTSAVES
|
||||||
|
#define FILENAME_FORMAT "save_file_%d.sav"
|
||||||
|
#define NUM_COURSES 15
|
||||||
|
#define NUM_BONUS_COURSES 10
|
||||||
|
#define NUM_FLAGS 21
|
||||||
|
#define NUM_CAP_ON 4
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016 rxi
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include "ini.h"
|
||||||
|
|
||||||
|
/* Case insensitive string compare */
|
||||||
|
static int strcmpci(const char *a, const char *b) {
|
||||||
|
for (;;) {
|
||||||
|
int d = tolower(*a) - tolower(*b);
|
||||||
|
if (d != 0 || !*a) {
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
a++, b++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the next string in the split data */
|
||||||
|
static char* next(ini_t *ini, char *p) {
|
||||||
|
p += strlen(p);
|
||||||
|
while (p < ini->end && *p == '\0') {
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trim_back(ini_t *ini, char *p) {
|
||||||
|
while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
|
||||||
|
*p-- = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* discard_line(ini_t *ini, char *p) {
|
||||||
|
while (p < ini->end && *p != '\n') {
|
||||||
|
*p++ = '\0';
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char *unescape_quoted_value(ini_t *ini, char *p) {
|
||||||
|
/* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
|
||||||
|
* as escape sequences are always larger than their resultant data */
|
||||||
|
char *q = p;
|
||||||
|
p++;
|
||||||
|
while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
|
||||||
|
if (*p == '\\') {
|
||||||
|
/* Handle escaped char */
|
||||||
|
p++;
|
||||||
|
switch (*p) {
|
||||||
|
default : *q = *p; break;
|
||||||
|
case 'r' : *q = '\r'; break;
|
||||||
|
case 'n' : *q = '\n'; break;
|
||||||
|
case 't' : *q = '\t'; break;
|
||||||
|
case '\r' :
|
||||||
|
case '\n' :
|
||||||
|
case '\0' : goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle normal char */
|
||||||
|
*q = *p;
|
||||||
|
}
|
||||||
|
q++, p++;
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Splits data in place into strings containing section-headers, keys and
|
||||||
|
* values using one or more '\0' as a delimiter. Unescapes quoted values */
|
||||||
|
static void split_data(ini_t *ini) {
|
||||||
|
char *value_start, *line_start;
|
||||||
|
char *p = ini->data;
|
||||||
|
|
||||||
|
while (p < ini->end) {
|
||||||
|
switch (*p) {
|
||||||
|
case '\r':
|
||||||
|
case '\n':
|
||||||
|
case '\t':
|
||||||
|
case ' ':
|
||||||
|
*p = '\0';
|
||||||
|
/* Fall through */
|
||||||
|
|
||||||
|
case '\0':
|
||||||
|
p++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
p += strcspn(p, "]\n");
|
||||||
|
*p = '\0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ';':
|
||||||
|
p = discard_line(ini, p);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
line_start = p;
|
||||||
|
p += strcspn(p, "=\n");
|
||||||
|
|
||||||
|
/* Is line missing a '='? */
|
||||||
|
if (*p != '=') {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
trim_back(ini, p - 1);
|
||||||
|
|
||||||
|
/* Replace '=' and whitespace after it with '\0' */
|
||||||
|
do {
|
||||||
|
*p++ = '\0';
|
||||||
|
} while (*p == ' ' || *p == '\r' || *p == '\t');
|
||||||
|
|
||||||
|
/* Is a value after '=' missing? */
|
||||||
|
if (*p == '\n' || *p == '\0') {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*p == '"') {
|
||||||
|
/* Handle quoted string value */
|
||||||
|
value_start = p;
|
||||||
|
p = unescape_quoted_value(ini, p);
|
||||||
|
|
||||||
|
/* Was the string empty? */
|
||||||
|
if (p == value_start) {
|
||||||
|
p = discard_line(ini, line_start);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Discard the rest of the line after the string value */
|
||||||
|
p = discard_line(ini, p);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle normal value */
|
||||||
|
p += strcspn(p, "\n");
|
||||||
|
trim_back(ini, p - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ini_t* ini_load(const char *filename) {
|
||||||
|
ini_t *ini = NULL;
|
||||||
|
FILE *fp = NULL;
|
||||||
|
int n, sz;
|
||||||
|
|
||||||
|
/* Init ini struct */
|
||||||
|
ini = malloc(sizeof(*ini));
|
||||||
|
if (!ini) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
memset(ini, 0, sizeof(*ini));
|
||||||
|
|
||||||
|
/* Open file */
|
||||||
|
fp = fopen(filename, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get file size */
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
sz = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
/* Load file content into memory, null terminate, init end var */
|
||||||
|
ini->data = malloc(sz + 1);
|
||||||
|
ini->data[sz] = '\0';
|
||||||
|
ini->end = ini->data + sz;
|
||||||
|
n = fread(ini->data, 1, sz, fp);
|
||||||
|
if (n != sz) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare data */
|
||||||
|
split_data(ini);
|
||||||
|
|
||||||
|
/* Clean up and return */
|
||||||
|
fclose(fp);
|
||||||
|
return ini;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (fp) fclose(fp);
|
||||||
|
if (ini) ini_free(ini);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ini_free(ini_t *ini) {
|
||||||
|
free(ini->data);
|
||||||
|
free(ini);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* ini_get(ini_t *ini, const char *section, const char *key) {
|
||||||
|
char *current_section = "";
|
||||||
|
char *val;
|
||||||
|
char *p = ini->data;
|
||||||
|
|
||||||
|
if (*p == '\0') {
|
||||||
|
p = next(ini, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (p < ini->end) {
|
||||||
|
if (*p == '[') {
|
||||||
|
/* Handle section */
|
||||||
|
current_section = p + 1;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
/* Handle key */
|
||||||
|
val = next(ini, p);
|
||||||
|
if (!section || !strcmpci(section, current_section)) {
|
||||||
|
if (!strcmpci(p, key)) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = next(ini, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int ini_sget(
|
||||||
|
ini_t *ini, const char *section, const char *key,
|
||||||
|
const char *scanfmt, void *dst
|
||||||
|
) {
|
||||||
|
const char *val = ini_get(ini, section, key);
|
||||||
|
if (!val) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (scanfmt) {
|
||||||
|
sscanf(val, scanfmt, dst);
|
||||||
|
} else {
|
||||||
|
*((const char**) dst) = val;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2016 rxi
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the MIT license. See `ini.c` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INI_H
|
||||||
|
#define INI_H
|
||||||
|
|
||||||
|
#define INI_VERSION "0.1.1"
|
||||||
|
|
||||||
|
typedef struct ini_t ini_t;
|
||||||
|
|
||||||
|
struct ini_t {
|
||||||
|
char *data;
|
||||||
|
char *end;
|
||||||
|
};
|
||||||
|
|
||||||
|
ini_t* ini_load(const char *filename);
|
||||||
|
void ini_free(ini_t *ini);
|
||||||
|
const char* ini_get(ini_t *ini, const char *section, const char *key);
|
||||||
|
int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue