From ab9c532d89cddf31a229ca17814804d2aa5f1a6c Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Fri, 15 Nov 2019 18:57:26 +0100 Subject: [PATCH] - save custom data as JSON. This makes it a lot easier to debug and test than binary blobs. --- source/common/secrets.cpp | 60 +++++++++---- source/common/statistics.cpp | 143 +++++++++++++++++++----------- source/duke3d/src/player.cpp | 10 +-- source/thirdparty/include/sjson.h | 8 +- 4 files changed, 141 insertions(+), 80 deletions(-) diff --git a/source/common/secrets.cpp b/source/common/secrets.cpp index e1235e712..21cd0d5d7 100644 --- a/source/common/secrets.cpp +++ b/source/common/secrets.cpp @@ -6,6 +6,7 @@ #include "c_cvars.h" #include "v_font.h" #include "v_draw.h" +#include "sjson.h" #include "savegamehelp.h" // Unlike in GZDoom we have to maintain this list here, because we got different game frontents that all store this info differently. @@ -109,28 +110,51 @@ CCMD(secret) void SECRET_Save() { - auto fil = WriteSavegameChunk("secrets.dat"); - fil->Write("SECR", 4); - unsigned count = discovered_secrets.Size(); - fil->Write(&count, 4); - fil->Write(discovered_secrets.Data(), 4 * count); - fil->Write("RCES", 4); - + sjson_context* ctx = sjson_create_context(0, 0, NULL); + if (!ctx) + { + return; + } + sjson_node* root = sjson_mkobject(ctx); + sjson_put_ints(ctx, root, "secrets", discovered_secrets.Data(), discovered_secrets.Size()); + + char* encoded = sjson_stringify(ctx, root, " "); + + FileWriter* fil = WriteSavegameChunk("secrets.json"); + if (!fil) + { + sjson_destroy_context(ctx); + return; + } + + fil->Write(encoded, strlen(encoded)); + + sjson_free_string(ctx, encoded); + sjson_destroy_context(ctx); } bool SECRET_Load() { - char buf[4]; - unsigned count; - auto fil = ReadSavegameChunk("secrets.dat"); - if (!fil.isOpen()) return false; - fil.Read(buf, 4); - if (memcmp(buf, "SECR", 4)) return false; - fil.Read(&count, 4); - discovered_secrets.Resize(count); - fil.Read(discovered_secrets.Data(), count * 4); - fil.Read(buf, 4); - if (memcmp(buf, "RCES", 4)) return false; + auto fil = ReadSavegameChunk("statistics.json"); + if (!fil.isOpen()) + { + return false; + } + + auto text = fil.ReadPadded(1); + fil.Close(); + + if (text.Size() == 0) + { + return false; + } + + sjson_context* ctx = sjson_create_context(0, 0, NULL); + sjson_node* root = sjson_decode(ctx, (const char*)text.Data()); + discovered_secrets.Resize(1000); // Retarted interface alert + int realsize = sjson_get_ints(discovered_secrets.Data(), 1000, root, "secrets"); + discovered_secrets.Resize(realsize); + sjson_destroy_context(ctx); return true; } diff --git a/source/common/statistics.cpp b/source/common/statistics.cpp index d1cc4f4c5..d6b5d23b1 100644 --- a/source/common/statistics.cpp +++ b/source/common/statistics.cpp @@ -46,6 +46,7 @@ #include "sc_man.h" #include "baselayer.h" #include "savegamehelp.h" +#include "sjson.h" CVAR(Int, savestatistics, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(String, statfile, "demolitionstat.txt", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) @@ -67,9 +68,9 @@ struct OneLevel // Current game's statistics static TArray LevelData; -static char StartEpisode[MAX_PATH]; +static FString StartEpisode; static int StartSkill; -static char LevelName[MAX_PATH]; +static FString LevelName; // The statistics for one level struct FLevelStatistics @@ -352,15 +353,15 @@ static void LevelStatEntry(FSessionStatistics *es, const char *level, const char void STAT_StartNewGame(const char *episode, int skill) { - strncpy(StartEpisode, episode, MAX_PATH); + StartEpisode = episode; StartSkill = skill; LevelData.Clear(); - *LevelName = 0; + LevelName = ""; } void STAT_NewLevel(const char* mapname) { - strncpy(LevelName, mapname, MAX_PATH); + LevelName = mapname; } //========================================================================== @@ -443,14 +444,14 @@ void STAT_Update(bool endofgame) } SaveStatistics(statfile, EpisodeStatistics); LevelData.Clear(); - *StartEpisode = *LevelName = 0; + StartEpisode = LevelName = ""; } } void STAT_Cancel() { LevelData.Clear(); - *StartEpisode = *LevelName = 0; + StartEpisode = LevelName = ""; } //========================================================================== @@ -459,67 +460,109 @@ void STAT_Cancel() // //========================================================================== -void SaveOneLevel(FileWriter& fil, OneLevel& l) +void SaveOneLevel(sjson_context *ctx, sjson_node *lev, OneLevel& l) { - fil.Write(&l.totalkills, 4); - fil.Write(&l.killcount, 4); - fil.Write(&l.totalsecrets, 4); - fil.Write(&l.secretcount, 4); - fil.Write(&l.leveltime, 4); - uint8_t siz = l.Levelname.Len(); - fil.Write(&siz, 1); - fil.Write(l.Levelname.GetChars(), siz); + sjson_put_int(ctx, lev, "totalkills", l.totalkills); + sjson_put_int(ctx, lev, "kills", l.killcount); + sjson_put_int(ctx, lev, "totalsecrets", l.totalsecrets); + sjson_put_int(ctx, lev, "secrets", l.secretcount); + sjson_put_int(ctx, lev, "leveltime", l.leveltime); + sjson_put_string(ctx, lev, "levelname", l.Levelname); } -void ReadOneLevel(FileReader& fil, OneLevel& l) +void ReadOneLevel(sjson_node *lev, OneLevel& l) { - fil.Read(&l.totalkills, 4); - fil.Read(&l.killcount, 4); - fil.Read(&l.totalsecrets, 4); - fil.Read(&l.secretcount, 4); - fil.Read(&l.leveltime, 4); - uint8_t siz; - fil.Read(&siz, 1); - auto p = l.Levelname.LockNewBuffer(siz); - fil.Read(p, siz); - l.Levelname.UnlockBuffer(); + + l.totalkills = sjson_get_int(lev, "totalkills", 0); + l.killcount = sjson_get_int(lev, "kills", 0); + l.totalsecrets = sjson_get_int(lev, "totalsecrets", 0); + l.secretcount = sjson_get_int(lev, "secrets", 0); + l.leveltime = sjson_get_int(lev, "leveltime", 0); + l.Levelname = sjson_get_string(lev, "levelname", ""); } void SaveStatistics() { - auto fil = WriteSavegameChunk("statistics.dat"); - fil->Write("STAT", 4); - fil->Write(LevelName, MAX_PATH); - fil->Write(&StartEpisode, MAX_PATH); - fil->Write(&StartSkill, 4); - int p = LevelData.Size(); - fil->Write(&p, 4); + sjson_context* ctx = sjson_create_context(0, 0, NULL); + if (!ctx) + { + return; + } + sjson_node* root = sjson_mkobject(ctx); + + sjson_put_string(ctx, root, "levelname", LevelName); + sjson_put_string(ctx, root, "episode", StartEpisode); + sjson_put_int(ctx, root, "skill", StartSkill); + + sjson_node* levels = sjson_mkarray(ctx); for (auto& lev : LevelData) { - SaveOneLevel(*fil, lev); + sjson_node* levj = sjson_mkobject(ctx); + SaveOneLevel(ctx, levj, lev); } - fil->Write("TATS", 4); + sjson_append_member(ctx, root, "levels", levels); + + char errmsg[256]; + if (!sjson_check(root, errmsg)) + { + buildprint(errmsg, "\n"); + sjson_destroy_context(ctx); + return; + } + + char* encoded = sjson_stringify(ctx, root, " "); + + FileWriter* fil = WriteSavegameChunk("statistics.json"); + if (!fil) + { + sjson_destroy_context(ctx); + return; + } + + fil->Write(encoded, strlen(encoded)); + + sjson_free_string(ctx, encoded); + sjson_destroy_context(ctx); + return; } bool ReadStatistics() { - char id[4]; - auto fil = ReadSavegameChunk("statistics.dat"); - if (!fil.isOpen()) return false; - fil.Read(id, 4); - if (memcmp(id, "STAT", 4)) return false; - fil.Read(LevelName, MAX_PATH); - fil.Read(&StartEpisode, MAX_PATH); - fil.Read(&StartSkill, 4); - int p; - fil.Read(&p, 4); - LevelData.Resize(p); + auto fil = ReadSavegameChunk("statistics.json"); + if (!fil.isOpen()) + { + return false; + } + + auto text = fil.ReadPadded(1); + fil.Close(); + + if (text.Size() == 0) + { + return false; + } + + sjson_context* ctx = sjson_create_context(0, 0, NULL); + sjson_node* root = sjson_decode(ctx, (const char*)text.Data()); + + LevelName = sjson_get_string(root, "levelname", ""); + StartEpisode = sjson_get_int(root, "episode", -1); + StartSkill = sjson_get_int(root, "skill", -1); + sjson_node* levels = sjson_find_member(root, "levels"); + + if (LevelName.Len() == 0 || StartEpisode == -1 || StartSkill == -1 || levels == nullptr) + { + sjson_destroy_context(ctx); + return false; + } + + int numlevels = sjson_child_count(levels); + LevelData.Resize(numlevels); for (auto& lev : LevelData) { - ReadOneLevel(fil, lev); + ReadOneLevel(levels, lev); } - fil.Read(id, 4); - if (memcmp(id, "TATS", 4)) return false; + sjson_destroy_context(ctx); return true; } diff --git a/source/duke3d/src/player.cpp b/source/duke3d/src/player.cpp index 1519e9b69..461530148 100644 --- a/source/duke3d/src/player.cpp +++ b/source/duke3d/src/player.cpp @@ -29,6 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "gamecvars.h" #include "d_event.h" #include "i_specialpaths.h" +#include "savegamehelp.h" BEGIN_DUKE_NS @@ -5631,13 +5632,6 @@ int portableBackupSave(const char * path, const char * name, int volume, int lev if (!FURY) return 0; - char fn[BMAX_PATH]; - - if (snprintf(fn, sizeof(fn), "%s%s.ext", M_GetSavegamesPath().GetChars(), path)) - { - return 1; - } - sjson_context * ctx = sjson_create_context(0, 0, NULL); if (!ctx) { @@ -5729,7 +5723,7 @@ int portableBackupSave(const char * path, const char * name, int volume, int lev char * encoded = sjson_stringify(ctx, root, " "); - FileWriter *fil = FileWriter::Open(fn); + FileWriter* fil = WriteSavegameChunk("ext.json"); if (!fil) { sjson_destroy_context(ctx); diff --git a/source/thirdparty/include/sjson.h b/source/thirdparty/include/sjson.h index 5ad121043..0cc649604 100644 --- a/source/thirdparty/include/sjson.h +++ b/source/thirdparty/include/sjson.h @@ -228,7 +228,7 @@ double sjson_get_double(sjson_node* parent, const char* key, double default_va const char* sjson_get_string(sjson_node* parent, const char* key, const char* default_val); bool sjson_get_bool(sjson_node* parent, const char* key, bool default_val); bool sjson_get_floats(float* out, int count, sjson_node* parent, const char* key); -bool sjson_get_ints(int* out, int count, sjson_node* parent, const char* key); +int sjson_get_ints(int* out, int count, sjson_node* parent, const char* key); bool sjson_get_uints(uint32_t* out, int count, sjson_node* parent, const char* key); bool sjson_get_int16s(int16_t* out, int count, sjson_node* parent, const char* key); bool sjson_get_uint16s(uint16_t* out, int count, sjson_node* parent, const char* key); @@ -1283,7 +1283,7 @@ bool sjson_get_floats(float* out, int count, sjson_node* parent, const char* key } } -bool sjson_get_ints(int* out, int count, sjson_node* parent, const char* key) +int sjson_get_ints(int* out, int count, sjson_node* parent, const char* key) { sjson_node* p = key ? sjson_find_member(parent, key) : parent; if (p) { @@ -1293,9 +1293,9 @@ bool sjson_get_ints(int* out, int count, sjson_node* parent, const char* key) sjson_assert(elem->tag == SJSON_NUMBER); out[index++] = (int)elem->number_; } - return index == count; + return index; } else { - return false; + return 0; } }