diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d8190ca..75a64616 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ add_executable(newserv src/Episode3/BattleRecord.cc src/Episode3/Card.cc src/Episode3/CardSpecial.cc - src/Episode3/DataIndex.cc + src/Episode3/DataIndexes.cc src/Episode3/DeckState.cc src/Episode3/MapState.cc src/Episode3/PlayerState.cc diff --git a/README.md b/README.md index 3db03d9c..b8d9e04f 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Current known issues / missing features / things to do: - Implement the C5 (battle/challenge records) command. - Implement choice search. - Episode 3 bugs + - Trial Edition can't select maps in battle setup. Fix this. - Fix behavior when joining a spectator team after the beginning of a battle. - Disconnecting during a match turns you into a COM if there are other humans in the match, even if the match is part of a tournament. This may be incorrect behavior for tournaments. - Disconnecting during a tournament when there are no other humans in the match simply cancels the match (so it can be replayed) instead of forfeiting, which is almost certainly incorrect behavior. (Then again, no one likes losing tournaments to COMs...) @@ -89,7 +90,7 @@ newserv supports several versions of PSO. Specifically: | GC Ep1&2 Trial | Untested (2) | Untested (2) | Untested (2) | Untested (2) | | GC Ep1&2 | Yes | Yes | Yes | Yes | | GC Ep1&2 Plus | Yes | Yes | Yes | Yes | -| GC Ep3 Trial | Yes | Yes | Yes | Yes | +| GC Ep3 Trial | Yes | Yes | Partial (5) | Yes | | GC Ep3 | Yes | Yes | Yes | Yes | | XBOX Ep1&2 | Untested (2) | Untested (2) | Untested (2) | Untested (2) | | BB (vanilla) | Yes | Yes | Yes (3) | Yes | @@ -100,6 +101,7 @@ newserv supports several versions of PSO. Specifically: 2. *newserv's implementations of these versions are based on disassembly of the client executables and have never been tested.* 3. *BB games are mostly playable, but there are still some unimplemented features (for example, some quests that use rare commands may not work). Please submit a GitHub issue if you find something that doesn't work.* 4. *Support for PSO Dreamcast Trial Edition and the December 2000 prototype is somewhat incomplete and probably never will be complete. These versions are rather unstable and seem to crash often, but it's not obvious whether it's because they're prototypes or because newserv sends data they can't handle.* +5. *Creating a game works, but choosing a map during battle setup causes the Trial Edition client to crash. This is likely due to the trial version's map format being slightly different from the final version's map format.* ## Setup @@ -201,7 +203,7 @@ Episode 3 state and game data is stored in the system/ep3 directory. The files i * maps-free/ and maps-quest/: Online free battle and quest maps (.mnm/.bin/.mnmd/.bind files). Free battle and quest files have exactly the same format; the only difference between the files in these directories is which section of the menu they will appear in on the client. * tournament-state.json: State of all active tournaments. This file is automatically written when any tournament changes state for any reason (e.g. a tournament is created/started/deleted or a match is resolved). -There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndex.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available. +There is no public editor for Episode 3 maps and quests, but the format is described fairly thoroughly in src/Episode3/DataIndexes.hh (see the MapDefinition structure). You'll need to use `newserv decompress-prs ...` to decompress .bin or .mnm files before editing them, but you don't need to compress the files again to use them - just put the .bind or .mnmd file in the maps directory and newserv will make it available. Like quests, Episode 3 card definitions, maps, and quests are cached in memory. If you've changed any of these files, you can run `reload ep3` in the interactive shell to make the changes take effect without restarting the server. diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 00d92788..792eb768 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -5,7 +5,7 @@ #include #include -#include "Episode3/DataIndex.hh" +#include "Episode3/DataIndexes.hh" #include "Episode3/DeckState.hh" #include "Episode3/MapState.hh" #include "Episode3/PlayerStateSubordinates.hh" @@ -5178,8 +5178,8 @@ struct G_MapList_GC_Ep3_6xB6x40 { le_uint16_t compressed_data_size; le_uint16_t unused; // PRS-compressed map list data follows here. newserv generates this from the - // map index at startup time; see the MapList struct in Episode3/DataIndex.hh - // and Episode3::DataIndex::get_compressed_map_list for details on the format. + // map index at startup; see the MapList struct in Episode3/DataIndexes.hh + // and Episode3::MapIndex::get_compressed_map_list for details on the format. } __packed__; struct G_MapData_GC_Ep3_6xB6x41 { diff --git a/src/Episode3/AssistServer.cc b/src/Episode3/AssistServer.cc index 89ef97ac..00c57a13 100644 --- a/src/Episode3/AssistServer.cc +++ b/src/Episode3/AssistServer.cc @@ -133,7 +133,7 @@ uint16_t AssistServer::card_id_for_card_ref(uint16_t card_ref) const { return this->server()->card_id_for_card_ref(card_ref); } -shared_ptr AssistServer::definition_for_card_id( +shared_ptr AssistServer::definition_for_card_id( uint16_t card_id) const { return this->server()->definition_for_card_id(card_id); } diff --git a/src/Episode3/AssistServer.hh b/src/Episode3/AssistServer.hh index cb0606a4..dd42b8fa 100644 --- a/src/Episode3/AssistServer.hh +++ b/src/Episode3/AssistServer.hh @@ -5,7 +5,7 @@ #include #include -#include "DataIndex.hh" +#include "DataIndexes.hh" #include "DeckState.hh" #include "PlayerState.hh" @@ -24,7 +24,7 @@ public: std::shared_ptr server() const; uint16_t card_id_for_card_ref(uint16_t card_ref) const; - std::shared_ptr definition_for_card_id(uint16_t card_id) const; + std::shared_ptr definition_for_card_id(uint16_t card_id) const; uint32_t compute_num_assist_effects_for_client(uint16_t client_id); uint32_t compute_num_assist_effects_for_team(uint32_t team_id); @@ -40,11 +40,11 @@ private: public: parray assist_effects; - std::shared_ptr assist_card_defs[4]; + std::shared_ptr assist_card_defs[4]; uint32_t num_assist_cards_set; parray client_ids_with_assists; parray active_assist_effects; - std::shared_ptr active_assist_card_defs[4]; + std::shared_ptr active_assist_card_defs[4]; uint32_t num_active_assists; std::shared_ptr hand_and_equip_states[4]; std::shared_ptr> card_short_statuses[4]; diff --git a/src/Episode3/Card.cc b/src/Episode3/Card.cc index 5fc35efc..6235b7a1 100644 --- a/src/Episode3/Card.cc +++ b/src/Episode3/Card.cc @@ -507,7 +507,7 @@ bool Card::get_attack_condition_value( cond_type, card_ref, def_effect_index, value, out_value); } -shared_ptr Card::get_definition() const { +shared_ptr Card::get_definition() const { return this->def_entry; } diff --git a/src/Episode3/Card.hh b/src/Episode3/Card.hh index cf691a13..c927aab8 100644 --- a/src/Episode3/Card.hh +++ b/src/Episode3/Card.hh @@ -6,7 +6,7 @@ #include "../CommandFormats.hh" #include "../Text.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" namespace Episode3 { @@ -58,7 +58,7 @@ public: uint8_t def_effect_index, uint16_t value, uint16_t* out_value) const; - std::shared_ptr get_definition() const; + std::shared_ptr get_definition() const; uint16_t get_card_ref() const; uint8_t get_client_id() const; uint8_t get_current_hp() const; @@ -99,12 +99,12 @@ private: public: int16_t max_hp; int16_t current_hp; - std::shared_ptr def_entry; + std::shared_ptr def_entry; uint8_t client_id; uint16_t card_id; uint16_t card_ref; uint16_t sc_card_ref; - std::shared_ptr sc_def_entry; + std::shared_ptr sc_def_entry; CardType sc_card_type; uint8_t team_id; uint32_t card_flags; diff --git a/src/Episode3/CardSpecial.cc b/src/Episode3/CardSpecial.cc index e3a56439..edd17dcd 100644 --- a/src/Episode3/CardSpecial.cc +++ b/src/Episode3/CardSpecial.cc @@ -816,7 +816,7 @@ shared_ptr CardSpecial::compute_replaced_target_based_on_conditions( // the Gifoie card's ID (00D9) for compute_effective_range. // TODO: We should fix this so it doesn't rely on a fixed card definition. parray range; - compute_effective_range(range, this->server()->base()->data_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, 0x00D9, target_card_loc, this->server()->base()->map_and_rules1); auto card_refs_in_parry_range = target_ps->get_all_cards_within_range( range, target_card_loc, 0xFF); @@ -2526,7 +2526,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); add_card_refs(ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM)); } } @@ -2566,7 +2566,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( if (ce && ps) { uint16_t range_card_id = this->get_card_id_with_effective_range(card1, ce->def.card_id, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); add_card_refs(ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id())); } } @@ -2631,7 +2631,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // should fix this eventually. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2655,7 +2655,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2721,7 +2721,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Again with the Gifoie hardcoding... uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2775,7 +2775,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Yet another Gifoie hardcode location :( uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2802,7 +2802,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Sigh. Gifoie again. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2898,7 +2898,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // Slay instead of Gifoie uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x009C, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2927,7 +2927,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: Sigh. Gifoie. Sigh. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -2965,7 +2965,7 @@ vector> CardSpecial::get_targeted_cards_for_condition( // TODO: One more Gifoie here. uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, range_card_id, card1_loc, this->server()->base()->map_and_rules1); auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, card1->get_team_id()); for (uint16_t result_card_ref : result_card_refs) { auto result_card = this->server()->card_for_set_card_ref(result_card_ref); @@ -3383,7 +3383,7 @@ void CardSpecial::check_for_defense_interference( shared_ptr target_card, int16_t* inout_unknown_p4) { // Note: This check is not part of the original implementation. - if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { + if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } @@ -4126,7 +4126,7 @@ vector> CardSpecial::filter_cards_by_range( // TODO: Remove hardcoded card ID here (Earthquake) uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2); parray range; - compute_effective_range(range, this->server()->base()->data_index, card_id, card1_loc, this->server()->base()->map_and_rules1); + compute_effective_range(range, this->server()->base()->card_index, card_id, card1_loc, this->server()->base()->map_and_rules1); auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM); for (auto card : cards) { @@ -4342,7 +4342,7 @@ void CardSpecial::unknown_8024A9D8(const ActionState& pa, uint16_t action_card_r void CardSpecial::check_for_attack_interference(shared_ptr unknown_p2) { // Note: This check is not part of the original implementation. - if (this->server()->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { + if (this->server()->base()->behavior_flags & BehaviorFlag::DISABLE_INTERFERENCE) { return; } diff --git a/src/Episode3/CardSpecial.hh b/src/Episode3/CardSpecial.hh index d47c147f..9c4e2faa 100644 --- a/src/Episode3/CardSpecial.hh +++ b/src/Episode3/CardSpecial.hh @@ -5,7 +5,7 @@ #include #include "../Text.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" namespace Episode3 { diff --git a/src/Episode3/DataIndex.cc b/src/Episode3/DataIndexes.cc similarity index 95% rename from src/Episode3/DataIndex.cc rename to src/Episode3/DataIndexes.cc index 82956fc8..47ced51f 100644 --- a/src/Episode3/DataIndex.cc +++ b/src/Episode3/DataIndexes.cc @@ -1,4 +1,4 @@ -#include "DataIndex.hh" +#include "DataIndexes.hh" #include @@ -1056,6 +1056,33 @@ Card: %04" PRIX32 " \"%s\"\n\ } } +void PlayerConfig::decrypt() { + if (!this->is_encrypted) { + return; + } + decrypt_trivial_gci_data( + &this->card_counts, + offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), + this->basis); + this->is_encrypted = 0; + this->basis = 0; +} + +void PlayerConfig::encrypt(uint8_t basis) { + if (this->is_encrypted) { + if (this->basis == basis) { + return; + } + this->decrypt(); + } + decrypt_trivial_gci_data( + &this->card_counts, + offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), + basis); + this->is_encrypted = 1; + this->basis = basis; +} + HPType hp_type_for_name(const char* name) { if (!strcmp(name, "DEFEAT_PLAYER")) { return HPType::DEFEAT_PLAYER; @@ -1351,7 +1378,7 @@ void StateFlags::clear_FF() { this->client_sc_card_types.clear(CardType::INVALID_FF); } -string MapDefinition::str(const DataIndex* data_index) const { +string MapDefinition::str(const CardIndex* card_index) const { deque lines; auto add_map = [&](const parray, 0x10>& tiles) { for (size_t y = 0; y < 0x10; y++) { @@ -1456,10 +1483,10 @@ string MapDefinition::str(const DataIndex* data_index) const { lines.emplace_back(" name: " + 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; - if (data_index) { + shared_ptr entry; + if (card_index) { try { - entry = data_index->definition_for_card_id(card_id); + entry = card_index->definition_for_id(card_id); } catch (const out_of_range&) { } } @@ -1496,10 +1523,10 @@ string MapDefinition::str(const DataIndex* data_index) const { } for (size_t z = 0; z < 0x10; z++) { uint16_t card_id = this->reward_card_ids[z]; - shared_ptr entry; - if (data_index) { + shared_ptr entry; + if (card_index) { try { - entry = data_index->definition_for_card_id(card_id); + entry = card_index->definition_for_id(card_id); } catch (const out_of_range&) { } } @@ -1671,14 +1698,12 @@ bool Rules::check_and_reset_invalid_fields() { return ret; } -DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) - : behavior_flags(behavior_flags) { - +CardIndex::CardIndex(const string& filename, const string& decompressed_filename, const string& text_filename) { unordered_map> card_tags; unordered_map card_text; - if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) { + if (!text_filename.empty()) { try { - string data = prs_decompress(load_file(directory + "/card-text.mnr")); + string data = prs_decompress(load_file(text_filename)); StringReader r(data); while (!r.eof()) { @@ -1769,20 +1794,20 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) try { string decompressed_data; - if (isfile(directory + "/card-definitions.mnrd")) { - this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnrd").st_mtime; - decompressed_data = load_file(directory + "/card-definitions.mnrd"); + this->mtime_for_card_definitions = stat(filename).st_mtime; + try { + decompressed_data = load_file(decompressed_filename); this->compressed_card_definitions.clear(); - } else { - this->mtime_for_card_definitions = stat(directory + "/card-definitions.mnr").st_mtime; - this->compressed_card_definitions = load_file(directory + "/card-definitions.mnr"); + } catch (const cannot_open_file&) { + this->compressed_card_definitions = load_file(filename); decompressed_data = prs_decompress(this->compressed_card_definitions); } if (decompressed_data.size() > 0x36EC0) { throw runtime_error("decompressed card list data is too long"); } - // There's a footer after the card definitions, but we ignore it + // There's a footer after the card definitions (it's a standard-format REL + // file), but we ignore it if (decompressed_data.size() % sizeof(CardDefinition) != sizeof(CardDefinitionsFooter)) { throw runtime_error(string_printf( "decompressed card update file size %zX is not aligned with card definition size %zX (%zX extra bytes)", @@ -1792,10 +1817,9 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) size_t max_cards = decompressed_data.size() / sizeof(CardDefinition); for (size_t x = 0; x < max_cards; x++) { // The last card entry has the build date and some other metadata (and - // isn't a real card, obviously), so skip it. Seems like the card ID is - // always a large number that won't fit in a uint16_t, so we use that to - // determine if the entry is a real card or not. - if (defs[x].card_id & 0xFFFF0000) { + // isn't a real card, obviously), so skip it. The game detects this by + // checking for a negative value in type, which we also do here. + if (static_cast(defs[x].type) < 0) { continue; } @@ -1816,7 +1840,7 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) entry->def.mv.decode_code(); entry->def.decode_range(); - if (this->behavior_flags & BehaviorFlag::LOAD_CARD_TEXT) { + if (!text_filename.empty()) { try { entry->text = std::move(card_text.at(defs[x].card_id)); } catch (const out_of_range&) { @@ -1836,6 +1860,25 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) "Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms", decompressed_data.size(), this->compressed_card_definitions.size(), diff); } + + if (this->compressed_card_definitions.size() > 0x7BF8) { + // Try to reduce the compressed size by clearing out text + static_game_data_log.info("Compressed card list data is too long; removing text"); + for (size_t x = 0; x < max_cards; x++) { + if (static_cast(defs[x].type) < 0) { + continue; + } + defs[x].jp_name.clear(); + } + uint64_t start = now(); + this->compressed_card_definitions = prs_compress(decompressed_data); + uint64_t diff = now() - start; + static_game_data_log.info( + "Compressed card definitions (%zu bytes -> %zu bytes) in %" PRIu64 "ms", + decompressed_data.size(), this->compressed_card_definitions.size(), diff); + print_data(stderr, this->compressed_card_definitions); + } + if (this->compressed_card_definitions.size() > 0x7BF8) { throw runtime_error("compressed card list data is too long"); } @@ -1844,7 +1887,36 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) } catch (const exception& e) { static_game_data_log.warning("Failed to load Episode 3 card update: %s", e.what()); } +} +const string& CardIndex::get_compressed_definitions() const { + if (this->compressed_card_definitions.empty()) { + throw runtime_error("card definitions are not available"); + } + return this->compressed_card_definitions; +} + +shared_ptr CardIndex::definition_for_id(uint32_t id) const { + return this->card_definitions.at(id); +} + +shared_ptr CardIndex::definition_for_name(const string& name) const { + return this->card_definitions_by_name.at(name); +} + +set CardIndex::all_ids() const { + set ret; + for (const auto& it : this->card_definitions) { + ret.emplace(it.first); + } + return ret; +} + +uint64_t CardIndex::definitions_mtime() const { + return this->mtime_for_card_definitions; +} + +MapIndex::MapIndex(const string& directory) { auto add_maps_from_dir = [&](const string& dir, bool is_quest) -> void { for (const auto& filename : list_directory(dir)) { try { @@ -1879,32 +1951,13 @@ DataIndex::DataIndex(const string& directory, uint32_t behavior_flags) }; add_maps_from_dir(directory + "/maps-free", false); add_maps_from_dir(directory + "/maps-quest", true); - - try { - auto json = JSONObject::parse(load_file(directory + "/com-decks.json")); - for (const auto& def_json : json->as_list()) { - auto& def = this->com_decks.emplace_back(new COMDeckDefinition()); - def->index = this->com_decks.size() - 1; - def->player_name = def_json->at(0)->as_string(); - def->deck_name = def_json->at(1)->as_string(); - auto card_ids_json = def_json->at(2)->as_list(); - for (size_t z = 0; z < 0x1F; z++) { - def->card_ids[z] = card_ids_json.at(z)->as_int(); - } - if (!this->com_decks_by_name.emplace(def->deck_name, def).second) { - throw runtime_error("duplicate COM deck name: " + def->deck_name); - } - } - } catch (const exception& e) { - static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what()); - } } -DataIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest) +MapIndex::MapEntry::MapEntry(const MapDefinition& map, bool is_quest) : map(map), is_quest(is_quest) {} -DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest) +MapIndex::MapEntry::MapEntry(const string& compressed, bool is_quest) : is_quest(is_quest), compressed_data(compressed) { string decompressed = prs_decompress(this->compressed_data); @@ -1916,43 +1969,14 @@ DataIndex::MapEntry::MapEntry(const string& compressed, bool is_quest) this->map = *reinterpret_cast(decompressed.data()); } -string DataIndex::MapEntry::compressed() const { +string MapIndex::MapEntry::compressed() const { if (this->compressed_data.empty()) { this->compressed_data = prs_compress(&this->map, sizeof(this->map)); } return this->compressed_data; } -const string& DataIndex::get_compressed_card_definitions() const { - if (this->compressed_card_definitions.empty()) { - throw runtime_error("card definitions are not available"); - } - return this->compressed_card_definitions; -} - -shared_ptr DataIndex::definition_for_card_id( - uint32_t id) const { - return this->card_definitions.at(id); -} - -shared_ptr DataIndex::definition_for_card_name( - const string& name) const { - return this->card_definitions_by_name.at(name); -} - -set DataIndex::all_card_ids() const { - set ret; - for (const auto& it : this->card_definitions) { - ret.emplace(it.first); - } - return ret; -} - -uint64_t DataIndex::card_definitions_mtime() const { - return this->mtime_for_card_definitions; -} - -const string& DataIndex::get_compressed_map_list() const { +const string& MapIndex::get_compressed_list() const { if (this->compressed_map_list.empty()) { StringWriter entries_w; StringWriter strings_w; @@ -2012,16 +2036,15 @@ const string& DataIndex::get_compressed_map_list() const { return this->compressed_map_list; } -shared_ptr DataIndex::definition_for_map_number(uint32_t id) const { +shared_ptr MapIndex::definition_for_number(uint32_t id) const { return this->maps.at(id); } -shared_ptr DataIndex::definition_for_map_name( - const string& name) const { +shared_ptr MapIndex::definition_for_name(const string& name) const { return this->maps_by_name.at(name); } -set DataIndex::all_map_ids() const { +set MapIndex::all_numbers() const { set ret; for (const auto& it : this->maps) { ret.emplace(it.first); @@ -2029,47 +2052,41 @@ set DataIndex::all_map_ids() const { return ret; } -size_t DataIndex::num_com_decks() const { - return this->com_decks.size(); -} - -shared_ptr DataIndex::com_deck(size_t which) const { - return this->com_decks.at(which); -} - -shared_ptr DataIndex::com_deck(const string& which) const { - return this->com_decks_by_name.at(which); -} - -shared_ptr DataIndex::random_com_deck() const { - return this->com_decks[random_object() % this->com_decks.size()]; -} - -void PlayerConfig::decrypt() { - if (!this->is_encrypted) { - return; - } - decrypt_trivial_gci_data( - &this->card_counts, - offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), - this->basis); - this->is_encrypted = 0; - this->basis = 0; -} - -void PlayerConfig::encrypt(uint8_t basis) { - if (this->is_encrypted) { - if (this->basis == basis) { - return; +COMDeckIndex::COMDeckIndex(const string& filename) { + try { + auto json = JSONObject::parse(load_file(filename)); + for (const auto& def_json : json->as_list()) { + auto& def = this->decks.emplace_back(new COMDeckDefinition()); + def->index = this->decks.size() - 1; + def->player_name = def_json->at(0)->as_string(); + def->deck_name = def_json->at(1)->as_string(); + auto card_ids_json = def_json->at(2)->as_list(); + for (size_t z = 0; z < 0x1F; z++) { + def->card_ids[z] = card_ids_json.at(z)->as_int(); + } + if (!this->decks_by_name.emplace(def->deck_name, def).second) { + throw runtime_error("duplicate COM deck name: " + def->deck_name); + } } - this->decrypt(); + } catch (const exception& e) { + static_game_data_log.warning("Failed to load Episode 3 COM decks: %s", e.what()); } - decrypt_trivial_gci_data( - &this->card_counts, - offsetof(PlayerConfig, decks) - offsetof(PlayerConfig, card_counts), - basis); - this->is_encrypted = 1; - this->basis = basis; +} + +size_t COMDeckIndex::num_decks() const { + return this->decks.size(); +} + +shared_ptr COMDeckIndex::deck_for_index(size_t which) const { + return this->decks.at(which); +} + +shared_ptr COMDeckIndex::deck_for_name(const string& which) const { + return this->decks_by_name.at(which); +} + +shared_ptr COMDeckIndex::random_deck() const { + return this->decks[random_object() % this->decks.size()]; } } // namespace Episode3 diff --git a/src/Episode3/DataIndex.hh b/src/Episode3/DataIndexes.hh similarity index 96% rename from src/Episode3/DataIndex.hh rename to src/Episode3/DataIndexes.hh index 9f2ee7bd..f53f3a9a 100644 --- a/src/Episode3/DataIndex.hh +++ b/src/Episode3/DataIndexes.hh @@ -15,21 +15,22 @@ namespace Episode3 { -// The comment in Server.hh does not apply to this file (and DataIndex.cc). +// The comment in Server.hh does not apply to this file (and DataIndexes.cc). // Except for the Location structure, these structures and functions are not // based on Sega's original implementation. -class DataIndex; +class CardIndex; +class MapIndex; +class COMDeckIndex; const char* name_for_link_color(uint8_t color); -enum BehaviorFlag { +enum BehaviorFlag : uint32_t { SKIP_DECK_VERIFY = 0x00000001, IGNORE_CARD_COUNTS = 0x00000002, SKIP_D1_D2_REPLACE = 0x00000004, DISABLE_TIME_LIMITS = 0x00000008, ENABLE_STATUS_MESSAGES = 0x00000010, - LOAD_CARD_TEXT = 0x00000020, ENABLE_RECORDING = 0x00000040, DISABLE_MASKING = 0x00000080, DISABLE_INTERFERENCE = 0x00000100, @@ -952,10 +953,9 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests uint8_t deck_type; } __attribute__((packed)); /* 5A10 */ parray entry_states; - /* 5A18 */ - std::string str(const DataIndex* data_index = nullptr) const; + std::string str(const CardIndex* card_index = nullptr) const; } __attribute__((packed)); struct COMDeckDefinition { @@ -965,9 +965,9 @@ struct COMDeckDefinition { parray card_ids; }; -class DataIndex { +class CardIndex { public: - DataIndex(const std::string& directory, uint32_t behavior_flags); + CardIndex(const std::string& filename, const std::string& decompressed_filename, const std::string& text_filename = ""); struct CardEntry { CardDefinition def; @@ -975,6 +975,23 @@ public: std::vector debug_tags; // Empty unless debug == true }; + const std::string& get_compressed_definitions() const; + std::shared_ptr definition_for_id(uint32_t id) const; + std::shared_ptr definition_for_name(const std::string& name) const; + std::set all_ids() const; + uint64_t definitions_mtime() const; + +private: + std::string compressed_card_definitions; + std::unordered_map> card_definitions; + std::unordered_map> card_definitions_by_name; + uint64_t mtime_for_card_definitions; +}; + +class MapIndex { +public: + MapIndex(const std::string& directory); + class MapEntry { public: MapDefinition map; @@ -989,42 +1006,33 @@ public: mutable std::string compressed_data; }; - const std::string& get_compressed_card_definitions() const; - std::shared_ptr definition_for_card_id(uint32_t id) const; - std::shared_ptr definition_for_card_name( - const std::string& name) const; - std::set all_card_ids() const; - uint64_t card_definitions_mtime() const; - - const std::string& get_compressed_map_list() const; - std::shared_ptr definition_for_map_number(uint32_t id) const; - std::shared_ptr definition_for_map_name( - const std::string& name) const; - std::set all_map_ids() const; - - size_t num_com_decks() const; - std::shared_ptr com_deck(size_t which) const; - std::shared_ptr com_deck(const std::string& name) const; - std::shared_ptr random_com_deck() const; - - const uint32_t behavior_flags; + const std::string& get_compressed_list() const; + std::shared_ptr definition_for_number(uint32_t id) const; + std::shared_ptr definition_for_name(const std::string& name) const; + std::set all_numbers() const; private: - std::string compressed_card_definitions; - std::unordered_map> card_definitions; - std::unordered_map> card_definitions_by_name; - uint64_t mtime_for_card_definitions; - // The compressed map list is generated on demand from the maps map below. - // It's marked mutable because the logical consistency of the DataIndex object + // It's marked mutable because the logical consistency of the MapIndex object // is not violated from the caller's perspective even if we don't generate the // compressed map list at load time. mutable std::string compressed_map_list; std::map> maps; std::unordered_map> maps_by_name; +}; - std::vector> com_decks; - std::unordered_map> com_decks_by_name; +class COMDeckIndex { +public: + COMDeckIndex(const std::string& filename); + + size_t num_decks() const; + std::shared_ptr deck_for_index(size_t which) const; + std::shared_ptr deck_for_name(const std::string& name) const; + std::shared_ptr random_deck() const; + +private: + std::vector> decks; + std::unordered_map> decks_by_name; }; } // namespace Episode3 diff --git a/src/Episode3/MapState.hh b/src/Episode3/MapState.hh index d136e7ea..1c78f7aa 100644 --- a/src/Episode3/MapState.hh +++ b/src/Episode3/MapState.hh @@ -5,7 +5,7 @@ #include #include "../Text.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" namespace Episode3 { diff --git a/src/Episode3/PlayerState.hh b/src/Episode3/PlayerState.hh index 7d1a123f..c278b901 100644 --- a/src/Episode3/PlayerState.hh +++ b/src/Episode3/PlayerState.hh @@ -6,7 +6,7 @@ #include "../Text.hh" #include "Card.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" #include "DeckState.hh" #include "PlayerStateSubordinates.hh" diff --git a/src/Episode3/PlayerStateSubordinates.hh b/src/Episode3/PlayerStateSubordinates.hh index f2bf8a5c..ef5701c1 100644 --- a/src/Episode3/PlayerStateSubordinates.hh +++ b/src/Episode3/PlayerStateSubordinates.hh @@ -5,7 +5,7 @@ #include #include "../Text.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" namespace Episode3 { diff --git a/src/Episode3/RulerServer.cc b/src/Episode3/RulerServer.cc index f4c8c2a8..907ce963 100644 --- a/src/Episode3/RulerServer.cc +++ b/src/Episode3/RulerServer.cc @@ -8,7 +8,7 @@ namespace Episode3 { void compute_effective_range( parray& ret, - shared_ptr data_index, + shared_ptr card_index, uint16_t card_id, const Location& loc, shared_ptr map_and_rules) { @@ -19,9 +19,9 @@ void compute_effective_range( // Heavy Fog: one tile directly in front range_def[3] = 0x00000100; } else { - shared_ptr ce; + shared_ptr ce; try { - ce = data_index->definition_for_card_id(card_id); + ce = card_index->definition_for_id(card_id); } catch (const out_of_range&) { return; } @@ -126,9 +126,9 @@ void compute_effective_range( } bool card_linkage_is_valid( - shared_ptr right_ce, - shared_ptr left_ce, - shared_ptr sc_ce, + shared_ptr right_ce, + shared_ptr left_ce, + shared_ptr sc_ce, bool has_permission_effect) { if (!right_ce) { return false; @@ -1613,7 +1613,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color( return false; } -shared_ptr RulerServer::definition_for_card_ref(uint16_t card_ref) const { +shared_ptr RulerServer::definition_for_card_ref(uint16_t card_ref) const { uint16_t card_id = this->card_id_for_card_ref(card_ref); if (card_id == 0xFFFF) { return nullptr; @@ -1972,8 +1972,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const { return 0xFFFF; } -shared_ptr RulerServer::definition_for_card_id( - uint32_t card_id) const { +shared_ptr RulerServer::definition_for_card_id(uint32_t card_id) const { return this->server()->definition_for_card_id(card_id); } diff --git a/src/Episode3/RulerServer.hh b/src/Episode3/RulerServer.hh index 491139c2..c2962b5d 100644 --- a/src/Episode3/RulerServer.hh +++ b/src/Episode3/RulerServer.hh @@ -5,7 +5,7 @@ #include #include "AssistServer.hh" -#include "DataIndex.hh" +#include "DataIndexes.hh" #include "DeckState.hh" #include "PlayerState.hh" @@ -15,15 +15,15 @@ class Server; void compute_effective_range( parray& ret, - std::shared_ptr data_index, + std::shared_ptr card_index, uint16_t card_id, const Location& loc, std::shared_ptr map_and_rules); bool card_linkage_is_valid( - std::shared_ptr right_def, - std::shared_ptr left_def, - std::shared_ptr sc_def, + std::shared_ptr right_def, + std::shared_ptr left_def, + std::shared_ptr sc_def, bool has_permission_effect); class RulerServer { @@ -130,7 +130,7 @@ public: uint16_t attacker_sc_card_ref) const; bool defense_card_matches_any_attack_card_top_color( const ActionState& pa) const; - std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; + std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; int32_t error_code_for_client_setting_card( uint8_t client_id, uint16_t card_ref, @@ -156,7 +156,7 @@ public: size_t num_occupied_tiles, size_t num_vacant_tiles) const; uint16_t get_ally_sc_card_ref(uint16_t card_ref) const; - std::shared_ptr definition_for_card_id( + std::shared_ptr definition_for_card_id( uint32_t card_id) const; uint32_t get_card_id_with_effective_range( uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const; diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index fd519f9c..9a81f280 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -26,11 +26,15 @@ void ServerBase::PresenceEntry::clear() { ServerBase::ServerBase( shared_ptr lobby, - shared_ptr data_index, + shared_ptr card_index, + shared_ptr map_index, + uint32_t behavior_flags, shared_ptr random_crypt, - shared_ptr map_if_tournament) + shared_ptr map_if_tournament) : lobby(lobby), - data_index(data_index), + card_index(card_index), + map_index(map_index), + behavior_flags(behavior_flags), log(lobby->log.prefix + "[Ep3::Server] "), random_crypt(random_crypt), is_tournament(!!map_if_tournament), @@ -182,7 +186,7 @@ void Server::send(const void* data, size_t size) const { } string masked_data; - if (!(this->base()->data_index->behavior_flags & BehaviorFlag::DISABLE_MASKING)) { + if (!(this->base()->behavior_flags & BehaviorFlag::DISABLE_MASKING)) { if (size >= 8) { masked_data.assign(reinterpret_cast(data), size); uint8_t mask_key = (random_object() % 0xFF) + 1; @@ -216,13 +220,12 @@ void Server::send_6xB4x46() const { G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; cmd46.version_signature = VERSION_SIGNATURE; - cmd46.date_str1 = format_time(this->base()->data_index->card_definitions_mtime() * 1000000); + cmd46.date_str1 = format_time(this->base()->card_index->definitions_mtime() * 1000000); cmd46.date_str2 = string_printf("Lobby/%08" PRIX32, l->lobby_id); this->send(cmd46); } -string Server::prepare_6xB6x41_map_definition( - shared_ptr map) { +string Server::prepare_6xB6x41_map_definition(shared_ptr map) { const auto& compressed = map->compressed(); StringWriter w; @@ -260,7 +263,7 @@ void Server::send_commands_for_joining_spectator(Channel& c) const { __attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, ...) const { auto l = this->base()->lobby.lock(); - if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { + if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { va_list va; va_start(va, fmt); this->base()->log.info_v(fmt, va); @@ -270,7 +273,7 @@ __attribute__((format(printf, 2, 3))) void Server::log_debug(const char* fmt, .. __attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const { auto l = this->base()->lobby.lock(); - if (l && (this->base()->data_index->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { + if (l && (this->base()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) { va_list va; va_start(va, fmt); std::string buf = string_vprintf(fmt, va); @@ -367,10 +370,9 @@ void Server::draw_phase_before() { } } -shared_ptr Server::definition_for_card_ref(uint16_t card_ref) const { +shared_ptr Server::definition_for_card_ref(uint16_t card_ref) const { try { - return this->base()->data_index->definition_for_card_id( - this->card_id_for_card_ref(card_ref)); + return this->base()->card_index->definition_for_id(this->card_id_for_card_ref(card_ref)); } catch (const out_of_range&) { return nullptr; } @@ -581,9 +583,9 @@ void Server::copy_player_states_to_prev_states() { } } -shared_ptr Server::definition_for_card_id(uint16_t card_id) const { +shared_ptr Server::definition_for_card_id(uint16_t card_id) const { try { - return this->base()->data_index->definition_for_card_id(card_id); + return this->base()->card_index->definition_for_id(card_id); } catch (const out_of_range&) { return nullptr; } @@ -1839,7 +1841,7 @@ void Server::handle_6xB3x13_update_map_during_setup(const string& data) { *b->map_and_rules1 = in_cmd.map_and_rules_state; *b->map_and_rules2 = in_cmd.map_and_rules_state; b->overlay_state = in_cmd.overlay_state; - if (b->data_index->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) { + if (b->behavior_flags & BehaviorFlag::DISABLE_TIME_LIMITS) { b->map_and_rules1->rules.overall_time_limit = 0; b->map_and_rules1->rules.phase_time_limit = 0; b->map_and_rules2->rules.overall_time_limit = 0; @@ -1869,9 +1871,9 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) { } DeckEntry entry = in_cmd.entry; int32_t verify_error = 0; - if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) { + if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_DECK_VERIFY)) { // Note: Sega's original implementation doesn't use the card counts here - if (this->base()->data_index->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) { + if (this->base()->behavior_flags & BehaviorFlag::IGNORE_CARD_COUNTS) { verify_error = this->ruler_server->verify_deck(entry.card_ids); } else { verify_error = this->ruler_server->verify_deck(entry.card_ids, @@ -1881,7 +1883,7 @@ void Server::handle_6xB3x14_update_deck_during_setup(const string& data) { if (verify_error) { throw runtime_error(string_printf("invalid deck: -0x%" PRIX32, verify_error)); } - if (!(this->base()->data_index->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { + if (!(this->base()->behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) { this->ruler_server->replace_D1_D2_rarity_cards_with_Attack(entry.card_ids); } *this->base()->deck_entries[in_cmd.client_id] = in_cmd.entry; @@ -2135,7 +2137,7 @@ void Server::handle_6xB3x40_map_list_request(const string& data) { throw runtime_error("lobby is deleted"); } - const auto& list_data = this->base()->data_index->get_compressed_map_list(); + const auto& list_data = this->base()->map_index->get_compressed_list(); StringWriter w; uint32_t subcommand_size = (list_data.size() + sizeof(G_MapList_GC_Ep3_6xB6x40) + 3) & (~3); @@ -2164,7 +2166,7 @@ void Server::handle_6xB3x41_map_request(const string& data) { throw runtime_error("lobby is deleted"); } - base->last_chosen_map = base->data_index->definition_for_map_number(cmd.map_number); + base->last_chosen_map = base->map_index->definition_for_number(cmd.map_number); auto out_cmd = this->prepare_6xB6x41_map_definition(base->last_chosen_map); send_command(l, 0x6C, 0x00, out_cmd); for (auto watcher_l : l->watcher_lobbies) { diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 7bf80f08..16fdb664 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -19,7 +19,7 @@ namespace Episode3 { /** * This implementation of Episode 3 battles (contained in all files in the - * src/Episode3 directory, except for DataIndex.hh/cc) is derived from Sega's + * src/Episode3 directory, except for DataIndexes.hh/cc) is derived from Sega's * original server implementation, reverse-engineered from the Episode 3 client * executable. The control flow, function breakdown, and structure definitions * in these files map very closely to how their server implementation was @@ -49,7 +49,7 @@ namespace Episode3 { // - - - - - - - DeckState // - - - - - - - HandAndEquipState // - - - - - - - MapAndRulesState / OverlayState -// - - - - - - - - Everything within DataIndex +// - - - - - - - - Everything within DataIndexes class Server; @@ -57,9 +57,11 @@ class ServerBase : public std::enable_shared_from_this { public: ServerBase( std::shared_ptr lobby, - std::shared_ptr data_index, + std::shared_ptr card_index, + std::shared_ptr map_index, + uint32_t behavior_flags, std::shared_ptr random_crypt, - std::shared_ptr map_if_tournament); + std::shared_ptr map_if_tournament); void init(); void reset(); void recreate_server(); @@ -73,11 +75,13 @@ public: } __attribute__((packed)); std::weak_ptr lobby; - std::shared_ptr data_index; + std::shared_ptr card_index; + std::shared_ptr map_index; + uint32_t behavior_flags; PrefixedLogger log; std::shared_ptr random_crypt; bool is_tournament; - std::shared_ptr last_chosen_map; + std::shared_ptr last_chosen_map; std::shared_ptr map_and_rules1; std::shared_ptr map_and_rules2; @@ -135,7 +139,7 @@ public: bool advance_battle_phase(); void action_phase_after(); void draw_phase_before(); - std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; + std::shared_ptr definition_for_card_ref(uint16_t card_ref) const; std::shared_ptr card_for_set_card_ref(uint16_t card_ref); std::shared_ptr card_for_set_card_ref(uint16_t card_ref) const; uint16_t card_id_for_card_ref(uint16_t card_ref) const; @@ -147,7 +151,7 @@ public: void compute_all_map_occupied_bits(); void compute_team_dice_boost(uint8_t team_id); void copy_player_states_to_prev_states(); - std::shared_ptr definition_for_card_id(uint16_t card_id) const; + std::shared_ptr definition_for_card_id(uint16_t card_id) const; void destroy_cards_with_zero_hp(); void determine_first_team_turn(); void dice_phase_after(); @@ -229,7 +233,7 @@ public: G_UpdateDecks_GC_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; G_SetPlayerNames_GC_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; static std::string prepare_6xB6x41_map_definition( - std::shared_ptr map); + std::shared_ptr map); G_SetTrapTileLocations_GC_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; std::vector> const_cast_set_cards_v( diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index 78b95474..8e5a4075 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -280,15 +280,17 @@ shared_ptr Tournament::Match::opponent_team_for_team( } Tournament::Tournament( - shared_ptr data_index, + shared_ptr map_index, + shared_ptr com_deck_index, uint8_t number, const string& name, - shared_ptr map, + shared_ptr map, const Rules& rules, size_t num_teams, bool is_2v2) : log(string_printf("[Tournament/%02hhX] ", number)), - data_index(data_index), + map_index(map_index), + com_deck_index(com_deck_index), number(number), name(name), map(map), @@ -308,11 +310,13 @@ Tournament::Tournament( } Tournament::Tournament( - std::shared_ptr data_index, + shared_ptr map_index, + shared_ptr com_deck_index, uint8_t number, std::shared_ptr json) : log(string_printf("[Tournament/%02hhX] ", number)), - data_index(data_index), + map_index(map_index), + com_deck_index(com_deck_index), source_json(json), number(number), current_state(State::REGISTRATION) {} @@ -324,7 +328,7 @@ void Tournament::init() { if (this->source_json) { auto& dict = this->source_json->as_dict(); this->name = dict.at("name")->as_string(); - this->map = this->data_index->definition_for_map_number(dict.at("map_number")->as_int()); + this->map = this->map_index->definition_for_number(dict.at("map_number")->as_int()); this->rules = Rules(dict.at("rules")); this->is_2v2 = dict.at("is_2v2")->as_bool(); is_registration_complete = dict.at("is_registration_complete")->as_bool(); @@ -344,8 +348,7 @@ void Tournament::init() { team->players.emplace_back(serial_number); this->all_player_serial_numbers.emplace(serial_number); } else { - team->players.emplace_back(this->data_index->com_deck( - player_json->as_string())); + team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string())); } } } @@ -486,10 +489,6 @@ std::shared_ptr Tournament::json() const { return shared_ptr(new JSONObject(std::move(dict))); } -std::shared_ptr Tournament::get_data_index() const { - return this->data_index; -} - uint8_t Tournament::get_number() const { return this->number; } @@ -498,7 +497,7 @@ const string& Tournament::get_name() const { return this->name; } -shared_ptr Tournament::get_map() const { +shared_ptr Tournament::get_map() const { return this->map; } @@ -593,13 +592,13 @@ void Tournament::start() { throw logic_error("non-human player on team before tournament start"); } } - if (this->data_index->num_com_decks() < t->max_players - t->players.size()) { + if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) { throw runtime_error("not enough COM decks to complete team"); } // TODO: Don't allow duplicate COM decks, nor duplicate COM SCs on the same // team while (t->players.size() < t->max_players) { - t->players.emplace_back(this->data_index->random_com_deck()); + t->players.emplace_back(this->com_deck_index->random_deck()); } } @@ -654,10 +653,12 @@ void Tournament::print_bracket(FILE* stream) const { } TournamentIndex::TournamentIndex( - shared_ptr data_index, + shared_ptr map_index, + shared_ptr com_deck_index, const string& state_filename, bool skip_load_state) - : data_index(data_index), + : map_index(map_index), + com_deck_index(com_deck_index), state_filename(state_filename) { if (this->state_filename.empty() || skip_load_state) { return; @@ -671,7 +672,7 @@ TournamentIndex::TournamentIndex( } for (size_t z = 0; z < 0x20; z++) { if (!list.at(z)->is_null()) { - this->tournaments[z].reset(new Tournament(this->data_index, z, list[z])); + this->tournaments[z].reset(new Tournament(this->map_index, this->com_deck_index, z, list[z])); this->tournaments[z]->init(); } } @@ -706,7 +707,7 @@ vector> TournamentIndex::all_tournaments() const { shared_ptr TournamentIndex::create_tournament( const string& name, - shared_ptr map, + shared_ptr map, const Rules& rules, size_t num_teams, bool is_2v2) { @@ -722,7 +723,7 @@ shared_ptr TournamentIndex::create_tournament( } auto t = make_shared( - this->data_index, number, name, map, rules, num_teams, is_2v2); + this->map_index, this->com_deck_index, number, name, map, rules, num_teams, is_2v2); t->init(); this->tournaments[number] = t; return t; diff --git a/src/Episode3/Tournament.hh b/src/Episode3/Tournament.hh index 9b5b0100..711ee3b6 100644 --- a/src/Episode3/Tournament.hh +++ b/src/Episode3/Tournament.hh @@ -92,15 +92,17 @@ public: }; Tournament( - std::shared_ptr data_index, + std::shared_ptr map_index, + std::shared_ptr com_deck_index, uint8_t number, const std::string& name, - std::shared_ptr map, + std::shared_ptr map, const Rules& rules, size_t num_teams, bool is_2v2); Tournament( - std::shared_ptr data_index, + std::shared_ptr map_index, + std::shared_ptr com_deck_index, uint8_t number, std::shared_ptr json); ~Tournament() = default; @@ -108,10 +110,9 @@ public: std::shared_ptr json() const; - std::shared_ptr get_data_index() const; uint8_t get_number() const; const std::string& get_name() const; - std::shared_ptr get_map() const; + std::shared_ptr get_map() const; const Rules& get_rules() const; bool get_is_2v2() const; State get_state() const; @@ -131,11 +132,12 @@ public: private: PrefixedLogger log; - std::shared_ptr data_index; + std::shared_ptr map_index; + std::shared_ptr com_deck_index; std::shared_ptr source_json; uint8_t number; std::string name; - std::shared_ptr map; + std::shared_ptr map; Rules rules; size_t num_teams; bool is_2v2; @@ -160,7 +162,8 @@ private: class TournamentIndex { public: explicit TournamentIndex( - std::shared_ptr data_index, + std::shared_ptr map_index, + std::shared_ptr com_deck_index, const std::string& state_filename, bool skip_load_state = false); ~TournamentIndex() = default; @@ -171,7 +174,7 @@ public: std::shared_ptr create_tournament( const std::string& name, - std::shared_ptr map, + std::shared_ptr map, const Rules& rules, size_t num_teams, bool is_2v2); @@ -183,7 +186,8 @@ public: uint32_t serial_number) const; private: - std::shared_ptr data_index; + std::shared_ptr map_index; + std::shared_ptr com_deck_index; std::string state_filename; std::shared_ptr tournaments[0x20]; }; diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index 85ac51f3..c7707ce1 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -195,7 +195,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { isdigit(filename[filename.size() - 12]) && (filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') && (filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') && - isdigit(filename[filename.size() - 9])) { + (isdigit(filename[filename.size() - 9]) || filename[filename.size() - 9] == 'T')) { specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9]; patch_name = filename.substr(0, filename.size() - 13); } @@ -375,6 +375,18 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) { } } } + { + // Generate entries for Trial Editions + data.region_code = 'J'; + data.system_code = 'D'; + data.version_code = 0; + uint32_t checksum = crc32(&data, sizeof(data)); + uint32_t specific_version = 0x33004A54 | (*game_code2 << 16); + if (!checksum_to_specific_version.emplace(checksum, specific_version).second) { + throw logic_error("multiple specific_versions have same header checksum"); + } + data.system_code = 'G'; + } } } return checksum_to_specific_version.at(header_checksum); diff --git a/src/Lobby.hh b/src/Lobby.hh index b25437c0..bcc6f5b1 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -37,6 +37,7 @@ struct Lobby : public std::enable_shared_from_this { SPECTATORS_FORBIDDEN = 0x00004000, START_BATTLE_PLAYER_IMMEDIATELY = 0x00008000, DROPS_ENABLED = 0x00010000, // Does not affect BB + IS_EP3_TRIAL = 0x00020000, // Flags used only for lobbies PUBLIC = 0x01000000, diff --git a/src/Main.cc b/src/Main.cc index 2d9da3c2..14bf387a 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1478,32 +1478,33 @@ int main(int argc, char** argv) { break; } - case Behavior::SHOW_EP3_MAPS: case Behavior::SHOW_EP3_CARDS: { - config_log.info("Collecting Episode 3 data"); - Episode3::DataIndex index("system/ep3", Episode3::BehaviorFlag::LOAD_CARD_TEXT); + Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr"); - if (behavior == Behavior::SHOW_EP3_MAPS) { - auto map_ids = index.all_map_ids(); - log_info("%zu maps", map_ids.size()); - for (uint32_t map_id : map_ids) { - auto map = index.definition_for_map_number(map_id); - string s = map->map.str(&index); - fprintf(stdout, "%s\n", s.c_str()); - } - - } else { - auto card_ids = index.all_card_ids(); - log_info("%zu card definitions", card_ids.size()); - for (uint32_t card_id : card_ids) { - auto entry = index.definition_for_card_id(card_id); - string s = entry->def.str(false); - string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", "); - string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n "); - fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str()); - } + auto card_ids = card_index.all_ids(); + log_info("%zu card definitions", card_ids.size()); + for (uint32_t card_id : card_ids) { + auto entry = card_index.definition_for_id(card_id); + string s = entry->def.str(false); + string tags = entry->debug_tags.empty() ? "(none)" : join(entry->debug_tags, ", "); + string text = entry->text.empty() ? "(No text available)" : str_replace_all(entry->text, "\n", "\n "); + fprintf(stdout, "%s\n Tags: %s\n Text:\n %s\n\n", s.c_str(), tags.c_str(), text.c_str()); } + break; + } + case Behavior::SHOW_EP3_MAPS: { + config_log.info("Collecting Episode 3 data"); + Episode3::MapIndex map_index("system/ep3"); + Episode3::CardIndex card_index("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr"); + + auto map_ids = map_index.all_numbers(); + log_info("%zu maps", map_ids.size()); + for (uint32_t map_id : map_ids) { + auto map = map_index.definition_for_number(map_id); + string s = map->map.str(&card_index); + fprintf(stdout, "%s\n", s.c_str()); + } break; } diff --git a/src/Player.hh b/src/Player.hh index 966b4390..b214da80 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -9,7 +9,7 @@ #include #include -#include "Episode3/DataIndex.hh" +#include "Episode3/DataIndexes.hh" #include "ItemData.hh" #include "LevelTable.hh" #include "Text.hh" diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 5a1e1bcb..c7f2990e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -303,7 +303,7 @@ static void set_console_client_flags( c->log.info("Game version changed to DC"); } else if (c->version() == GameVersion::GC) { c->flags |= Client::Flag::IS_GC_TRIAL_EDITION; - c->log.info("Trial edition flag set"); + c->log.info("GC Trial Edition flag set"); } } c->flags |= flags_for_version(c->version(), sub_version); @@ -930,7 +930,7 @@ static bool add_next_game_client( state_cmd.state.first_team_turn = 0xFF; state_cmd.state.tournament_flag = 0x01; state_cmd.state.client_sc_card_types.clear(Episode3::CardType::INVALID_FF); - if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (random_object() % 0xFF) + 1; set_mask_for_ep3_game_command(&state_cmd, sizeof(state_cmd), mask_key); } @@ -958,7 +958,7 @@ static bool add_next_game_client( ex_cmd.lose_entries[z].threshold = lose_entries[z].first; ex_cmd.lose_entries[z].value = lose_entries[z].second; } - if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (random_object() % 0xFF) + 1; set_mask_for_ep3_game_command(&ex_cmd, sizeof(ex_cmd), mask_key); } @@ -1239,7 +1239,7 @@ static void on_DC_Ep3(shared_ptr s, shared_ptr c, if (l->tournament_match) { auto tourn = l->tournament_match->tournament.lock(); if (tourn) { - send_ep3_set_tournament_player_decks(l, c, l->tournament_match); + send_ep3_set_tournament_player_decks(s, l, c, l->tournament_match); string data = Episode3::Server::prepare_6xB6x41_map_definition( tourn->get_map()); c->channel.send(0x6C, 0x00, data); @@ -1312,8 +1312,14 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, l->log.info("Recreating Episode 3 server state"); } auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr; + bool is_trial = (l->flags & Lobby::Flag::IS_EP3_TRIAL); l->ep3_server_base = make_shared( - l, s->ep3_data_index, l->random_crypt, tourn ? tourn->get_map() : nullptr); + l, + is_trial ? s->ep3_card_index_trial : s->ep3_card_index, + s->ep3_map_index, + s->ep3_behavior_flags, + l->random_crypt, + tourn ? tourn->get_map() : nullptr); l->ep3_server_base->init(); if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { @@ -1366,7 +1372,7 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, } else { throw logic_error("invalid winner team id"); } - send_ep3_tournament_match_result(l, l->tournament_match); + send_ep3_tournament_match_result(s, l, l->tournament_match); on_tournament_bracket_updated(s, tourn); l->ep3_server_base->server->tournament_match_result_sent = true; @@ -1866,6 +1872,7 @@ static void on_10(shared_ptr s, shared_ptr c, } if ((game->version != c->version()) || (!game->is_ep3() != !(c->flags & Client::Flag::IS_EPISODE_3)) || + (!(game->flags & Lobby::Flag::IS_EP3_TRIAL) != !(c->flags & Client::Flag::IS_EP3_TRIAL_EDITION)) || ((game->flags & Lobby::Flag::NON_V1_ONLY) && (c->flags & Client::Flag::IS_DC_V1))) { send_lobby_message_box(c, u"$C6You cannot join this\ngame because it is\nfor a different\nversion of PSO."); break; @@ -2141,6 +2148,11 @@ static void on_84(shared_ptr s, shared_ptr c, return; } + if (new_lobby->is_game()) { + send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The specified lobby\nis a game."); + return; + } + if (new_lobby->is_ep3() && !(c->flags & Client::Flag::IS_EPISODE_3)) { send_lobby_message_box(c, u"$C6Can't change lobby\n\n$C7The lobby is for\nEpisode 3 only."); return; @@ -3203,10 +3215,11 @@ shared_ptr create_game_generic( (c->version() == GameVersion::BB) || (s->item_tracking_enabled && (mode == GameMode::NORMAL || mode == GameMode::SOLO)); - // only disable drops if the config flag is set and are playing regualr multi-mode. - // drops are still enabled for battle and challenge modes. - bool drops_enabled = - (s->drops_enabled || (mode != GameMode::NORMAL)); + // Only disable drops if the config flag is set and are playing regular + // multi-mode. Drops are still enabled for battle and challenge modes. + bool drops_enabled = (s->drops_enabled || (mode != GameMode::NORMAL)); + + bool is_ep3_trial = (c->version() == GameVersion::GC) && (c->flags & Client::Flag::IS_EPISODE_3) && (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION); shared_ptr game = s->create_lobby(); game->name = name; @@ -3214,6 +3227,7 @@ shared_ptr create_game_generic( Lobby::Flag::GAME | (item_tracking_enabled ? Lobby::Flag::ITEM_TRACKING_ENABLED : 0) | (drops_enabled ? Lobby::Flag::DROPS_ENABLED : 0) | + (is_ep3_trial ? Lobby::Flag::IS_EP3_TRIAL : 0) | ((s->cheat_mode_behavior == ServerState::CheatModeBehavior::ON_BY_DEFAULT) ? Lobby::Flag::CHEATS_ENABLED : 0); game->password = password; game->version = c->version(); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 8fb7cb03..b0c7edc5 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -380,7 +380,7 @@ static void on_ep3_battle_subs(shared_ptr s, } } - if (!(s->ep3_data_index->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = 0; while (!mask_key) { mask_key = random_object(); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 52820fda..0aad89f7 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2180,7 +2180,9 @@ void send_rare_enemy_index_list(shared_ptr c, const vector& inde void send_ep3_card_list_update(shared_ptr s, shared_ptr c) { if (!(c->flags & Client::Flag::HAS_EP3_CARD_DEFS)) { - const auto& data = s->ep3_data_index->get_compressed_card_definitions(); + const auto& data = (c->flags & Client::Flag::IS_EP3_TRIAL_EDITION) + ? s->ep3_card_index_trial->get_compressed_definitions() + : s->ep3_card_index->get_compressed_definitions(); StringWriter w; w.put_u32l(data.size()); @@ -2486,6 +2488,7 @@ void send_ep3_game_details(shared_ptr c, shared_ptr l) { } void send_ep3_set_tournament_player_decks( + shared_ptr s, shared_ptr l, shared_ptr c, shared_ptr match) { @@ -2532,7 +2535,7 @@ void send_ep3_set_tournament_player_decks( add_entries_for_team(match->preceding_a->winner_team, 0); add_entries_for_team(match->preceding_b->winner_team, 2); - if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (random_object() % 0xFF) + 1; set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); } @@ -2543,7 +2546,7 @@ void send_ep3_set_tournament_player_decks( } void send_ep3_tournament_match_result( - shared_ptr l, shared_ptr match) { + shared_ptr s, shared_ptr l, shared_ptr match) { auto tourn = match->tournament.lock(); if (!tourn) { return; @@ -2585,13 +2588,13 @@ void send_ep3_tournament_match_result( // the player 1000000 and never charge for anything. cmd.meseta_amount = 100; cmd.meseta_reward_text = "You got %s meseta!"; - if (!(tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { + if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (random_object() % 0xFF) + 1; set_mask_for_ep3_game_command(&cmd, sizeof(cmd), mask_key); } send_command_t(l, 0xC9, 0x00, cmd); - if (tourn->get_data_index()->behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { + if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { send_text_message_printf(l, "$C5TOURN/%02hhX/%zu WIN %c", tourn->get_number(), match->round_num, match->winner_team == match->preceding_a->winner_team ? 'A' : 'B'); @@ -2806,6 +2809,10 @@ void send_card_auction_if_all_clients_ready( distribution_size += it.second.first; } + auto card_index = (l->flags & Lobby::Flag::IS_EP3_TRIAL) + ? s->ep3_card_index_trial + : s->ep3_card_index; + S_StartCardAuction_GC_Ep3_EF cmd; cmd.points_available = s->ep3_card_auction_points; for (size_t z = 0; z < num_cards; z++) { @@ -2814,7 +2821,7 @@ void send_card_auction_if_all_clients_ready( if (v >= it.second.first) { v -= it.second.first; } else { - cmd.entries[z].card_id = s->ep3_data_index->definition_for_card_name(it.first)->def.card_id.load(); + cmd.entries[z].card_id = card_index->definition_for_name(it.first)->def.card_id.load(); cmd.entries[z].min_price = it.second.second; break; } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index c06a49c8..5ad57f29 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -352,10 +352,12 @@ void send_ep3_tournament_info( std::shared_ptr c, std::shared_ptr t); void send_ep3_set_tournament_player_decks( + std::shared_ptr s, std::shared_ptr l, std::shared_ptr c, std::shared_ptr match); void send_ep3_tournament_match_result( + std::shared_ptr s, std::shared_ptr l, std::shared_ptr match); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index a41c8b10..47d2e8c3 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -451,7 +451,7 @@ Proxy session commands:\n\ } else if (command_name == "create-tournament") { string name = get_quoted_string(command_args); string map_name = get_quoted_string(command_args); - auto map = this->state->ep3_data_index->definition_for_map_name(map_name); + auto map = this->state->ep3_map_index->definition_for_name(map_name); uint32_t num_teams = stoul(get_quoted_string(command_args), nullptr, 0); Episode3::Rules rules; rules.set_defaults(); diff --git a/src/ServerState.cc b/src/ServerState.cc index dd58c128..500d1afc 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -888,19 +888,24 @@ void ServerState::load_item_tables() { } void ServerState::load_ep3_data() { - config_log.info("Collecting Episode 3 data"); - this->ep3_data_index.reset(new Episode3::DataIndex( - "system/ep3", this->ep3_behavior_flags)); + config_log.info("Collecting Episode 3 maps"); + this->ep3_map_index.reset(new Episode3::MapIndex("system/ep3")); + config_log.info("Loading Episode 3 card definitions"); + this->ep3_card_index.reset(new Episode3::CardIndex("system/ep3/card-definitions.mnr", "system/ep3/card-definitions.mnrd", "system/ep3/card-text.mnr")); + config_log.info("Loading Episode 3 trial card definitions"); + this->ep3_card_index_trial.reset(new Episode3::CardIndex("system/ep3/card-definitions-trial.mnr", "system/ep3/card-definitions-trial.mnrd", "system/ep3/card-text-trial.mnr")); + config_log.info("Loading Episode 3 COM decks"); + this->ep3_com_deck_index.reset(new Episode3::COMDeckIndex("system/ep3/com-decks.json")); const string& tournament_state_filename = "system/ep3/tournament-state.json"; try { this->ep3_tournament_index.reset(new Episode3::TournamentIndex( - this->ep3_data_index, tournament_state_filename)); + this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename)); config_log.info("Loaded Episode 3 tournament state"); } catch (const exception& e) { config_log.warning("Cannot load Episode 3 tournament state: %s", e.what()); this->ep3_tournament_index.reset(new Episode3::TournamentIndex( - this->ep3_data_index, tournament_state_filename, true)); + this->ep3_map_index, this->ep3_com_deck_index, tournament_state_filename, true)); } } diff --git a/src/ServerState.hh b/src/ServerState.hh index 85ad351a..45b1e664 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -11,7 +11,7 @@ #include "Client.hh" #include "CommonItemSet.hh" -#include "Episode3/DataIndex.hh" +#include "Episode3/DataIndexes.hh" #include "Episode3/Tournament.hh" #include "FunctionCompiler.hh" #include "GSLArchive.hh" @@ -70,7 +70,10 @@ struct ServerState { std::shared_ptr pc_patch_file_index; std::shared_ptr bb_patch_file_index; std::shared_ptr dol_file_index; - std::shared_ptr ep3_data_index; + std::shared_ptr ep3_card_index; + std::shared_ptr ep3_card_index_trial; + std::shared_ptr ep3_map_index; + std::shared_ptr ep3_com_deck_index; std::shared_ptr quest_category_index; std::shared_ptr quest_index; std::shared_ptr level_table; diff --git a/src/Version.cc b/src/Version.cc index 7181f933..76ea16d7 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -87,7 +87,6 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { return Client::Flag::NO_D6_AFTER_LOBBY | Client::Flag::IS_EPISODE_3 | Client::Flag::IS_EP3_TRIAL_EDITION | - Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL | Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case 0x42: // GC Ep3 JP return Client::Flag::NO_D6_AFTER_LOBBY | diff --git a/system/config.example.json b/system/config.example.json index 0b9e71da..c62a5a2c 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -279,7 +279,6 @@ // 0x0008 => Disable overall and per-phase battle time limits, regardless of // the values chosen during battle rules setup // 0x0010 => Enable debug messages in Episode 3 games and battles - // 0x0020 => This flag is used internally and has no effect on battles // 0x0040 => Enable battle recording (after a battle, players can save the // recording with the $saverec command) // 0x0080 => Disable command masking during battles diff --git a/system/ep3/card-definitions-trial.mnr b/system/ep3/card-definitions-trial.mnr new file mode 100755 index 00000000..239c71d0 Binary files /dev/null and b/system/ep3/card-definitions-trial.mnr differ diff --git a/system/ep3/card-text-trial.mnr b/system/ep3/card-text-trial.mnr new file mode 100755 index 00000000..4f48eda8 Binary files /dev/null and b/system/ep3/card-text-trial.mnr differ diff --git a/system/ppc/VersionDetect.s b/system/ppc/VersionDetect.s index 244d23a9..cf6afed9 100644 --- a/system/ppc/VersionDetect.s +++ b/system/ppc/VersionDetect.s @@ -3,9 +3,10 @@ # The returned value has the format SSGGRRVV, where: # S = 33 (which represents PSO GC) -# G = game (4F (O) = Episodes 1&2, 53 (S) = Episode 3) +# G = game (4F (O) = Ep1&2, 53 (S) = Ep3) # R = region (45 (E), 4A (J), or 50 (P)) -# V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.) +# V = minor version | 30 (30 = 1.00, 31 = 1.01, 32 = 1.02, etc.), or 54 for +# Trial Edition # This results in a 4-character ASCII-printable version code which encodes all # of the above information. This value is called specific_version in the places # where it's used by the server. @@ -19,16 +20,23 @@ reloc0: start: lis r3, 0x8000 lwz r4, [r3] - lbz r5, [r3 + 7] - li r3, -1 - - rlwinm r0, r4, 16, 16, 31 - cmplwi r0, 0x4750 - bnelr - - lis r3, 0x3300 - rlwimi r3, r4, 8, 8, 23 - rlwimi r3, r5, 0, 24, 31 - ori r3, r3, 0x0030 + # For Trial Editions, set the V field to 54; for other versions, set it to + # 0x30 | disc_version + rlwinm r0, r4, 8, 24, 31 + cmplwi r0, 0x47 # Check if high byte of game ID is 'G' + beq not_trial + cmplwi r0, 0x44 # Check if high byte of game ID is 'D' + beq is_trial + li r3, 0 + blr +is_trial: + li r3, 0x0054 + b end_trial_check +not_trial: + lbz r3, [r3 + 7] + ori r3, r3, 0x0030 +end_trial_check: + oris r3, r3, 0x3300 # Set high byte ('3') + rlwimi r3, r4, 8, 8, 23 # Set middle two bytes to last to bytes of game ID blr