From 294d180e68448f71de8537fc6db71d540e2eac1b Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 23 Feb 2024 23:50:03 -0800 Subject: [PATCH] use system randomness by default unless overridden --- TODO.md | 1 + src/CommonItemSet.hh | 8 ++--- src/Episode3/DeckState.cc | 8 ++--- src/Episode3/DeckState.hh | 6 ++-- src/Episode3/PlayerState.cc | 2 +- src/Episode3/Server.cc | 29 ++++++++++-------- src/Episode3/Server.hh | 2 +- src/HTTPServer.cc | 4 +-- src/ItemCreator.cc | 61 +++++++++++++++++++------------------ src/ItemCreator.hh | 6 ++-- src/Items.cc | 4 +-- src/Items.hh | 2 +- src/Lobby.cc | 40 +++++++++++++++++------- src/Lobby.hh | 11 ++++--- src/Main.cc | 8 +++-- src/Map.cc | 17 ++++++----- src/Map.hh | 10 ++++-- src/PSOEncryption.hh | 5 +++ src/ProxyCommands.cc | 2 ++ src/ProxyServer.cc | 8 ++--- src/ReceiveCommands.cc | 6 ++-- src/ReceiveSubcommands.cc | 14 ++++----- 22 files changed, 148 insertions(+), 106 deletions(-) diff --git a/TODO.md b/TODO.md index 6b02d7d7..0d8af722 100644 --- a/TODO.md +++ b/TODO.md @@ -26,3 +26,4 @@ - Test all quest item subcommands - Figure out why Pouilly Slime EXP doesn't work +- Make server-specified rare enemies work with maps loaded by the proxy diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 2a9dc1e7..329a5fe3 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -298,22 +298,22 @@ struct ProbabilityTable { return this->items[--this->count]; } - void shuffle(PSOLFGEncryption& random_crypt) { + void shuffle(std::shared_ptr opt_rand_crypt) { for (size_t z = 1; z < this->count; z++) { - size_t other_z = random_crypt.next() % (z + 1); + size_t other_z = random_from_optional_crypt(opt_rand_crypt) % (z + 1); ItemT t = this->items[z]; this->items[z] = this->items[other_z]; this->items[other_z] = t; } } - ItemT sample(PSOLFGEncryption& random_crypt) const { + ItemT sample(std::shared_ptr opt_rand_crypt) const { if (this->count == 0) { throw std::runtime_error("sample from empty probability table"); } else if (this->count == 1) { return this->items[0]; } else { - return this->items[random_crypt.next() % this->count]; + return this->items[random_from_optional_crypt(opt_rand_crypt) % this->count]; } } }; diff --git a/src/Episode3/DeckState.cc b/src/Episode3/DeckState.cc index b931522c..0d205816 100644 --- a/src/Episode3/DeckState.cc +++ b/src/Episode3/DeckState.cc @@ -205,8 +205,8 @@ void DeckState::do_mulligan(bool is_nte) { size_t max = this->num_drawable_cards() - 5; uint8_t base_index = this->draw_index + 5; for (size_t z = 0; z < this->card_refs.size(); z++) { - uint8_t index1 = this->random_crypt->next() % max; - uint8_t index2 = this->random_crypt->next() % max; + uint8_t index1 = random_from_optional_crypt(this->opt_rand_crypt) % max; + uint8_t index2 = random_from_optional_crypt(this->opt_rand_crypt) % max; uint16_t temp_ref = this->card_refs[base_index + index1]; this->card_refs[base_index + index1] = this->card_refs[base_index + index2]; this->card_refs[base_index + index2] = temp_ref; @@ -265,8 +265,8 @@ void DeckState::shuffle() { // instead swap each item with another random item (possibly itself) that // doesn't appear earlier than it in the array, but this is not what Sega // did. - uint8_t index1 = this->draw_index + this->random_crypt->next() % max; - uint8_t index2 = this->draw_index + this->random_crypt->next() % max; + uint8_t index1 = this->draw_index + random_from_optional_crypt(this->opt_rand_crypt) % max; + uint8_t index2 = this->draw_index + random_from_optional_crypt(this->opt_rand_crypt) % max; uint16_t temp_ref = this->card_refs[index1]; this->card_refs[index1] = this->card_refs[index2]; this->card_refs[index2] = temp_ref; diff --git a/src/Episode3/DeckState.hh b/src/Episode3/DeckState.hh index aeedfd6d..3e424605 100644 --- a/src/Episode3/DeckState.hh +++ b/src/Episode3/DeckState.hh @@ -57,13 +57,13 @@ public: DeckState( uint8_t client_id, const parray& card_ids, - std::shared_ptr random_crypt) + std::shared_ptr opt_rand_crypt) : client_id(client_id), draw_index(1), card_ref_base(this->client_id << 8), shuffle_enabled(true), loop_enabled(true), - random_crypt(random_crypt) { + opt_rand_crypt(opt_rand_crypt) { for (size_t z = 0; z < card_ids.size(); z++) { auto& e = this->entries[z]; e.card_id = card_ids[z]; @@ -112,7 +112,7 @@ private: parray entries; parray card_refs; - std::shared_ptr random_crypt; + std::shared_ptr opt_rand_crypt; }; } // namespace Episode3 diff --git a/src/Episode3/PlayerState.cc b/src/Episode3/PlayerState.cc index f69c84b3..cf26d307 100644 --- a/src/Episode3/PlayerState.cc +++ b/src/Episode3/PlayerState.cc @@ -49,7 +49,7 @@ void PlayerState::init() { throw logic_error("replacing a player state object is not permitted"); } - this->deck_state = make_shared(this->client_id, s->deck_entries[client_id]->card_ids, s->options.random_crypt); + this->deck_state = make_shared(this->client_id, s->deck_entries[client_id]->card_ids, s->options.opt_rand_crypt); if (s->map_and_rules->rules.disable_deck_shuffle) { this->deck_state->disable_shuffle(); } diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index b2c7a861..a6bcf83b 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -99,10 +99,10 @@ void Server::init() { this->card_special = make_shared(this->shared_from_this()); // Note: The original implementation calls the default PSOV2Encryption - // constructor for random_crypt, which just uses 0 as the seed. It then + // constructor for opt_rand_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 = make_shared(0); + // this->opt_rand_crypt = make_shared(0); this->state_flags = make_shared(); @@ -273,10 +273,15 @@ void Server::send_6xB4x46() const { G_ServerVersionStrings_Ep3_6xB4x46 cmd; cmd.version_signature.encode(VERSION_SIGNATURE, 1); cmd.date_str1.encode(format_time(this->options.card_index->definitions_mtime() * 1000000), 1); - string date_str2 = string_printf( - "Random:%08" PRIX32 "+%08" PRIX32, - this->options.random_crypt->seed(), - this->options.random_crypt->absolute_offset()); + string date_str2; + if (this->options.opt_rand_crypt) { + date_str2 = string_printf( + "Random:%08" PRIX32 "+%08" PRIX32, + this->options.opt_rand_crypt->seed(), + this->options.opt_rand_crypt->absolute_offset()); + } else { + date_str2 = "Random:"; + } if (this->last_chosen_map) { date_str2 += string_printf(" Map:%08" PRIX32, this->last_chosen_map->map_number); } @@ -1080,14 +1085,14 @@ shared_ptr Server::get_player_state(uint8_t client_id) const uint32_t Server::get_random(uint32_t max) { // The original implementation was essentially: - // return (static_cast(this->random_crypt->next() >> 16) / 65536.0) * max - // This is unnecessarily complicated, so we instead just do this: - return this->options.random_crypt->next() % max; + // return (static_cast(this->opt_rand_crypt->next() >> 16) / 65536.0) * max + // This is unnecessarily complicated and imprecise, so we instead just do: + return random_from_optional_crypt(this->options.opt_rand_crypt) % max; } float Server::get_random_float_0_1() { // This lacks some precision, but matches the original implementation. - return (static_cast(this->options.random_crypt->next() >> 16) / 65536.0); + return (static_cast(random_from_optional_crypt(this->options.opt_rand_crypt) >> 16) / 65536.0); } uint32_t Server::get_round_num() const { @@ -1544,8 +1549,8 @@ void Server::setup_and_start_battle() { this->setup_phase = SetupPhase::STARTER_ROLLS; - // Note: This is where original implementation re-seeds random_crypt (it uses - // time() as the seed value). + // Note: This is where original implementation re-seeds opt_rand_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 c4bb771a..e32360fb 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -71,7 +71,7 @@ public: std::shared_ptr card_index; std::shared_ptr map_index; uint32_t behavior_flags; - std::shared_ptr random_crypt; + std::shared_ptr opt_rand_crypt; std::shared_ptr tournament; std::array, 5> trap_card_ids; diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 15a8f396..b11dd94e 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -643,8 +643,8 @@ JSON HTTPServer::generate_lobby_json(shared_ptr l) const { } auto battle_state_json = JSON::dict({ {"BehaviorFlags", ep3s->options.behavior_flags}, - {"RandomSeed", ep3s->options.random_crypt ? ep3s->options.random_crypt->seed() : JSON(nullptr)}, - {"RandomOffset", ep3s->options.random_crypt ? ep3s->options.random_crypt->absolute_offset() : JSON(nullptr)}, + {"RandomSeed", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->seed() : JSON(nullptr)}, + {"RandomOffset", ep3s->options.opt_rand_crypt ? ep3s->options.opt_rand_crypt->absolute_offset() : JSON(nullptr)}, {"Tournament", ep3s->options.tournament ? ep3s->options.tournament->json() : nullptr}, {"MapNumber", ep3s->last_chosen_map ? ep3s->last_chosen_map->map_number : JSON(nullptr)}, {"EnvironmentNumber", ep3s->map_and_rules ? ep3s->map_and_rules->environment_number : JSON(nullptr)}, diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index aa201238..305ea644 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -23,7 +23,7 @@ ItemCreator::ItemCreator( GameMode mode, uint8_t difficulty, uint8_t section_id, - uint32_t random_seed, + std::shared_ptr opt_rand_crypt, shared_ptr restrictions) : log(string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level), version(stack_limits->version), @@ -40,17 +40,12 @@ ItemCreator::ItemCreator( item_parameter_table(item_parameter_table), pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)), restrictions(restrictions), - random_crypt(random_seed) { + opt_rand_crypt(opt_rand_crypt ? make_shared(opt_rand_crypt->seed()) : nullptr) { this->generate_unit_stars_tables(); } -void ItemCreator::set_random_state(uint32_t seed, uint32_t absolute_offset) { - if ((this->random_crypt.seed() != seed) || (this->random_crypt.absolute_offset() > absolute_offset)) { - this->random_crypt = PSOV2Encryption(seed); - } - while (this->random_crypt.absolute_offset() < absolute_offset) { - this->random_crypt.next(); - } +void ItemCreator::set_random_crypt(shared_ptr new_random_crypt) { + this->opt_rand_crypt = new_random_crypt; } bool ItemCreator::are_rare_drops_allowed() const { @@ -141,8 +136,11 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u } ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) { - this->log.info("Box drop checks for area_norm %02hhX; random state: %08" PRIX32 " %08" PRIX32, - area_norm, this->random_crypt.seed(), this->random_crypt.absolute_offset()); + this->log.info("Box drop checks for area_norm %02hhX", area_norm); + if (this->opt_rand_crypt) { + this->log.info("Random state: %08" PRIX32 " %08" PRIX32, + this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset()); + } DropResult res; res.item = this->check_rare_specs_and_create_rare_box_item(area_norm); if (!res.item.empty()) { @@ -189,7 +187,11 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop_with_area_norm(uint32_ this->log.warning("Invalid enemy type: %" PRIX32, enemy_type); return DropResult(); } - this->log.info("Enemy type: %" PRIX32 "; random state: %08" PRIX32 " %08" PRIX32, enemy_type, this->random_crypt.seed(), this->random_crypt.absolute_offset()); + this->log.info("Enemy type: %" PRIX32 "", enemy_type); + if (this->opt_rand_crypt) { + this->log.info("Random state: %08" PRIX32 " %08" PRIX32, + this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset()); + } uint8_t type_drop_prob = this->pt->enemy_type_drop_probs.at(enemy_type); uint8_t drop_sample = this->rand_int(100); @@ -281,12 +283,12 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor } uint32_t ItemCreator::rand_int(uint64_t max) { - return this->random_crypt.next() % max; + return random_from_optional_crypt(this->opt_rand_crypt) % max; } float ItemCreator::rand_float_0_1_from_crypt() { // This lacks some precision, but matches the original implementation. - return (static_cast(this->random_crypt.next() >> 16) / 65536.0); + return (static_cast(random_from_optional_crypt(this->opt_rand_crypt) >> 16) / 65536.0); } template @@ -706,9 +708,9 @@ void ItemCreator::generate_common_mag_variances(ItemData& item) { if (is_pre_v1(this->version)) { item.data2[3] = 0x00; } else if (is_v1_or_v2(this->version)) { - item.data2[3] = this->random_crypt.next() % 0x0E; + item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x0E; } else { - item.data2[3] = this->random_crypt.next() % 0x12; + item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x12; } } } @@ -1018,8 +1020,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2( return true; } -void ItemCreator::generate_armor_shop_armors( - vector& shop, size_t player_level) { +void ItemCreator::generate_armor_shop_armors(vector& shop, size_t player_level) { size_t num_items; if (player_level < 11) { num_items = 4; @@ -1039,7 +1040,7 @@ void ItemCreator::generate_armor_shop_armors( pt.push(src_table.first[z].value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); for (size_t items_generated = 0; items_generated < num_items;) { ItemData item; @@ -1083,7 +1084,7 @@ void ItemCreator::generate_armor_shop_shields(vector& shop, size_t pla pt.push(src_table.first[z].value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); for (size_t items_generated = 0; items_generated < num_items;) { ItemData item; @@ -1126,7 +1127,7 @@ void ItemCreator::generate_armor_shop_units(vector& shop, size_t playe pt.push(src_table.first[z].value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); for (size_t items_generated = 0; items_generated < num_items;) { ItemData item; @@ -1229,7 +1230,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items( pt.push(e.value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); size_t effective_num_items = num_items; size_t items_generated = 0; @@ -1272,7 +1273,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector& shop, size_t p pt.push(e.value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); static const array tech_num_map = { 0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07, @@ -1373,7 +1374,7 @@ vector ItemCreator::generate_weapon_shop_contents(size_t player_level) pt.push(e.value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); vector shop; while (shop.size() < num_items) { @@ -1573,7 +1574,7 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe // Note: The original code shuffles pt and then pops a single value from it. // For simplicity, we just sample a single value (and don't pop it) instead. - switch (pt.sample(this->random_crypt)) { + switch (pt.sample(this->opt_rand_crypt)) { case 0: item.data1[4] = 0; break; @@ -1625,7 +1626,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1( // Note: The original code shuffles pt and then pops a single value from it. // For simplicity, we just sample a single value (and don't pop it) instead. - item.data1[6] = pt.sample(this->random_crypt); + item.data1[6] = pt.sample(this->opt_rand_crypt); if (item.data1[6] == 0) { item.data1[7] = 0; @@ -1666,7 +1667,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player pt.push(e.value); } } - pt.shuffle(this->random_crypt); + pt.shuffle(this->opt_rand_crypt); do { item.data1[8] = pt.pop(); @@ -1752,7 +1753,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) { // Adjust the weapon's special { const auto& prob_table = this->tekker_adjustment_set->get_special_upgrade_prob_table(section_id, favored); - uint8_t delta_index = prob_table.sample(this->random_crypt); + uint8_t delta_index = prob_table.sample(this->opt_rand_crypt); int8_t delta = delta_table.at(delta_index); this->log.info("(Special) Delta index %hhu, delta %hhd", delta_index, delta); // Note: The original code checks specifically for -1 and +1 here, but the @@ -1788,7 +1789,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) { if (!this->item_parameter_table->is_item_rare(item)) { const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]); const auto& prob_table = this->tekker_adjustment_set->get_grind_delta_prob_table(section_id, favored); - uint8_t delta_index = prob_table.sample(this->random_crypt); + uint8_t delta_index = prob_table.sample(this->opt_rand_crypt); int8_t delta = delta_table.at(delta_index); this->log.info("(Grind) Delta index %hhu, delta %hhd", delta_index, delta); int16_t new_grind = static_cast(item.data1[3]) + static_cast(delta); @@ -1804,7 +1805,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) { const auto& prob_table = this->tekker_adjustment_set->get_bonus_delta_prob_table(section_id, favored); // Note: The original code really does use the same delta for all three // bonuses. - uint8_t delta_index = prob_table.sample(this->random_crypt); + uint8_t delta_index = prob_table.sample(this->opt_rand_crypt); int8_t delta = delta_table.at(delta_index); this->log.info("(Bonuses) Delta index %hhu, delta %hhd", delta_index, delta); // Note: The original code doesn't check if there's actually a bonus in each diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index 20cfe37b..f07e5e9b 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -24,11 +24,11 @@ public: GameMode mode, uint8_t difficulty, uint8_t section_id, - uint32_t random_seed, + std::shared_ptr opt_rand_crypt, std::shared_ptr restrictions = nullptr); ~ItemCreator() = default; - void set_random_state(uint32_t seed, uint32_t absolute_offset); + void set_random_crypt(std::shared_ptr new_random_crypt); struct DropResult { ItemData item; @@ -78,7 +78,7 @@ private: // Note: The original implementation uses 17 different random states for some // reason. We forego that and use only one for simplicity. - PSOV2Encryption random_crypt; + std::shared_ptr opt_rand_crypt; inline bool is_v3() const { return !is_v1_or_v2(this->version); diff --git a/src/Items.cc b/src/Items.cc index 4fe646ee..dd7a5994 100644 --- a/src/Items.cc +++ b/src/Items.cc @@ -6,7 +6,7 @@ using namespace std; -void player_use_item(shared_ptr c, size_t item_index, shared_ptr random_crypt) { +void player_use_item(shared_ptr c, size_t item_index, shared_ptr opt_rand_crypt) { auto s = c->require_server_state(); // On PC (and presumably DC), the client sends a 6x29 after this to delete the @@ -177,7 +177,7 @@ void player_use_item(shared_ptr c, size_t item_index, shared_ptrnext() % sum; + size_t det = random_from_optional_crypt(opt_rand_crypt) % sum; for (size_t z = 0; z < table.second; z++) { const auto& entry = table.first[z]; if (det > entry.probability) { diff --git a/src/Items.hh b/src/Items.hh index 42fd1979..2924d762 100644 --- a/src/Items.hh +++ b/src/Items.hh @@ -12,7 +12,7 @@ #include "ServerState.hh" #include "StaticGameData.hh" -void player_use_item(std::shared_ptr c, size_t item_index, std::shared_ptr random_crypt); +void player_use_item(std::shared_ptr c, size_t item_index, std::shared_ptr opt_rand_crypt); void player_feed_mag(std::shared_ptr c, size_t mag_item_index, size_t fed_item_index); void apply_mag_feed_result( diff --git a/src/Lobby.cc b/src/Lobby.cc index a6a3d61d..706af209 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -257,7 +257,7 @@ void Lobby::create_item_creator() { (this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode, this->difficulty, this->section_id, - this->random_seed, + this->opt_rand_crypt, this->quest ? this->quest->battle_rules : nullptr); } @@ -268,9 +268,10 @@ shared_ptr Lobby::load_maps( uint8_t event, uint32_t lobby_id, shared_ptr rare_rates, - shared_ptr random_crypt, + uint32_t random_seed, + shared_ptr opt_rand_crypt, shared_ptr quest_dat_contents_decompressed) { - auto map = make_shared(version, lobby_id, random_crypt); + auto map = make_shared(version, lobby_id, random_seed, opt_rand_crypt); map->add_enemies_and_objects_from_quest_data( episode, difficulty, @@ -291,12 +292,26 @@ shared_ptr Lobby::load_maps( shared_ptr sdt, function(Version, const string&)> get_file_data, shared_ptr rare_rates, - shared_ptr random_crypt, + uint32_t random_seed, + shared_ptr opt_rand_crypt, const parray& variations, const PrefixedLogger* log) { auto enemy_filenames = sdt->map_filenames_for_variations(variations, episode, mode, true); auto object_filenames = sdt->map_filenames_for_variations(variations, episode, mode, false); - return Lobby::load_maps(enemy_filenames, object_filenames, version, episode, mode, difficulty, event, lobby_id, get_file_data, rare_rates, random_crypt, log); + return Lobby::load_maps( + enemy_filenames, + object_filenames, + version, + episode, + mode, + difficulty, + event, + lobby_id, + get_file_data, + rare_rates, + random_seed, + opt_rand_crypt, + log); } shared_ptr Lobby::load_maps( @@ -310,9 +325,10 @@ shared_ptr Lobby::load_maps( uint32_t lobby_id, function(Version, const string&)> get_file_data, shared_ptr rare_rates, - shared_ptr random_crypt, + uint32_t rare_seed, + shared_ptr opt_rand_crypt, const PrefixedLogger* log) { - auto map = make_shared(version, lobby_id, random_crypt); + auto map = make_shared(version, lobby_id, rare_seed, opt_rand_crypt); // Don't load free-roam maps in Challenge mode, since players can't go to // Ragol without a quest loaded @@ -384,7 +400,8 @@ void Lobby::load_maps() { this->event, this->lobby_id, rare_rates, - this->random_crypt, + this->random_seed, + this->opt_rand_crypt, vq->dat_contents_decompressed); } else if (this->mode != GameMode::CHALLENGE) { @@ -399,12 +416,13 @@ void Lobby::load_maps() { s->set_data_table(this->base_version, this->episode, this->mode, this->difficulty), bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2), rare_rates, - this->random_crypt, + this->random_seed, + this->opt_rand_crypt, this->variations, &this->log); } else { - this->map = make_shared(this->base_version, this->lobby_id, this->random_crypt); + this->map = make_shared(this->base_version, this->lobby_id, this->random_seed, this->opt_rand_crypt); } this->log.info("Generated objects list (%zu entries):", this->map->objects.size()); @@ -434,7 +452,7 @@ void Lobby::create_ep3_server() { .card_index = is_nte ? s->ep3_card_index_trial : s->ep3_card_index, .map_index = s->ep3_map_index, .behavior_flags = s->ep3_behavior_flags, - .random_crypt = this->random_crypt, + .opt_rand_crypt = this->opt_rand_crypt, .tournament = tourn, .trap_card_ids = s->ep3_trap_card_ids, }; diff --git a/src/Lobby.hh b/src/Lobby.hh index 48e25839..bee07bb3 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -117,7 +117,7 @@ struct Lobby : public std::enable_shared_from_this { std::string name; // This seed is also sent to the client for rare enemy generation uint32_t random_seed; - std::shared_ptr random_crypt; + std::shared_ptr opt_rand_crypt; uint8_t allowed_drop_modes; DropMode drop_mode; std::shared_ptr item_creator; @@ -202,7 +202,8 @@ struct Lobby : public std::enable_shared_from_this { uint8_t event, uint32_t lobby_id, std::shared_ptr rare_rates, - std::shared_ptr random_crypt, + uint32_t random_seed, + std::shared_ptr opt_rand_crypt, std::shared_ptr quest_dat_contents_decompressed); static std::shared_ptr load_maps( Version version, @@ -214,7 +215,8 @@ struct Lobby : public std::enable_shared_from_this { std::shared_ptr sdt, std::function(Version, const std::string&)> get_file_data, std::shared_ptr rare_rates, - std::shared_ptr random_crypt, + uint32_t random_seed, + std::shared_ptr opt_rand_crypt, const parray& variations, const PrefixedLogger* log = nullptr); static std::shared_ptr load_maps( @@ -228,7 +230,8 @@ struct Lobby : public std::enable_shared_from_this { uint32_t lobby_id, std::function(Version, const std::string&)> get_file_data, std::shared_ptr rare_rates, - std::shared_ptr random_crypt, + uint32_t random_seed, + std::shared_ptr opt_rand_crypt, const PrefixedLogger* log = nullptr); void load_maps(); void create_ep3_server(); diff --git a/src/Main.cc b/src/Main.cc index 4dce82d9..95d47ad7 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2133,7 +2133,8 @@ Action a_find_rare_enemy_seeds( if (!vq->dat_contents_decompressed) { throw runtime_error("quest does not have DAT data"); } - map = Lobby::load_maps(version, episode, difficulty, 0, 0, rare_rates, random_crypt, vq->dat_contents_decompressed); + map = Lobby::load_maps( + version, episode, difficulty, 0, 0, rare_rates, seed, random_crypt, vq->dat_contents_decompressed); } else { generate_variations_deprecated(variations, random_crypt, version, episode, (mode == GameMode::SOLO)); @@ -2147,6 +2148,7 @@ Action a_find_rare_enemy_seeds( s->set_data_table(version, episode, mode, difficulty), bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2), rare_rates, + seed, random_crypt, variations); } @@ -2295,12 +2297,12 @@ Action a_replay_ep3_battle_commands( s->load_ep3_cards(false); s->load_ep3_maps(false); - auto random_crypt = make_shared(args.get("seed", 0, Arguments::IntFormat::HEX)); + auto opt_rand_crypt = make_shared(args.get("seed", 0, Arguments::IntFormat::HEX)); Episode3::Server::Options options = { .card_index = s->ep3_card_index, .map_index = s->ep3_map_index, .behavior_flags = 0x0092, - .random_crypt = random_crypt, + .opt_rand_crypt = opt_rand_crypt, .tournament = nullptr, .trap_card_ids = {}, }; diff --git a/src/Map.cc b/src/Map.cc index 59a3d93f..4de8ec55 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -689,10 +689,11 @@ string Map::Object::str() const { this->item_drop_checked ? "true" : "false"); } -Map::Map(Version version, uint32_t lobby_id, std::shared_ptr random_crypt) +Map::Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr opt_rand_crypt) : log(string_printf("[Lobby:%08" PRIX32 ":map] ", lobby_id), lobby_log.min_level), version(version), - random_crypt(random_crypt) {} + rare_seed(rare_seed), + opt_rand_crypt(opt_rand_crypt) {} void Map::clear() { this->objects.clear(); @@ -735,7 +736,7 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) { // versions, we must match the client's logic, even though it's more // computationally expensive. if (this->version == Version::BB_V4) { - if ((this->rare_enemy_indexes.size() < 0x10) && (this->random_crypt->next() < rare_rate)) { + if ((this->rare_enemy_indexes.size() < 0x10) && (random_from_optional_crypt(this->opt_rand_crypt) < rare_rate)) { this->rare_enemy_indexes.emplace_back(this->enemies.size()); return true; } @@ -744,7 +745,7 @@ bool Map::check_and_log_rare_enemy(bool default_is_rare, uint32_t rare_rate) { // TODO: We only need the first value from this crypt, so it's unfortunate // that we have to initialize the entire thing. Find a way to make this // faster. - PSOV2Encryption crypt(this->random_crypt->seed() + 0x1000 + this->enemies.size()); + PSOV2Encryption crypt(this->rare_seed + 0x1000 + this->enemies.size()); float det = (static_cast((crypt.next() >> 16) & 0xFFFF) / 65536.0f); // On v1 and v2 (and GC NTE), the rare rate is 0.1% instead of 0.2%. float threshold = is_v1_or_v2(this->version) ? 0.001f : 0.002f; @@ -1534,7 +1535,7 @@ void Map::add_enemies_and_objects_from_quest_data( const auto& random_enemy_locations_header = r.pget(floor_sections.random_enemy_locations); const auto& random_enemy_definitions_header = r.pget(floor_sections.random_enemy_definitions); if (!random_state) { - random_state = make_shared(this->random_crypt->seed()); + random_state = make_shared(this->rare_seed); } this->add_random_enemies_from_map_data( episode, @@ -1640,12 +1641,12 @@ string Map::disassemble_quest_data(const void* data, size_t size) { SetDataTableBase::SetDataTableBase(Version version) : version(version) {} parray SetDataTableBase::generate_variations( - Episode episode, bool is_solo, std::shared_ptr random_crypt) const { + Episode episode, bool is_solo, std::shared_ptr opt_rand_crypt) const { parray ret; for (size_t floor = 0; floor < 0x10; floor++) { auto num_vars = this->num_free_roam_variations_for_floor(episode, is_solo, floor); - ret[floor * 2] = (num_vars.first > 1) ? (random_crypt->next() % num_vars.first) : 0; - ret[floor * 2 + 1] = (num_vars.second > 1) ? (random_crypt->next() % num_vars.second) : 0; + ret[floor * 2] = (num_vars.first > 1) ? (random_from_optional_crypt(opt_rand_crypt) % num_vars.first) : 0; + ret[floor * 2 + 1] = (num_vars.second > 1) ? (random_from_optional_crypt(opt_rand_crypt) % num_vars.second) : 0; } return ret; } diff --git a/src/Map.hh b/src/Map.hh index a6088507..2ea087df 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -256,7 +256,7 @@ struct Map { void generate_shuffled_location_table(const Map::RandomEnemyLocationsHeader& header, StringReader r, uint16_t section); }; - Map(Version version, uint32_t lobby_id, std::shared_ptr random_crypt); + Map(Version version, uint32_t lobby_id, uint32_t rare_seed, std::shared_ptr opt_rand_crypt); ~Map() = default; void clear(); @@ -315,7 +315,8 @@ struct Map { PrefixedLogger log; Version version; - std::shared_ptr random_crypt; + uint32_t rare_seed; + std::shared_ptr opt_rand_crypt; std::vector objects; std::vector enemies; std::vector rare_enemy_indexes; @@ -325,7 +326,10 @@ class SetDataTableBase { public: virtual ~SetDataTableBase() = default; - parray generate_variations(Episode episode, bool is_solo, std::shared_ptr random_crypt) const; + parray generate_variations( + Episode episode, + bool is_solo, + std::shared_ptr opt_rand_crypt = nullptr) const; virtual std::pair num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0; virtual std::pair num_free_roam_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0; diff --git a/src/PSOEncryption.hh b/src/PSOEncryption.hh index c0f13a9d..8e74addb 100644 --- a/src/PSOEncryption.hh +++ b/src/PSOEncryption.hh @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -342,3 +343,7 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size, } return ret; } + +inline uint32_t random_from_optional_crypt(std::shared_ptr random_crypt) { + return random_crypt ? random_crypt->next() : random_object(); +} diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index b30df374..34952e3b 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1347,6 +1347,7 @@ static HandlerResult S_13_A7(shared_ptr ses, uint16_ ses->lobby_event, ses->id, Map::DEFAULT_RARE_ENEMIES, + ses->lobby_random_seed, make_shared(ses->lobby_random_seed), quest_dat_data); } @@ -1656,6 +1657,7 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, s->set_data_table(ses->version(), ses->lobby_episode, ses->lobby_mode, ses->lobby_difficulty), bind(&ServerState::load_map_file, s.get(), placeholders::_1, placeholders::_2), Map::DEFAULT_RARE_ENEMIES, + ses->lobby_random_seed, make_shared(ses->lobby_random_seed), cmd->variations, &ses->log); diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index f1cc75e1..94e4f9d5 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -771,9 +771,9 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) { default: throw logic_error("invalid lobby base version"); } - uint32_t random_seed = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED) - ? this->config.override_random_seed - : this->lobby_random_seed; + auto opt_rand_crypt = this->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED) + ? make_shared(this->config.override_random_seed) + : nullptr; this->item_creator = make_shared( common_item_set, rare_item_set, @@ -787,7 +787,7 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) { (this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode, this->lobby_difficulty, this->lobby_section_id, - random_seed, + opt_rand_crypt, // TODO: Can we get battle rules here somehow? nullptr); } else { diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 49953cd0..f205c624 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -4147,8 +4147,8 @@ shared_ptr create_game_generic( game->difficulty = difficulty; if (c->config.check_flag(Client::Flag::USE_OVERRIDE_RANDOM_SEED)) { game->random_seed = c->config.override_random_seed; + game->opt_rand_crypt = make_shared(game->random_seed); } - game->random_crypt = make_shared(game->random_seed); if (battle_player) { game->battle_player = battle_player; battle_player->set_lobby(game); @@ -4239,14 +4239,14 @@ shared_ptr create_game_generic( // GC NTE ignores the passed-in variations and always uses all zeroes if (game->base_version != Version::GC_NTE) { auto sdt = s->set_data_table(game->base_version, game->episode, game->mode, game->difficulty); - game->variations = sdt->generate_variations(game->episode, is_solo, game->random_crypt); + game->variations = sdt->generate_variations(game->episode, is_solo, game->opt_rand_crypt); } else { game->variations.clear(0); } game->load_maps(); } else { game->variations.clear(0); - game->map = make_shared(game->base_version, game->lobby_id, game->random_crypt); + game->map = make_shared(game->base_version, game->lobby_id, game->random_seed, game->opt_rand_crypt); } return game; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 5a4bb048..0d260edc 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1803,7 +1803,7 @@ static void on_use_item( const auto& item = p->inventory.items[index].data; name = s->describe_item(c->version(), item, false); } - player_use_item(c, index, l->random_crypt); + player_use_item(c, index, l->opt_rand_crypt); if (l->log.should_log(LogLevel::INFO)) { l->log.info("Player %hhu used item %hu:%08" PRIX32 " (%s)", @@ -3531,7 +3531,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, ItemData item = (s->secret_lottery_results.size() == 1) ? s->secret_lottery_results[0] - : s->secret_lottery_results[l->random_crypt->next() % s->secret_lottery_results.size()]; + : s->secret_lottery_results[random_from_optional_crypt(l->opt_rand_crypt) % s->secret_lottery_results.size()]; item.enforce_min_stack_size(limits); item.id = l->generate_item_id(c->lobby_client_id); p->add_item(item, limits); @@ -3547,7 +3547,7 @@ static void on_secret_lottery_ticket_exchange_bb(shared_ptr c, uint8_t, out_cmd.unknown_a3.clear(1); } else { for (size_t z = 0; z < out_cmd.unknown_a3.size(); z++) { - out_cmd.unknown_a3[z] = l->random_crypt->next() % s->secret_lottery_results.size(); + out_cmd.unknown_a3[z] = random_from_optional_crypt(l->opt_rand_crypt) % s->secret_lottery_results.size(); } } send_command_t(c, 0x24, (slt_index >= 0) ? 0 : 1, out_cmd); @@ -3578,7 +3578,7 @@ static void on_quest_F95E_result_bb(shared_ptr c, uint8_t, uint8_t, void if (results.empty()) { throw runtime_error("invalid result type"); } - ItemData item = (results.size() == 1) ? results[0] : results[l->random_crypt->next() % results.size()]; + ItemData item = (results.size() == 1) ? results[0] : results[random_from_optional_crypt(l->opt_rand_crypt) % results.size()]; if (item.data1[0] == 0x04) { // Meseta // TODO: What is the right amount of Meseta to use here? Presumably it // should be random within a certain range, but it's not obvious what @@ -3656,10 +3656,10 @@ static void on_quest_F960_result_bb(shared_ptr c, uint8_t, uint8_t, void size_t tier = cmd.result_tier - num_failures; const auto& results = s->quest_F960_success_results.at(tier); uint64_t probability = results.base_probability + num_failures * results.probability_upgrade; - if (l->random_crypt->next() <= probability) { + if (random_from_optional_crypt(l->opt_rand_crypt) <= probability) { c->log.info("Tier %zu yielded a prize", tier); const auto& result_items = results.results.at(weekday); - item = result_items[l->random_crypt->next() % result_items.size()]; + item = result_items[random_from_optional_crypt(l->opt_rand_crypt) % result_items.size()]; break; } else { c->log.info("Tier %zu did not yield a prize", tier); @@ -3668,7 +3668,7 @@ static void on_quest_F960_result_bb(shared_ptr c, uint8_t, uint8_t, void if (item.empty()) { c->log.info("Choosing result from failure tier"); const auto& result_items = s->quest_F960_failure_results.results.at(weekday); - item = result_items[l->random_crypt->next() % result_items.size()]; + item = result_items[random_from_optional_crypt(l->opt_rand_crypt) % result_items.size()]; } if (item.empty()) { throw runtime_error("no item produced, even from failure tier");