diff --git a/src/Episode3/DeckState.hh b/src/Episode3/DeckState.hh index 7be36fa8..b79d6b76 100644 --- a/src/Episode3/DeckState.hh +++ b/src/Episode3/DeckState.hh @@ -54,7 +54,7 @@ public: DeckState( uint8_t client_id, const parray& card_ids, - std::shared_ptr random_crypt) + std::shared_ptr random_crypt) : client_id(client_id), draw_index(1), card_ref_base(this->client_id << 8), @@ -107,7 +107,7 @@ private: parray entries; parray card_refs; - std::shared_ptr random_crypt; + std::shared_ptr random_crypt; }; } // namespace Episode3 diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index ff91fe31..30efe5cb 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -27,12 +27,12 @@ void ServerBase::PresenceEntry::clear() { ServerBase::ServerBase( shared_ptr lobby, shared_ptr data_index, - uint32_t random_seed, + shared_ptr random_crypt, shared_ptr map_if_tournament) : lobby(lobby), data_index(data_index), log(lobby->log.prefix + "[Ep3::Server] "), - random_seed(random_seed), + random_crypt(random_crypt), is_tournament(!!map_if_tournament), last_chosen_map(map_if_tournament) {} @@ -75,6 +75,7 @@ Server::Server(shared_ptr base) num_pending_attacks(0), client_done_enqueuing_attacks(false), player_ready_to_end_phase(false), + random_crypt(base->random_crypt), unknown_a10(0), overall_time_expired(false), battle_start_usecs(0), @@ -102,9 +103,12 @@ Server::Server(shared_ptr base) void Server::init() { this->card_special.reset(new CardSpecial(this->shared_from_this())); - // The default PSOV2Encryption constructor in the original implementation just - // uses 0 as the seed. We'll replace this object later when the battle starts. - this->random_crypt.reset(new PSOV2Encryption(0)); + // Note: The original implementation calls the default PSOV2Encryption + // constructor for random_crypt, which just uses 0 as the seed. It then + // re-seeds the generator later. We instead expect the caller to provide a + // seeded generator, and we don't re-seed it at all. + // this->random_crypt.reset(new PSOV2Encryption(0)); + this->state_flags.reset(new StateFlags()); this->clear_player_flags_after_dice_phase(); @@ -213,8 +217,7 @@ 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_str2 = string_printf("Lobby/%08" PRIX32 " random %08" PRIX32, - l->lobby_id, l->random_seed); + cmd46.date_str2 = string_printf("Lobby/%08" PRIX32, l->lobby_id); this->send(cmd46); } @@ -1171,8 +1174,8 @@ void Server::set_client_id_ready_to_advance_phase(uint8_t client_id) { // TODO: It'd be nice to do this in a constant-randomness way, but I'm // lazy, and this matches Sega's original implementation. The less-lazy // way to do it would be to roll three dice: one in the range [1, N], - // one in the range [3, N], and on in the range [1, 2] to decide whether - // to swap the first two results. + // one in the range [3, N], and one in the range [1, 2] to decide + // whether to swap the first two results. for (size_t z = 0; z < 200; z++) { ps->roll_main_dice(); if ((ps->get_atk_points() >= 3) || (ps->get_def_points() >= 3)) { @@ -1302,9 +1305,9 @@ void Server::set_player_deck_valid(uint8_t client_id) { void Server::setup_and_start_battle() { this->setup_phase = SetupPhase::STARTER_ROLLS; - // Note: The original implementation uses time() as the random seed; we use a - // user-settable value in order to support replays and deterministic testing - this->random_crypt.reset(new PSOV2Encryption(this->base()->random_seed)); + + // Note: This is where original implementation re-seeds random_crypt (it uses + // time() as the seed value). for (size_t z = 0; z < 4; z++) { if (!this->check_presence_entry(z)) { diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 2af9acbb..7bf80f08 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -58,7 +58,7 @@ public: ServerBase( std::shared_ptr lobby, std::shared_ptr data_index, - uint32_t random_seed, + std::shared_ptr random_crypt, std::shared_ptr map_if_tournament); void init(); void reset(); @@ -75,7 +75,7 @@ public: std::weak_ptr lobby; std::shared_ptr data_index; PrefixedLogger log; - uint32_t random_seed; + std::shared_ptr random_crypt; bool is_tournament; std::shared_ptr last_chosen_map; @@ -258,7 +258,7 @@ public: uint32_t num_pending_attacks; parray client_done_enqueuing_attacks; parray player_ready_to_end_phase; - std::shared_ptr random_crypt; + std::shared_ptr random_crypt; uint32_t unknown_a10; uint32_t overall_time_expired; // Note: In the original implementation, this is a uint32_t and is measured in diff --git a/src/Lobby.cc b/src/Lobby.cc index 5ea4ba52..2953934e 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -22,7 +22,6 @@ Lobby::Lobby(uint32_t id) mode(GameMode::NORMAL), difficulty(0), random_seed(random_object()), - random(new mt19937(this->random_seed)), event(0), block(0), type(0), diff --git a/src/Lobby.hh b/src/Lobby.hh index 24daac0a..7d73e3b5 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -72,7 +72,7 @@ struct Lobby : public std::enable_shared_from_this { std::u16string name; // This seed is also sent to the client for rare enemy generation uint32_t random_seed; - std::shared_ptr random; + std::shared_ptr random_crypt; std::shared_ptr item_creator; // Ep3 stuff diff --git a/src/Map.cc b/src/Map.cc index 22d04745..0b76d742 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -4,6 +4,7 @@ #include #include "Loggers.hh" +#include "PSOEncryption.hh" #include "StaticGameData.hh" using namespace std; @@ -665,7 +666,7 @@ const vector>& map_file_info_for_episode(Episode ep) { void generate_variations( parray& variations, - shared_ptr random, + shared_ptr random_crypt, Episode episode, bool is_solo) { const auto& ep_index = map_file_info_for_episode(episode); @@ -681,8 +682,8 @@ void generate_variations( variations[z * 2 + 0] = 0; variations[z * 2 + 1] = 0; } else { - variations[z * 2 + 0] = (a->variation1_values.size() < 2) ? 0 : ((*random)() % a->variation1_values.size()); - variations[z * 2 + 1] = (a->variation2_values.size() < 2) ? 0 : ((*random)() % a->variation2_values.size()); + variations[z * 2 + 0] = (a->variation1_values.size() < 2) ? 0 : (random_crypt->next() % a->variation1_values.size()); + variations[z * 2 + 1] = (a->variation2_values.size() < 2) ? 0 : (random_crypt->next() % a->variation2_values.size()); } } } diff --git a/src/Map.hh b/src/Map.hh index 279fc8ef..bbba41f3 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -8,6 +8,7 @@ #include #include +#include "PSOEncryption.hh" #include "StaticGameData.hh" #include "Text.hh" @@ -117,7 +118,7 @@ private: void generate_variations( parray& variations, - std::shared_ptr random, + std::shared_ptr random, Episode episode, bool is_solo); std::vector map_filenames_for_variation( diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index e18c2b0b..c532d359 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1322,7 +1322,7 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, } auto tourn = l->tournament_match ? l->tournament_match->tournament.lock() : nullptr; l->ep3_server_base = make_shared( - l, s->ep3_data_index, l->random_seed, tourn ? tourn->get_map() : nullptr); + l, s->ep3_data_index, l->random_crypt, tourn ? tourn->get_map() : nullptr); l->ep3_server_base->init(); if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES) { @@ -1331,7 +1331,6 @@ static void on_CA_Ep3(shared_ptr s, shared_ptr c, send_text_message_printf(l->clients[z], "Your client ID: $C6%zu", z); } } - send_text_message_printf(l, "State seed: $C6%08" PRIX32, l->random_seed); } if (s->ep3_behavior_flags & Episode3::BehaviorFlag::ENABLE_RECORDING) { @@ -3167,8 +3166,8 @@ shared_ptr create_game_generic( game->difficulty = difficulty; if (c->options.override_random_seed >= 0) { game->random_seed = c->options.override_random_seed; - game->random->seed(game->random_seed); } + game->random_crypt.reset(new PSOV2Encryption(game->random_seed)); if (battle_player) { game->battle_player = battle_player; battle_player->set_lobby(game); @@ -3204,7 +3203,7 @@ shared_ptr create_game_generic( if (game->is_ep3() || (c->version() == GameVersion::DC && (c->flags & (Client::Flag::IS_TRIAL_EDITION | Client::Flag::IS_DC_V1_PROTOTYPE)))) { game->variations.clear(0); } else { - generate_variations(game->variations, game->random, game->episode, is_solo); + generate_variations(game->variations, game->random_crypt, game->episode, is_solo); } if (game->version == GameVersion::BB) { diff --git a/tests/GC-Episode3Battle.test.txt b/tests/GC-Episode3Battle.test.txt index d6f3dde7..f8931898 100644 --- a/tests/GC-Episode3Battle.test.txt +++ b/tests/GC-Episode3Battle.test.txt @@ -3372,10 +3372,6 @@ I 48349 2023-05-26 15:59:20 - [Commands] Sending to C-4 (Tali) (version=GC comma 0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60 0020 | 00 00 00 00 | I 48349 2023-05-26 15:59:20 - [Commands] Sending to C-4 (Tali) (version=GC command=B0 flag=00) -0000 | B0 00 28 00 00 00 00 00 00 00 00 00 53 74 61 74 | ( Stat -0010 | 65 20 73 65 65 64 3A 20 09 43 36 34 34 34 34 34 | e seed: C644444 -0020 | 34 34 34 00 00 00 00 00 | 444 -I 48349 2023-05-26 15:59:20 - [Commands] Sending to C-4 (Tali) (version=GC command=B0 flag=00) 0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R 0010 | 65 63 6F 72 64 69 6E 67 20 65 6E 61 62 6C 65 64 | ecording enabled 0020 | 00 00 00 00 | @@ -25143,10 +25139,6 @@ I 48349 2023-05-26 16:04:46 - [Commands] Sending to C-4 (Tali) (version=GC comma 0010 | 20 63 6C 69 65 6E 74 20 49 44 3A 20 09 43 36 30 | client ID: C60 0020 | 00 00 00 00 | I 48349 2023-05-26 16:04:46 - [Commands] Sending to C-4 (Tali) (version=GC command=B0 flag=00) -0000 | B0 00 28 00 00 00 00 00 00 00 00 00 53 74 61 74 | ( Stat -0010 | 65 20 73 65 65 64 3A 20 09 43 36 34 34 34 34 34 | e seed: C644444 -0020 | 34 34 34 00 00 00 00 00 | 444 -I 48349 2023-05-26 16:04:46 - [Commands] Sending to C-4 (Tali) (version=GC command=B0 flag=00) 0000 | B0 00 24 00 00 00 00 00 00 00 00 00 09 43 36 52 | $ C6R 0010 | 65 63 6F 72 64 69 6E 67 20 63 6F 6D 70 6C 65 74 | ecording complet 0020 | 65 00 00 00 | e