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.