From a5e7be56c195e0f313ef7602148b59355662f4ce Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 19 Feb 2024 18:27:24 -0600 Subject: [PATCH 1/8] Add seek to file streams --- src/io/streams.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++++++ src/io/streams.hpp | 4 +--- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/src/io/streams.cpp b/src/io/streams.cpp index 17d03e0ad..3d4baffd2 100644 --- a/src/io/streams.cpp +++ b/src/io/streams.cpp @@ -166,3 +166,63 @@ void FileStream::close() file_ = nullptr; } + +static int portable_fseek64(FILE* file, int64_t offset, int origin) +{ +#ifdef _MSC_VER + return _fseeki64(file, offset, origin); +#elif __APPLE__ + return fseeko(file, offset, origin); +#else + return fseeko64(file, offset, origin); +#endif +} + +static int64_t portable_ftell64(FILE* file) +{ +#ifdef _MSC_VER + return _ftelli64(file); +#elif __APPLE__ + return ftello(file); +#else + return ftello64(file); +#endif +} + +StreamSize FileStream::seek(SeekFrom seek_from, StreamOffset offset) +{ + if (!file_) + { + throw std::domain_error("FileStream is empty"); + } + + int origin; + switch (seek_from) + { + case SeekFrom::kStart: + origin = SEEK_SET; + break; + case SeekFrom::kCurrent: + origin = SEEK_CUR; + break; + case SeekFrom::kEnd: + origin = SEEK_END; + break; + default: + throw std::invalid_argument("invalid SeekFrom"); + } + + if (portable_fseek64((FILE*)(file_), offset, origin) != 0) + { + int err = errno; + throw make_exception_from_errno(err); + } + + StreamOffset newpos = portable_ftell64((FILE*)(file_)); + if (newpos < 0) + { + int err = errno; + throw make_exception_from_errno(err); + } + return newpos; +} diff --git a/src/io/streams.hpp b/src/io/streams.hpp index c4456d2d4..a91b0ebbc 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -616,9 +616,7 @@ public: StreamSize read(tcb::span buffer); StreamSize write(tcb::span buffer); - - // not bothering with seeking for now -- apparently 64-bit file positions is not available in ansi c - // StreamSize seek(SeekFrom seek_from, StreamOffset offset); + StreamSize seek(SeekFrom seek_from, StreamOffset offset); void close(); }; From cf84724feff89d3767f2329f3744d30650c4dd0f Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 19 Feb 2024 18:35:24 -0600 Subject: [PATCH 2/8] k_profiles.c -> k_profiles.cpp --- src/CMakeLists.txt | 2 +- src/{k_profiles.c => k_profiles.cpp} | 66 ++++++++++++++-------------- 2 files changed, 34 insertions(+), 34 deletions(-) rename src/{k_profiles.c => k_profiles.cpp} (90%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bdaa9bec1..e0f3ba4ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -145,7 +145,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_terrain.c k_director.cpp k_follower.c - k_profiles.c + k_profiles.cpp k_specialstage.c k_roulette.c k_podium.cpp diff --git a/src/k_profiles.c b/src/k_profiles.cpp similarity index 90% rename from src/k_profiles.c rename to src/k_profiles.cpp index a9d433855..47366eee2 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.cpp @@ -34,11 +34,11 @@ INT32 PR_GetNumProfiles(void) return numprofiles; } -static void PR_GenerateProfileKeys(profile_t *new) +static void PR_GenerateProfileKeys(profile_t *newprofile) { static uint8_t seed[32]; csprng(seed, 32); - crypto_eddsa_key_pair(new->secret_key, new->public_key, seed); + crypto_eddsa_key_pair(newprofile->secret_key, newprofile->public_key, seed); } profile_t* PR_MakeProfile( @@ -49,52 +49,52 @@ profile_t* PR_MakeProfile( INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING], boolean guest) { - profile_t *new = Z_Calloc(sizeof(profile_t), PU_STATIC, NULL); + profile_t *newprofile = static_cast(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL)); - new->version = PROFILEVER; + newprofile->version = PROFILEVER; - memset(new->secret_key, 0, sizeof(new->secret_key)); - memset(new->public_key, 0, sizeof(new->public_key)); + memset(newprofile->secret_key, 0, sizeof(newprofile->secret_key)); + memset(newprofile->public_key, 0, sizeof(newprofile->public_key)); if (!guest) { - PR_GenerateProfileKeys(new); + PR_GenerateProfileKeys(newprofile); } - strcpy(new->profilename, prname); - new->profilename[sizeof new->profilename - 1] = '\0'; + strcpy(newprofile->profilename, prname); + newprofile->profilename[sizeof newprofile->profilename - 1] = '\0'; - strcpy(new->skinname, sname); - strcpy(new->playername, pname); - new->color = col; + strcpy(newprofile->skinname, sname); + strcpy(newprofile->playername, pname); + newprofile->color = col; - strcpy(new->follower, fname); - new->followercolor = fcol; - new->kickstartaccel = false; - new->autoroulette = false; - new->litesteer = true; - new->rumble = true; + strcpy(newprofile->follower, fname); + newprofile->followercolor = fcol; + newprofile->kickstartaccel = false; + newprofile->autoroulette = false; + newprofile->litesteer = true; + newprofile->rumble = true; // Copy from gamecontrol directly as we'll be setting controls up directly in the profile. - memcpy(new->controls, controlarray, sizeof(new->controls)); + memcpy(newprofile->controls, controlarray, sizeof(newprofile->controls)); - new->wins = 0; + newprofile->wins = 0; - return new; + return newprofile; } profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum) { // Generate profile using the player's gamecontrol, as we set them directly when making profiles from menus. - profile_t *new = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false); + profile_t *newprofile = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum], false); // Player bound cvars: - new->kickstartaccel = cv_kickstartaccel[pnum].value; - new->autoroulette = cv_autoroulette[pnum].value; - new->litesteer = cv_litesteer[pnum].value; - new->rumble = cv_rumble[pnum].value; + newprofile->kickstartaccel = cv_kickstartaccel[pnum].value; + newprofile->autoroulette = cv_autoroulette[pnum].value; + newprofile->litesteer = cv_litesteer[pnum].value; + newprofile->rumble = cv_rumble[pnum].value; - return new; + return newprofile; } boolean PR_AddProfile(profile_t *p) @@ -352,7 +352,7 @@ void PR_LoadProfiles(void) for (i = 1; i < numprofiles; i++) { - profilesList[i] = Z_Calloc(sizeof(profile_t), PU_STATIC, NULL); + profilesList[i] = static_cast(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL)); // Version. (We always update this on successful forward step) profilesList[i]->version = PROFILEVER; @@ -383,7 +383,7 @@ void PR_LoadProfiles(void) ; // Valid, even outside the bounds } else if (profilesList[i]->color >= numskincolors - || K_ColorUsable(profilesList[i]->color, false, false) == false) + || K_ColorUsable(static_cast(profilesList[i]->color), false, false) == false) { profilesList[i]->color = PROFILEDEFAULTCOLOR; } @@ -399,7 +399,7 @@ void PR_LoadProfiles(void) ; // Valid, even outside the bounds } else if (profilesList[i]->followercolor >= numskincolors - || K_ColorUsable(profilesList[i]->followercolor, true, false) == false) + || K_ColorUsable(static_cast(profilesList[i]->followercolor), true, false) == false) { profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; } @@ -422,7 +422,7 @@ void PR_LoadProfiles(void) if (version < 7) { profilesList[i]->autoroulette = false; - + } else { @@ -433,7 +433,7 @@ void PR_LoadProfiles(void) if (version < 8) { profilesList[i]->litesteer = true; - + } else { @@ -638,7 +638,7 @@ char *GetPrettyRRID(const unsigned char *bin, boolean brief) rrid_buf[i*2] = "0123456789ABCDEF"[bin[i] >> 4]; rrid_buf[i*2+1] = "0123456789ABCDEF"[bin[i] & 0x0F]; } - + rrid_buf[len*2] = '\0'; return rrid_buf; From e3d0ec0a629c2fe8ab6ad8a533bb4689153d9d97 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Mon, 19 Feb 2024 20:49:55 -0600 Subject: [PATCH 3/8] Add endian parameter to io read_ funcs --- src/io/streams.hpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/io/streams.hpp b/src/io/streams.hpp index a91b0ebbc..3f815b9e8 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -183,9 +183,9 @@ void read(uint16_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -uint16_t read_uint16(I& stream) { +uint16_t read_uint16(I& stream, Endian endian = Endian::kLE) { uint16_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -211,9 +211,9 @@ void read(int16_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -int16_t read_int16(I& stream) { +int16_t read_int16(I& stream, Endian endian = Endian::kLE) { int16_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -235,9 +235,9 @@ void read(uint32_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -uint32_t read_uint32(I& stream) { +uint32_t read_uint32(I& stream, Endian endian = Endian::kLE) { uint32_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -267,9 +267,9 @@ void read(int32_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -int32_t read_int32(I& stream) { +int32_t read_int32(I& stream, Endian endian = Endian::kLE) { int32_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -295,9 +295,9 @@ void read(uint64_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -uint64_t read_uint64(I& stream) { +uint64_t read_uint64(I& stream, Endian endian = Endian::kLE) { uint64_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -335,9 +335,9 @@ void read(int64_t& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -int64_t read_int64(I& stream) { +int64_t read_int64(I& stream, Endian endian = Endian::kLE) { int64_t ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -354,9 +354,9 @@ void read(float& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -float read_float(I& stream) { +float read_float(I& stream, Endian endian = Endian::kLE) { float ret; - read(ret, stream); + read(ret, stream, endian); return ret; } @@ -373,9 +373,9 @@ void read(double& value, I& stream, Endian endian = Endian::kLE) { } template >* = nullptr> -double read_double(I& stream) { +double read_double(I& stream, Endian endian = Endian::kLE) { double ret; - read(ret, stream); + read(ret, stream, endian); return ret; } From 7b40b4c8c691bc39aac8795c891c858da8a4e9aa Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 20 Feb 2024 14:29:34 -0600 Subject: [PATCH 4/8] Rewrite ringprofiles format --- src/k_profiles.cpp | 358 ++++++++++++++++++++++----------------------- src/k_profiles.h | 78 +++++++++- 2 files changed, 256 insertions(+), 180 deletions(-) diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index 47366eee2..ccec96ad9 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -10,6 +10,11 @@ /// \file k_profiles.c /// \brief implements methods for profiles etc. +#include + +#include + +#include "io/streams.hpp" #include "doomtype.h" #include "d_main.h" // pandf #include "byteptr.h" // READ/WRITE macros @@ -236,10 +241,10 @@ void PR_InitNewProfile(void) void PR_SaveProfiles(void) { - size_t length = 0; - const size_t headerlen = strlen(PROFILEHEADER); - UINT8 i, j, k; - savebuffer_t save = {0}; + namespace fs = std::filesystem; + using json = nlohmann::json; + using namespace srb2; + namespace io = srb2::io; if (profilesList[PROFILE_GUEST] == NULL) { @@ -247,65 +252,94 @@ void PR_SaveProfiles(void) return; } - if (P_SaveBufferAlloc(&save, sizeof(UINT32) + (numprofiles * sizeof(profile_t))) == false) + ProfilesJson ng{}; + + for (size_t i = 1; i < numprofiles; i++) { - I_Error("No more free memory for saving profiles\n"); - return; - } + ProfileJson jsonprof; + profile_t* cprof = profilesList[i]; - // Add header. - WRITESTRINGN(save.p, PROFILEHEADER, headerlen); - WRITEUINT8(save.p, PROFILEVER); - WRITEUINT8(save.p, numprofiles); - - for (i = 1; i < numprofiles; i++) - { - // Names and keys, all the string data up front - WRITESTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN); - WRITEMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key)); - WRITEMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key)); - WRITESTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME); - - // Character and colour. - WRITESTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE); - WRITEUINT16(save.p, profilesList[i]->color); - - // Follower and colour. - WRITESTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE); - WRITEUINT16(save.p, profilesList[i]->followercolor); - - WRITEUINT32(save.p, profilesList[i]->wins); - - // Consvars. - WRITEUINT8(save.p, profilesList[i]->kickstartaccel); - WRITEUINT8(save.p, profilesList[i]->autoroulette); - WRITEUINT8(save.p, profilesList[i]->litesteer); - WRITEUINT8(save.p, profilesList[i]->rumble); - - // Controls. - for (j = 0; j < num_gamecontrols; j++) + jsonprof.version = PROFILEVER; + jsonprof.profilename = std::string(cprof->profilename); + std::copy(std::begin(cprof->public_key), std::end(cprof->public_key), std::begin(jsonprof.publickey)); + std::copy(std::begin(cprof->secret_key), std::end(cprof->secret_key), std::begin(jsonprof.secretkey)); + jsonprof.playername = std::string(cprof->playername); + jsonprof.skinname = std::string(cprof->skinname); + jsonprof.colorname = std::string(skincolors[cprof->color].name); + jsonprof.followername = std::string(cprof->follower); + if (cprof->followercolor == FOLLOWERCOLOR_MATCH) { - for (k = 0; k < MAXINPUTMAPPING; k++) + jsonprof.followercolorname = "Match"; + } + else if (cprof->followercolor == FOLLOWERCOLOR_OPPOSITE) + { + jsonprof.followercolorname = "Opposite"; + } + else if (cprof->followercolor == SKINCOLOR_NONE) + { + jsonprof.followercolorname = "Default"; + } + else if (cprof->followercolor >= numskincolors) + { + jsonprof.followercolorname = std::string(); + } + else + { + jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name); + } + jsonprof.records.wins = cprof->wins; + jsonprof.preferences.kickstartaccel = cprof->kickstartaccel; + jsonprof.preferences.autoroulette = cprof->autoroulette; + jsonprof.preferences.litesteer = cprof->litesteer; + jsonprof.preferences.rumble = cprof->rumble; + + for (size_t j = 0; j < num_gamecontrols; j++) + { + for (size_t k = 0; k < MAXINPUTMAPPING; k++) { - WRITEINT32(save.p, profilesList[i]->controls[j][k]); + jsonprof.controls[j][k] = cprof->controls[j][k]; } } + + ng.profiles.emplace_back(std::move(jsonprof)); } - length = save.p - save.buffer; + std::vector ubjson = json::to_ubjson(ng); - if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length)) + std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE); + std::string tmppath = fmt::format("{}.tmp", realpath); + + try + { + io::FileStream file {tmppath, io::FileStreamMode::kWrite}; + io::BufferedOutputStream bos {std::move(file)}; + + io::write(static_cast(0x52494E47), bos, io::Endian::kBE); // "RING" + io::write(static_cast(0x5052464C), bos, io::Endian::kBE); // "PRFL" + io::write(static_cast(0), bos); // reserved1 + io::write(static_cast(0), bos); // reserved2 + io::write(static_cast(0), bos); // reserved3 + io::write(static_cast(0), bos); // reserved4 + io::write_exact(bos, tcb::as_bytes(tcb::make_span(ubjson))); + bos.flush(); + file = bos.stream(); + file.close(); + + fs::rename(tmppath, realpath); + } + catch (...) { - P_SaveBufferFree(&save); I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); } - P_SaveBufferFree(&save); } void PR_LoadProfiles(void) { - const size_t headerlen = strlen(PROFILEHEADER); - UINT8 i, j, k, version; + namespace fs = std::filesystem; + using namespace srb2; + namespace io = srb2::io; + using json = nlohmann::json; + profile_t *dprofile = PR_MakeProfile( PROFILEDEFAULTNAME, PROFILEDEFAULTPNAME, @@ -314,166 +348,132 @@ void PR_LoadProfiles(void) gamecontroldefault, true ); - savebuffer_t save = {0}; - if (P_SaveBufferFromFile(&save, va(pandf, srb2home, PROFILESFILE)) == false) + std::string datapath {fmt::format("{}/{}", srb2home, PROFILESFILE)}; + + io::BufferedInputStream bis; + try + { + io::FileStream file {datapath, io::FileStreamMode::kRead}; + bis = io::BufferedInputStream(std::move(file)); + } + catch (const io::FileStreamException& ex) { - // No profiles. Add the default one. PR_AddProfile(dprofile); return; } - if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen)) + ProfilesJson js; + try { - const char *gdfolder = "the Ring Racers folder"; - if (strcmp(srb2home,".")) - gdfolder = srb2home; + uint32_t magic1; + uint32_t magic2; + uint8_t reserved1; + uint8_t reserved2; + uint8_t reserved3; + uint8_t reserved4; + magic1 = io::read_uint32(bis, io::Endian::kBE); + magic2 = io::read_uint32(bis, io::Endian::kBE); + reserved1 = io::read_uint8(bis); + reserved2 = io::read_uint8(bis); + reserved3 = io::read_uint8(bis); + reserved4 = io::read_uint8(bis); - P_SaveBufferFree(&save); - I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder); - } - save.p += headerlen; + if (magic1 != 0x52494E47 || magic2 != 0x5052464C || reserved1 != 0 || reserved2 != 0 || reserved3 != 0 || reserved4 != 0) + { + throw std::domain_error("Header is incompatible"); + } - version = READUINT8(save.p); - if (version > PROFILEVER) - { - P_SaveBufferFree(&save); - I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); + std::vector remainder = io::read_to_vec(bis); + // safety: std::byte repr is always uint8_t 1-byte aligned + tcb::span remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size()); + json parsed = json::from_ubjson(remainder_as_u8); + js = parsed.template get(); } - else if (version < PROFILEVER) + catch (...) { - // We're converting - let'd create a backup. - FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, PROFILESFILE), save.buffer, save.size); + I_Error("Profiles file is corrupt"); + return; } - numprofiles = READUINT8(save.p); - if (numprofiles > MAXPROFILES) - numprofiles = MAXPROFILES; - - for (i = 1; i < numprofiles; i++) + numprofiles = js.profiles.size() + 1; // 1 for guest + if (numprofiles > MAXPROFILES+1) { - profilesList[i] = static_cast(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL)); + numprofiles = MAXPROFILES+1; + } - // Version. (We always update this on successful forward step) - profilesList[i]->version = PROFILEVER; + for (size_t i = 1; i < numprofiles; i++) + { + auto& jsprof = js.profiles[i - 1]; + profile_t* newprof = static_cast(Z_Calloc(sizeof(profile_t), PU_STATIC, NULL)); + profilesList[i] = newprof; - // Names and keys, all the identity stuff up front - READSTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN); + newprof->version = jsprof.version; + strlcpy(newprof->profilename, jsprof.profilename.c_str(), sizeof(newprof->profilename)); + memcpy(newprof->public_key, jsprof.publickey.data(), sizeof(newprof->public_key)); + memcpy(newprof->secret_key, jsprof.secretkey.data(), sizeof(newprof->secret_key)); - // Profile update 2-->3: Add profile keys. - if (version < 3) + strlcpy(newprof->playername, jsprof.playername.c_str(), sizeof(newprof->playername)); + strlcpy(newprof->skinname, jsprof.skinname.c_str(), sizeof(newprof->skinname)); + newprof->color = PROFILEDEFAULTCOLOR; + for (size_t c = 0; c < numskincolors; c++) { - // Generate missing keys. - PR_GenerateProfileKeys(profilesList[i]); - } - else - { - READMEM(save.p, profilesList[i]->public_key, sizeof(((profile_t *)0)->public_key)); - READMEM(save.p, profilesList[i]->secret_key, sizeof(((profile_t *)0)->secret_key)); - } - - READSTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME); - - // Character and colour. - READSTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE); - profilesList[i]->color = READUINT16(save.p); - - if (profilesList[i]->color == SKINCOLOR_NONE) - { - ; // Valid, even outside the bounds - } - else if (profilesList[i]->color >= numskincolors - || K_ColorUsable(static_cast(profilesList[i]->color), false, false) == false) - { - profilesList[i]->color = PROFILEDEFAULTCOLOR; - } - - // Follower and colour. - READSTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE); - profilesList[i]->followercolor = READUINT16(save.p); - - if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH - || profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE - || profilesList[i]->followercolor == SKINCOLOR_NONE) - { - ; // Valid, even outside the bounds - } - else if (profilesList[i]->followercolor >= numskincolors - || K_ColorUsable(static_cast(profilesList[i]->followercolor), true, false) == false) - { - profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; - } - - // Profile update 5-->6: PWR isn't in profile data anymore. - if (version < 6) - { - save.p += PWRLV_NUMTYPES*2; - profilesList[i]->wins = 0; - } - else - { - profilesList[i]->wins = READUINT32(save.p); - } - - // Consvars. - profilesList[i]->kickstartaccel = (boolean)READUINT8(save.p); - - // 6->7, add autoroulette - if (version < 7) - { - profilesList[i]->autoroulette = false; - - } - else - { - profilesList[i]->autoroulette = (boolean)READUINT8(save.p); - } - - // 7->8, add litesteer - if (version < 8) - { - profilesList[i]->litesteer = true; - - } - else - { - profilesList[i]->litesteer = (boolean)READUINT8(save.p); - } - - if (version < 4) - { - profilesList[i]->rumble = true; - } - else - { - profilesList[i]->rumble = (boolean)READUINT8(save.p); - } - - // Controls. - for (j = 0; j < num_gamecontrols; j++) - { -#ifdef DEVELOP - // Profile update 1-->2: Add gc_rankings. - // Profile update 4-->5: Add gc_startlossless. - if ((j == gc_rankings && version < 2) || - (j == gc_startlossless && version < 5)) + if (jsprof.colorname == skincolors[c].name && K_ColorUsable(static_cast(c), false, false)) { - for (k = 0; k < MAXINPUTMAPPING; k++) + newprof->color = c; + break; + } + } + + strlcpy(newprof->follower, jsprof.followername.c_str(), sizeof(newprof->follower)); + newprof->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; + if (jsprof.followercolorname == "Match") + { + newprof->followercolor = FOLLOWERCOLOR_MATCH; + } + else if (jsprof.followercolorname == "Opposite") + { + newprof->followercolor = FOLLOWERCOLOR_OPPOSITE; + } + else if (jsprof.followercolorname == "Default") + { + newprof->followercolor = SKINCOLOR_NONE; + } + else if (!jsprof.followercolorname.empty()) + { + for (size_t c = 0; c < numskincolors; c++) + { + if (jsprof.followercolorname == skincolors[c].name && K_ColorUsable(static_cast(c), false, false)) { - profilesList[i]->controls[j][k] = gamecontroldefault[j][k]; + newprof->followercolor = c; + break; } - continue; } -#endif + } - for (k = 0; k < MAXINPUTMAPPING; k++) + newprof->wins = jsprof.records.wins; + newprof->kickstartaccel = jsprof.preferences.kickstartaccel; + newprof->autoroulette = jsprof.preferences.autoroulette; + newprof->litesteer = jsprof.preferences.litesteer; + newprof->rumble = jsprof.preferences.rumble; + + try + { + for (size_t j = 0; j < num_gamecontrols; j++) { - profilesList[i]->controls[j][k] = READINT32(save.p); + for (size_t k = 0; k < MAXINPUTMAPPING; k++) + { + newprof->controls[j][k] = jsprof.controls.at(j).at(k); + } } } + catch (const std::out_of_range& ex) + { + I_Error("Profile '%s' controls are corrupt", jsprof.playername.c_str()); + return; + } } - // Add the the default profile directly to avoid letting anyone tamper with it. profilesList[PROFILE_GUEST] = dprofile; } diff --git a/src/k_profiles.h b/src/k_profiles.h index e920fe9be..2fb5cf9fa 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -23,6 +23,82 @@ #include "k_follower.h" // followers #ifdef __cplusplus + +#include +#include +#include +#include + +#include + +namespace srb2 +{ + +struct ProfileRecordsJson +{ + uint32_t wins; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins) +}; + +struct ProfilePreferencesJson +{ + bool kickstartaccel; + bool autoroulette; + bool litesteer; + bool rumble; + tm test; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + ProfilePreferencesJson, + kickstartaccel, + autoroulette, + litesteer, + rumble + ) +}; + +struct ProfileJson +{ + uint32_t version; + std::string profilename; + std::string playername; + std::array publickey = {{}}; + std::array secretkey = {{}}; + std::string skinname; + std::string colorname; + std::string followername; + std::string followercolorname; + ProfileRecordsJson records; + ProfilePreferencesJson preferences; + std::array, gamecontrols_e::num_gamecontrols> controls = {{{{}}}}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + ProfileJson, + version, + profilename, + playername, + publickey, + secretkey, + skinname, + colorname, + followername, + followercolorname, + records, + preferences, + controls + ) +}; + +struct ProfilesJson +{ + std::vector profiles; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles) +}; + +} // namespace srb2 + extern "C" { #endif @@ -31,7 +107,7 @@ extern "C" { #define SKINNAMESIZE 16 #define PROFILENAMELEN 6 -#define PROFILEVER 8 +#define PROFILEVER 1 #define MAXPROFILES 16 #define PROFILESFILE "ringprofiles.prf" #define PROFILE_GUEST 0 From 0f96337124a81fa7aec0ffefbea1d30fdb3f5ada Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 20 Feb 2024 15:27:05 -0600 Subject: [PATCH 5/8] g_demo.c -> g_demo.cpp --- src/CMakeLists.txt | 2 +- src/{g_demo.c => g_demo.cpp} | 58 +++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) rename src/{g_demo.c => g_demo.cpp} (97%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e0f3ba4ce..6d13a1462 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,7 +17,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 f_finale.c f_wipe.cpp g_build_ticcmd.cpp - g_demo.c + g_demo.cpp g_game.c g_gamedata.cpp g_input.c diff --git a/src/g_demo.c b/src/g_demo.cpp similarity index 97% rename from src/g_demo.c rename to src/g_demo.cpp index f3321cc90..2f84953b7 100644 --- a/src/g_demo.c +++ b/src/g_demo.cpp @@ -11,6 +11,8 @@ /// \file g_demo.c /// \brief Demo recording and playback +#include + #include "doomdef.h" #include "console.h" #include "d_main.h" @@ -266,7 +268,7 @@ void G_ReadDemoExtraData(void) break; case DXD_PST_LEFT: - CL_RemovePlayer(p, 0); + CL_RemovePlayer(p, static_cast(0)); break; } @@ -353,9 +355,9 @@ void G_ReadDemoExtraData(void) { rng = READUINT32(demobuf.p); - if (P_GetRandSeed(i) != rng) + if (P_GetRandSeed(static_cast(i)) != rng) { - P_SetRandSeed(i, rng); + P_SetRandSeed(static_cast(i), rng); if (demosynced) CONS_Alert(CONS_WARNING, "Demo playback has desynced (RNG class %d)!\n", i); @@ -494,7 +496,7 @@ void G_WriteDemoExtraData(void) for (i = 0; i < PRNUMSYNCED; i++) { - WRITEUINT32(demobuf.p, P_GetRandSeed(i)); + WRITEUINT32(demobuf.p, P_GetRandSeed(static_cast(i))); } } } @@ -706,7 +708,7 @@ void G_GhostAddHit(INT32 playernum, mobj_t *victim) return; ghostext[playernum].flags |= EZT_HIT; ghostext[playernum].hits++; - ghostext[playernum].hitlist = Z_Realloc(ghostext[playernum].hitlist, ghostext[playernum].hits * sizeof(mobj_t *), PU_LEVEL, NULL); + ghostext[playernum].hitlist = static_cast(Z_Realloc(ghostext[playernum].hitlist, ghostext[playernum].hits * sizeof(mobj_t *), PU_LEVEL, NULL)); P_SetTarget(ghostext[playernum].hitlist + (ghostext[playernum].hits-1), victim); } @@ -1400,7 +1402,7 @@ readghosttic: } } if (xziptic & EZT_SPRITE) - g->mo->sprite = READUINT16(g->p); + g->mo->sprite = static_cast(READUINT16(g->p)); if (xziptic & EZT_ITEMDATA) g->p += 1 + 1 + 4; // itemtype, itemamount, health if (xziptic & EZT_STATDATA) @@ -1467,7 +1469,7 @@ readghosttic: follow->sprite2 = READUINT8(g->p); else follow->sprite2 = 0; - follow->sprite = READUINT16(g->p); + follow->sprite = static_cast(READUINT16(g->p)); follow->frame = (READUINT8(g->p)) | (g->mo->frame & FF_TRANSMASK); follow->angle = g->mo->angle; follow->color = READUINT16(g->p); @@ -1582,7 +1584,7 @@ void G_StoreRewindInfo(void) return; timetolog = 8; - info = Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL); + info = static_cast(Z_Calloc(sizeof(rewindinfo_t), PU_STATIC, NULL)); for (i = 0; i < MAXPLAYERS; i++) { @@ -1907,7 +1909,7 @@ static UINT8 G_CheckDemoExtraFiles(savebuffer_t *info, boolean quick) { if (!toomany) { - strlcpy(filename, (char *)info->p, min(P_SaveBufferRemaining(info) + 1, sizeof filename)); + strlcpy(filename, (char *)info->p, std::min(P_SaveBufferRemaining(info) + 1, sizeof filename)); } SKIPSTRINGN(info->p, P_SaveBufferRemaining(info)); @@ -2011,7 +2013,7 @@ static democharlist_t *G_LoadDemoSkins(savebuffer_t *info, UINT8 *worknumskins, if (!(*worknumskins)) return NULL; - skinlist = Z_Calloc(sizeof(democharlist_t) * (*worknumskins), PU_STATIC, NULL); + skinlist = static_cast(Z_Calloc(sizeof(democharlist_t) * (*worknumskins), PU_STATIC, NULL)); if (!skinlist) { I_Error("G_LoadDemoSkins: Insufficient memory to allocate list"); @@ -2181,7 +2183,7 @@ void G_BeginRecording(void) for (i = 0; i < PRNUMSYNCED; i++) { - WRITEUINT32(demobuf.p, P_GetInitSeed(i)); + WRITEUINT32(demobuf.p, P_GetInitSeed(static_cast(i))); } // Reserved for extrainfo location from start of file @@ -2584,7 +2586,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) goto badreplay; } info.p += 4; // "PLAY" - READSTRINGN(info.p, mapname, min(P_SaveBufferRemaining(&info), sizeof(mapname))); + READSTRINGN(info.p, mapname, std::min(P_SaveBufferRemaining(&info), sizeof(mapname))); pdemo->map = G_MapNumber(mapname); info.p += 16; // mapmd5 @@ -2602,7 +2604,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) goto badreplay; } - READSTRINGN(info.p, gtname, min(P_SaveBufferRemaining(&info), sizeof(gtname))); // gametype + READSTRINGN(info.p, gtname, std::min(P_SaveBufferRemaining(&info), sizeof(gtname))); // gametype pdemo->gametype = G_GetGametypeByName(gtname); if (P_SaveBufferRemaining(&info) < 1) @@ -2774,7 +2776,7 @@ void G_DoPlayDemo(const char *defdemoname) if (defdemoname == NULL) { demobuf.p = demobuf.buffer; - pdemoname = ZZ_Alloc(1); // Easier than adding checks for this everywhere it's freed + pdemoname = static_cast(ZZ_Alloc(1)); // Easier than adding checks for this everywhere it's freed } else { @@ -2787,7 +2789,7 @@ void G_DoPlayDemo(const char *defdemoname) n--; if (n != defdemoname) n++; - pdemoname = ZZ_Alloc(strlen(n)+1); + pdemoname = static_cast(ZZ_Alloc(strlen(n)+1)); strcpy(pdemoname,n); M_SetPlaybackMenuPointer(); @@ -3065,7 +3067,7 @@ void G_DoPlayDemo(const char *defdemoname) grandprixinfo.gp = true; grandprixinfo.gamespeed = READUINT8(demobuf.p); grandprixinfo.masterbots = READUINT8(demobuf.p) != 0; - grandprixinfo.eventmode = READUINT8(demobuf.p); + grandprixinfo.eventmode = static_cast(READUINT8(demobuf.p)); } // Sigh ... it's an empty demo. @@ -3236,7 +3238,7 @@ void G_DoPlayDemo(const char *defdemoname) clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demobuf.p); // Followitem - players[p].followitem = READUINT32(demobuf.p); + players[p].followitem = static_cast(READUINT32(demobuf.p)); // GP players[p].lives = READSINT8(demobuf.p); @@ -3262,7 +3264,7 @@ void G_DoPlayDemo(const char *defdemoname) if (demo.attract == DEMO_ATTRACT_TITLE) { splitscreen = M_RandomKey(6)-1; - splitscreen = min(min(3, numslots-1), splitscreen); // Bias toward 1p and 4p views + splitscreen = std::min(std::min(3, numslots-1), splitscreen); // Bias toward 1p and 4p views for (p = 0; p <= splitscreen; p++) G_ResetView(p+1, slots[M_RandomKey(numslots)], false); @@ -3272,7 +3274,7 @@ void G_DoPlayDemo(const char *defdemoname) for (i = 0; i < PRNUMSYNCED; i++) { - P_SetRandSeed(i, randseed[i]); + P_SetRandSeed(static_cast(i), randseed[i]); } G_InitNew((demoflags & DF_ENCORE) != 0, gamemap, true, true); // Doesn't matter whether you reset or not here, given changes to resetplayer. @@ -3484,7 +3486,7 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) } - gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL); + gh = static_cast(Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL)); gh->next = ghosts; gh->buffer = buffer->buffer; M_Memcpy(gh->checksum, md5, 16); @@ -3651,7 +3653,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) M_Memcpy(temp.name, p, 16); - ret = Z_Malloc(sizeof(staffbrief_t), PU_STATIC, NULL); + ret = static_cast(Z_Malloc(sizeof(staffbrief_t), PU_STATIC, NULL)); if (ret) M_Memcpy(ret, &temp, sizeof(staffbrief_t)); @@ -3732,7 +3734,7 @@ static void G_StopTimingDemo(void) if (timedemo_csv) { FILE *f; - const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv"); + const char *csvpath = va("%s" PATHSEP "%s", srb2home, "timedemo.csv"); const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n"; const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n"; boolean headerrow = !FIL_FileExists(csvpath); @@ -3836,7 +3838,7 @@ boolean G_CheckDemoStatus(void) if (!demo.recording) return false; - if (modeattacking || demo.savemode != DSM_NOTSAVING) + if (modeattacking || demo.savemode != demovars_s::DSM_NOTSAVING) { if (demobuf.p) { @@ -3923,13 +3925,13 @@ void G_SaveDemo(void) #endif if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. - demo.savemode = DSM_SAVED; + demo.savemode = demovars_s::DSM_SAVED; Z_Free(demobuf.buffer); demo.recording = false; if (!modeattacking) { - if (demo.savemode == DSM_SAVED) + if (demo.savemode == demovars_s::DSM_SAVED) { CONS_Printf(M_GetText("Demo %s recorded\n"), demoname); if (gamedata->eversavedreplay == false) @@ -3957,13 +3959,13 @@ boolean G_DemoTitleResponder(event_t *ev) // Only ESC and non-keyboard keys abort connection if (ch == KEY_ESCAPE) { - demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING; + demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? demovars_s::DSM_WILLAUTOSAVE : demovars_s::DSM_NOTSAVING; return true; } if (ch == KEY_ENTER || ch >= NUMKEYS) { - demo.savemode = DSM_WILLSAVE; + demo.savemode = demovars_s::DSM_WILLSAVE; return true; } @@ -4001,7 +4003,7 @@ boolean G_CheckDemoTitleEntry(void) if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0)) return false; - demo.savemode = DSM_TITLEENTRY; + demo.savemode = demovars_s::DSM_TITLEENTRY; return true; } From 7a4c4fb5bfd27ed419fc18b8c78204a05f96f063 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sat, 24 Feb 2024 12:06:31 -0600 Subject: [PATCH 6/8] Replace strncpy calls to strlcpy to fix warnings --- src/g_demo.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 2f84953b7..a7d4874c3 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -435,7 +435,7 @@ void G_WriteDemoExtraData(void) { // Color memset(name, 0, 16); - strncpy(name, skincolors[players[i].skincolor].name, 16); + strlcpy(name, skincolors[players[i].skincolor].name, 16); M_Memcpy(demobuf.p,name,16); demobuf.p += 16; } @@ -443,7 +443,7 @@ void G_WriteDemoExtraData(void) { // Name memset(name, 0, 16); - strncpy(name, player_names[i], 16); + strlcpy(name, player_names[i], 16); M_Memcpy(demobuf.p,name,16); demobuf.p += 16; } @@ -454,7 +454,7 @@ void G_WriteDemoExtraData(void) if (players[i].followerskin == -1) strncpy(name, "None", 16); else - strncpy(name, followers[players[i].followerskin].name, 16); + strlcpy(name, followers[players[i].followerskin].name, 16); M_Memcpy(demobuf.p, name, 16); demobuf.p += 16; @@ -465,7 +465,7 @@ void G_WriteDemoExtraData(void) if (Followercolor_cons_t[j].value == players[i].followercolor) break; } - strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + strlcpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" M_Memcpy(demobuf.p,name,16); demobuf.p += 16; @@ -1983,7 +1983,7 @@ static void G_SaveDemoSkins(UINT8 **pp) { // Skinname, for first attempt at identification. memset(skin, 0, 16); - strncpy(skin, skins[i].name, 16); + strlcpy(skin, skins[i].name, 16); WRITEMEM((*pp), skin, 16); // Backup information for second pass. @@ -2234,7 +2234,7 @@ void G_BeginRecording(void) // Name memset(name, 0, 16); - strncpy(name, player_names[p], 16); + strlcpy(name, player_names[p], 16); M_Memcpy(demobuf.p,name,16); demobuf.p += 16; From 995af69eab689b4a1cfffa848932227fb44eb57e Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 20 Feb 2024 17:39:03 -0600 Subject: [PATCH 7/8] y_inter.c -> y_inter.cpp --- src/CMakeLists.txt | 2 +- src/{y_inter.c => y_inter.cpp} | 120 +++++++++++++++++---------------- 2 files changed, 62 insertions(+), 60 deletions(-) rename src/{y_inter.c => y_inter.cpp} (89%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6d13a1462..bf9fa2cfc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 hu_stuff.c i_time.c i_video_common.cpp - y_inter.c + y_inter.cpp st_stuff.c m_aatree.c m_anigif.c diff --git a/src/y_inter.c b/src/y_inter.cpp similarity index 89% rename from src/y_inter.c rename to src/y_inter.cpp index ebf67be62..e6d0f90b3 100644 --- a/src/y_inter.c +++ b/src/y_inter.cpp @@ -9,6 +9,8 @@ /// \file y_inter.c /// \brief Tally screens, or "Intermissions" as they were formally called in Doom +#include + #include "doomdef.h" #include "doomstat.h" #include "d_main.h" @@ -308,7 +310,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.showrank = (rankforline >= GRADE_A); data.linemeter = - (min(rankforline, GRADE_A) + (std::min(rankforline, GRADE_A) * (2 * TICRATE) ) / GRADE_A; @@ -465,7 +467,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) INT32 hilicol = highlightflags; - patch_t *resbar = W_CachePatchName("R_RESBAR", PU_PATCH); // Results bars for players + patch_t *resbar = static_cast(W_CachePatchName("R_RESBAR", PU_PATCH)); // Results bars for players if (drawping || standings->rankingsmode != 0) { @@ -550,7 +552,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) UINT8 *charcolormap = NULL; if (standings->color[i] != SKINCOLOR_NONE) { - charcolormap = R_GetTranslationColormap(standings->character[i], standings->color[i], GTC_CACHE); + charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); } if (standings->isduel) @@ -558,8 +560,8 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) INT32 duelx = x + 22 + (datarightofcolumn ? inwardshim : -inwardshim); INT32 duely = y - 80; - V_DrawScaledPatch(duelx, duely, 0, W_CachePatchName("DUELGRPH", PU_CACHE)); - V_DrawScaledPatch(duelx + 8, duely + 9, V_TRANSLUCENT, W_CachePatchName("PREVBACK", PU_CACHE)); + V_DrawScaledPatch(duelx, duely, 0, static_cast(W_CachePatchName("DUELGRPH", PU_CACHE))); + V_DrawScaledPatch(duelx + 8, duely + 9, V_TRANSLUCENT, static_cast(W_CachePatchName("PREVBACK", PU_CACHE))); UINT8 spr2 = SPR2_STIN; if (standings->pos[i] == 2) @@ -591,7 +593,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) if (j > splitscreen) { - V_DrawScaledPatch(letterpos, duely, 0, W_CachePatchName(va("CHAR%s", (players[pnum].bot ? "CPU" : "EGGA")), PU_CACHE)); + V_DrawScaledPatch(letterpos, duely, 0, static_cast(W_CachePatchName(va("CHAR%s", (players[pnum].bot ? "CPU" : "EGGA")), PU_CACHE))); } else { @@ -599,12 +601,12 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) UINT8 profilen = cv_lastprofile[j].value; - V_DrawScaledPatch(duelx, duely, 0, W_CachePatchName("FILEBACK", PU_CACHE)); + V_DrawScaledPatch(duelx, duely, 0, static_cast(W_CachePatchName("FILEBACK", PU_CACHE))); if (datarightofcolumn && j == 0) letterpos++; // A is one pixel thinner - V_DrawScaledPatch(letterpos, duely, 0, W_CachePatchName(va("CHARSEL%c", 'A' + j), PU_CACHE)); + V_DrawScaledPatch(letterpos, duely, 0, static_cast(W_CachePatchName(va("CHARSEL%c", 'A' + j), PU_CACHE))); profile_t *pr = PR_GetProfile(profilen); @@ -628,13 +630,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) V_DrawMappedPatch( x+14, y-5, 0, - W_CachePatchName("MINIDEAD", PU_CACHE), - R_GetTranslationColormap(TC_DEFAULT, standings->color[i], GTC_CACHE) + static_cast(W_CachePatchName("MINIDEAD", PU_CACHE)), + R_GetTranslationColormap(TC_DEFAULT, static_cast(standings->color[i]), GTC_CACHE) ); } else { - charcolormap = R_GetTranslationColormap(standings->character[i], standings->color[i], GTC_CACHE); + charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); V_DrawMappedPatch(x+14, y-5, 0, faceprefix[standings->character[i]][FACE_MINIMAP], charcolormap); } } @@ -742,27 +744,27 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) } else if (standings->grade[pnum] != GRADE_INVALID) { - patch_t *gradePtc = W_CachePatchName(va("R_INRNK%c", K_GetGradeChar(standings->grade[pnum])), PU_PATCH); + patch_t *gradePtc = static_cast(W_CachePatchName(va("R_INRNK%c", K_GetGradeChar(static_cast(standings->grade[pnum]))), PU_PATCH)); patch_t *gradeBG = NULL; UINT16 gradeColor = SKINCOLOR_NONE; UINT8 *gradeClm = NULL; - gradeColor = K_GetGradeColor(standings->grade[pnum]); + gradeColor = K_GetGradeColor(static_cast(standings->grade[pnum])); if (gradeColor != SKINCOLOR_NONE) { - gradeClm = R_GetTranslationColormap(TC_DEFAULT, gradeColor, GTC_CACHE); + gradeClm = R_GetTranslationColormap(TC_DEFAULT, static_cast(gradeColor), GTC_CACHE); } if (datarightofcolumn) { - gradeBG = W_CachePatchName("R_INRNKR", PU_PATCH); + gradeBG = static_cast(W_CachePatchName("R_INRNKR", PU_PATCH)); V_DrawMappedPatch(x + 118, y, 0, gradeBG, gradeClm); V_DrawMappedPatch(x + 118 + 4, y - 1, 0, gradePtc, gradeClm); } else { - gradeBG = W_CachePatchName("R_INRNKL", PU_PATCH); + gradeBG = static_cast(W_CachePatchName("R_INRNKL", PU_PATCH)); V_DrawMappedPatch(x - 12, y, 0, gradeBG, gradeClm); V_DrawMappedPatch(x - 12 + 3, y - 1, 0, gradePtc, gradeClm); } @@ -828,27 +830,27 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, INT32 bufferspace = ((vid.width/vid.dupx) - BASEVIDWIDTH) / 2; // Background pieces - patch_t *queuebg_flat = W_CachePatchName("R_RMBG1", PU_PATCH); - patch_t *queuebg_upwa = W_CachePatchName("R_RMBG2", PU_PATCH); - patch_t *queuebg_down = W_CachePatchName("R_RMBG3", PU_PATCH); - patch_t *queuebg_prize = W_CachePatchName("R_RMBG4", PU_PATCH); + patch_t *queuebg_flat = static_cast(W_CachePatchName("R_RMBG1", PU_PATCH)); + patch_t *queuebg_upwa = static_cast(W_CachePatchName("R_RMBG2", PU_PATCH)); + patch_t *queuebg_down = static_cast(W_CachePatchName("R_RMBG3", PU_PATCH)); + patch_t *queuebg_prize = static_cast(W_CachePatchName("R_RMBG4", PU_PATCH)); // Progression lines patch_t *line_upwa[BPP_MAX]; patch_t *line_down[BPP_MAX]; patch_t *line_flat[BPP_MAX]; - line_upwa[BPP_AHEAD] = W_CachePatchName("R_RRMLN1", PU_PATCH); - line_upwa[BPP_DONE] = W_CachePatchName("R_RRMLN3", PU_PATCH); - line_upwa[BPP_SHADOW] = W_CachePatchName("R_RRMLS1", PU_PATCH); + line_upwa[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMLN1", PU_PATCH)); + line_upwa[BPP_DONE] = static_cast(W_CachePatchName("R_RRMLN3", PU_PATCH)); + line_upwa[BPP_SHADOW] = static_cast(W_CachePatchName("R_RRMLS1", PU_PATCH)); - line_down[BPP_AHEAD] = W_CachePatchName("R_RRMLN2", PU_PATCH); - line_down[BPP_DONE] = W_CachePatchName("R_RRMLN4", PU_PATCH); - line_down[BPP_SHADOW] = W_CachePatchName("R_RRMLS2", PU_PATCH); + line_down[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMLN2", PU_PATCH)); + line_down[BPP_DONE] = static_cast(W_CachePatchName("R_RRMLN4", PU_PATCH)); + line_down[BPP_SHADOW] = static_cast(W_CachePatchName("R_RRMLS2", PU_PATCH)); - line_flat[BPP_AHEAD] = W_CachePatchName("R_RRMLN5", PU_PATCH); - line_flat[BPP_DONE] = W_CachePatchName("R_RRMLN6", PU_PATCH); - line_flat[BPP_SHADOW] = W_CachePatchName("R_RRMLS3", PU_PATCH); + line_flat[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMLN5", PU_PATCH)); + line_flat[BPP_DONE] = static_cast(W_CachePatchName("R_RRMLN6", PU_PATCH)); + line_flat[BPP_SHADOW] = static_cast(W_CachePatchName("R_RRMLS3", PU_PATCH)); // Progress markers patch_t *level_dot[BPP_MAIN]; @@ -856,17 +858,17 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, patch_t *capsu_dot[BPP_MAIN]; patch_t *prize_dot[BPP_MAIN]; - level_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK2", PU_PATCH); - level_dot[BPP_DONE] = W_CachePatchName("R_RRMRK1", PU_PATCH); + level_dot[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMRK2", PU_PATCH)); + level_dot[BPP_DONE] = static_cast(W_CachePatchName("R_RRMRK1", PU_PATCH)); - bonus_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK7", PU_PATCH); - bonus_dot[BPP_DONE] = W_CachePatchName("R_RRMRK8", PU_PATCH); + bonus_dot[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMRK7", PU_PATCH)); + bonus_dot[BPP_DONE] = static_cast(W_CachePatchName("R_RRMRK8", PU_PATCH)); - capsu_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK3", PU_PATCH); - capsu_dot[BPP_DONE] = W_CachePatchName("R_RRMRK5", PU_PATCH); + capsu_dot[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMRK3", PU_PATCH)); + capsu_dot[BPP_DONE] = static_cast(W_CachePatchName("R_RRMRK5", PU_PATCH)); - prize_dot[BPP_AHEAD] = W_CachePatchName("R_RRMRK4", PU_PATCH); - prize_dot[BPP_DONE] = W_CachePatchName("R_RRMRK6", PU_PATCH); + prize_dot[BPP_AHEAD] = static_cast(W_CachePatchName("R_RRMRK4", PU_PATCH)); + prize_dot[BPP_DONE] = static_cast(W_CachePatchName("R_RRMRK6", PU_PATCH)); UINT8 *colormap = NULL, *oppositemap = NULL; fixed_t playerx = 0, playery = 0; @@ -891,8 +893,8 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, pcolor = players[standings->mainplayer].skincolor; } - colormap = R_GetTranslationColormap(TC_DEFAULT, pcolor, GTC_CACHE); - oppositemap = R_GetTranslationColormap(TC_DEFAULT, skincolors[pcolor].invcolor, GTC_CACHE); + colormap = R_GetTranslationColormap(TC_DEFAULT, static_cast(pcolor), GTC_CACHE); + oppositemap = R_GetTranslationColormap(TC_DEFAULT, static_cast(skincolors[pcolor].invcolor), GTC_CACHE); UINT8 workingqueuesize = roundqueue.size; boolean upwa = false; @@ -924,7 +926,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, SINT8 deferxoffs = 0; const INT32 desiredx2 = (290 + bufferspace); - spacetospecial = max(desiredx2 - widthofroundqueue - (24 - bufferspace), 16); + spacetospecial = std::max(desiredx2 - widthofroundqueue - (24 - bufferspace), 16); if (roundqueue.position == roundqueue.size) { @@ -1237,7 +1239,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, } else { - const fixed_t fillend = min((playerx / FRACUNIT) + 2, barend); + const fixed_t fillend = std::min((playerx / FRACUNIT) + 2, barend); while (xiter < fillend) { @@ -1353,8 +1355,8 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, if (playery != 0) { patch_t *rpmark[2]; - rpmark[0] = W_CachePatchName("R_RPMARK", PU_PATCH); - rpmark[1] = W_CachePatchName("R_R2MARK", PU_PATCH); + rpmark[0] = static_cast(W_CachePatchName("R_RPMARK", PU_PATCH)); + rpmark[1] = static_cast(W_CachePatchName("R_R2MARK", PU_PATCH)); // Change alignment playerx -= (10 * FRACUNIT); @@ -1377,7 +1379,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, FRACUNIT, baseflags, faceprefix[pskin][FACE_RANK], - R_GetTranslationColormap(pskin, pcolor, GTC_CACHE) + R_GetTranslationColormap(pskin, static_cast(pcolor), GTC_CACHE) ); } else @@ -1473,7 +1475,7 @@ void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const ch } // Header bar - patch_t *rtpbr = W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH); + patch_t *rtpbr = static_cast(W_CachePatchName((small ? "R_RTPB4" : "R_RTPBR"), PU_PATCH)); V_DrawFixedPatch((20 * frac) + x, (24 * frac) + y, FRACUNIT, small_flag, rtpbr, NULL); fixed_t headerx, headery, headerwidth = 0; @@ -1517,7 +1519,7 @@ void Y_DrawIntermissionHeader(fixed_t x, fixed_t y, boolean gotthrough, const ch if (gotthrough) { // GOT THROUGH ROUND - patch_t *gthro = W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH); + patch_t *gthro = static_cast(W_CachePatchName((small ? "R_GTHR4" : "R_GTHRO"), PU_PATCH)); V_DrawFixedPatch((50 * frac) + x, (42 * frac) + y, FRACUNIT, small_flag, gthro, NULL); } @@ -1594,15 +1596,15 @@ void Y_IntermissionDrawer(void) fixed_t x; // Checker scroll - patch_t *rbgchk = W_CachePatchName("R_RBGCHK", PU_PATCH); + patch_t *rbgchk = static_cast(W_CachePatchName("R_RBGCHK", PU_PATCH)); // Scrolling marquee - patch_t *rrmq = W_CachePatchName("R_RRMQ", PU_PATCH); + patch_t *rrmq = static_cast(W_CachePatchName("R_RRMQ", PU_PATCH)); fixed_t mqloop = SHORT(rrmq->width)*FRACUNIT; fixed_t chkloop = SHORT(rbgchk->width)*FRACUNIT; - UINT8 *bgcolor = R_GetTranslationColormap(TC_INTERMISSION, 0, GTC_CACHE); + UINT8 *bgcolor = R_GetTranslationColormap(TC_INTERMISSION, static_cast(0), GTC_CACHE); // Draw the background K_DrawMapThumbnail(0, 0, BASEVIDWIDTH<intermission; + intertype = static_cast(gametypes[gametype]->intermission); // special cases if (intertype == int_scoreortimeattack) @@ -2060,7 +2062,7 @@ void Y_StartIntermission(void) else { // Minimum two seconds for match results, then two second slideover approx halfway through - sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); + sorttic = std::max((timer/2) - 2*TICRATE, 2*TICRATE); } // TODO: code's a mess, I'm just making it extra clear @@ -2147,8 +2149,8 @@ void Y_StartIntermission(void) } Automate_Run(AEV_INTERMISSIONSTART); - bgpatch = W_CachePatchName("MENUBG", PU_STATIC); - widebgpatch = W_CachePatchName("WEIRDRES", PU_STATIC); + bgpatch = static_cast(W_CachePatchName("MENUBG", PU_STATIC)); + widebgpatch = static_cast(W_CachePatchName("WEIRDRES", PU_STATIC)); } // ====== From 32715fbe66e7e75426e41aeb2b7a963e24cdcc79 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 20 Feb 2024 18:23:38 -0600 Subject: [PATCH 8/8] Rewrite replay standings extrainfo --- src/g_demo.cpp | 201 +++++++++++++++++++++++++++++++++++------------- src/g_demo.h | 39 +++++++++- src/y_inter.cpp | 22 ++++-- 3 files changed, 200 insertions(+), 62 deletions(-) diff --git a/src/g_demo.cpp b/src/g_demo.cpp index a7d4874c3..4c089acec 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -12,6 +12,10 @@ /// \brief Demo recording and playback #include +#include + +#include +#include #include "doomdef.h" #include "console.h" @@ -158,6 +162,7 @@ static ticcmd_t oldcmd[MAXPLAYERS]; // Below consts are only used for demo extrainfo sections #define DW_STANDING 0x00 +#define DW_STANDING2 0x01 // For time attack ghosts #define GZT_XYZ 0x01 @@ -2326,36 +2331,26 @@ void G_BeginRecording(void) } } -void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val) +void srb2::write_current_demo_standings(const srb2::StandingsJson& standings) { - char temp[16]; + using namespace srb2; + using json = nlohmann::json; - if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) - { - WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker - *(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer; - } + // TODO populate standings data - WRITEUINT8(demobuf.p, DW_STANDING); - WRITEUINT8(demobuf.p, ranking); + std::vector ubjson = json::to_ubjson(standings); + uint32_t bytes = ubjson.size(); - // Name - memset(temp, 0, 16); - strncpy(temp, name, 16); - M_Memcpy(demobuf.p,temp,16); - demobuf.p += 16; + WRITEUINT8(demobuf.p, DW_STANDING2); - // Skin - WRITEUINT8(demobuf.p, skinnum); + WRITEUINT32(demobuf.p, bytes); + WRITEMEM(demobuf.p, ubjson.data(), bytes); +} - // Color - memset(temp, 0, 16); - strncpy(temp, skincolors[color].name, 16); - M_Memcpy(demobuf.p,temp,16); - demobuf.p += 16; - - // Score/time/whatever - WRITEUINT32(demobuf.p, val); +void srb2::write_current_demo_end_marker() +{ + WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker + *(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer; } void G_SetDemoTime(UINT32 ptime, UINT32 plap) @@ -2510,6 +2505,52 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) return c; } +static bool load_ubjson_standing(menudemo_t* pdemo, tcb::span slice, tcb::span demoskins) +{ + using namespace srb2; + using json = nlohmann::json; + + StandingsJson js; + try + { + js = json::from_ubjson(slice).template get(); + } + catch (...) + { + return false; + } + + size_t toread = std::min(js.standings.size(), MAXPLAYERS); + for (size_t i = 0; i < toread; i++) + { + StandingJson& jsstanding = js.standings[i]; + auto& memstanding = pdemo->standings[i]; + memstanding.ranking = jsstanding.ranking; + strlcpy(memstanding.name, jsstanding.name.c_str(), 17); + if (jsstanding.demoskin >= demoskins.size()) + { + memstanding.skin = demoskins[0].mapping; + } + else + { + memstanding.skin = demoskins[jsstanding.demoskin].mapping; + } + memstanding.color = SKINCOLOR_NONE; + for (size_t j = 0; j < numskincolors; j++) + { + skincolor_t& skincolor = skincolors[j]; + if (jsstanding.skincolor == skincolor.name) + { + memstanding.color = j; + break; + } + } + memstanding.timeorscore = jsstanding.timeorscore; + } + + return true; +} + void G_LoadDemoInfo(menudemo_t *pdemo) { savebuffer_t info = {0}; @@ -2518,6 +2559,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) UINT16 pdemoflags; democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; + UINT16 legacystandingplayercount; char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; INT32 i; @@ -2670,44 +2712,95 @@ void G_LoadDemoInfo(menudemo_t *pdemo) pdemo->gp = true; // Read standings! - count = 0; + legacystandingplayercount = 0; info.p = extrainfo_p; - while (P_SaveBufferRemaining(&info) >= 1+1+16+1+16+4 && - READUINT8(info.p) == DW_STANDING) // Assume standings are always first in the extrainfo + while (P_SaveBufferRemaining(&info) > 1) { - char temp[16]; + UINT8 extrainfotag = READUINT8(info.p); - pdemo->standings[count].ranking = READUINT8(info.p); - - // Name - M_Memcpy(pdemo->standings[count].name, info.p, 16); - info.p += 16; - - // Skin - skinid = READUINT8(info.p); - if (skinid > worknumskins) - skinid = 0; - pdemo->standings[count].skin = skinlist[skinid].mapping; - - // Color - M_Memcpy(temp,info.p,16); - info.p += 16; - for (i = 0; i < numskincolors; i++) - if (!stricmp(skincolors[i].name,temp)) // SRB2kart + switch (extrainfotag) + { + case DW_STANDING: { - pdemo->standings[count].color = i; + // This is the only extrainfo tag that is not length prefixed. All others must be. + constexpr size_t kLegacyStandingSize = 1+16+1+16+4; + if (P_SaveBufferRemaining(&info) < kLegacyStandingSize) + { + goto corrupt; + } + if (legacystandingplayercount >= MAXPLAYERS) + { + info.p += kLegacyStandingSize; + break; // switch + } + char temp[16]; + + pdemo->standings[legacystandingplayercount].ranking = READUINT8(info.p); + + // Name + M_Memcpy(pdemo->standings[legacystandingplayercount].name, info.p, 16); + info.p += 16; + + // Skin + skinid = READUINT8(info.p); + if (skinid > worknumskins) + skinid = 0; + pdemo->standings[legacystandingplayercount].skin = skinlist[skinid].mapping; + + // Color + M_Memcpy(temp,info.p,16); + info.p += 16; + for (i = 0; i < numskincolors; i++) + if (!stricmp(skincolors[i].name,temp)) // SRB2kart + { + pdemo->standings[legacystandingplayercount].color = i; + break; + } + + // Score/time/whatever + pdemo->standings[legacystandingplayercount].timeorscore = READUINT32(info.p); + + legacystandingplayercount++; break; } - - // Score/time/whatever - pdemo->standings[count].timeorscore = READUINT32(info.p); - - count++; - - if (count >= MAXPLAYERS) - break; //@TODO still cycle through the rest of these if extra demo data is ever used + case DW_STANDING2: + { + if (P_SaveBufferRemaining(&info) < 4) + { + goto corrupt; + } + UINT32 size = READUINT32(info.p); + if (P_SaveBufferRemaining(&info) < size) + { + goto corrupt; + } + tcb::span slice = tcb::as_writable_bytes(tcb::span(info.p, size)); + tcb::span demoskins {skinlist, worknumskins}; + info.p += size; + if (!load_ubjson_standing(pdemo, slice, demoskins)) + { + goto corrupt; + } + break; + } + default: + { + // Gracefully ignore other extrainfo tags by skipping their data + if (P_SaveBufferRemaining(&info) < 4) + { + goto corrupt; + } + UINT32 size = READUINT32(info.p); + if (P_SaveBufferRemaining(&info) < size) + { + goto corrupt; + } + info.p += size; + break; + } + } } if (P_SaveBufferRemaining(&info) == 0) diff --git a/src/g_demo.h b/src/g_demo.h index 808f3ff25..68d7bb6ed 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -19,6 +19,44 @@ #include "d_event.h" #ifdef __cplusplus + +#include +#include + +#include + +// Modern json formats +namespace srb2 +{ +struct StandingJson +{ + uint8_t ranking; + std::string name; + uint8_t demoskin; + std::string skincolor; + uint32_t timeorscore; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + StandingJson, + ranking, + name, + demoskin, + skincolor, + timeorscore + ) +}; +struct StandingsJson +{ + std::vector standings; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings) +}; + +void write_current_demo_standings(const StandingsJson& standings); +void write_current_demo_end_marker(); + +} // namespace srb2 + extern "C" { #endif @@ -105,7 +143,6 @@ void G_RecordDemo(const char *name); void G_BeginRecording(void); // Only called by shutdown code. -void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val); void G_SetDemoTime(UINT32 ptime, UINT32 plap); UINT8 G_CmpDemoTime(char *oldname, char *newname); diff --git a/src/y_inter.cpp b/src/y_inter.cpp index e6d0f90b3..1822debde 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -196,6 +196,8 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.isduel = (numplayersingame <= 2); + srb2::StandingsJson standings {}; + for (j = 0; j < numplayersingame; j++) { for (i = 0; i < MAXPLAYERS; i++) @@ -240,13 +242,13 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) if (demo.recording) { - G_WriteStanding( - data.pos[data.numplayers], - player_names[i], - data.character[data.numplayers], - data.color[data.numplayers], - data.val[data.numplayers] - ); + srb2::StandingJson standing {}; + standing.ranking = data.pos[data.numplayers]; + standing.name = std::string(player_names[i]); + standing.demoskin = data.character[data.numplayers]; + standing.skincolor = std::string(skincolors[data.color[data.numplayers]].name); + standing.timeorscore = data.val[data.numplayers]; + standings.standings.emplace_back(std::move(standing)); } if (data.val[data.numplayers] == (UINT32_MAX-1)) @@ -284,6 +286,12 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.numplayers++; } + if (demo.recording) + { + srb2::write_current_demo_end_marker(); + srb2::write_current_demo_standings(standings); + } + if (getmainplayer == true) { // Okay, player scores have been set now - we can calculate GP-relevant material.