rewrite map loader

This commit is contained in:
Martin Michelsen
2022-08-03 17:20:36 -07:00
parent 9b837c5b6c
commit 7a1eb677dc
14 changed files with 388 additions and 174 deletions
+18 -17
View File
@@ -11,27 +11,26 @@ using namespace std;
FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) { } FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) { }
FileContentsCache::File::File(const string& name, shared_ptr<const string> contents, FileContentsCache::File::File(
uint64_t load_time) : name(name), contents(contents), load_time(load_time) { } const string& name,
string&& data,
uint64_t load_time)
: name(name), data(move(data)), load_time(load_time) { }
shared_ptr<const string> FileContentsCache::replace( shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
const string& name, string&& data, uint64_t t) { const string& name, string&& data, uint64_t t) {
if (t == 0) { if (t == 0) {
t = now(); t = now();
} }
shared_ptr<const string> contents(new string(move(data))); shared_ptr<File> new_file(new File(name, move(data), t));
auto emplace_ret = this->name_to_file.emplace( auto emplace_ret = this->name_to_file.emplace(name, new_file);
piecewise_construct,
forward_as_tuple(name),
forward_as_tuple(name, contents, t));
if (!emplace_ret.second) { if (!emplace_ret.second) {
emplace_ret.first->second.contents = contents; emplace_ret.first->second = new_file;
emplace_ret.first->second.load_time = t;
} }
return contents; return new_file;
} }
shared_ptr<const string> FileContentsCache::replace( shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
const string& name, const void* data, size_t size, uint64_t t) { const string& name, const void* data, size_t size, uint64_t t) {
string s(reinterpret_cast<const char*>(data), size); string s(reinterpret_cast<const char*>(data), size);
return this->replace(name, move(s), t); 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)); return this->get_or_load(string(name));
} }
shared_ptr<const string> FileContentsCache::get_or_throw(const std::string& name) { shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(
const std::string& name) {
auto throw_fn = +[](const std::string&) -> string { auto throw_fn = +[](const std::string&) -> string {
throw out_of_range("file missing from cache"); throw out_of_range("file missing from cache");
}; };
return this->get(name, throw_fn).data; return this->get(name, throw_fn).file;
} }
shared_ptr<const string> FileContentsCache::get_or_throw(const char* name) { shared_ptr<const FileContentsCache::File> FileContentsCache::get_or_throw(
const char* name) {
return this->get_or_throw(string(name)); return this->get_or_throw(string(name));
} }
@@ -61,8 +62,8 @@ FileContentsCache::GetResult FileContentsCache::get(const std::string& name,
uint64_t t = now(); uint64_t t = now();
try { try {
auto& entry = this->name_to_file.at(name); auto& entry = this->name_to_file.at(name);
if (this->ttl_usecs && (t - entry.load_time < this->ttl_usecs)) { if (this->ttl_usecs && (t - entry->load_time < this->ttl_usecs)) {
return {entry.contents, false}; return {entry, false};
} }
} catch (const out_of_range& e) { } } catch (const out_of_range& e) { }
return {this->replace(name, generate(name)), true}; return {this->replace(name, generate(name)), true};
+19 -21
View File
@@ -11,15 +11,14 @@ using namespace std;
class FileContentsCache { class FileContentsCache {
private: public:
struct File { struct File {
std::string name; std::string name;
std::shared_ptr<const std::string> contents; std::string data;
uint64_t load_time; uint64_t load_time;
File() = delete; File() = delete;
File(const std::string& name, std::shared_ptr<const std::string> contents, File(const std::string& name, std::string&& contents, uint64_t load_time);
uint64_t load_time);
File(const File&) = delete; File(const File&) = delete;
File(File&&) = delete; File(File&&) = delete;
File& operator=(const File&) = delete; File& operator=(const File&) = delete;
@@ -27,7 +26,6 @@ private:
~File() = default; ~File() = default;
}; };
public:
explicit FileContentsCache(uint64_t ttl_usecs); explicit FileContentsCache(uint64_t ttl_usecs);
FileContentsCache(const FileContentsCache&) = delete; FileContentsCache(const FileContentsCache&) = delete;
FileContentsCache(FileContentsCache&&) = delete; FileContentsCache(FileContentsCache&&) = delete;
@@ -40,20 +38,20 @@ public:
return this->name_to_file.erase(key); return this->name_to_file.erase(key);
} }
std::shared_ptr<const std::string> replace( std::shared_ptr<const File> replace(
const std::string& name, std::string&& data, uint64_t t = 0); const std::string& name, std::string&& data, uint64_t t = 0);
std::shared_ptr<const std::string> replace( std::shared_ptr<const File> replace(
const std::string& name, const void* data, size_t size, uint64_t t = 0); const std::string& name, const void* data, size_t size, uint64_t t = 0);
struct GetResult { struct GetResult {
std::shared_ptr<const std::string> data; std::shared_ptr<const File> file;
bool generate_called; bool generate_called;
}; };
GetResult get_or_load(const std::string& name); GetResult get_or_load(const std::string& name);
GetResult get_or_load(const char* name); GetResult get_or_load(const char* name);
std::shared_ptr<const string> get_or_throw(const std::string& name); std::shared_ptr<const File> get_or_throw(const std::string& name);
std::shared_ptr<const string> get_or_throw(const char* name); std::shared_ptr<const File> get_or_throw(const char* name);
GetResult get( GetResult get(
const std::string& name, std::function<std::string(const std::string&)> generate); const std::string& name, std::function<std::string(const std::string&)> generate);
@@ -63,36 +61,36 @@ public:
template <typename T> template <typename T>
struct GetObjResult { struct GetObjResult {
const T& obj; const T& obj;
std::shared_ptr<const std::string> data; std::shared_ptr<const File> data;
bool generate_called; bool generate_called;
}; };
template <typename T, typename NameT> template <typename T, typename NameT>
GetObjResult<T> get_obj_or_load(NameT name) { GetObjResult<T> get_obj_or_load(NameT name) {
auto res = this->get_or_load(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"); throw runtime_error("cached string size is incorrect");
} }
return {*reinterpret_cast<const T*>(res.data->data()), res.data, res.generate_called}; return {*reinterpret_cast<const T*>(res.file->data.data()), res.file, res.generate_called};
} }
template <typename T, typename NameT> template <typename T, typename NameT>
GetObjResult<T> get_obj_or_throw(NameT name) { GetObjResult<T> get_obj_or_throw(NameT name) {
auto res = this->get_or_throw(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"); throw runtime_error("cached string size is incorrect");
} }
return {*reinterpret_cast<const T*>(res.data->data()), res.data, res.generate_called}; return {*reinterpret_cast<const T*>(res.file->data.data()), res.file, res.generate_called};
} }
template <typename T, typename NameT> template <typename T, typename NameT>
GetObjResult<T> get_obj(NameT name, std::function<T(const std::string&)> generate) { GetObjResult<T> get_obj(NameT name, std::function<T(const std::string&)> generate) {
uint64_t t = now(); uint64_t t = now();
try { try {
auto& entry = this->name_to_file.at(name); auto& f = this->name_to_file.at(name);
if (entry.contents->size() != sizeof(T)) { if (f->data.size() != sizeof(T)) {
throw runtime_error("cached string size is incorrect"); throw runtime_error("cached string size is incorrect");
} }
if (this->ttl_usecs && (t - entry.load_time < this->ttl_usecs)) { if (this->ttl_usecs && (t - f->load_time < this->ttl_usecs)) {
return {*reinterpret_cast<const T*>(entry.contents->data()), entry.contents, false}; return {*reinterpret_cast<const T*>(f->data.data()), f, false};
} }
} catch (const out_of_range& e) { } } catch (const out_of_range& e) { }
T value = generate(name); T value = generate(name);
@@ -103,10 +101,10 @@ public:
template <typename T, typename NameT> template <typename T, typename NameT>
GetObjResult<T> replace_obj(NameT name, const T& value) { GetObjResult<T> replace_obj(NameT name, const T& value) {
auto cached_value = this->replace(name, &value, sizeof(value)); auto cached_value = this->replace(name, &value, sizeof(value));
return {*reinterpret_cast<const T*>(cached_value->data()), cached_value, false}; return {*reinterpret_cast<const T*>(cached_value->data.data()), cached_value, false};
} }
private: private:
std::unordered_map<std::string, File> name_to_file; std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
uint64_t ttl_usecs; uint64_t ttl_usecs;
}; };
+2 -2
View File
@@ -32,9 +32,9 @@ string CompiledFunctionCode::generate_client_command(
const string& suffix) const { const string& suffix) const {
S_ExecuteCode_Footer_GC_B2 footer; S_ExecuteCode_Footer_GC_B2 footer;
footer.num_relocations = this->relocation_deltas.size(); footer.num_relocations = this->relocation_deltas.size();
footer.unused1.clear(); footer.unused1.clear(0);
footer.entrypoint_addr_offset = this->entrypoint_offset_offset; footer.entrypoint_addr_offset = this->entrypoint_offset_offset;
footer.unused2.clear(); footer.unused2.clear(0);
StringWriter w; StringWriter w;
if (!label_writes.empty()) { if (!label_writes.empty()) {
+1 -1
View File
@@ -475,7 +475,7 @@ int main(int argc, char** argv) {
} }
config_log.info("Loading battle parameters"); 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"); config_log.info("Loading level table");
state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true)); state->level_table.reset(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
+48 -38
View File
@@ -10,27 +10,35 @@ using namespace std;
static void load_battle_param_file(const string& filename, BattleParams* entries) { BattleParamsIndex::BattleParamsIndex(const char* prefix) {
scoped_fd fd(filename, O_RDONLY); for (uint8_t is_solo = 0; is_solo < 2; is_solo++) {
readx(fd, entries, 0x60 * sizeof(BattleParams)); 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) { const BattleParams& BattleParamsIndex::get(bool solo, uint8_t episode,
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,
uint8_t difficulty, uint8_t monster_type) const { uint8_t difficulty, uint8_t monster_type) const {
if (episode > 3) { if (episode > 3) {
throw invalid_argument("incorrect episode"); throw invalid_argument("incorrect episode");
@@ -41,18 +49,19 @@ const BattleParams& BattleParamTable::get(bool solo, uint8_t episode,
if (monster_type > 0x60) { if (monster_type > 0x60) {
throw invalid_argument("incorrect monster type"); 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, shared_ptr<const BattleParamsIndex::TableT>
uint8_t difficulty) const { BattleParamsIndex::get_subtable(
bool solo, uint8_t episode, uint8_t difficulty) const {
if (episode > 3) { if (episode > 3) {
throw invalid_argument("incorrect episode"); throw invalid_argument("incorrect episode");
} }
if (difficulty > 4) { if (difficulty > 4) {
throw invalid_argument("incorrect difficulty"); 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 uint64_t next_enemy_id = 1;
static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty, vector<PSOEnemy> parse_map(
const BattleParams* battle_params, const EnemyEntry* map, uint8_t episode,
size_t entry_count, bool alt_enemies) { uint8_t difficulty,
shared_ptr<const BattleParamsIndex::TableT> battle_params_table,
const void* data,
size_t size,
bool alt_enemies) {
const auto* map = reinterpret_cast<const EnemyEntry*>(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<PSOEnemy> enemies; vector<PSOEnemy> enemies;
auto create_clones = [&](size_t count) { auto create_clones = [&](size_t count) {
@@ -103,6 +122,7 @@ static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
} }
}; };
const auto& battle_params = *battle_params_table;
for (size_t y = 0; y < entry_count; y++) { for (size_t y = 0; y < entry_count; y++) {
const auto& e = map[y]; const auto& e = map[y];
size_t num_clones = e.num_clones; size_t num_clones = e.num_clones;
@@ -254,7 +274,7 @@ static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
case 0xC0: // Dragon or Gal Gryphon case 0xC0: // Dragon or Gal Gryphon
if (episode == 1) { if (episode == 1) {
enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x12].experience, 44); 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); enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x1E].experience, 77);
} }
break; break;
@@ -343,7 +363,7 @@ static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
} }
break; break;
case 0xE0: // Epsilon, Sinow Zoa and Zele 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); enemies.emplace_back(next_enemy_id++, e.base, battle_params[0x23].experience, 84);
create_clones(4); create_clones(4);
} else { } else {
@@ -413,13 +433,3 @@ static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
return enemies; return enemies;
} }
vector<PSOEnemy> 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<const string> data = map_file_cache.get_or_load(filename).data;
const EnemyEntry* entries = reinterpret_cast<const EnemyEntry*>(data->data());
size_t entry_count = data->size() / sizeof(EnemyEntry);
return parse_map(episode, difficulty, battle_params, entries, entry_count,
alt_enemies);
}
+20 -13
View File
@@ -6,6 +6,8 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include "Text.hh"
struct BattleParams { struct BattleParams {
@@ -21,24 +23,24 @@ struct BattleParams {
le_uint32_t difficulty; le_uint32_t difficulty;
} __attribute__((packed)); } __attribute__((packed));
struct BattleParamTable { class BattleParamsIndex {
BattleParams entries[2][3][4][0x60]; // online/offline, episode, difficulty, monster type public:
using TableT = parray<BattleParams, 0x60>;
BattleParamTable(const char* filename_prefix); BattleParamsIndex(const char* filename_prefix);
const BattleParams& get(bool solo, uint8_t episode, uint8_t difficulty, const BattleParams& get(bool solo, uint8_t episode, uint8_t difficulty,
uint8_t monster_type) const; uint8_t monster_type) const;
const BattleParams* get_subtable(bool solo, uint8_t episode, std::shared_ptr<const TableT> get_subtable(
uint8_t difficulty) const; bool solo, uint8_t episode, uint8_t difficulty) const;
} __attribute__((packed));
private:
// online/offline, episode, difficulty
std::shared_ptr<TableT> entries[2][3][4];
};
struct BattleParamIndex {
BattleParamTable table_for_episode[3];
} __attribute__((packed));
// an enemy entry as loaded by the game
struct PSOEnemy { struct PSOEnemy {
uint64_t id; uint64_t id;
uint16_t source_type; uint16_t source_type;
@@ -53,5 +55,10 @@ struct PSOEnemy {
std::string str() const; std::string str() const;
} __attribute__((packed)); } __attribute__((packed));
std::vector<PSOEnemy> load_map(const std::string& filename, uint8_t episode, std::vector<PSOEnemy> parse_map(
uint8_t difficulty, const BattleParams* bp, bool alt_enemies); uint8_t episode,
uint8_t difficulty,
std::shared_ptr<const BattleParamsIndex::TableT> battle_params,
const void* data,
size_t size,
bool alt_enemies);
+9 -9
View File
@@ -285,9 +285,9 @@ GuildCardBB::GuildCardBB() noexcept
void GuildCardBB::clear() { void GuildCardBB::clear() {
this->guild_card_number = 0; this->guild_card_number = 0;
this->name.clear(); this->name.clear(0);
this->team_name.clear(); this->team_name.clear(0);
this->description.clear(); this->description.clear(0);
this->present = 0; this->present = 0;
this->language = 0; this->language = 0;
this->section_id = 0; this->section_id = 0;
@@ -296,7 +296,7 @@ void GuildCardBB::clear() {
void GuildCardEntryBB::clear() { void GuildCardEntryBB::clear() {
this->data.clear(); this->data.clear();
this->unknown_a1.clear(); this->unknown_a1.clear(0);
} }
uint32_t GuildCardFileBB::checksum() const { uint32_t GuildCardFileBB::checksum() const {
@@ -495,7 +495,7 @@ void ClientGameData::import_player(const PSOPlayerDataV3& gc) {
if (gc.auto_reply_enabled) { if (gc.auto_reply_enabled) {
player->auto_reply = gc.auto_reply; player->auto_reply = gc.auto_reply;
} else { } 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) { if (bb.auto_reply_enabled) {
player->auto_reply = bb.auto_reply; player->auto_reply = bb.auto_reply;
} else { } else {
player->auto_reply.clear(); player->auto_reply.clear(0);
} }
} }
@@ -520,7 +520,7 @@ PlayerBB ClientGameData::export_player_bb() {
PlayerBB ret; PlayerBB ret;
ret.inventory = player->inventory; ret.inventory = player->inventory;
ret.disp = player->disp; ret.disp = player->disp;
ret.unknown.clear(); ret.unknown.clear(0);
ret.option_flags = account->option_flags; ret.option_flags = account->option_flags;
ret.quest_data1 = player->quest_data1; ret.quest_data1 = player->quest_data1;
ret.bank = player->bank; ret.bank = player->bank;
@@ -537,10 +537,10 @@ PlayerBB ClientGameData::export_player_bb() {
ret.shortcuts = account->shortcuts; ret.shortcuts = account->shortcuts;
ret.auto_reply = player->auto_reply; ret.auto_reply = player->auto_reply;
ret.info_board = player->info_board; ret.info_board = player->info_board;
ret.unknown5.clear(); ret.unknown5.clear(0);
ret.challenge_data = player->challenge_data; ret.challenge_data = player->challenge_data;
ret.tech_menu_config = player->tech_menu_config; ret.tech_menu_config = player->tech_menu_config;
ret.unknown6.clear(); ret.unknown6.clear(0);
ret.quest_data2 = player->quest_data2; ret.quest_data2 = player->quest_data2;
ret.key_config = account->key_config; ret.key_config = account->key_config;
return ret; return ret;
+35 -58
View File
@@ -1424,9 +1424,9 @@ void process_gba_file_request(shared_ptr<ServerState>, shared_ptr<Client> c,
strip_trailing_zeroes(filename); strip_trailing_zeroes(filename);
static FileContentsCache gba_file_cache(300 * 1000 * 1000); 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<ServerState>, shared_ptr<Client> c,
for (size_t z = 0; z < max_count; z++) { for (size_t z = 0; z < max_count; z++) {
if (!gcf.entries[z].data.present) { if (!gcf.entries[z].data.present) {
gcf.entries[z].data = new_gc; 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", c->log.info("Added guild card %" PRIu32 " at position %zu",
new_gc.guild_card_number.load(), z); new_gc.guild_card_number.load(), z);
break; break;
@@ -1997,7 +1997,7 @@ void process_set_auto_reply_t(shared_ptr<ServerState>, shared_ptr<Client> c,
void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c, void process_disable_auto_reply(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // C8 uint16_t, uint32_t, const string& data) { // C8
check_size_v(data.size(), 0); 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<ServerState>, shared_ptr<Client> c, void process_set_blocked_senders_list(shared_ptr<ServerState>, shared_ptr<Client> c,
@@ -2021,22 +2021,6 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
const std::u16string& password, uint8_t episode, uint8_t difficulty, const std::u16string& password, uint8_t episode, uint8_t difficulty,
uint8_t battle, uint8_t challenge, uint8_t solo) { 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 // A player's actual level is their displayed level - 1, so the minimums for
// Episode 1 (for example) are actually 1, 20, 40, 80. // Episode 1 (for example) are actually 1, 20, 40, 80.
static const uint32_t default_minimum_levels[3][4] = { static const uint32_t default_minimum_levels[3][4] = {
@@ -2102,7 +2086,6 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
game->min_level = min_level; game->min_level = min_level;
game->max_level = 0xFFFFFFFF; game->max_level = 0xFFFFFFFF;
const uint32_t* variation_maxes = nullptr;
if (game->version == GameVersion::BB) { if (game->version == GameVersion::BB) {
// TODO: cache these somewhere so we don't read the file every time, lolz // 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", game->rare_item_set.reset(new RareItemSet("system/blueburst/ItemRT.rel",
@@ -2113,34 +2096,38 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> s,
} }
game->next_game_item_id = 0x00810000; 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); game->episode - 1, game->difficulty);
const char* type_chars = (game->mode == 3) ? "sm" : "m"; generate_variations(
if (episode > 0 && episode < 4) { game->variations, game->random, game->episode, game->mode == 3);
variation_maxes = (game->mode == 3) ? variation_maxes_solo[episode - 1] : variation_maxes_online[episode - 1];
}
for (size_t x = 0; x < 0x10; x++) { for (size_t x = 0; x < 0x10; x++) {
for (const char* type = type_chars; *type; type++) { try {
auto filename = string_printf( auto file = map_data_for_variation(
"system/blueburst/map/%c%hhX%zX%" PRIX32 "%" PRIX32 ".dat", game->episode,
*type, game->episode, x, game->mode == 3,
game->variations.data()[x * 2].load(), x,
game->variations.data()[(x * 2) + 1].load()); game->variations[x * 2 + 0],
try { game->variations[x * 2 + 1]);
auto enemies = load_map(filename.c_str(), game->episode, auto area_enemies = parse_map(
game->difficulty, bp_subtable, false); game->episode,
game->enemies.insert(game->enemies.end(), enemies.begin(), enemies.end()); game->difficulty,
c->log.info("Loaded map %s (%zu entries)", filename.c_str(), enemies.size()); bp_subtable,
for (size_t z = 0; z < enemies.size(); z++) { file->data.data(),
string e_str = enemies[z].str(); file->data.size(),
static_game_data_log.info("(Entry %zu) %s", z, e_str.c_str()); false);
} game->enemies.insert(
break; game->enemies.end(),
} catch (const exception& e) { area_enemies.begin(),
c->log.warning("Failed to load map %s: %s", filename.c_str(), e.what()); 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<Lobby> create_game_generic(shared_ptr<ServerState> s,
} }
c->log.info("Loaded maps contain %zu entries overall", game->enemies.size()); c->log.info("Loaded maps contain %zu entries overall", game->enemies.size());
} else { } else if (is_ep3) {
// In non-BB games, just set the variations (we don't track items/enemies/ game->variations.clear(0);
// etc.)
if (episode > 0 && episode < 4 && !is_ep3) {
variation_maxes = variation_maxes_online[episode - 1];
}
}
if (variation_maxes) {
for (size_t x = 0; x < 0x20; x++) {
game->variations.data()[x] = (*game->random)() % variation_maxes[x];
}
} else { } else {
for (size_t x = 0; x < 0x20; x++) { // In non-BB non-Ep3 games, just set the variations (we don't track enemies)
game->variations.data()[x] = 0; generate_variations(game->variations, game->random, game->episode, false);
}
} }
s->change_client_lobby(c, game); s->change_client_lobby(c, game);
+10 -10
View File
@@ -376,16 +376,16 @@ void send_stream_file_index_bb(shared_ptr<Client> c) {
string key = "system/blueburst/" + filename; string key = "system/blueburst/" + filename;
auto cache_res = bb_stream_files_cache.get_or_load(key); auto cache_res = bb_stream_files_cache.get_or_load(key);
auto& e = entries.emplace_back(); 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 // 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, // data. If the cache result was just populated, then it may be different,
// so we always recompute the checksum in that case. // so we always recompute the checksum in that case.
if (cache_res.generate_called) { 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<uint32_t>(key + ".crc32", e.checksum); bb_stream_files_cache.replace_obj<uint32_t>(key + ".crc32", e.checksum);
} else { } else {
auto compute_checksum = [&](const string&) -> uint32_t { 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<uint32_t>(key + ".crc32", compute_checksum).obj; e.checksum = bb_stream_files_cache.get_obj<uint32_t>(key + ".crc32", compute_checksum).obj;
} }
@@ -400,26 +400,26 @@ void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
auto cache_result = bb_stream_files_cache.get("<BB stream file>", +[](const string&) -> string { auto cache_result = bb_stream_files_cache.get("<BB stream file>", +[](const string&) -> string {
size_t bytes = 0; size_t bytes = 0;
for (const auto& name : stream_file_entries) { 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; string ret;
ret.reserve(bytes); ret.reserve(bytes);
for (const auto& name : stream_file_entries) { 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; return ret;
}); });
auto contents = cache_result.data; const auto& contents = cache_result.file->data;
S_StreamFileChunk_BB_02EB chunk_cmd; S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = chunk_index; chunk_cmd.chunk_index = chunk_index;
size_t offset = sizeof(chunk_cmd.data) * 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"); throw runtime_error("client requested chunk beyond end of stream file");
} }
size_t bytes = min<size_t>(contents->size() - offset, sizeof(chunk_cmd.data)); size_t bytes = min<size_t>(contents.size() - offset, sizeof(chunk_cmd.data));
memcpy(chunk_cmd.data, contents->data() + offset, bytes); memcpy(chunk_cmd.data, contents.data() + offset, bytes);
size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes; size_t cmd_size = offsetof(S_StreamFileChunk_BB_02EB, data) + bytes;
cmd_size = (cmd_size + 3) & ~3; cmd_size = (cmd_size + 3) & ~3;
@@ -1484,7 +1484,7 @@ void send_quest_open_file_t(
default: default:
throw logic_error("invalid quest file type"); throw logic_error("invalid quest file type");
} }
cmd.unused.clear(); cmd.unused.clear(0);
cmd.file_size = file_size; cmd.file_size = file_size;
cmd.filename = filename.c_str(); cmd.filename = filename.c_str();
send_command_t(c, command_num, 0x00, cmd); send_command_t(c, command_num, 0x00, cmd);
+1 -1
View File
@@ -163,7 +163,7 @@ Proxy commands (these will only work when exactly one client is connected):\n\
shared_ptr<LicenseManager> lm(new LicenseManager("system/licenses.nsi")); shared_ptr<LicenseManager> lm(new LicenseManager("system/licenses.nsi"));
this->state->license_manager = lm; this->state->license_manager = lm;
} else if (type == "battle-params") { } else if (type == "battle-params") {
shared_ptr<BattleParamTable> bpt(new BattleParamTable("system/blueburst/BattleParamEntry")); shared_ptr<BattleParamsIndex> bpt(new BattleParamsIndex("system/blueburst/BattleParamEntry"));
this->state->battle_params = bpt; this->state->battle_params = bpt;
} else if (type == "level-table") { } else if (type == "level-table") {
shared_ptr<LevelTable> lt(new LevelTable("system/blueburst/PlyLevelTbl.prs", true)); shared_ptr<LevelTable> lt(new LevelTable("system/blueburst/PlyLevelTbl.prs", true));
+1 -1
View File
@@ -55,7 +55,7 @@ struct ServerState {
std::shared_ptr<const Ep3DataIndex> ep3_data_index; std::shared_ptr<const Ep3DataIndex> ep3_data_index;
std::shared_ptr<const QuestIndex> quest_index; std::shared_ptr<const QuestIndex> quest_index;
std::shared_ptr<const LevelTable> level_table; std::shared_ptr<const LevelTable> level_table;
std::shared_ptr<const BattleParamTable> battle_params; std::shared_ptr<const BattleParamsIndex> battle_params;
std::shared_ptr<const CommonItemData> common_item_data; std::shared_ptr<const CommonItemData> common_item_data;
std::shared_ptr<LicenseManager> license_manager; std::shared_ptr<LicenseManager> license_manager;
+206
View File
@@ -2,10 +2,216 @@
#include <array> #include <array>
#include "FileContentsCache.hh"
using namespace std; using namespace std;
struct AreaMapFileIndex {
const char* name_token;
std::vector<uint32_t> variation1_values;
std::vector<uint32_t> variation2_values;
AreaMapFileIndex(
const char* name_token,
std::vector<uint32_t> variation1_values,
std::vector<uint32_t> 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<std::vector<std::vector<AreaMapFileIndex>>> 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<le_uint32_t, 0x20>& variations,
std::shared_ptr<std::mt19937> 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<const FileContentsCache::File> 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_<name_token>[_VV][_VV][_off]<e|o>[_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<string> section_id_to_name({ const vector<string> section_id_to_name({
"Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", "Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria",
"Oran", "Yellowboze", "Whitill"}); "Oran", "Yellowboze", "Whitill"});
+11
View File
@@ -3,11 +3,22 @@
#include <stdint.h> #include <stdint.h>
#include <unordered_map> #include <unordered_map>
#include <random>
#include "FileContentsCache.hh"
#include "Player.hh" #include "Player.hh"
void generate_variations(
parray<le_uint32_t, 0x20>& variations,
std::shared_ptr<std::mt19937> random,
uint8_t episode,
bool is_solo);
std::shared_ptr<const FileContentsCache::File> 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(uint8_t data0, uint8_t data1);
size_t stack_size_for_item(const ItemData& item); size_t stack_size_for_item(const ItemData& item);
+7 -3
View File
@@ -192,9 +192,13 @@ template <typename ItemT, size_t Count>
struct parray { struct parray {
ItemT items[Count]; ItemT items[Count];
template <typename ArgT = ItemT> requires (std::is_arithmetic<ItemT>::value)
parray() { parray() {
this->clear(); this->clear(0);
} }
template <typename ArgT = ItemT> requires (!std::is_arithmetic<ItemT>::value)
parray() { }
parray(const parray& other) { parray(const parray& other) {
this->operator=(other); this->operator=(other);
} }
@@ -281,7 +285,7 @@ struct parray {
return !this->operator==(s); return !this->operator==(s);
} }
void clear(ItemT v = 0) { void clear(ItemT v) {
for (size_t x = 0; x < Count; x++) { for (size_t x = 0; x < Count; x++) {
this->items[x] = v; this->items[x] = v;
} }
@@ -312,7 +316,7 @@ struct parray {
template <typename CharT, size_t Count> template <typename CharT, size_t Count>
struct ptext : parray<CharT, Count> { struct ptext : parray<CharT, Count> {
ptext() { ptext() {
this->clear(); this->clear(0);
} }
ptext(const ptext& other) : parray<CharT, Count>(other) { } ptext(const ptext& other) : parray<CharT, Count>(other) { }
ptext(ptext&& s) = delete; ptext(ptext&& s) = delete;