From c33af99ae5ad77d9a1d9d4110c37abfea2ee21b3 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Tue, 10 Oct 2023 10:55:49 -0700 Subject: [PATCH] name some previously-unknown fields --- src/CommandFormats.hh | 2 +- src/Episode3/DataIndexes.cc | 122 ++++++++++++++++++++++++------------ src/Episode3/DataIndexes.hh | 50 ++++++++++----- src/Episode3/Server.cc | 2 +- 4 files changed, 119 insertions(+), 57 deletions(-) diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 3998b7fa..70958664 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5761,7 +5761,7 @@ struct G_UpdateShortStatuses_GC_Ep3_6xB4x04 { struct G_UpdateMap_GC_Ep3_6xB4x05 { G_CardBattleCommandHeader header = {0xB4, sizeof(G_UpdateMap_GC_Ep3_6xB4x05) / 4, 0, 0x05, 0, 0, 0}; Episode3::MapAndRulesState state; - uint8_t unknown_a1 = 0; + uint8_t start_battle = 0; parray unused; } __packed__; diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 8d2d876f..97d318cd 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -18,6 +18,43 @@ using namespace std; namespace Episode3 { +const char* name_for_environment_number(uint8_t environment_number) { + static constexpr array names = { + "Unguis Lapis", + "Nebula Montana 1", + "Lupus Silva 1", + "Lupus Silva 2", + "Molae Venti", + "Nebula Montana 2", + "Tener Sinus", + "Mortis Fons", + "Morgue (destroyed)", + "Tower of Caelum", + "MapMan", + "Cyber", + "Morgue (not destroyed)", + "Castor/Pollux map", + "Dolor Odor", + "Ravum Aedes Sacra", + "Amplum Umbra map", + "Via Tubus", + "Morgue", + "TCardDemo", + "unknown", + "unknown", + "Battle Results", + "Game Over", + "Staff roll", + "View Battle waiting room", + "TCard00_Select", + "blank", + }; + if (environment_number >= names.size()) { + return "unknown"; + } + return names[environment_number]; +} + const char* name_for_link_color(uint8_t color) { switch (color) { case 1: @@ -1527,13 +1564,13 @@ string MapDefinition::str(const CardIndex* card_index) const { lines.emplace_back(string_printf("Map %08" PRIX32 ": %hhux%hhu", this->map_number.load(), this->width, this->height)); - lines.emplace_back(string_printf(" a1=%08" PRIX32, this->unknown_a1.load())); - lines.emplace_back(string_printf(" environment_number=%02hhX", this->environment_number)); - lines.emplace_back(string_printf(" num_camera_zones=%02hhX", this->num_camera_zones)); + lines.emplace_back(string_printf(" tag: %08" PRIX32, this->tag.load())); + lines.emplace_back(string_printf(" environment_number: %02hhX (%s)", this->environment_number, name_for_environment_number(this->environment_number))); + lines.emplace_back(string_printf(" num_camera_zones: %02hhX", this->num_camera_zones)); lines.emplace_back(" tiles:"); add_map(this->map_tiles); lines.emplace_back(string_printf( - " start_tile_definitions=[1p=%02hhX 2p=%02hhX,%02hhX 3p=%02hhX,%02hhX,%02hhX], [1p=%02hhX 2p=%02hhX,%02hhX 3p=%02hhX,%02hhX,%02hhX]", + " start_tile_definitions: A:[1p: %02hhX; 2p: %02hhX,%02hhX; 3p: %02hhX,%02hhX,%02hhX], B:[1p: %02hhX; 2p: %02hhX,%02hhX; 3p: %02hhX,%02hhX,%02hhX]", this->start_tile_definitions[0][0], this->start_tile_definitions[0][1], this->start_tile_definitions[0][2], this->start_tile_definitions[0][3], this->start_tile_definitions[0][4], this->start_tile_definitions[0][5], @@ -1542,7 +1579,7 @@ string MapDefinition::str(const CardIndex* card_index) const { this->start_tile_definitions[1][4], this->start_tile_definitions[1][5])); for (size_t z = 0; z < this->num_camera_zones; z++) { for (size_t w = 0; w < 2; w++) { - lines.emplace_back(string_printf(" camera zone %zu (team %zu):", z, w)); + lines.emplace_back(string_printf(" camera zone %zu (team %c):", z, w ? 'A' : 'B')); add_map(this->camera_zone_maps[w][z]); } for (size_t w = 0; w < 2; w++) { @@ -1552,39 +1589,39 @@ string MapDefinition::str(const CardIndex* card_index) const { for (size_t w = 0; w < 3; w++) { for (size_t z = 0; z < 2; z++) { string spec_str = this->overview_specs[w][z].str(); - lines.emplace_back(string_printf(" overview_specs[%zu][team %zu]=%s", w, z, spec_str.c_str())); + lines.emplace_back(string_printf(" overview_specs[%zu][team %zu]: %s", w, z, spec_str.c_str())); } } lines.emplace_back(" modification tiles:"); add_map(this->modification_tiles); for (size_t z = 0; z < 0x70; z += 0x10) { lines.emplace_back(string_printf( - " a5[0x%02zX:0x%02zX]=%02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX", z, z + 0x10, + " a5[0x%02zX:0x%02zX]: %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX", z, z + 0x10, this->unknown_a5[z + 0x00], this->unknown_a5[z + 0x01], this->unknown_a5[z + 0x02], this->unknown_a5[z + 0x03], this->unknown_a5[z + 0x04], this->unknown_a5[z + 0x05], this->unknown_a5[z + 0x06], this->unknown_a5[z + 0x07], this->unknown_a5[z + 0x08], this->unknown_a5[z + 0x09], this->unknown_a5[z + 0x0A], this->unknown_a5[z + 0x0B], this->unknown_a5[z + 0x0C], this->unknown_a5[z + 0x0D], this->unknown_a5[z + 0x0E], this->unknown_a5[z + 0x0F])); } lines.emplace_back(string_printf( - " a5[0x70:0x74]=%02hhX %02hhX %02hhX %02hhX", + " a5[0x70:0x74]: %02hhX %02hhX %02hhX %02hhX", this->unknown_a5[0x70], this->unknown_a5[0x71], this->unknown_a5[0x72], this->unknown_a5[0x73])); lines.emplace_back(" default_rules: " + this->default_rules.str()); - lines.emplace_back(" name: " + string(this->name)); - lines.emplace_back(" location_name: " + string(this->location_name)); - lines.emplace_back(" quest_name: " + string(this->quest_name)); - lines.emplace_back(" description: " + string(this->description)); + lines.emplace_back(" name: " + format_data_string(this->name)); + lines.emplace_back(" location_name: " + format_data_string(this->location_name)); + lines.emplace_back(" quest_name: " + format_data_string(this->quest_name)); + lines.emplace_back(" description: " + format_data_string(this->description)); lines.emplace_back(string_printf(" map_xy: %hu %hu", this->map_x.load(), this->map_y.load())); for (size_t z = 0; z < 3; z++) { lines.emplace_back(string_printf(" npc_chars[%zu]:", z)); - lines.emplace_back(" name: " + string(this->npc_ai_params[z].name)); + lines.emplace_back(" name: " + format_data_string(this->npc_ai_params[z].name)); lines.emplace_back(string_printf( - " ai_params=(a1=%04hX %04hX, is_arkz=%02hhX, a2=%02hX %02hX %02hX)", + " ai_params: (a1: %04hX %04hX, is_arkz: %02hhX, a2: %02hX %02hX %02hX)", this->npc_ai_params[z].unknown_a1[0].load(), this->npc_ai_params[z].unknown_a1[1].load(), this->npc_ai_params[z].is_arkz, this->npc_ai_params[z].unknown_a2[0], this->npc_ai_params[z].unknown_a2[1], this->npc_ai_params[z].unknown_a2[2])); for (size_t w = 0; w < 0x78; w += 0x08) { lines.emplace_back(string_printf( - " ai_params.a3[0x%02zX:0x%02zX]=%04hX %04hX %04hX %04hX %04hX %04hX %04hX %04hX", + " ai_params.a3[0x%02zX:0x%02zX]: %04hX %04hX %04hX %04hX %04hX %04hX %04hX %04hX", w, w + 0x08, this->npc_ai_params[z].params[w + 0x00].load(), this->npc_ai_params[z].params[w + 0x01].load(), this->npc_ai_params[z].params[w + 0x02].load(), this->npc_ai_params[z].params[w + 0x03].load(), @@ -1592,12 +1629,12 @@ string MapDefinition::str(const CardIndex* card_index) const { this->npc_ai_params[z].params[w + 0x06].load(), this->npc_ai_params[z].params[w + 0x07].load())); } lines.emplace_back(string_printf( - " ai_params.a3[0x78:0x7E]=%04hX %04hX %04hX %04hX %04hX %04hX", + " ai_params.a3[0x78:0x7E]: %04hX %04hX %04hX %04hX %04hX %04hX", this->npc_ai_params[z].params[0x78].load(), this->npc_ai_params[z].params[0x79].load(), this->npc_ai_params[z].params[0x7A].load(), this->npc_ai_params[z].params[0x7B].load(), this->npc_ai_params[z].params[0x7C].load(), this->npc_ai_params[z].params[0x7D].load())); lines.emplace_back(string_printf(" npc_decks[%zu]:", z)); - lines.emplace_back(" name: " + string(this->npc_decks[z].name)); + lines.emplace_back(" name: " + format_data_string(this->npc_decks[z].name)); for (size_t w = 0; w < 0x20; w++) { uint16_t card_id = this->npc_decks[z].card_ids[w]; shared_ptr entry; @@ -1615,28 +1652,31 @@ string MapDefinition::str(const CardIndex* card_index) const { } } for (size_t x = 0; x < 0x10; x++) { - lines.emplace_back(string_printf(" npc_dialogue[%zu][%zu]:", z, x)); - lines.emplace_back(string_printf(" a1=%04hX", this->dialogue_sets[z][x].unknown_a1.load())); - lines.emplace_back(string_printf(" a2=%04hX", this->dialogue_sets[z][x].unknown_a2.load())); + const auto& set = this->dialogue_sets[z][x]; + if (set.when == -1 && set.percent_chance == 0xFFFF) { + continue; + } + lines.emplace_back(string_printf(" npc_dialogue[%zu][%zu] (when: %04hX, chance: %hu%%):", + z, x, set.when.load(), set.percent_chance.load())); for (size_t w = 0; w < 4; w++) { - if (this->dialogue_sets[z][x].strings[w][0] != 0 && - static_cast(this->dialogue_sets[z][x].strings[w][0]) != 0xFF) { - lines.emplace_back(string_printf(" strings[%zu]=", w) + string(this->dialogue_sets[z][x].strings[w])); + if (set.strings[w][0] != 0 && static_cast(set.strings[w][0]) != 0xFF) { + string escaped = format_data_string(set.strings[w]); + lines.emplace_back(string_printf(" strings[%zu]: %s", w, escaped.c_str())); } } } } - lines.emplace_back(" a7=" + format_data_string(this->unknown_a7.data(), this->unknown_a7.bytes())); - lines.emplace_back(string_printf(" npc_ai_params_entry_index=[%08" PRIX32 " %08" PRIX32 " %08" PRIX32 "]", + lines.emplace_back(" a7: " + format_data_string(this->unknown_a7.data(), this->unknown_a7.bytes())); + lines.emplace_back(string_printf(" npc_ai_params_entry_index: [%08" PRIX32 ", %08" PRIX32 ", %08" PRIX32 "]", this->npc_ai_params_entry_index[0].load(), this->npc_ai_params_entry_index[1].load(), this->npc_ai_params_entry_index[2].load())); if (this->before_message[0]) { - lines.emplace_back(" before_message: " + string(this->before_message)); + lines.emplace_back(" before_message: " + format_data_string(this->before_message)); } if (this->after_message[0]) { - lines.emplace_back(" after_message: " + string(this->after_message)); + lines.emplace_back(" after_message: " + format_data_string(this->after_message)); } if (this->dispatch_message[0]) { - lines.emplace_back(" dispatch_message: " + string(this->dispatch_message)); + lines.emplace_back(" dispatch_message: " + format_data_string(this->dispatch_message)); } for (size_t z = 0; z < 0x10; z++) { uint16_t card_id = this->reward_card_ids[z]; @@ -1654,12 +1694,12 @@ string MapDefinition::str(const CardIndex* card_index) const { lines.emplace_back(string_printf(" reward_cards[%02zu]: %04hX", z, card_id)); } } - lines.emplace_back(string_printf(" level_overrides=[win=%" PRId32 ", loss=%" PRId32 "]", + lines.emplace_back(string_printf(" level_overrides: [win: %" PRId32 ", loss: %" PRId32 "]", this->win_level_override.load(), this->loss_level_override.load())); - lines.emplace_back(string_printf(" field_offset=(x: %hd units, y:%hd units) (x: %lg tiles, y: %lg tiles)", this->field_offset_x.load(), this->field_offset_y.load(), static_cast(this->field_offset_x) / 25.0, static_cast(this->field_offset_y) / 25.0)); - lines.emplace_back(string_printf(" map_category=%02hhX", this->map_category)); - lines.emplace_back(string_printf(" cyber_block_type=%02hhX", this->cyber_block_type)); - lines.emplace_back(string_printf(" a11=%02hhX%02hhX", this->unknown_a11[0], this->unknown_a11[1])); + lines.emplace_back(string_printf(" field_offset: (x: %hd units, y:%hd units) (x: %lg tiles, y: %lg tiles)", this->field_offset_x.load(), this->field_offset_y.load(), static_cast(this->field_offset_x) / 25.0, static_cast(this->field_offset_y) / 25.0)); + lines.emplace_back(string_printf(" map_category: %02hhX", this->map_category)); + lines.emplace_back(string_printf(" cyber_block_type: %02hhX", this->cyber_block_type)); + lines.emplace_back(string_printf(" a11: %02hhX%02hhX", this->unknown_a11[0], this->unknown_a11[1])); static const array sc_card_entry_names = { "00 (Guykild; 0005)", "01 (Kylria; 0006)", @@ -1686,12 +1726,12 @@ string MapDefinition::str(const CardIndex* card_index) const { "16 (K.C.; 011E)", "17 (Ohgun; 011F)", }; - string unavailable_sc_cards = " unavailable_sc_cards=["; + string unavailable_sc_cards = " unavailable_sc_cards: ["; for (size_t z = 0; z < 0x18; z++) { if (this->unavailable_sc_cards[z] == 0xFFFF) { continue; } - if (unavailable_sc_cards.size() > 24) { + if (unavailable_sc_cards.size() > 25) { unavailable_sc_cards += ", "; } if (this->unavailable_sc_cards[z] >= sc_card_entry_names.size()) { @@ -1743,13 +1783,13 @@ string MapDefinition::str(const CardIndex* card_index) const { break; } lines.emplace_back(string_printf( - " entry_states[%zu] = %s / %s", z, player_type.c_str(), deck_type.c_str())); + " entry_states[%zu]: %s / %s", z, player_type.c_str(), deck_type.c_str())); } return join(lines, "\n"); } MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map) - : unknown_a1(map.unknown_a1), + : tag(map.tag), map_number(map.map_number), width(map.width), height(map.height), @@ -1793,7 +1833,9 @@ MapDefinitionTrial::MapDefinitionTrial(const MapDefinition& map) MapDefinitionTrial::operator MapDefinition() const { MapDefinition ret; - ret.unknown_a1 = this->unknown_a1; + // Trial Edition maps seem to have different tag values; we just always use + // the value that the final version expects. + ret.tag = 0x00000100; ret.map_number = this->map_number; ret.width = this->width; ret.height = this->height; @@ -1824,8 +1866,8 @@ MapDefinitionTrial::operator MapDefinition() const { for (size_t z = 0; z < ret.dialogue_sets.size(); z++) { ret.dialogue_sets[z].sub<8>(0) = this->dialogue_sets[z]; for (size_t x = 8; x < ret.dialogue_sets[z].size(); x++) { - ret.dialogue_sets[z][x].unknown_a1 = 0xFFFF; - ret.dialogue_sets[z][x].unknown_a2 = 0xFFFF; + ret.dialogue_sets[z][x].when = -1; + ret.dialogue_sets[z][x].percent_chance = 0xFFFF; for (size_t w = 0; w < 4; w++) { ret.dialogue_sets[z][x].strings[w].clear(0xFF); ret.dialogue_sets[z][x].strings[w][0] = 0x00; diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index e4b09628..c9ad8aab 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -23,6 +23,7 @@ class CardIndex; class MapIndex; class COMDeckIndex; +const char* name_for_environment_number(uint8_t environment_number); const char* name_for_link_color(uint8_t color); enum BehaviorFlag : uint32_t { @@ -721,6 +722,8 @@ struct CardDefinition { } __attribute__((packed)); // 0x128 bytes in total struct CardDefinitionsFooter { + // Technically the card definitions file is a REL file, so the last 0x20 bytes + // here should be a separate structure. /* 00 */ be_uint32_t num_cards1; /* 04 */ be_uint32_t cards_offset; // == 0 /* 08 */ be_uint32_t num_cards2; @@ -728,9 +731,9 @@ struct CardDefinitionsFooter { /* 18 */ parray relocations; /* 38 */ be_uint32_t relocations_offset; /* 3C */ be_uint32_t num_relocations; - /* 40 */ parray unknown_a4; + /* 40 */ parray unused1; /* 48 */ be_uint32_t footer_offset; - /* 4C */ parray unknown_a5; + /* 4C */ parray unused2; /* 58 */ } __attribute__((packed)); @@ -771,6 +774,8 @@ struct PlayerConfig { // records structure and didn't remove the codepath that reads from this. /* 0138:---- */ PlayerRecords_Battle unused_offline_records; /* 0150:---- */ parray unknown_a4; + // The PlayerDataSegment structure begins here. In newserv, we combine this + // structure into PlayerConfig since the two are always used together. /* 0154:0000 */ uint8_t is_encrypted; /* 0155:0001 */ uint8_t basis; /* 0156:0002 */ parray unused; @@ -819,15 +824,18 @@ struct PlayerConfig { /* 00 */ be_uint32_t guild_card_number; /* 04 */ ptext player_name; } __attribute__((packed)); - // TODO: What do these player references mean? When are entries added to or - // removed from this list? It appears to happen sometime during processing of - // the 6xB4x05 on the client, but the exact conditions aren't yet clear. - /* 2128:1FD4 */ parray unknown_a9; + // This array is updated when a battle is started (via a 6xB4x05 command). The + // client adds the opposing players' info to ths first two entries here if the + // opponents are human. (The existing entries are always moved back by two + // slots, but if either or both opponents are not humans, one or both of the + // newly-vacated slots is not filled in.) + /* 2128:1FD4 */ parray recent_human_opponents; /* 2240:20EC */ parray unknown_a10; - // TODO: These three fields are timestamps, but it's not clear what they're - // used for. - /* 2268:2114 */ be_uint32_t unknown_t1; - /* 226C:2118 */ be_uint32_t unknown_t2; + /* 2268:2114 */ be_uint32_t init_timestamp; + /* 226C:2118 */ be_uint32_t last_online_battle_start_timestamp; + // In a certain situation, unknown_t3 is set to init_timestamp plus a multiple + // of two weeks (1209600 seconds). unknown_t3 appears never to be used for + // anything, though. /* 2270:211C */ be_uint32_t unknown_t3; // This visual config is copied to the player's main visual config when the // player's name or proportions have changed, or when certain buttons on the @@ -991,8 +999,15 @@ struct CompressedMapHeader { // .mnm file format } __attribute__((packed)); struct MapDefinition { // .mnmd format; also the format of (decompressed) quests - /* 0000 */ be_uint32_t unknown_a1; // Should be 0x00000100 + // If tag is not 0x00000100, the game considers the map to be corrupt in + // offline mode and will delete it (if it's a download quest). The tag field + // doesn't seem to have any other use. + /* 0000 */ be_uint32_t tag; + /* 0004 */ be_uint32_t map_number; // Must be unique across all maps + + // The maximum map size is 16 tiles in either dimension, since the various + // tiles arrays below are fixed sizes. /* 0008 */ uint8_t width; /* 0009 */ uint8_t height; @@ -1186,12 +1201,17 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 2760 */ ptext dispatch_message; struct DialogueSet { - /* 0000 */ be_uint16_t unknown_a1; - /* 0002 */ be_uint16_t unknown_a2; // Always 0x0064 if valid, 0xFFFF if unused? + /* 0000 */ be_int16_t when; // 0x00-0x0C, or FFFF if unused + /* 0002 */ be_uint16_t percent_chance; // 0-100, or FFFF if unused + // If the dialogue set activates, the game randomly chooses one of these + // strings, excluding any that are empty or begin with the character '^'. /* 0004 */ parray, 4> strings; /* 0104 */ } __attribute__((packed)); - /* 28F0 */ parray, 3> dialogue_sets; // Up to 0x10 per valid NPC + // There are up to 0x10 of these per valid NPC, but only the first 13 of them + // are used, since each one must have a unique value for .when and the values + // there can only be 0-12. + /* 28F0 */ parray, 3> dialogue_sets; // These card IDs are always given to the player when they win a battle on // this map. Unused entries should be set to FFFF. @@ -1280,7 +1300,7 @@ struct MapDefinitionTrial { // This is the format of Episode 3 Trial Edition maps. See the comments in // MapDefinition for what each field means. - /* 0000 */ be_uint32_t unknown_a1; + /* 0000 */ be_uint32_t tag; /* 0004 */ be_uint32_t map_number; /* 0008 */ uint8_t width; /* 0009 */ uint8_t height; diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 93728f7e..be4b1934 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -1514,7 +1514,7 @@ void Server::setup_and_start_battle() { G_UpdateMap_GC_Ep3_6xB4x05 cmd05; cmd05.state = *this->map_and_rules; - cmd05.unknown_a1 = 1; + cmd05.start_battle = 1; this->send(cmd05); this->battle_start_usecs = now();