diff --git a/src/FileContentsCache.cc b/src/FileContentsCache.cc index 267d0c81..1d2f4629 100644 --- a/src/FileContentsCache.cc +++ b/src/FileContentsCache.cc @@ -11,27 +11,26 @@ using namespace std; FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) { } -FileContentsCache::File::File(const string& name, shared_ptr contents, - uint64_t load_time) : name(name), contents(contents), load_time(load_time) { } +FileContentsCache::File::File( + const string& name, + string&& data, + uint64_t load_time) + : name(name), data(move(data)), load_time(load_time) { } -shared_ptr FileContentsCache::replace( +shared_ptr FileContentsCache::replace( const string& name, string&& data, uint64_t t) { if (t == 0) { t = now(); } - shared_ptr contents(new string(move(data))); - auto emplace_ret = this->name_to_file.emplace( - piecewise_construct, - forward_as_tuple(name), - forward_as_tuple(name, contents, t)); + shared_ptr new_file(new File(name, move(data), t)); + auto emplace_ret = this->name_to_file.emplace(name, new_file); if (!emplace_ret.second) { - emplace_ret.first->second.contents = contents; - emplace_ret.first->second.load_time = t; + emplace_ret.first->second = new_file; } - return contents; + return new_file; } -shared_ptr FileContentsCache::replace( +shared_ptr FileContentsCache::replace( const string& name, const void* data, size_t size, uint64_t t) { string s(reinterpret_cast(data), size); return this->replace(name, move(s), t); @@ -45,14 +44,16 @@ FileContentsCache::GetResult FileContentsCache::get_or_load(const char* name) { return this->get_or_load(string(name)); } -shared_ptr FileContentsCache::get_or_throw(const std::string& name) { +shared_ptr FileContentsCache::get_or_throw( + const std::string& name) { auto throw_fn = +[](const std::string&) -> string { throw out_of_range("file missing from cache"); }; - return this->get(name, throw_fn).data; + return this->get(name, throw_fn).file; } -shared_ptr FileContentsCache::get_or_throw(const char* name) { +shared_ptr FileContentsCache::get_or_throw( + const char* name) { return this->get_or_throw(string(name)); } @@ -61,8 +62,8 @@ FileContentsCache::GetResult FileContentsCache::get(const std::string& name, uint64_t t = now(); try { auto& entry = this->name_to_file.at(name); - if (this->ttl_usecs && (t - entry.load_time < this->ttl_usecs)) { - return {entry.contents, false}; + if (this->ttl_usecs && (t - entry->load_time < this->ttl_usecs)) { + return {entry, false}; } } catch (const out_of_range& e) { } return {this->replace(name, generate(name)), true}; diff --git a/src/FileContentsCache.hh b/src/FileContentsCache.hh index d3ea0c72..eb385421 100644 --- a/src/FileContentsCache.hh +++ b/src/FileContentsCache.hh @@ -11,15 +11,14 @@ using namespace std; class FileContentsCache { -private: +public: struct File { std::string name; - std::shared_ptr contents; + std::string data; uint64_t load_time; File() = delete; - File(const std::string& name, std::shared_ptr contents, - uint64_t load_time); + File(const std::string& name, std::string&& contents, uint64_t load_time); File(const File&) = delete; File(File&&) = delete; File& operator=(const File&) = delete; @@ -27,7 +26,6 @@ private: ~File() = default; }; -public: explicit FileContentsCache(uint64_t ttl_usecs); FileContentsCache(const FileContentsCache&) = delete; FileContentsCache(FileContentsCache&&) = delete; @@ -40,20 +38,20 @@ public: return this->name_to_file.erase(key); } - std::shared_ptr replace( + std::shared_ptr replace( const std::string& name, std::string&& data, uint64_t t = 0); - std::shared_ptr replace( + std::shared_ptr replace( const std::string& name, const void* data, size_t size, uint64_t t = 0); struct GetResult { - std::shared_ptr data; + std::shared_ptr file; bool generate_called; }; GetResult get_or_load(const std::string& name); GetResult get_or_load(const char* name); - std::shared_ptr get_or_throw(const std::string& name); - std::shared_ptr get_or_throw(const char* name); + std::shared_ptr get_or_throw(const std::string& name); + std::shared_ptr get_or_throw(const char* name); GetResult get( const std::string& name, std::function generate); @@ -63,36 +61,36 @@ public: template struct GetObjResult { const T& obj; - std::shared_ptr data; + std::shared_ptr data; bool generate_called; }; template GetObjResult get_obj_or_load(NameT name) { auto res = this->get_or_load(name); - if (res.data->size() != sizeof(T)) { + if (res.file->data.size() != sizeof(T)) { throw runtime_error("cached string size is incorrect"); } - return {*reinterpret_cast(res.data->data()), res.data, res.generate_called}; + return {*reinterpret_cast(res.file->data.data()), res.file, res.generate_called}; } template GetObjResult get_obj_or_throw(NameT name) { auto res = this->get_or_throw(name); - if (res->size() != sizeof(T)) { + if (res.file->data.size() != sizeof(T)) { throw runtime_error("cached string size is incorrect"); } - return {*reinterpret_cast(res.data->data()), res.data, res.generate_called}; + return {*reinterpret_cast(res.file->data.data()), res.file, res.generate_called}; } template GetObjResult get_obj(NameT name, std::function generate) { uint64_t t = now(); try { - auto& entry = this->name_to_file.at(name); - if (entry.contents->size() != sizeof(T)) { + auto& f = this->name_to_file.at(name); + if (f->data.size() != sizeof(T)) { throw runtime_error("cached string size is incorrect"); } - if (this->ttl_usecs && (t - entry.load_time < this->ttl_usecs)) { - return {*reinterpret_cast(entry.contents->data()), entry.contents, false}; + if (this->ttl_usecs && (t - f->load_time < this->ttl_usecs)) { + return {*reinterpret_cast(f->data.data()), f, false}; } } catch (const out_of_range& e) { } T value = generate(name); @@ -103,10 +101,10 @@ public: template GetObjResult replace_obj(NameT name, const T& value) { auto cached_value = this->replace(name, &value, sizeof(value)); - return {*reinterpret_cast(cached_value->data()), cached_value, false}; + return {*reinterpret_cast(cached_value->data.data()), cached_value, false}; } private: - std::unordered_map name_to_file; + std::unordered_map> name_to_file; uint64_t ttl_usecs; }; diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index cf9ec722..12b384f5 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -32,9 +32,9 @@ string CompiledFunctionCode::generate_client_command( const string& suffix) const { S_ExecuteCode_Footer_GC_B2 footer; footer.num_relocations = this->relocation_deltas.size(); - footer.unused1.clear(); + footer.unused1.clear(0); footer.entrypoint_addr_offset = this->entrypoint_offset_offset; - footer.unused2.clear(); + footer.unused2.clear(0); StringWriter w; if (!label_writes.empty()) { diff --git a/src/Main.cc b/src/Main.cc index 76975b3b..a150b15c 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -475,7 +475,7 @@ int main(int argc, char** argv) { } config_log.info("Loading battle parameters"); - state->battle_params.reset(new BattleParamTable("system/blueburst/BattleParamEntry")); + state->battle_params.reset(new BattleParamsIndex("system/blueburst/BattleParamEntry")); config_log.info("Loading level table"); state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true)); diff --git a/src/Map.cc b/src/Map.cc index 05a711dc..b0b8a39d 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -10,27 +10,35 @@ using namespace std; -static void load_battle_param_file(const string& filename, BattleParams* entries) { - scoped_fd fd(filename, O_RDONLY); - readx(fd, entries, 0x60 * sizeof(BattleParams)); +BattleParamsIndex::BattleParamsIndex(const char* prefix) { + for (uint8_t is_solo = 0; is_solo < 2; is_solo++) { + for (uint8_t episode = 0; episode < 3; episode++) { + string filename = prefix; + if (episode == 1) { + filename += "_lab"; + } else if (episode == 2) { + filename += "_ep4"; + } + if (!is_solo) { + filename += "_on"; + } + filename += ".dat"; + + this->entries[is_solo][episode][0].reset(new TableT()); + this->entries[is_solo][episode][1].reset(new TableT()); + this->entries[is_solo][episode][2].reset(new TableT()); + this->entries[is_solo][episode][3].reset(new TableT()); + + scoped_fd fd(filename, O_RDONLY); + readx(fd, this->entries[is_solo][episode][0].get(), sizeof(TableT)); + readx(fd, this->entries[is_solo][episode][1].get(), sizeof(TableT)); + readx(fd, this->entries[is_solo][episode][2].get(), sizeof(TableT)); + readx(fd, this->entries[is_solo][episode][3].get(), sizeof(TableT)); + } + } } -BattleParamTable::BattleParamTable(const char* prefix) { - load_battle_param_file(string_printf("%s_on.dat", prefix), - &this->entries[0][0][0][0]); - load_battle_param_file(string_printf("%s_lab_on.dat", prefix), - &this->entries[0][1][0][0]); - load_battle_param_file(string_printf("%s_ep4_on.dat", prefix), - &this->entries[0][2][0][0]); - load_battle_param_file(string_printf("%s.dat", prefix), - &this->entries[1][0][0][0]); - load_battle_param_file(string_printf("%s_lab.dat", prefix), - &this->entries[1][1][0][0]); - load_battle_param_file(string_printf("%s_ep4.dat", prefix), - &this->entries[1][2][0][0]); -} - -const BattleParams& BattleParamTable::get(bool solo, uint8_t episode, +const BattleParams& BattleParamsIndex::get(bool solo, uint8_t episode, uint8_t difficulty, uint8_t monster_type) const { if (episode > 3) { throw invalid_argument("incorrect episode"); @@ -41,18 +49,19 @@ const BattleParams& BattleParamTable::get(bool solo, uint8_t episode, if (monster_type > 0x60) { throw invalid_argument("incorrect monster type"); } - return this->entries[!!solo][episode][difficulty][monster_type]; + return (*this->entries[!!solo][episode][difficulty])[monster_type]; } -const BattleParams* BattleParamTable::get_subtable(bool solo, uint8_t episode, - uint8_t difficulty) const { +shared_ptr +BattleParamsIndex::get_subtable( + bool solo, uint8_t episode, uint8_t difficulty) const { if (episode > 3) { throw invalid_argument("incorrect episode"); } if (difficulty > 4) { throw invalid_argument("incorrect difficulty"); } - return &this->entries[!!solo][episode][difficulty][0]; + return this->entries[!!solo][episode][difficulty]; } @@ -92,9 +101,19 @@ struct EnemyEntry { static uint64_t next_enemy_id = 1; -static vector parse_map(uint8_t episode, uint8_t difficulty, - const BattleParams* battle_params, const EnemyEntry* map, - size_t entry_count, bool alt_enemies) { +vector parse_map( + uint8_t episode, + uint8_t difficulty, + shared_ptr battle_params_table, + const void* data, + size_t size, + bool alt_enemies) { + + const auto* map = reinterpret_cast(data); + size_t entry_count = size / sizeof(EnemyEntry); + if (size != entry_count * sizeof(EnemyEntry)) { + throw runtime_error("data size is not a multiple of entry size"); + } vector enemies; auto create_clones = [&](size_t count) { @@ -103,6 +122,7 @@ static vector parse_map(uint8_t episode, uint8_t difficulty, } }; + const auto& battle_params = *battle_params_table; for (size_t y = 0; y < entry_count; y++) { const auto& e = map[y]; size_t num_clones = e.num_clones; @@ -254,7 +274,7 @@ static vector parse_map(uint8_t episode, uint8_t difficulty, case 0xC0: // Dragon or Gal Gryphon if (episode == 1) { enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x12].experience, 44); - } else if (episode == 0x02) { + } else if (episode == 2) { enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x1E].experience, 77); } break; @@ -343,7 +363,7 @@ static vector parse_map(uint8_t episode, uint8_t difficulty, } break; case 0xE0: // Epsilon, Sinow Zoa and Zele - if ((episode == 0x02) && (alt_enemies)) { + if ((episode == 2) && (alt_enemies)) { enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x23].experience, 84); create_clones(4); } else { @@ -413,13 +433,3 @@ static vector parse_map(uint8_t episode, uint8_t difficulty, return enemies; } - -vector load_map(const std::string& filename, uint8_t episode, - uint8_t difficulty, const BattleParams* battle_params, bool alt_enemies) { - static FileContentsCache map_file_cache(300 * 1000 * 1000); - shared_ptr data = map_file_cache.get_or_load(filename).data; - const EnemyEntry* entries = reinterpret_cast(data->data()); - size_t entry_count = data->size() / sizeof(EnemyEntry); - return parse_map(episode, difficulty, battle_params, entries, entry_count, - alt_enemies); -} diff --git a/src/Map.hh b/src/Map.hh index cce3db0a..df80956a 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -6,6 +6,8 @@ #include #include +#include "Text.hh" + struct BattleParams { @@ -21,24 +23,24 @@ struct BattleParams { le_uint32_t difficulty; } __attribute__((packed)); -struct BattleParamTable { - BattleParams entries[2][3][4][0x60]; // online/offline, episode, difficulty, monster type +class BattleParamsIndex { +public: + using TableT = parray; - BattleParamTable(const char* filename_prefix); + BattleParamsIndex(const char* filename_prefix); const BattleParams& get(bool solo, uint8_t episode, uint8_t difficulty, uint8_t monster_type) const; - const BattleParams* get_subtable(bool solo, uint8_t episode, - uint8_t difficulty) const; -} __attribute__((packed)); + std::shared_ptr get_subtable( + bool solo, uint8_t episode, uint8_t difficulty) const; + +private: + // online/offline, episode, difficulty + std::shared_ptr entries[2][3][4]; +}; -struct BattleParamIndex { - BattleParamTable table_for_episode[3]; -} __attribute__((packed)); - -// an enemy entry as loaded by the game struct PSOEnemy { uint64_t id; uint16_t source_type; @@ -53,5 +55,10 @@ struct PSOEnemy { std::string str() const; } __attribute__((packed)); -std::vector load_map(const std::string& filename, uint8_t episode, - uint8_t difficulty, const BattleParams* bp, bool alt_enemies); +std::vector parse_map( + uint8_t episode, + uint8_t difficulty, + std::shared_ptr battle_params, + const void* data, + size_t size, + bool alt_enemies); diff --git a/src/Player.cc b/src/Player.cc index 70436567..ac2799eb 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -285,9 +285,9 @@ GuildCardBB::GuildCardBB() noexcept void GuildCardBB::clear() { this->guild_card_number = 0; - this->name.clear(); - this->team_name.clear(); - this->description.clear(); + this->name.clear(0); + this->team_name.clear(0); + this->description.clear(0); this->present = 0; this->language = 0; this->section_id = 0; @@ -296,7 +296,7 @@ void GuildCardBB::clear() { void GuildCardEntryBB::clear() { this->data.clear(); - this->unknown_a1.clear(); + this->unknown_a1.clear(0); } uint32_t GuildCardFileBB::checksum() const { @@ -495,7 +495,7 @@ void ClientGameData::import_player(const PSOPlayerDataV3& gc) { if (gc.auto_reply_enabled) { player->auto_reply = gc.auto_reply; } else { - player->auto_reply.clear(); + player->auto_reply.clear(0); } } @@ -509,7 +509,7 @@ void ClientGameData::import_player(const PSOPlayerDataBB& bb) { if (bb.auto_reply_enabled) { player->auto_reply = bb.auto_reply; } else { - player->auto_reply.clear(); + player->auto_reply.clear(0); } } @@ -520,7 +520,7 @@ PlayerBB ClientGameData::export_player_bb() { PlayerBB ret; ret.inventory = player->inventory; ret.disp = player->disp; - ret.unknown.clear(); + ret.unknown.clear(0); ret.option_flags = account->option_flags; ret.quest_data1 = player->quest_data1; ret.bank = player->bank; @@ -537,10 +537,10 @@ PlayerBB ClientGameData::export_player_bb() { ret.shortcuts = account->shortcuts; ret.auto_reply = player->auto_reply; ret.info_board = player->info_board; - ret.unknown5.clear(); + ret.unknown5.clear(0); ret.challenge_data = player->challenge_data; ret.tech_menu_config = player->tech_menu_config; - ret.unknown6.clear(); + ret.unknown6.clear(0); ret.quest_data2 = player->quest_data2; ret.key_config = account->key_config; return ret; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 57f17751..46766ac0 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1424,9 +1424,9 @@ void process_gba_file_request(shared_ptr, shared_ptr c, strip_trailing_zeroes(filename); static FileContentsCache gba_file_cache(300 * 1000 * 1000); - auto contents = gba_file_cache.get_or_load("system/gba/" + filename).data; + auto f = gba_file_cache.get_or_load("system/gba/" + filename).file; - send_quest_file(c, "", filename, *contents, QuestFileType::GBA_DEMO); + send_quest_file(c, "", filename, f->data, QuestFileType::GBA_DEMO); } @@ -1656,7 +1656,7 @@ void process_client_checksum_bb(shared_ptr, shared_ptr c, for (size_t z = 0; z < max_count; z++) { if (!gcf.entries[z].data.present) { gcf.entries[z].data = new_gc; - gcf.entries[z].unknown_a1.clear(); + gcf.entries[z].unknown_a1.clear(0); c->log.info("Added guild card %" PRIu32 " at position %zu", new_gc.guild_card_number.load(), z); break; @@ -1997,7 +1997,7 @@ void process_set_auto_reply_t(shared_ptr, shared_ptr c, void process_disable_auto_reply(shared_ptr, shared_ptr c, uint16_t, uint32_t, const string& data) { // C8 check_size_v(data.size(), 0); - c->game_data.player()->auto_reply.clear(); + c->game_data.player()->auto_reply.clear(0); } void process_set_blocked_senders_list(shared_ptr, shared_ptr c, @@ -2021,22 +2021,6 @@ shared_ptr create_game_generic(shared_ptr s, const std::u16string& password, uint8_t episode, uint8_t difficulty, uint8_t battle, uint8_t challenge, uint8_t solo) { - static const uint32_t variation_maxes_online[3][0x20] = { - {1, 1, 1, 5, 1, 5, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, - 3, 2, 3, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 3, 1, 3, 1, 3, - 2, 2, 1, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1}, - {1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; - - static const uint32_t variation_maxes_solo[3][0x20] = { - {1, 1, 1, 3, 1, 3, 3, 1, 3, 1, 3, 1, 3, 2, 3, 2, - 3, 2, 3, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 3, 1, 3, 1, 3, - 2, 2, 1, 3, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {1, 1, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 3, 1, 1, 3, - 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}; - // A player's actual level is their displayed level - 1, so the minimums for // Episode 1 (for example) are actually 1, 20, 40, 80. static const uint32_t default_minimum_levels[3][4] = { @@ -2102,7 +2086,6 @@ shared_ptr create_game_generic(shared_ptr s, game->min_level = min_level; game->max_level = 0xFFFFFFFF; - const uint32_t* variation_maxes = nullptr; if (game->version == GameVersion::BB) { // TODO: cache these somewhere so we don't read the file every time, lolz game->rare_item_set.reset(new RareItemSet("system/blueburst/ItemRT.rel", @@ -2113,34 +2096,38 @@ shared_ptr create_game_generic(shared_ptr s, } game->next_game_item_id = 0x00810000; - const auto* bp_subtable = s->battle_params->get_subtable(game->mode == 3, + auto bp_subtable = s->battle_params->get_subtable(game->mode == 3, game->episode - 1, game->difficulty); - const char* type_chars = (game->mode == 3) ? "sm" : "m"; - if (episode > 0 && episode < 4) { - variation_maxes = (game->mode == 3) ? variation_maxes_solo[episode - 1] : variation_maxes_online[episode - 1]; - } + generate_variations( + game->variations, game->random, game->episode, game->mode == 3); for (size_t x = 0; x < 0x10; x++) { - for (const char* type = type_chars; *type; type++) { - auto filename = string_printf( - "system/blueburst/map/%c%hhX%zX%" PRIX32 "%" PRIX32 ".dat", - *type, game->episode, x, - game->variations.data()[x * 2].load(), - game->variations.data()[(x * 2) + 1].load()); - try { - auto enemies = load_map(filename.c_str(), game->episode, - game->difficulty, bp_subtable, false); - game->enemies.insert(game->enemies.end(), enemies.begin(), enemies.end()); - c->log.info("Loaded map %s (%zu entries)", filename.c_str(), enemies.size()); - for (size_t z = 0; z < enemies.size(); z++) { - string e_str = enemies[z].str(); - static_game_data_log.info("(Entry %zu) %s", z, e_str.c_str()); - } - break; - } catch (const exception& e) { - c->log.warning("Failed to load map %s: %s", filename.c_str(), e.what()); + try { + auto file = map_data_for_variation( + game->episode, + game->mode == 3, + x, + game->variations[x * 2 + 0], + game->variations[x * 2 + 1]); + auto area_enemies = parse_map( + game->episode, + game->difficulty, + bp_subtable, + file->data.data(), + file->data.size(), + false); + game->enemies.insert( + game->enemies.end(), + area_enemies.begin(), + area_enemies.end()); + c->log.info("Loaded map for area %zu (%zu entries)", x, area_enemies.size()); + for (size_t z = 0; z < area_enemies.size(); z++) { + string e_str = area_enemies[z].str(); + static_game_data_log.info("(Entry %zu) %s", z, e_str.c_str()); } + } catch (const exception& e) { + c->log.warning("Failed to load map for area %zu: %s", x, e.what()); } } @@ -2149,22 +2136,12 @@ shared_ptr create_game_generic(shared_ptr s, } c->log.info("Loaded maps contain %zu entries overall", game->enemies.size()); - } else { - // In non-BB games, just set the variations (we don't track items/enemies/ - // etc.) - if (episode > 0 && episode < 4 && !is_ep3) { - variation_maxes = variation_maxes_online[episode - 1]; - } - } + } else if (is_ep3) { + game->variations.clear(0); - if (variation_maxes) { - for (size_t x = 0; x < 0x20; x++) { - game->variations.data()[x] = (*game->random)() % variation_maxes[x]; - } } else { - for (size_t x = 0; x < 0x20; x++) { - game->variations.data()[x] = 0; - } + // In non-BB non-Ep3 games, just set the variations (we don't track enemies) + generate_variations(game->variations, game->random, game->episode, false); } s->change_client_lobby(c, game); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index fc780c52..4bb752fd 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -376,16 +376,16 @@ void send_stream_file_index_bb(shared_ptr c) { string key = "system/blueburst/" + filename; auto cache_res = bb_stream_files_cache.get_or_load(key); auto& e = entries.emplace_back(); - e.size = cache_res.data->size(); + e.size = cache_res.file->data.size(); // Computing the checksum can be slow, so we cache it along with the file // data. If the cache result was just populated, then it may be different, // so we always recompute the checksum in that case. if (cache_res.generate_called) { - e.checksum = crc32(cache_res.data->data(), e.size); + e.checksum = crc32(cache_res.file->data.data(), e.size); bb_stream_files_cache.replace_obj(key + ".crc32", e.checksum); } else { auto compute_checksum = [&](const string&) -> uint32_t { - return crc32(cache_res.data->data(), e.size); + return crc32(cache_res.file->data.data(), e.size); }; e.checksum = bb_stream_files_cache.get_obj(key + ".crc32", compute_checksum).obj; } @@ -400,26 +400,26 @@ void send_stream_file_chunk_bb(shared_ptr c, uint32_t chunk_index) { auto cache_result = bb_stream_files_cache.get("", +[](const string&) -> string { size_t bytes = 0; for (const auto& name : stream_file_entries) { - bytes += bb_stream_files_cache.get_or_load("system/blueburst/" + name).data->size(); + bytes += bb_stream_files_cache.get_or_load("system/blueburst/" + name).file->data.size(); } string ret; ret.reserve(bytes); for (const auto& name : stream_file_entries) { - ret += *bb_stream_files_cache.get_or_load("system/blueburst/" + name).data; + ret += bb_stream_files_cache.get_or_load("system/blueburst/" + name).file->data; } return ret; }); - auto contents = cache_result.data; + const auto& contents = cache_result.file->data; S_StreamFileChunk_BB_02EB chunk_cmd; chunk_cmd.chunk_index = chunk_index; size_t offset = sizeof(chunk_cmd.data) * chunk_index; - if (offset > contents->size()) { + if (offset > contents.size()) { throw runtime_error("client requested chunk beyond end of stream file"); } - size_t bytes = min(contents->size() - offset, sizeof(chunk_cmd.data)); - memcpy(chunk_cmd.data, contents->data() + offset, bytes); + size_t bytes = min(contents.size() - offset, sizeof(chunk_cmd.data)); + memcpy(chunk_cmd.data, contents.data() + offset, bytes); size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes; cmd_size = (cmd_size + 3) & ~3; @@ -1484,7 +1484,7 @@ void send_quest_open_file_t( default: throw logic_error("invalid quest file type"); } - cmd.unused.clear(); + cmd.unused.clear(0); cmd.file_size = file_size; cmd.filename = filename.c_str(); send_command_t(c, command_num, 0x00, cmd); diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 4416b1c2..596a4f18 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -163,7 +163,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\ shared_ptr lm(new LicenseManager("system/licenses.nsi")); this->state->license_manager = lm; } else if (type == "battle-params") { - shared_ptr bpt(new BattleParamTable("system/blueburst/BattleParamEntry")); + shared_ptr bpt(new BattleParamsIndex("system/blueburst/BattleParamEntry")); this->state->battle_params = bpt; } else if (type == "level-table") { shared_ptr lt(new LevelTable("system/blueburst/PlyLevelTbl.prs", true)); diff --git a/src/ServerState.hh b/src/ServerState.hh index 4cd88c8b..ab8e1b4e 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -55,7 +55,7 @@ struct ServerState { std::shared_ptr ep3_data_index; std::shared_ptr quest_index; std::shared_ptr level_table; - std::shared_ptr battle_params; + std::shared_ptr battle_params; std::shared_ptr common_item_data; std::shared_ptr license_manager; diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index 3801ba8b..f2093240 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -2,10 +2,216 @@ #include +#include "FileContentsCache.hh" + using namespace std; +struct AreaMapFileIndex { + const char* name_token; + std::vector variation1_values; + std::vector variation2_values; + + AreaMapFileIndex( + const char* name_token, + std::vector variation1_values, + std::vector variation2_values) + : name_token(name_token), + variation1_values(variation1_values), + variation2_values(variation2_values) { } +}; + +// These are indexed as [episode][is_solo][area] +// (Note that Lobby::episode is 1-3, so we actually use episode - 1) +static const std::vector>> map_file_info = { + { // Episode 1 + { // Non-solo + {"city00", {}, {0}}, + {"forest01", {}, {0, 1, 2, 3, 4}}, + {"forest02", {}, {0, 1, 2, 3, 4}}, + {"cave01", {0, 1, 2}, {0, 1}}, + {"cave02", {0, 1, 2}, {0, 1}}, + {"cave03", {0, 1, 2}, {0, 1}}, + {"machine01", {0, 1, 2}, {0, 1}}, + {"machine02", {0, 1, 2}, {0, 1}}, + {"ancient01", {0, 1, 2}, {0, 1}}, + {"ancient02", {0, 1, 2}, {0, 1}}, + {"ancient03", {0, 1, 2}, {0, 1}}, + {"boss01", {}, {}}, + {"boss02", {}, {}}, + {"boss03", {}, {}}, + {"boss04", {}, {}}, + {nullptr, {}, {}}, + }, + { // Solo + {"city00", {}, {0}}, + {"forest01", {}, {0, 2, 4}}, + {"forest02", {}, {0, 3, 4}}, + {"cave01", {0, 1, 2}, {0}}, + {"cave02", {0, 1, 2}, {0}}, + {"cave03", {0, 1, 2}, {0}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + }, + }, + { // Episode 2 + { // Non-solo + {"labo00", {}, {0}}, + {"ruins01", {0, 1}, {0}}, + {"ruins02", {0, 1}, {0}}, + {"space01", {0, 1}, {0}}, + {"space02", {0, 1}, {0}}, + {"jungle01", {}, {0, 1, 2}}, + {"jungle02", {}, {0, 1, 2}}, + {"jungle03", {}, {0, 1, 2}}, + {"jungle04", {0, 1}, {0, 1}}, + {"jungle05", {}, {0, 1, 2}}, + {"seabed01", {0, 1}, {0, 1}}, + {"seabed02", {0, 1}, {0, 1}}, + {"boss05", {}, {}}, + {"boss06", {}, {}}, + {"boss07", {}, {}}, + {"boss08", {}, {}}, + }, + { // Solo + {"labo00", {}, {0}}, + {"ruins01", {0, 1}, {0}}, + {"ruins02", {0, 1}, {0}}, + {"space01", {0, 1}, {0}}, + {"space02", {0, 1}, {0}}, + {"jungle01", {}, {0, 1, 2}}, + {"jungle02", {}, {0, 1, 2}}, + {"jungle03", {}, {0, 1, 2}}, + {"jungle04", {0, 1}, {0, 1}}, + {"jungle05", {}, {0, 1, 2}}, + {"seabed01", {0, 1}, {0}}, + {"seabed02", {0, 1}, {0}}, + {"boss05", {}, {}}, + {"boss06", {}, {}}, + {"boss07", {}, {}}, + {"boss08", {}, {}}, + }, + }, + { // Episode 4 + { // Non-solo + {"city02", {0}, {0}}, + {"wilds01", {0}, {0, 1, 2}}, + {"wilds01", {1}, {0, 1, 2}}, + {"wilds01", {2}, {0, 1, 2}}, + {"wilds01", {3}, {0, 1, 2}}, + {"crater01", {0}, {0, 1, 2}}, + {"desert01", {0, 1, 2}, {0}}, + {"desert02", {0}, {0, 1, 2}}, + {"desert03", {0, 1, 2}, {0}}, + {"boss09", {0}, {0}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + }, + { // Solo + {"city02", {0}, {0}}, + {"wilds01", {0}, {0, 1, 2}}, + {"wilds01", {1}, {0, 1, 2}}, + {"wilds01", {2}, {0, 1, 2}}, + {"wilds01", {3}, {0, 1, 2}}, + {"crater01", {0}, {0, 1, 2}}, + {"desert01", {0, 1, 2}, {0}}, + {"desert02", {0}, {0, 1, 2}}, + {"desert03", {0, 1, 2}, {0}}, + {"boss09", {0}, {0}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + {nullptr, {}, {}}, + }, + }, +}; + +void generate_variations( + parray& variations, + std::shared_ptr random, + uint8_t episode, + bool is_solo) { + const auto& ep_index = map_file_info.at(episode - 1); + for (size_t z = 0; z < 0x10; z++) { + const AreaMapFileIndex* a = nullptr; + if (is_solo) { + a = &ep_index.at(true).at(z); + } + if (!a || !a->name_token) { + a = &ep_index.at(false).at(z); + } + if (!a->name_token) { + 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()); + } + } +} + +std::shared_ptr map_data_for_variation( + uint8_t episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2) { + static FileContentsCache cache(300 * 1000 * 1000); + + // Map filenames are like map_[_VV][_VV][_off][_s].dat + // name_token comes from AreaMapFileIndex + // _VV are the values from the variation<1|2>_values vector (in contrast to + // the values sent in the 64 command, which are INDEXES INTO THAT VECTOR) + // _off or _s are used for solo mode (try both - city uses _s whereas levels + // use _off apparently) + // e|o specifies what kind of data: e = enemies, o = objects + const auto& ep_index = map_file_info.at(episode - 1); + const AreaMapFileIndex* a = nullptr; + if (is_solo) { + a = &ep_index.at(true).at(area); + } + if (!a || !a->name_token) { + a = &ep_index.at(false).at(area); + } + if (!a->name_token) { + throw out_of_range("no map data for area"); + } + + string filename = "system/blueburst/map/map_"; + filename += a->name_token; + if (!a->variation1_values.empty()) { + filename += string_printf("_%02" PRIX32, a->variation1_values.at(var1)); + } + if (!a->variation2_values.empty()) { + filename += string_printf("_%02" PRIX32, a->variation2_values.at(var2)); + } + if (is_solo) { + // Try both _offe.dat and e_s.dat suffixes + try { + return cache.get_or_load(filename + "_offe.dat").file; + } catch (const cannot_open_file&) { + return cache.get_or_load(filename + "e_s.dat").file; + } + } else { + return cache.get_or_load(filename + "e.dat").file; + } +} + + + const vector section_id_to_name({ "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", "Oran", "Yellowboze", "Whitill"}); diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index 19fb6bd8..8eb70190 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -3,11 +3,22 @@ #include #include +#include +#include "FileContentsCache.hh" #include "Player.hh" +void generate_variations( + parray& variations, + std::shared_ptr random, + uint8_t episode, + bool is_solo); +std::shared_ptr map_data_for_variation( + uint8_t episode, bool is_solo, uint8_t area, uint32_t var1, uint32_t var2); +void load_map_files(); + size_t stack_size_for_item(uint8_t data0, uint8_t data1); size_t stack_size_for_item(const ItemData& item); diff --git a/src/Text.hh b/src/Text.hh index 5fe59040..03c2125b 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -192,9 +192,13 @@ template struct parray { ItemT items[Count]; + template requires (std::is_arithmetic::value) parray() { - this->clear(); + this->clear(0); } + template requires (!std::is_arithmetic::value) + parray() { } + parray(const parray& other) { this->operator=(other); } @@ -281,7 +285,7 @@ struct parray { return !this->operator==(s); } - void clear(ItemT v = 0) { + void clear(ItemT v) { for (size_t x = 0; x < Count; x++) { this->items[x] = v; } @@ -312,7 +316,7 @@ struct parray { template struct ptext : parray { ptext() { - this->clear(); + this->clear(0); } ptext(const ptext& other) : parray(other) { } ptext(ptext&& s) = delete;