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::File::File(const string& name, shared_ptr<const string> 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<const string> FileContentsCache::replace(
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
const string& name, string&& data, uint64_t t) {
if (t == 0) {
t = now();
}
shared_ptr<const string> 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<File> 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<const string> FileContentsCache::replace(
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
const string& name, const void* data, size_t size, uint64_t t) {
string s(reinterpret_cast<const char*>(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<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 {
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));
}
@@ -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};
+19 -21
View File
@@ -11,15 +11,14 @@ using namespace std;
class FileContentsCache {
private:
public:
struct File {
std::string name;
std::shared_ptr<const std::string> contents;
std::string data;
uint64_t load_time;
File() = delete;
File(const std::string& name, std::shared_ptr<const std::string> 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<const std::string> replace(
std::shared_ptr<const File> replace(
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);
struct GetResult {
std::shared_ptr<const std::string> data;
std::shared_ptr<const File> file;
bool generate_called;
};
GetResult get_or_load(const std::string& name);
GetResult get_or_load(const char* name);
std::shared_ptr<const string> 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 std::string& name);
std::shared_ptr<const File> get_or_throw(const char* name);
GetResult get(
const std::string& name, std::function<std::string(const std::string&)> generate);
@@ -63,36 +61,36 @@ public:
template <typename T>
struct GetObjResult {
const T& obj;
std::shared_ptr<const std::string> data;
std::shared_ptr<const File> data;
bool generate_called;
};
template <typename T, typename NameT>
GetObjResult<T> 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<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>
GetObjResult<T> 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<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>
GetObjResult<T> get_obj(NameT name, std::function<T(const std::string&)> 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<const T*>(entry.contents->data()), entry.contents, false};
if (this->ttl_usecs && (t - f->load_time < this->ttl_usecs)) {
return {*reinterpret_cast<const T*>(f->data.data()), f, false};
}
} catch (const out_of_range& e) { }
T value = generate(name);
@@ -103,10 +101,10 @@ public:
template <typename T, typename NameT>
GetObjResult<T> replace_obj(NameT name, const T& 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:
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;
};
+2 -2
View File
@@ -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()) {
+1 -1
View File
@@ -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));
+48 -38
View File
@@ -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<const BattleParamsIndex::TableT>
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<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
const BattleParams* battle_params, const EnemyEntry* map,
size_t entry_count, bool alt_enemies) {
vector<PSOEnemy> parse_map(
uint8_t episode,
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;
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++) {
const auto& e = map[y];
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
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<PSOEnemy> 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<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
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 <string>
#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<BattleParams, 0x60>;
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<const TableT> get_subtable(
bool solo, uint8_t episode, uint8_t difficulty) const;
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 {
uint64_t id;
uint16_t source_type;
@@ -53,5 +55,10 @@ struct PSOEnemy {
std::string str() const;
} __attribute__((packed));
std::vector<PSOEnemy> load_map(const std::string& filename, uint8_t episode,
uint8_t difficulty, const BattleParams* bp, bool alt_enemies);
std::vector<PSOEnemy> parse_map(
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() {
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;
+35 -58
View File
@@ -1424,9 +1424,9 @@ void process_gba_file_request(shared_ptr<ServerState>, shared_ptr<Client> 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<ServerState>, shared_ptr<Client> 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<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
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,
@@ -2021,22 +2021,6 @@ shared_ptr<Lobby> create_game_generic(shared_ptr<ServerState> 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<Lobby> create_game_generic(shared_ptr<ServerState> 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<Lobby> create_game_generic(shared_ptr<ServerState> 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<Lobby> create_game_generic(shared_ptr<ServerState> 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);
+10 -10
View File
@@ -376,16 +376,16 @@ void send_stream_file_index_bb(shared_ptr<Client> 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<uint32_t>(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<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 {
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<size_t>(contents->size() - offset, sizeof(chunk_cmd.data));
memcpy(chunk_cmd.data, contents->data() + offset, bytes);
size_t bytes = min<size_t>(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);
+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"));
this->state->license_manager = lm;
} 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;
} else if (type == "level-table") {
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 QuestIndex> quest_index;
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<LicenseManager> license_manager;
+206
View File
@@ -2,10 +2,216 @@
#include <array>
#include "FileContentsCache.hh"
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({
"Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria",
"Oran", "Yellowboze", "Whitill"});
+11
View File
@@ -3,11 +3,22 @@
#include <stdint.h>
#include <unordered_map>
#include <random>
#include "FileContentsCache.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(const ItemData& item);
+7 -3
View File
@@ -192,9 +192,13 @@ template <typename ItemT, size_t Count>
struct parray {
ItemT items[Count];
template <typename ArgT = ItemT> requires (std::is_arithmetic<ItemT>::value)
parray() {
this->clear();
this->clear(0);
}
template <typename ArgT = ItemT> requires (!std::is_arithmetic<ItemT>::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 <typename CharT, size_t Count>
struct ptext : parray<CharT, Count> {
ptext() {
this->clear();
this->clear(0);
}
ptext(const ptext& other) : parray<CharT, Count>(other) { }
ptext(ptext&& s) = delete;