diff --git a/src/Episode3/DataIndex.cc b/src/Episode3/DataIndex.cc index 0c91e5e3..af7df56f 100644 --- a/src/Episode3/DataIndex.cc +++ b/src/Episode3/DataIndex.cc @@ -1346,9 +1346,11 @@ 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->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"); decompressed_data = prs_decompress(this->compressed_card_definitions); } @@ -1514,6 +1516,10 @@ set DataIndex::all_card_ids() const { return ret; } +uint64_t DataIndex::card_definitions_mtime() const { + return this->mtime_for_card_definitions; +} + const string& DataIndex::get_compressed_map_list() const { if (this->compressed_map_list.empty()) { // TODO: Write a version of prs_compress that takes iovecs (or something diff --git a/src/Episode3/DataIndex.hh b/src/Episode3/DataIndex.hh index 31c8eec8..9bf28d50 100644 --- a/src/Episode3/DataIndex.hh +++ b/src/Episode3/DataIndex.hh @@ -892,6 +892,7 @@ public: 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; @@ -910,6 +911,7 @@ 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 diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index d0345ed1..51b6ba94 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -14,8 +14,7 @@ namespace Episode3 { // These strings in the original implementation did not contain the semicolons // (or anything after them). static const char* VERSION_SIGNATURE = - "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya; newserv Ep3 engine"; -static const char* SIGNATURE_DATE = "Jan 21 2004 18:36:47; updated 2022"; + "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya"; @@ -124,10 +123,7 @@ void Server::init() { this->ruler_server->link_objects( this->base()->map_and_rules1, this->state_flags, this->assist_server); - G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd; - cmd.version_signature = VERSION_SIGNATURE; - cmd.date_str1 = SIGNATURE_DATE; - this->send(cmd); + this->send_6xB4x46(); } shared_ptr Server::base() { @@ -182,6 +178,7 @@ int8_t Server::get_winner_team_id() const { } void Server::send(const void* data, size_t size) const { + // Note: This function is (obviously) not part of the original implementation. auto l = this->base()->lobby.lock(); if (!l) { throw runtime_error("lobby is deleted"); @@ -198,6 +195,10 @@ void Server::send(const void* data, size_t size) const { } } + // Note: Sega's servers sent battle commands with the 60 command. The handlers + // for 60, 62, and C9 on the client are identical, so we choose to use C9 + // instead because it's unique to Episode 3, and therefore seems more + // appropriate to convey battle commands. send_command(l, 0xC9, 0x00, data, size); for (auto watcher_l : l->watcher_lobbies) { send_command_if_not_loading(watcher_l, 0xC9, 0x00, data, size); @@ -208,6 +209,22 @@ void Server::send(const void* data, size_t size) const { } } +void Server::send_6xB4x46() const { + // Note: This function is not part of the original implementation; it was + // factored out from its callsites in this file and the strings were changed. + auto l = this->base()->lobby.lock(); + if (!l) { + throw runtime_error("lobby is deleted"); + } + + 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_str2 = string_printf("Lobby/%08" PRIX32 " random %08" PRIX32, + l->lobby_id, l->random_seed); + this->send(cmd46); +} + string Server::prepare_6xB6x41_map_definition( shared_ptr map) { const auto& compressed = map->compressed(); @@ -1425,10 +1442,7 @@ void Server::setup_and_start_battle() { this->battle_start_usecs = now(); - G_ServerVersionStrings_GC_Ep3_6xB4x46 cmd46; - cmd46.version_signature = VERSION_SIGNATURE; - cmd46.date_str1 = SIGNATURE_DATE; - this->send(cmd46); + this->send_6xB4x46(); } void Server::update_battle_state_flags_and_send_6xB4x03_if_needed( diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 970e9706..1f031dc3 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -136,6 +136,8 @@ public: void send_debug_message_if_error_code_nonzero( uint8_t client_id, int32_t error_code) const; + void send_6xB4x46() const; + void add_team_exp(uint8_t team_id, int32_t exp); bool advance_battle_phase(); void action_phase_after(); diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index ac6a3fb9..fafcf4ae 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -292,6 +292,16 @@ void ReplaySession::apply_default_mask(shared_ptr ev) { } break; } + case 0xC9: { + if (cmd_size == 0xCC) { + auto& cmd_mask = check_size_t( + cmd_data, cmd_size); + cmd_mask.version_signature.clear(0); + cmd_mask.date_str1.clear(0); + cmd_mask.date_str2.clear(0); + } + break; + } } break; }