From c4790068ef5363a990c37e6383754e69487d57cd Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 21 Oct 2023 20:44:01 -0700 Subject: [PATCH] fix cross-version lobby appearance and name colors --- src/ChatCommands.cc | 4 +- src/CommandFormats.hh | 5 +- src/PlayerSubordinates.cc | 134 +++++++++++++++++++++++++++++++++----- src/PlayerSubordinates.hh | 17 ++++- src/QuestScript.cc | 116 ++++++++++++++++----------------- src/ReplaySession.cc | 50 ++++++++++++++ src/SendCommands.cc | 5 ++ src/StaticGameData.cc | 9 +++ src/StaticGameData.hh | 2 + 9 files changed, 259 insertions(+), 83 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 530187e7..b9cdc631 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -834,7 +834,7 @@ static void server_command_edit(shared_ptr c, const std::u16string& args } else if (tokens.at(0) == "npc") { if (tokens.at(1) == "none") { p->disp.visual.extra_model = 0; - p->disp.visual.v2_flags &= 0xFD; + p->disp.visual.validation_flags &= 0xFD; } else { uint8_t npc = npc_for_name(decode_sjis(tokens.at(1))); if (npc == 0xFF) { @@ -842,7 +842,7 @@ static void server_command_edit(shared_ptr c, const std::u16string& args return; } p->disp.visual.extra_model = npc; - p->disp.visual.v2_flags |= 0x02; + p->disp.visual.validation_flags |= 0x02; } } else if (tokens.at(0) == "tech") { uint8_t level = stoul(tokens.at(2)) - 1; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 299ab6fc..aa8aa57a 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -1258,10 +1258,11 @@ struct S_JoinGame_GC_Ep3_64 : S_JoinGame_GC_64 { // This field is only present if the game (and client) is Episode 3. Similarly // to lobby_data in the base struct, all four of these are always present and // they are filled in in slot positions. - struct { + struct Ep3PlayerEntry { PlayerInventory inventory; PlayerDispDataDCPCV3 disp; - } __packed__ players_ep3[4]; + } __packed__; + parray players_ep3; } __packed__; struct S_JoinGame_XB_64 : S_JoinGame_DC_PC { diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index f631ebfd..5e6f4338 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include "ItemData.hh" @@ -19,29 +20,126 @@ using namespace std; FileContentsCache player_files_cache(300 * 1000 * 1000); +uint32_t PlayerVisualConfig::compute_name_color_checksum(uint32_t name_color) { + uint8_t x = (random_object() % 0xFF) + 1; + uint8_t y = (random_object() % 0xFF) + 1; + // name_color = ABCDEFGHabcdefghIJKLMNOPijklmnop + // name_color_checksum = ---------ijklmabcdeIJKLM-------- ^ (xxxxxxxxyyyyyyyyxxxxxxxxyyyyyyyy) + uint32_t xbrgx95558 = ((name_color << 15) & 0x007C0000) | ((name_color >> 6) & 0x0003E000) | ((name_color >> 3) & 0x00001F00); + uint32_t mask = (x << 24) | (y << 16) | (x << 8) | y; + return xbrgx95558 ^ mask; +} + +void PlayerVisualConfig::compute_name_color_checksum() { + this->name_color_checksum = this->compute_name_color_checksum(this->name_color); +} + void PlayerDispDataDCPCV3::enforce_lobby_join_limits(GameVersion target_version) { + struct ClassMaxes { + uint16_t costume; + uint16_t skin; + uint16_t face; + uint16_t head; + uint16_t hair; + uint16_t hair_r; + uint16_t hair_g; + uint16_t hair_b; + }; + static constexpr ClassMaxes v1_v2_class_maxes[14] = { + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0009, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x0007, 0x0100, 0x0100, 0x0100}, + {0x0009, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + }; + static constexpr ClassMaxes v3_v4_class_maxes[19] = { + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0019, 0x0000, 0x0005, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0012, 0x0004, 0x0005, 0x0000, 0x000A, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0100, 0x0100, 0x0100}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}, + {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}}; + + const ClassMaxes* maxes; if ((target_version == GameVersion::PC) || (target_version == GameVersion::DC)) { // V1/V2 have fewer classes, so we'll substitute some here - if (this->visual.char_class == 9) { - this->visual.char_class = 5; // HUcaseal -> RAcaseal - } else if (this->visual.char_class == 10) { - this->visual.char_class = 0; // FOmar -> HUmar - } else if (this->visual.char_class == 11) { - this->visual.char_class = 1; // RAmarl -> HUnewearl + switch (this->visual.char_class) { + case 0: // HUmar + case 1: // HUnewearl + case 2: // HUcast + case 3: // RAmar + case 4: // RAcast + case 5: // RAcaseal + case 6: // FOmarl + case 7: // FOnewm + case 8: // FOnewearl + case 12: // V3 custom 1 + case 13: // V3 custom 2 + break; + case 9: // HUcaseal + this->visual.char_class = 5; // HUcaseal -> RAcaseal + break; + case 10: // FOmar + this->visual.char_class = 0; // FOmar -> HUmar + break; + case 11: // RAmarl + this->visual.char_class = 1; // RAmarl -> HUnewearl + break; + case 14: // V2 custom 1 / V3 custom 3 + case 15: // V2 custom 2 / V3 custom 4 + case 16: // V2 custom 3 / V3 custom 5 + case 17: // V2 custom 4 / V3 custom 6 + case 18: // V2 custom 5 / V3 custom 7 + this->visual.char_class -= 5; + break; + default: + this->visual.char_class = 0; // Invalid classes -> HUmar } - // V1/V2 has fewer costumes and android skins, so substitute them here too - this->visual.costume %= 9; - this->visual.skin %= 9; - - // If the player is somehow still not a valid class, make them appear as the - // "ninja" NPC - if (this->visual.char_class > 8) { - this->visual.extra_model = 0; - this->visual.v2_flags |= 2; - } + maxes = &v1_v2_class_maxes[this->visual.char_class]; this->visual.version = 2; + + } else { + if (this->visual.char_class >= 19) { + this->visual.char_class = 0; // Invalid classes -> HUmar + } + maxes = &v3_v4_class_maxes[this->visual.char_class]; } + + // V1/V2 has fewer costumes and android skins, so substitute them here + this->visual.costume %= maxes->costume; + this->visual.skin %= maxes->skin; + this->visual.face %= maxes->face; + this->visual.head %= maxes->head; + this->visual.hair %= maxes->hair; + this->visual.hair_r %= maxes->hair_r; + this->visual.hair_g %= maxes->hair_g; + this->visual.hair_b %= maxes->hair_b; + + this->visual.compute_name_color_checksum(); + this->visual.class_flags = class_flags_for_class(this->visual.char_class); } void PlayerDispDataBB::enforce_lobby_join_limits(GameVersion) { @@ -90,10 +188,10 @@ void PlayerDispDataBB::apply_preview(const PlayerDispDataBBPreview& pre) { void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) { this->visual.name_color = pre.visual.name_color; this->visual.extra_model = pre.visual.extra_model; - this->visual.unknown_a3 = pre.visual.unknown_a3; + this->visual.name_color_checksum = pre.visual.name_color_checksum; this->visual.section_id = pre.visual.section_id; this->visual.char_class = pre.visual.char_class; - this->visual.v2_flags = pre.visual.v2_flags; + this->visual.validation_flags = pre.visual.validation_flags; this->visual.version = pre.visual.version; this->visual.class_flags = pre.visual.class_flags; this->visual.costume = pre.visual.costume; diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 13077318..cc2a3717 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -104,13 +104,21 @@ struct PlayerDispDataBB; struct PlayerVisualConfig { /* 00 */ ptext name; /* 10 */ parray unknown_a2; - /* 18 */ le_uint32_t name_color = 0x00000000; // RGBA + /* 18 */ le_uint32_t name_color = 0x00000000; // ARGB /* 1C */ uint8_t extra_model = 0; /* 1D */ parray unused; - /* 2C */ le_uint32_t unknown_a3 = 0; + // See compute_name_color_checksum for details on how this is computed. This + // field is ignored on V3 and BB. + /* 2C */ le_uint32_t name_color_checksum = 0; /* 30 */ uint8_t section_id = 0; /* 31 */ uint8_t char_class = 0; - /* 32 */ uint8_t v2_flags = 0; + // validation_flags specifies that some parts of this structure are not valid + // and should be ignored. The bits are: + // -----FCS + // F = class_flags is incorrect for the character's char_class value + // C = char_class is out of range + // S = section_id is out of range + /* 32 */ uint8_t validation_flags = 0; /* 33 */ uint8_t version = 0; // class_flags specifies features of the character's class. The bits are: // -------- -------- -------- FRHANMfm @@ -129,6 +137,9 @@ struct PlayerVisualConfig { /* 48 */ le_float proportion_x = 0.0; /* 4C */ le_float proportion_y = 0.0; /* 50 */ + + static uint32_t compute_name_color_checksum(uint32_t name_color); + void compute_name_color_checksum(); } __attribute__((packed)); struct PlayerDispDataDCPCV3 { diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 1d832ed0..21474c75 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -1398,76 +1398,76 @@ std::string disassemble_quest_script(const void* data, size_t size, QuestScriptV print_as_struct.template operator()([&](const PlayerVisualConfig& visual) -> void { lines.emplace_back(" // As PlayerVisualConfig"); string name = format_data_string(visual.name); - lines.emplace_back(string_printf(" %04zX name %s", l->offset + offsetof(PlayerVisualConfig, name), name.c_str())); - lines.emplace_back(string_printf(" %04zX name_color %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color.load())); + lines.emplace_back(string_printf(" %04zX name %s", l->offset + offsetof(PlayerVisualConfig, name), name.c_str())); + lines.emplace_back(string_printf(" %04zX name_color %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color.load())); string a2_str = format_data_string(visual.unknown_a2.data(), sizeof(visual.unknown_a2)); - lines.emplace_back(string_printf(" %04zX a2 %s", l->offset + offsetof(PlayerVisualConfig, unknown_a2), a2_str.c_str())); - lines.emplace_back(string_printf(" %04zX extra_model %02hhX", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model)); + lines.emplace_back(string_printf(" %04zX a2 %s", l->offset + offsetof(PlayerVisualConfig, unknown_a2), a2_str.c_str())); + lines.emplace_back(string_printf(" %04zX extra_model %02hhX", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model)); string unused = format_data_string(visual.unused.data(), visual.unused.bytes()); - lines.emplace_back(string_printf(" %04zX unused %s", l->offset + offsetof(PlayerVisualConfig, unused), unused.c_str())); - lines.emplace_back(string_printf(" %04zX a3 %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, unknown_a3), visual.unknown_a3.load())); + lines.emplace_back(string_printf(" %04zX unused %s", l->offset + offsetof(PlayerVisualConfig, unused), unused.c_str())); + lines.emplace_back(string_printf(" %04zX name_color_checksum %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color_checksum), visual.name_color_checksum.load())); string secid_name = name_for_section_id(visual.section_id); - lines.emplace_back(string_printf(" %04zX section_id %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, secid_name.c_str())); - lines.emplace_back(string_printf(" %04zX char_class %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class))); - lines.emplace_back(string_printf(" %04zX v2_flags %02hhX", l->offset + offsetof(PlayerVisualConfig, v2_flags), visual.v2_flags)); - lines.emplace_back(string_printf(" %04zX version %02hhX", l->offset + offsetof(PlayerVisualConfig, version), visual.version)); - lines.emplace_back(string_printf(" %04zX class_flags %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags.load())); - lines.emplace_back(string_printf(" %04zX costume %04hX", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume.load())); - lines.emplace_back(string_printf(" %04zX skin %04hX", l->offset + offsetof(PlayerVisualConfig, skin), visual.skin.load())); - lines.emplace_back(string_printf(" %04zX face %04hX", l->offset + offsetof(PlayerVisualConfig, face), visual.face.load())); - lines.emplace_back(string_printf(" %04zX head %04hX", l->offset + offsetof(PlayerVisualConfig, head), visual.head.load())); - lines.emplace_back(string_printf(" %04zX hair %04hX", l->offset + offsetof(PlayerVisualConfig, hair), visual.hair.load())); - lines.emplace_back(string_printf(" %04zX hair_color %04hX, %04hX, %04hX", l->offset + offsetof(PlayerVisualConfig, hair_r), visual.hair_r.load(), visual.hair_g.load(), visual.hair_b.load())); - lines.emplace_back(string_printf(" %04zX proportion %g, %g", l->offset + offsetof(PlayerVisualConfig, proportion_x), visual.proportion_x.load(), visual.proportion_y.load())); + lines.emplace_back(string_printf(" %04zX section_id %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, secid_name.c_str())); + lines.emplace_back(string_printf(" %04zX char_class %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class))); + lines.emplace_back(string_printf(" %04zX validation_flags %02hhX", l->offset + offsetof(PlayerVisualConfig, validation_flags), visual.validation_flags)); + lines.emplace_back(string_printf(" %04zX version %02hhX", l->offset + offsetof(PlayerVisualConfig, version), visual.version)); + lines.emplace_back(string_printf(" %04zX class_flags %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags.load())); + lines.emplace_back(string_printf(" %04zX costume %04hX", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume.load())); + lines.emplace_back(string_printf(" %04zX skin %04hX", l->offset + offsetof(PlayerVisualConfig, skin), visual.skin.load())); + lines.emplace_back(string_printf(" %04zX face %04hX", l->offset + offsetof(PlayerVisualConfig, face), visual.face.load())); + lines.emplace_back(string_printf(" %04zX head %04hX", l->offset + offsetof(PlayerVisualConfig, head), visual.head.load())); + lines.emplace_back(string_printf(" %04zX hair %04hX", l->offset + offsetof(PlayerVisualConfig, hair), visual.hair.load())); + lines.emplace_back(string_printf(" %04zX hair_color %04hX, %04hX, %04hX", l->offset + offsetof(PlayerVisualConfig, hair_r), visual.hair_r.load(), visual.hair_g.load(), visual.hair_b.load())); + lines.emplace_back(string_printf(" %04zX proportion %g, %g", l->offset + offsetof(PlayerVisualConfig, proportion_x), visual.proportion_x.load(), visual.proportion_y.load())); }); print_as_struct.template operator()([&](const PlayerStats& stats) -> void { lines.emplace_back(" // As PlayerStats"); - lines.emplace_back(string_printf(" %04zX atp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.atp), stats.char_stats.atp.load(), stats.char_stats.atp.load())); - lines.emplace_back(string_printf(" %04zX mst %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.mst), stats.char_stats.mst.load(), stats.char_stats.mst.load())); - lines.emplace_back(string_printf(" %04zX evp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.evp), stats.char_stats.evp.load(), stats.char_stats.evp.load())); - lines.emplace_back(string_printf(" %04zX hp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.hp), stats.char_stats.hp.load(), stats.char_stats.hp.load())); - lines.emplace_back(string_printf(" %04zX dfp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.dfp), stats.char_stats.dfp.load(), stats.char_stats.dfp.load())); - lines.emplace_back(string_printf(" %04zX ata %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.ata), stats.char_stats.ata.load(), stats.char_stats.ata.load())); - lines.emplace_back(string_printf(" %04zX lck %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.lck), stats.char_stats.lck.load(), stats.char_stats.lck.load())); - lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hu */", l->offset + offsetof(PlayerStats, unknown_a1), stats.unknown_a1.load(), stats.unknown_a1.load())); - lines.emplace_back(string_printf(" %04zX a2 %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, unknown_a2), stats.unknown_a2.load_raw(), stats.unknown_a2.load())); - lines.emplace_back(string_printf(" %04zX a3 %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, unknown_a3), stats.unknown_a3.load_raw(), stats.unknown_a3.load())); - lines.emplace_back(string_printf(" %04zX level %08" PRIX32 " /* level %" PRIu32 " */", l->offset + offsetof(PlayerStats, level), stats.level.load(), stats.level.load() + 1)); - lines.emplace_back(string_printf(" %04zX experience %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, experience), stats.experience.load(), stats.experience.load())); - lines.emplace_back(string_printf(" %04zX meseta %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, meseta), stats.meseta.load(), stats.meseta.load())); + lines.emplace_back(string_printf(" %04zX atp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.atp), stats.char_stats.atp.load(), stats.char_stats.atp.load())); + lines.emplace_back(string_printf(" %04zX mst %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.mst), stats.char_stats.mst.load(), stats.char_stats.mst.load())); + lines.emplace_back(string_printf(" %04zX evp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.evp), stats.char_stats.evp.load(), stats.char_stats.evp.load())); + lines.emplace_back(string_printf(" %04zX hp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.hp), stats.char_stats.hp.load(), stats.char_stats.hp.load())); + lines.emplace_back(string_printf(" %04zX dfp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.dfp), stats.char_stats.dfp.load(), stats.char_stats.dfp.load())); + lines.emplace_back(string_printf(" %04zX ata %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.ata), stats.char_stats.ata.load(), stats.char_stats.ata.load())); + lines.emplace_back(string_printf(" %04zX lck %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.lck), stats.char_stats.lck.load(), stats.char_stats.lck.load())); + lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hu */", l->offset + offsetof(PlayerStats, unknown_a1), stats.unknown_a1.load(), stats.unknown_a1.load())); + lines.emplace_back(string_printf(" %04zX a2 %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, unknown_a2), stats.unknown_a2.load_raw(), stats.unknown_a2.load())); + lines.emplace_back(string_printf(" %04zX a3 %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, unknown_a3), stats.unknown_a3.load_raw(), stats.unknown_a3.load())); + lines.emplace_back(string_printf(" %04zX level %08" PRIX32 " /* level %" PRIu32 " */", l->offset + offsetof(PlayerStats, level), stats.level.load(), stats.level.load() + 1)); + lines.emplace_back(string_printf(" %04zX experience %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, experience), stats.experience.load(), stats.experience.load())); + lines.emplace_back(string_printf(" %04zX meseta %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, meseta), stats.meseta.load(), stats.meseta.load())); }); print_as_struct.template operator()([&](const ResistData& resist) -> void { lines.emplace_back(" // As ResistData"); - lines.emplace_back(string_printf(" %04zX evp_bonus %04hX /* %hu */", l->offset + offsetof(ResistData, evp_bonus), resist.evp_bonus.load(), resist.evp_bonus.load())); - lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a1), resist.unknown_a1.load(), resist.unknown_a1.load())); - lines.emplace_back(string_printf(" %04zX a2 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a2), resist.unknown_a2.load(), resist.unknown_a2.load())); - lines.emplace_back(string_printf(" %04zX a3 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a3), resist.unknown_a3.load(), resist.unknown_a3.load())); - lines.emplace_back(string_printf(" %04zX a4 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a4), resist.unknown_a4.load(), resist.unknown_a4.load())); - lines.emplace_back(string_printf(" %04zX a5 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a5), resist.unknown_a5.load(), resist.unknown_a5.load())); - lines.emplace_back(string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a6), resist.unknown_a6.load(), resist.unknown_a6.load())); - lines.emplace_back(string_printf(" %04zX a7 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a7), resist.unknown_a7.load(), resist.unknown_a7.load())); - lines.emplace_back(string_printf(" %04zX a8 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a8), resist.unknown_a8.load(), resist.unknown_a8.load())); - lines.emplace_back(string_printf(" %04zX a9 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a9), resist.unknown_a9.load(), resist.unknown_a9.load())); - lines.emplace_back(string_printf(" %04zX dfp_bonus %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, dfp_bonus), resist.dfp_bonus.load(), resist.dfp_bonus.load())); + lines.emplace_back(string_printf(" %04zX evp_bonus %04hX /* %hu */", l->offset + offsetof(ResistData, evp_bonus), resist.evp_bonus.load(), resist.evp_bonus.load())); + lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a1), resist.unknown_a1.load(), resist.unknown_a1.load())); + lines.emplace_back(string_printf(" %04zX a2 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a2), resist.unknown_a2.load(), resist.unknown_a2.load())); + lines.emplace_back(string_printf(" %04zX a3 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a3), resist.unknown_a3.load(), resist.unknown_a3.load())); + lines.emplace_back(string_printf(" %04zX a4 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a4), resist.unknown_a4.load(), resist.unknown_a4.load())); + lines.emplace_back(string_printf(" %04zX a5 %04hX /* %hu */", l->offset + offsetof(ResistData, unknown_a5), resist.unknown_a5.load(), resist.unknown_a5.load())); + lines.emplace_back(string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a6), resist.unknown_a6.load(), resist.unknown_a6.load())); + lines.emplace_back(string_printf(" %04zX a7 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a7), resist.unknown_a7.load(), resist.unknown_a7.load())); + lines.emplace_back(string_printf(" %04zX a8 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a8), resist.unknown_a8.load(), resist.unknown_a8.load())); + lines.emplace_back(string_printf(" %04zX a9 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a9), resist.unknown_a9.load(), resist.unknown_a9.load())); + lines.emplace_back(string_printf(" %04zX dfp_bonus %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, dfp_bonus), resist.dfp_bonus.load(), resist.dfp_bonus.load())); }); print_as_struct.template operator()([&](const AttackData& attack) -> void { lines.emplace_back(" // As AttackData"); - lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hd */", l->offset + offsetof(AttackData, unknown_a1), attack.unknown_a1.load(), attack.unknown_a1.load())); - lines.emplace_back(string_printf(" %04zX a2 %04hX /* %hd */", l->offset + offsetof(AttackData, unknown_a2), attack.unknown_a2.load(), attack.unknown_a2.load())); - lines.emplace_back(string_printf(" %04zX a3 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a3), attack.unknown_a3.load(), attack.unknown_a3.load())); - lines.emplace_back(string_printf(" %04zX a4 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a4), attack.unknown_a4.load(), attack.unknown_a4.load())); - lines.emplace_back(string_printf(" %04zX a5 %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, unknown_a5), attack.unknown_a5.load_raw(), attack.unknown_a5.load())); - lines.emplace_back(string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a6), attack.unknown_a6.load(), attack.unknown_a6.load())); - lines.emplace_back(string_printf(" %04zX a7 %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, unknown_a7), attack.unknown_a7.load_raw(), attack.unknown_a7.load())); - lines.emplace_back(string_printf(" %04zX a8 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a8), attack.unknown_a8.load(), attack.unknown_a8.load())); - lines.emplace_back(string_printf(" %04zX a9 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a9), attack.unknown_a9.load(), attack.unknown_a9.load())); - lines.emplace_back(string_printf(" %04zX a10 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a10), attack.unknown_a10.load(), attack.unknown_a10.load())); - lines.emplace_back(string_printf(" %04zX a11 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a11), attack.unknown_a11.load(), attack.unknown_a11.load())); - lines.emplace_back(string_printf(" %04zX a12 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a12), attack.unknown_a12.load(), attack.unknown_a12.load())); - lines.emplace_back(string_printf(" %04zX a13 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a13), attack.unknown_a13.load(), attack.unknown_a13.load())); - lines.emplace_back(string_printf(" %04zX a14 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a14), attack.unknown_a14.load(), attack.unknown_a14.load())); - lines.emplace_back(string_printf(" %04zX a15 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a15), attack.unknown_a15.load(), attack.unknown_a15.load())); - lines.emplace_back(string_printf(" %04zX a16 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a16), attack.unknown_a16.load(), attack.unknown_a16.load())); + lines.emplace_back(string_printf(" %04zX a1 %04hX /* %hd */", l->offset + offsetof(AttackData, unknown_a1), attack.unknown_a1.load(), attack.unknown_a1.load())); + lines.emplace_back(string_printf(" %04zX a2 %04hX /* %hd */", l->offset + offsetof(AttackData, unknown_a2), attack.unknown_a2.load(), attack.unknown_a2.load())); + lines.emplace_back(string_printf(" %04zX a3 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a3), attack.unknown_a3.load(), attack.unknown_a3.load())); + lines.emplace_back(string_printf(" %04zX a4 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a4), attack.unknown_a4.load(), attack.unknown_a4.load())); + lines.emplace_back(string_printf(" %04zX a5 %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, unknown_a5), attack.unknown_a5.load_raw(), attack.unknown_a5.load())); + lines.emplace_back(string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a6), attack.unknown_a6.load(), attack.unknown_a6.load())); + lines.emplace_back(string_printf(" %04zX a7 %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, unknown_a7), attack.unknown_a7.load_raw(), attack.unknown_a7.load())); + lines.emplace_back(string_printf(" %04zX a8 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a8), attack.unknown_a8.load(), attack.unknown_a8.load())); + lines.emplace_back(string_printf(" %04zX a9 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a9), attack.unknown_a9.load(), attack.unknown_a9.load())); + lines.emplace_back(string_printf(" %04zX a10 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a10), attack.unknown_a10.load(), attack.unknown_a10.load())); + lines.emplace_back(string_printf(" %04zX a11 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a11), attack.unknown_a11.load(), attack.unknown_a11.load())); + lines.emplace_back(string_printf(" %04zX a12 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a12), attack.unknown_a12.load(), attack.unknown_a12.load())); + lines.emplace_back(string_printf(" %04zX a13 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a13), attack.unknown_a13.load(), attack.unknown_a13.load())); + lines.emplace_back(string_printf(" %04zX a14 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a14), attack.unknown_a14.load(), attack.unknown_a14.load())); + lines.emplace_back(string_printf(" %04zX a15 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a15), attack.unknown_a15.load(), attack.unknown_a15.load())); + lines.emplace_back(string_printf(" %04zX a16 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a16), attack.unknown_a16.load(), attack.unknown_a16.load())); }); print_as_struct.template operator()([&](const MovementData& movement) -> void { lines.emplace_back(" // As MovementData"); diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index cb0cd678..e8bf0f13 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -281,12 +281,62 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { mask_data, mask_size, sizeof(S_JoinGame_GC_Ep3_64)); mask.variations.clear(0); mask.rare_seed = 0; + for (size_t offset = sizeof(S_JoinGame_GC_64) + + offsetof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinGame_GC_Ep3_64::Ep3PlayerEntry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } + break; + case 0xEB: + if (version != GameVersion::GC) { + break; + } + [[fallthrough]]; + case 0x65: + case 0x67: + case 0x68: + if ((version == GameVersion::DC) || (version == GameVersion::GC)) { + for (size_t offset = offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB, entries) + + offsetof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_DC_GC_65_67_68_Ep3_EB::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } else if (version == GameVersion::XB) { + for (size_t offset = offsetof(S_JoinLobby_XB_65_67_68, entries) + + offsetof(S_JoinLobby_XB_65_67_68::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_XB_65_67_68::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } else if (version == GameVersion::PC) { + for (size_t offset = offsetof(S_JoinLobby_PC_65_67_68, entries) + + offsetof(S_JoinLobby_PC_65_67_68::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_PC_65_67_68::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } + } else if (version == GameVersion::BB) { + for (size_t offset = offsetof(S_JoinLobby_BB_65_67_68, entries) + + offsetof(S_JoinLobby_BB_65_67_68::Entry, disp.visual.name_color_checksum); + offset + 4 <= mask_size; + offset += sizeof(S_JoinLobby_BB_65_67_68::Entry)) { + *reinterpret_cast(reinterpret_cast(mask_data) + offset) = 0; + } } break; case 0xE8: if (version == GameVersion::GC) { auto& mask = check_size_t(mask_data, mask_size); mask.rare_seed = 0; + for (size_t z = 0; z < 4; z++) { + mask.players[z].disp.visual.name_color_checksum = 0; + } + for (size_t z = 0; z < 8; z++) { + mask.spectator_players[z].disp.visual.name_color_checksum = 0; + } } break; case 0xB1: diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 7ea3409c..b8b5af26 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -618,6 +618,7 @@ void send_complete_player_bb(shared_ptr c) { SC_SyncCharacterSaveFile_BB_00E7 cmd; cmd.inventory = player->inventory; cmd.disp = player->disp; + cmd.disp.visual.compute_name_color_checksum(); cmd.disp.play_time = 0; cmd.unknown_a1 = 0; cmd.creation_timestamp = 0; @@ -1451,6 +1452,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) p.inventory.encode_mags(c->version()); p.disp = wc_p->disp.to_dcpcv3(); remove_language_marker_inplace(p.disp.visual.name); + p.disp.enforce_lobby_join_limits(c->version()); auto& e = cmd.entries[z]; e.player_tag = 0x00010000; @@ -1491,6 +1493,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) p.inventory.encode_mags(c->version()); p.disp = entry.disp; remove_language_marker_inplace(p.disp.visual.name); + p.disp.enforce_lobby_join_limits(c->version()); auto& e = cmd.entries[client_id]; e.player_tag = 0x00010000; @@ -1522,6 +1525,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd_p.inventory = other_p->inventory; cmd_p.disp = other_p->disp.to_dcpcv3(); remove_language_marker_inplace(cmd_p.disp.visual.name); + cmd_p.disp.enforce_lobby_join_limits(c->version()); cmd_e.player_tag = 0x00010000; cmd_e.guild_card_number = other_c->license->serial_number; @@ -1628,6 +1632,7 @@ void send_join_game(shared_ptr c, shared_ptr l) { cmd.players_ep3[x].inventory = other_p->inventory; cmd.players_ep3[x].inventory.encode_mags(c->version()); cmd.players_ep3[x].disp = convert_player_disp_data(other_p->disp); + cmd.players_ep3[x].disp.enforce_lobby_join_limits(c->version()); } } send_command_t(c, 0x64, player_count, cmd); diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 76d817c1..d268e23c 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -793,3 +793,12 @@ const char* name_for_area(Episode episode, uint8_t area) { throw logic_error("invalid episode for drop area"); } } + +uint32_t class_flags_for_class(uint8_t char_class) { + static constexpr uint8_t flags[12] = { + 0x25, 0x2A, 0x31, 0x45, 0x51, 0x52, 0x86, 0x89, 0x8A, 0x32, 0x85, 0x46}; + if (char_class >= 12) { + throw runtime_error("invalid character class"); + } + return flags[char_class]; +} diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index e3de82cd..23d777a1 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -84,3 +84,5 @@ extern const std::unordered_map mag_color_for_name; size_t area_limit_for_episode(Episode ep); uint8_t area_for_name(const std::string& name); const char* name_for_area(Episode episode, uint8_t area); + +uint32_t class_flags_for_class(uint8_t char_class);