make replays useful on BB

This commit is contained in:
Martin Michelsen
2022-07-07 23:46:50 -07:00
parent 58f0501010
commit e5227080b8
18 changed files with 382 additions and 89 deletions
+1
View File
@@ -21,6 +21,7 @@ Current known issues / missing features:
- Find a way to silence audio in RunDOL.s. Some old DOLs don't reset audio systems at load time and it's annoying to hear the crash buzz when the GC hasn't actually crashed.
- Implement private and overflow lobbies.
- Enforce client-side size limits (e.g. for 60/62 commands) on the server side as well. (For 60/62 specifically, perhaps transform them to 6C/6D if needed.)
- Encapsulate BB server-side random state and make replays deterministic.
## Usage
+55 -24
View File
@@ -8,36 +8,67 @@
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) { }
shared_ptr<const string> FileContentsCache::get(const std::string& name) {
return this->get(name, [name]() -> string { return load_file(name); });
}
shared_ptr<const string> FileContentsCache::get(const char* name) {
return this->get(string(name));
}
shared_ptr<const string> FileContentsCache::get(const std::string& name,
std::function<std::string()> generate) {
uint64_t t = now();
try {
auto& entry = this->name_to_file.at(name);
if (t - entry.load_time < 300000000) { // not 5 minutes old? return it
return entry.contents;
}
} catch (const out_of_range& e) { }
shared_ptr<const string> contents(new string(generate()));
this->name_to_file.erase(name);
this->name_to_file.emplace(piecewise_construct, forward_as_tuple(name),
shared_ptr<const string> 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));
if (!emplace_ret.second) {
emplace_ret.first->second.contents = contents;
emplace_ret.first->second.load_time = t;
}
return contents;
}
shared_ptr<const string> FileContentsCache::get(const char* name,
std::function<std::string()> generate) {
shared_ptr<const string> 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);
}
FileContentsCache::GetResult FileContentsCache::get_or_load(const std::string& name) {
return this->get(name, load_file);
}
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) {
auto throw_fn = +[](const std::string&) -> string {
throw out_of_range("file missing from cache");
};
return this->get(name, throw_fn).data;
}
shared_ptr<const string> FileContentsCache::get_or_throw(const char* name) {
return this->get_or_throw(string(name));
}
FileContentsCache::GetResult FileContentsCache::get(const std::string& name,
std::function<std::string(const std::string&)> generate) {
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};
}
} catch (const out_of_range& e) { }
return {this->replace(name, generate(name)), true};
}
FileContentsCache::GetResult FileContentsCache::get(const char* name,
std::function<std::string(const std::string&)> generate) {
return this->get(string(name), generate);
}
+73 -7
View File
@@ -5,6 +5,8 @@
#include <unordered_map>
#include <functional>
#include <phosg/Time.hh>
using namespace std;
@@ -26,21 +28,85 @@ private:
};
public:
FileContentsCache() = default;
explicit FileContentsCache(uint64_t ttl_usecs);
FileContentsCache(const FileContentsCache&) = delete;
FileContentsCache(FileContentsCache&&) = delete;
FileContentsCache& operator=(const FileContentsCache&) = delete;
FileContentsCache& operator=(FileContentsCache&&) = delete;
~FileContentsCache() = default;
std::shared_ptr<const std::string> get(const std::string& name);
std::shared_ptr<const std::string> get(const char* name);
template <typename NameT>
bool delete_key(NameT key) {
return this->name_to_file.erase(key);
}
std::shared_ptr<const std::string> get(
const std::string& name, std::function<std::string()> generate);
std::shared_ptr<const std::string> get(
const char* name, std::function<std::string()> generate);
std::shared_ptr<const std::string> replace(
const std::string& name, std::string&& data, uint64_t t = 0);
std::shared_ptr<const std::string> replace(
const std::string& name, const void* data, size_t size, uint64_t t = 0);
struct GetResult {
std::shared_ptr<const std::string> data;
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);
GetResult get(
const std::string& name, std::function<std::string(const std::string&)> generate);
GetResult get(
const char* name, std::function<std::string(const std::string&)> generate);
template <typename T>
struct GetObjResult {
const T& obj;
std::shared_ptr<const std::string> 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)) {
throw runtime_error("cached string size is incorrect");
}
return {*reinterpret_cast<const T*>(res.data->data()), res.data, 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)) {
throw runtime_error("cached string size is incorrect");
}
return {*reinterpret_cast<const T*>(res.data->data()), res.data, 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)) {
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};
}
} catch (const out_of_range& e) { }
T value = generate(name);
auto ret = this->replace_obj(name, value);
ret.generate_called = true;
return ret;
}
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};
}
private:
std::unordered_map<std::string, File> name_to_file;
uint64_t ttl_usecs;
};
+15 -4
View File
@@ -41,7 +41,8 @@ string License::str() const {
LicenseManager::LicenseManager(const string& filename) : filename(filename) {
LicenseManager::LicenseManager(const string& filename)
: filename(filename), autosave(true) {
try {
auto licenses = load_vector_file<License>(this->filename);
for (const auto& read_license : licenses) {
@@ -73,6 +74,10 @@ void LicenseManager::save() const {
}
}
void LicenseManager::set_autosave(bool autosave) {
this->autosave = autosave;
}
shared_ptr<const License> LicenseManager::verify_pc(uint32_t serial_number,
const string& access_key) const {
auto& license = this->serial_number_to_license.at(serial_number);
@@ -132,7 +137,9 @@ size_t LicenseManager::count() const {
void LicenseManager::ban_until(uint32_t serial_number, uint64_t end_time) {
this->serial_number_to_license.at(serial_number)->ban_end_time = end_time;
this->save();
if (this->autosave) {
this->save();
}
}
void LicenseManager::add(shared_ptr<License> l) {
@@ -141,7 +148,9 @@ void LicenseManager::add(shared_ptr<License> l) {
if (!l->username.empty()) {
this->bb_username_to_license.emplace(l->username, l);
}
this->save();
if (this->autosave) {
this->save();
}
}
void LicenseManager::remove(uint32_t serial_number) {
@@ -150,7 +159,9 @@ void LicenseManager::remove(uint32_t serial_number) {
if (!l->username.empty()) {
this->bb_username_to_license.erase(l->username);
}
this->save();
if (this->autosave) {
this->save();
}
}
vector<License> LicenseManager::snapshot() const {
+5 -2
View File
@@ -49,6 +49,9 @@ public:
LicenseManager(const std::string& filename);
~LicenseManager() = default;
void save() const;
void set_autosave(bool autosave);
std::shared_ptr<const License> verify_pc(uint32_t serial_number,
const std::string& access_key) const;
std::shared_ptr<const License> verify_gc(uint32_t serial_number,
@@ -75,9 +78,9 @@ public:
const std::string& password, bool temporary);
protected:
void save() const;
std::string filename;
bool autosave;
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license;
std::unordered_map<uint32_t, std::shared_ptr<License>> serial_number_to_license;
};
+7 -3
View File
@@ -11,7 +11,6 @@
#include <unordered_map>
#include "DNSServer.hh"
#include "FileContentsCache.hh"
#include "IPStackSimulator.hh"
#include "Loggers.hh"
#include "NetworkAddresses.hh"
@@ -27,7 +26,6 @@ using namespace std;
FileContentsCache file_cache;
bool use_terminal_colors = false;
@@ -372,8 +370,14 @@ int main(int argc, char** argv) {
config_log.info("Creating menus");
state->create_menus(config_json);
if (replay_log_filename) {
state->allow_saving = false;
state->license_manager->set_autosave(false);
config_log.info("Saving disabled because this is a replay session");
}
shared_ptr<DNSServer> dns_server;
if (state->dns_server_port) {
if (state->dns_server_port && !replay_log_filename) {
config_log.info("Starting DNS server");
dns_server.reset(new DNSServer(base, state->local_address,
state->external_address));
+2 -3
View File
@@ -8,8 +8,6 @@
using namespace std;
extern FileContentsCache file_cache;
static void load_battle_param_file(const string& filename, BattleParams* entries) {
@@ -437,7 +435,8 @@ static vector<PSOEnemy> parse_map(uint8_t episode, uint8_t difficulty,
vector<PSOEnemy> load_map(const std::string& filename, uint8_t episode,
uint8_t difficulty, const BattleParams* battle_params, bool alt_enemies) {
shared_ptr<const string> data = file_cache.get(filename);
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,
+1 -1
View File
@@ -186,7 +186,7 @@ std::string prepend_command_header(
case GameVersion::BB: {
PSOCommandHeaderBB header;
if (encryption_enabled) {
header.size = (sizeof(header) + data.size() + 7) & ~7;
header.size = (sizeof(header) + data.size() + 3) & ~3;
} else {
header.size = (sizeof(header) + data.size());
}
+32 -16
View File
@@ -55,38 +55,54 @@ union PSOSubcommand {
// This function is used in a lot of places to check received command sizes and
// cast them to the appropriate type
template <typename T>
const T& check_size_t(
const void* data,
size_t size,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, size));
}
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, size));
}
return *reinterpret_cast<const T*>(data);
}
template <typename T>
const T& check_size_t(
const std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
return check_size_t<T>(data.data(), data.size(), min_size, max_size);
}
template <typename T>
T& check_size_t(
void* data,
size_t size,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (size < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
min_size, size));
}
if (data.size() > max_size) {
if (size > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
max_size, size));
}
return *reinterpret_cast<const T*>(data.data());
return *reinterpret_cast<T*>(data);
}
template <typename T>
T& check_size_t(
std::string& data,
size_t min_size = sizeof(T),
size_t max_size = sizeof(T)) {
if (data.size() < min_size) {
throw std::runtime_error(string_printf(
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
min_size, data.size()));
}
if (data.size() > max_size) {
throw std::runtime_error(string_printf(
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
max_size, data.size()));
}
return *reinterpret_cast<T*>(data.data());
return check_size_t<T>(data.data(), data.size(), min_size, max_size);
}
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
+37 -14
View File
@@ -7,10 +7,11 @@
#include <stdexcept>
#include <phosg/Filesystem.hh>
#include "FileContentsCache.hh"
#include "Loggers.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "Version.hh"
#include "StaticGameData.hh"
using namespace std;
@@ -26,6 +27,10 @@ static const string ACCOUNT_FILE_SIGNATURE =
static FileContentsCache player_files_cache(300 * 1000 * 1000);
PlayerStats::PlayerStats() noexcept
: atp(0), mst(0), evp(0), hp(0), dfp(0), ata(0), lck(0) { }
@@ -271,14 +276,17 @@ GuildCardBB::GuildCardBB() noexcept
void PlayerBank::load(const string& filename) {
*this = load_object_file<PlayerBank>(filename);
*this = player_files_cache.get_obj_or_load<PlayerBank>(filename).obj;
for (uint32_t x = 0; x < this->num_items; x++) {
this->items[x].data.id = 0x0F010000 + x;
}
}
void PlayerBank::save(const string& filename) const {
save_file(filename, this, sizeof(*this));
void PlayerBank::save(const string& filename, bool save_to_filesystem) const {
player_files_cache.replace(filename, this, sizeof(*this));
if (save_to_filesystem) {
save_file(filename, this, sizeof(*this));
}
}
@@ -381,35 +389,45 @@ void ClientGameData::load_account_data() {
shared_ptr<SavedAccountDataBB> data;
try {
data.reset(new SavedAccountDataBB(
load_object_file<SavedAccountDataBB>(filename)));
player_files_cache.get_obj_or_load<SavedAccountDataBB>(filename).obj));
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
throw runtime_error("account data header is incorrect");
}
player_data_log.info("Loaded account data file %s", filename.c_str());
} catch (const exception& e) {
player_data_log.info("No account data for %s; using default",
this->bb_username.c_str());
player_data_log.info("Cannot load account data for %s (%s); using default",
this->bb_username.c_str(), e.what());
player_files_cache.delete_key(filename);
data.reset(new SavedAccountDataBB(
load_object_file<SavedAccountDataBB>("system/players/default.nsa")));
player_files_cache.get_obj_or_load<SavedAccountDataBB>(
"system/players/default.nsa").obj));
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
throw runtime_error("default account data header is incorrect");
}
player_data_log.info("Loaded default account data file");
}
this->account_data = data;
player_data_log.info("Loaded account data file %s", filename.c_str());
}
void ClientGameData::save_account_data() const {
string filename = this->account_data_filename();
save_file(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
player_data_log.info("Saved account data file %s", filename.c_str());
player_files_cache.replace(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
if (this->should_save) {
save_file(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
player_data_log.info("Saved account data file %s to filesystem", filename.c_str());
} else {
player_data_log.info("Saved account data file %s to cache only", filename.c_str());
}
}
void ClientGameData::load_player_data() {
string filename = this->player_data_filename();
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
load_object_file<SavedPlayerDataBB>(filename)));
player_files_cache.get_obj_or_load<SavedPlayerDataBB>(filename).obj));
if (data->signature != PLAYER_FILE_SIGNATURE) {
player_files_cache.delete_key(filename);
throw runtime_error("player data header is incorrect");
}
this->player_data = data;
@@ -418,8 +436,13 @@ void ClientGameData::load_player_data() {
void ClientGameData::save_player_data() const {
string filename = this->player_data_filename();
save_file(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
player_data_log.info("Saved player data file %s", filename.c_str());
player_files_cache.replace(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
if (this->should_save) {
save_file(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
player_data_log.info("Saved player data file %s to filesystem", filename.c_str());
} else {
player_data_log.info("Saved player data file %s to cache only", filename.c_str());
}
}
void ClientGameData::import_player(const PSOPlayerDataPC& pc) {
+3 -2
View File
@@ -71,7 +71,7 @@ struct PlayerBank {
PlayerBankItem items[200];
void load(const std::string& filename);
void save(const std::string& filename) const;
void save(const std::string& filename, bool save_to_filesystem) const;
bool switch_with_file(const std::string& save_filename,
const std::string& load_filename);
@@ -407,8 +407,9 @@ public:
size_t bb_player_index;
PlayerInventoryItem identify_result;
std::vector<ItemData> shop_contents;
bool should_save;
ClientGameData() : serial_number(0), bb_player_index(0) { }
ClientGameData() : serial_number(0), bb_player_index(0), should_save(true) { }
~ClientGameData();
std::shared_ptr<SavedAccountDataBB> account(bool should_load = true);
+4 -6
View File
@@ -25,10 +25,6 @@ using namespace std;
extern FileContentsCache file_cache;
vector<MenuItem> quest_categories_menu({
MenuItem(static_cast<uint32_t>(QuestCategory::RETRIEVAL), u"Retrieval", u"$E$C6Quests that involve\nretrieving an object", 0),
MenuItem(static_cast<uint32_t>(QuestCategory::EXTERMINATION), u"Extermination", u"$E$C6Quests that involve\ndestroying all\nmonsters", 0),
@@ -339,7 +335,7 @@ void process_login_bb(shared_ptr<ServerState> s, shared_ptr<Client> c,
return;
} else {
shared_ptr<License> l = LicenseManager::create_license_bb(
fnv1a32(cmd.username), cmd.username, cmd.password, true);
fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true);
s->license_manager->add(l);
c->set_license(l);
}
@@ -1346,7 +1342,9 @@ void process_gba_file_request(shared_ptr<ServerState>, shared_ptr<Client> c,
uint16_t, uint32_t, const string& data) { // D7
string filename(data);
strip_trailing_zeroes(filename);
auto contents = file_cache.get("system/gba/" + filename);
static FileContentsCache gba_file_cache(300 * 1000 * 1000);
auto contents = gba_file_cache.get_or_load("system/gba/" + filename).data;
send_quest_file(c, "", filename, *contents, QuestFileType::GBA_DEMO);
}
+121
View File
@@ -44,6 +44,117 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(
return event;
}
void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
auto version = this->clients.at(ev->client_id)->version;
void* cmd_data = ev->mask.data() + ((version == GameVersion::BB) ? 8 : 4);
size_t cmd_size = ev->mask.size() - ((version == GameVersion::BB) ? 8 : 4);
switch (version) {
case GameVersion::PATCH: {
const auto& header = check_size_t<PSOCommandHeaderPC>(
ev->data, sizeof(PSOCommandHeaderPC), 0xFFFF);
if (header.command == 0x02) {
auto& cmd_mask = check_size_t<S_ServerInit_Patch_02>(cmd_data, cmd_size);
cmd_mask.server_key = 0;
cmd_mask.client_key = 0;
}
break;
}
case GameVersion::PC:
case GameVersion::GC: {
uint8_t command;
if (version == GameVersion::PC) {
command = check_size_t<PSOCommandHeaderPC>(
ev->data, sizeof(PSOCommandHeaderPC), 0xFFFF).command;
} else {
command = check_size_t<PSOCommandHeaderDCGC>(
ev->data, sizeof(PSOCommandHeaderDCGC), 0xFFFF).command;
}
switch (command) {
case 0x02:
case 0x17:
case 0x91:
case 0x9B: {
auto& cmd_mask = check_size_t<S_ServerInit_DC_PC_GC_02_17_91_9B>(
cmd_data, cmd_size, sizeof(S_ServerInit_DC_PC_GC_02_17_91_9B), 0xFFFF);
cmd_mask.server_key = 0;
cmd_mask.client_key = 0;
break;
}
case 0x0019: {
auto& cmd_mask = check_size_t<S_Reconnect_19>(cmd_data, cmd_size);
cmd_mask.address = 0;
break;
}
case 0x64: {
if (version == GameVersion::PC) {
auto& cmd_mask = check_size_t<S_JoinGame_PC_64>(cmd_data, cmd_size,
offsetof(S_JoinGame_GC_64, players_ep3));
cmd_mask.variations.clear(0);
cmd_mask.rare_seed = 0;
} else { // GC
auto& cmd_mask = check_size_t<S_JoinGame_GC_64>(cmd_data, cmd_size,
offsetof(S_JoinGame_GC_64, players_ep3));
cmd_mask.variations.clear(0);
cmd_mask.rare_seed = 0;
}
break;
}
case 0xB1: {
for (size_t x = 8; x < ev->mask.size(); x++) {
ev->mask[x] = 0;
}
break;
}
}
break;
}
case GameVersion::BB: {
uint16_t command = check_size_t<PSOCommandHeaderBB>(
ev->data, sizeof(PSOCommandHeaderBB), 0xFFFF).command;
switch (command) {
case 0x0003: {
auto& cmd_mask = check_size_t<S_ServerInit_BB_03_9B>(
cmd_data, cmd_size, sizeof(S_ServerInit_BB_03_9B), 0xFFFF);
cmd_mask.server_key.clear(0);
cmd_mask.client_key.clear(0);
break;
}
case 0x0019: {
auto& cmd_mask = check_size_t<S_Reconnect_19>(cmd_data, cmd_size);
cmd_mask.address = 0;
break;
}
case 0x0064: {
auto& cmd_mask = check_size_t<S_JoinGame_BB_64>(cmd_data, cmd_size,
offsetof(S_JoinGame_BB_64, players_ep3),
offsetof(S_JoinGame_BB_64, players_ep3));
cmd_mask.variations.clear(0);
cmd_mask.rare_seed = 0;
break;
}
case 0x00B1: {
for (size_t x = 8; x < ev->mask.size(); x++) {
ev->mask[x] = 0;
}
break;
}
case 0x00E6: {
auto& cmd_mask = check_size_t<S_ClientInit_BB_00E6>(cmd_data, cmd_size);
cmd_mask.team_id = 0;
break;
}
}
break;
}
case GameVersion::DC:
throw logic_error("DC auto-masking is not implemented");
default:
throw logic_error("invalid game version");
}
}
ReplaySession::ReplaySession(
shared_ptr<struct event_base> base,
FILE* input_log,
@@ -79,6 +190,9 @@ ReplaySession::ReplaySession(
parsing_command->mask += mask_bytes;
continue;
} else {
if (parsing_command->type == Event::Type::RECEIVE) {
this->apply_default_mask(parsing_command);
}
parsing_command = nullptr;
}
}
@@ -304,6 +418,13 @@ void ReplaySession::on_command_received(
switch (c->version) {
case GameVersion::DC:
throw runtime_error("DC encryption is not supported during replays");
case GameVersion::PATCH:
if (command == 0x02) {
auto& cmd = check_size_t<S_ServerInit_Patch_02>(data);
c->channel.crypt_in.reset(new PSOPCEncryption(cmd.server_key));
c->channel.crypt_out.reset(new PSOPCEncryption(cmd.client_key));
}
break;
case GameVersion::PC:
case GameVersion::GC:
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
+2
View File
@@ -78,6 +78,8 @@ private:
Event::Type type, std::shared_ptr<Client> c);
void update_timeout_event();
void apply_default_mask(std::shared_ptr<Event> ev);
static void dispatch_on_timeout(evutil_socket_t fd, short events, void* ctx);
static void dispatch_on_command_received(
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
+20 -7
View File
@@ -344,6 +344,7 @@ static const vector<string> stream_file_entries = {
"BattleParamEntry_ep4_on.dat",
"PlyLevelTbl.prs",
};
static FileContentsCache bb_stream_files_cache(3600 * 1000 * 1000);
void send_stream_file_index_bb(shared_ptr<Client> c) {
@@ -357,11 +358,22 @@ void send_stream_file_index_bb(shared_ptr<Client> c) {
vector<S_StreamFileIndexEntry_BB_01EB> entries;
size_t offset = 0;
for (const string& filename : stream_file_entries) {
auto file_data = file_cache.get("system/blueburst/" + filename);
string key = "system/blueburst/" + filename;
auto cache_res = bb_stream_files_cache.get_or_load(key);
auto& e = entries.emplace_back();
e.size = file_data->size();
// TODO: memoize the checksum somewhere; computing it can be slow
e.checksum = crc32(file_data->data(), e.size);
e.size = cache_res.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);
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);
};
e.checksum = bb_stream_files_cache.get_obj<uint32_t>(key + ".crc32", compute_checksum).obj;
}
e.offset = offset;
e.filename = filename;
offset += e.size;
@@ -370,19 +382,20 @@ void send_stream_file_index_bb(shared_ptr<Client> c) {
}
void send_stream_file_chunk_bb(shared_ptr<Client> c, uint32_t chunk_index) {
auto contents = file_cache.get("<BB stream file>", +[]() -> string {
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 += file_cache.get("system/blueburst/" + name)->size();
bytes += bb_stream_files_cache.get_or_load("system/blueburst/" + name).data->size();
}
string ret;
ret.reserve(bytes);
for (const auto& name : stream_file_entries) {
ret += *file_cache.get("system/blueburst/" + name);
ret += *bb_stream_files_cache.get_or_load("system/blueburst/" + name).data;
}
return ret;
});
auto contents = cache_result.data;
S_StreamFileChunk_BB_02EB chunk_cmd;
chunk_cmd.chunk_index = chunk_index;
+2
View File
@@ -80,6 +80,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
shared_ptr<Client> c(new Client(
bev, listening_socket->version, listening_socket->behavior));
c->game_data.should_save = this->state->allow_saving;
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
@@ -100,6 +101,7 @@ void Server::connect_client(
struct bufferevent* bev, uint32_t address, uint16_t client_port,
uint16_t server_port, GameVersion version, ServerBehavior initial_state) {
shared_ptr<Client> c(new Client(bev, version, initial_state));
c->game_data.should_save = this->state->allow_saving;
c->channel.on_command_received = Server::on_client_input;
c->channel.on_error = Server::on_client_error;
c->channel.context_obj = this;
+1
View File
@@ -19,6 +19,7 @@ ServerState::ServerState()
: dns_server_port(0),
ip_stack_debug(false),
allow_unregistered_users(false),
allow_saving(true),
item_tracking_enabled(true),
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
pre_lobby_event(0),
+1
View File
@@ -46,6 +46,7 @@ struct ServerState {
std::vector<std::string> ip_stack_addresses;
bool ip_stack_debug;
bool allow_unregistered_users;
bool allow_saving;
bool item_tracking_enabled;
RunShellBehavior run_shell_behavior;
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;