make replays useful on BB
This commit is contained in:
@@ -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.
|
- 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.
|
- 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.)
|
- 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
|
## Usage
|
||||||
|
|
||||||
|
|||||||
+55
-24
@@ -8,36 +8,67 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) { }
|
||||||
|
|
||||||
FileContentsCache::File::File(const string& name, shared_ptr<const string> contents,
|
FileContentsCache::File::File(const string& name, shared_ptr<const string> contents,
|
||||||
uint64_t load_time) : name(name), contents(contents), load_time(load_time) { }
|
uint64_t load_time) : name(name), contents(contents), load_time(load_time) { }
|
||||||
|
|
||||||
shared_ptr<const string> FileContentsCache::get(const std::string& name) {
|
shared_ptr<const string> FileContentsCache::replace(
|
||||||
return this->get(name, [name]() -> string { return load_file(name); });
|
const string& name, string&& data, uint64_t t) {
|
||||||
}
|
if (t == 0) {
|
||||||
|
t = now();
|
||||||
shared_ptr<const string> FileContentsCache::get(const char* name) {
|
}
|
||||||
return this->get(string(name));
|
shared_ptr<const string> contents(new string(move(data)));
|
||||||
}
|
auto emplace_ret = this->name_to_file.emplace(
|
||||||
|
piecewise_construct,
|
||||||
shared_ptr<const string> FileContentsCache::get(const std::string& name,
|
forward_as_tuple(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),
|
|
||||||
forward_as_tuple(name, contents, t));
|
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;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
shared_ptr<const string> FileContentsCache::get(const char* name,
|
shared_ptr<const string> FileContentsCache::replace(
|
||||||
std::function<std::string()> generate) {
|
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);
|
return this->get(string(name), generate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include <phosg/Time.hh>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
@@ -26,21 +28,85 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileContentsCache() = default;
|
explicit FileContentsCache(uint64_t ttl_usecs);
|
||||||
FileContentsCache(const FileContentsCache&) = delete;
|
FileContentsCache(const FileContentsCache&) = delete;
|
||||||
FileContentsCache(FileContentsCache&&) = delete;
|
FileContentsCache(FileContentsCache&&) = delete;
|
||||||
FileContentsCache& operator=(const FileContentsCache&) = delete;
|
FileContentsCache& operator=(const FileContentsCache&) = delete;
|
||||||
FileContentsCache& operator=(FileContentsCache&&) = delete;
|
FileContentsCache& operator=(FileContentsCache&&) = delete;
|
||||||
~FileContentsCache() = default;
|
~FileContentsCache() = default;
|
||||||
|
|
||||||
std::shared_ptr<const std::string> get(const std::string& name);
|
template <typename NameT>
|
||||||
std::shared_ptr<const std::string> get(const char* name);
|
bool delete_key(NameT key) {
|
||||||
|
return this->name_to_file.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<const std::string> get(
|
std::shared_ptr<const std::string> replace(
|
||||||
const std::string& name, std::function<std::string()> generate);
|
const std::string& name, std::string&& data, uint64_t t = 0);
|
||||||
std::shared_ptr<const std::string> get(
|
std::shared_ptr<const std::string> replace(
|
||||||
const char* name, std::function<std::string()> generate);
|
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:
|
private:
|
||||||
std::unordered_map<std::string, File> name_to_file;
|
std::unordered_map<std::string, File> name_to_file;
|
||||||
|
uint64_t ttl_usecs;
|
||||||
};
|
};
|
||||||
|
|||||||
+15
-4
@@ -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 {
|
try {
|
||||||
auto licenses = load_vector_file<License>(this->filename);
|
auto licenses = load_vector_file<License>(this->filename);
|
||||||
for (const auto& read_license : licenses) {
|
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,
|
shared_ptr<const License> LicenseManager::verify_pc(uint32_t serial_number,
|
||||||
const string& access_key) const {
|
const string& access_key) const {
|
||||||
auto& license = this->serial_number_to_license.at(serial_number);
|
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) {
|
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->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) {
|
void LicenseManager::add(shared_ptr<License> l) {
|
||||||
@@ -141,7 +148,9 @@ void LicenseManager::add(shared_ptr<License> l) {
|
|||||||
if (!l->username.empty()) {
|
if (!l->username.empty()) {
|
||||||
this->bb_username_to_license.emplace(l->username, l);
|
this->bb_username_to_license.emplace(l->username, l);
|
||||||
}
|
}
|
||||||
this->save();
|
if (this->autosave) {
|
||||||
|
this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LicenseManager::remove(uint32_t serial_number) {
|
void LicenseManager::remove(uint32_t serial_number) {
|
||||||
@@ -150,7 +159,9 @@ void LicenseManager::remove(uint32_t serial_number) {
|
|||||||
if (!l->username.empty()) {
|
if (!l->username.empty()) {
|
||||||
this->bb_username_to_license.erase(l->username);
|
this->bb_username_to_license.erase(l->username);
|
||||||
}
|
}
|
||||||
this->save();
|
if (this->autosave) {
|
||||||
|
this->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<License> LicenseManager::snapshot() const {
|
vector<License> LicenseManager::snapshot() const {
|
||||||
|
|||||||
+5
-2
@@ -49,6 +49,9 @@ public:
|
|||||||
LicenseManager(const std::string& filename);
|
LicenseManager(const std::string& filename);
|
||||||
~LicenseManager() = default;
|
~LicenseManager() = default;
|
||||||
|
|
||||||
|
void save() const;
|
||||||
|
void set_autosave(bool autosave);
|
||||||
|
|
||||||
std::shared_ptr<const License> verify_pc(uint32_t serial_number,
|
std::shared_ptr<const License> verify_pc(uint32_t serial_number,
|
||||||
const std::string& access_key) const;
|
const std::string& access_key) const;
|
||||||
std::shared_ptr<const License> verify_gc(uint32_t serial_number,
|
std::shared_ptr<const License> verify_gc(uint32_t serial_number,
|
||||||
@@ -75,9 +78,9 @@ public:
|
|||||||
const std::string& password, bool temporary);
|
const std::string& password, bool temporary);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void save() const;
|
|
||||||
|
|
||||||
std::string filename;
|
std::string filename;
|
||||||
|
bool autosave;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license;
|
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;
|
std::unordered_map<uint32_t, std::shared_ptr<License>> serial_number_to_license;
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-3
@@ -11,7 +11,6 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "DNSServer.hh"
|
#include "DNSServer.hh"
|
||||||
#include "FileContentsCache.hh"
|
|
||||||
#include "IPStackSimulator.hh"
|
#include "IPStackSimulator.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
#include "NetworkAddresses.hh"
|
#include "NetworkAddresses.hh"
|
||||||
@@ -27,7 +26,6 @@ using namespace std;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
FileContentsCache file_cache;
|
|
||||||
bool use_terminal_colors = false;
|
bool use_terminal_colors = false;
|
||||||
|
|
||||||
|
|
||||||
@@ -372,8 +370,14 @@ int main(int argc, char** argv) {
|
|||||||
config_log.info("Creating menus");
|
config_log.info("Creating menus");
|
||||||
state->create_menus(config_json);
|
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;
|
shared_ptr<DNSServer> dns_server;
|
||||||
if (state->dns_server_port) {
|
if (state->dns_server_port && !replay_log_filename) {
|
||||||
config_log.info("Starting DNS server");
|
config_log.info("Starting DNS server");
|
||||||
dns_server.reset(new DNSServer(base, state->local_address,
|
dns_server.reset(new DNSServer(base, state->local_address,
|
||||||
state->external_address));
|
state->external_address));
|
||||||
|
|||||||
+2
-3
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
extern FileContentsCache file_cache;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static void load_battle_param_file(const string& filename, BattleParams* entries) {
|
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,
|
vector<PSOEnemy> load_map(const std::string& filename, uint8_t episode,
|
||||||
uint8_t difficulty, const BattleParams* battle_params, bool alt_enemies) {
|
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());
|
const EnemyEntry* entries = reinterpret_cast<const EnemyEntry*>(data->data());
|
||||||
size_t entry_count = data->size() / sizeof(EnemyEntry);
|
size_t entry_count = data->size() / sizeof(EnemyEntry);
|
||||||
return parse_map(episode, difficulty, battle_params, entries, entry_count,
|
return parse_map(episode, difficulty, battle_params, entries, entry_count,
|
||||||
|
|||||||
+1
-1
@@ -186,7 +186,7 @@ std::string prepend_command_header(
|
|||||||
case GameVersion::BB: {
|
case GameVersion::BB: {
|
||||||
PSOCommandHeaderBB header;
|
PSOCommandHeaderBB header;
|
||||||
if (encryption_enabled) {
|
if (encryption_enabled) {
|
||||||
header.size = (sizeof(header) + data.size() + 7) & ~7;
|
header.size = (sizeof(header) + data.size() + 3) & ~3;
|
||||||
} else {
|
} else {
|
||||||
header.size = (sizeof(header) + data.size());
|
header.size = (sizeof(header) + data.size());
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-16
@@ -55,38 +55,54 @@ union PSOSubcommand {
|
|||||||
// This function is used in a lot of places to check received command sizes and
|
// This function is used in a lot of places to check received command sizes and
|
||||||
// cast them to the appropriate type
|
// cast them to the appropriate type
|
||||||
template <typename T>
|
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 T& check_size_t(
|
||||||
const std::string& data,
|
const std::string& data,
|
||||||
size_t min_size = sizeof(T),
|
size_t min_size = sizeof(T),
|
||||||
size_t max_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(
|
throw std::runtime_error(string_printf(
|
||||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
"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(
|
throw std::runtime_error(string_printf(
|
||||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
"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>
|
template <typename T>
|
||||||
T& check_size_t(
|
T& check_size_t(
|
||||||
std::string& data,
|
std::string& data,
|
||||||
size_t min_size = sizeof(T),
|
size_t min_size = sizeof(T),
|
||||||
size_t max_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);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
|
void check_size_v(size_t size, size_t min_size, size_t max_size = 0);
|
||||||
|
|||||||
+37
-14
@@ -7,10 +7,11 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <phosg/Filesystem.hh>
|
#include <phosg/Filesystem.hh>
|
||||||
|
|
||||||
|
#include "FileContentsCache.hh"
|
||||||
#include "Loggers.hh"
|
#include "Loggers.hh"
|
||||||
|
#include "StaticGameData.hh"
|
||||||
#include "Text.hh"
|
#include "Text.hh"
|
||||||
#include "Version.hh"
|
#include "Version.hh"
|
||||||
#include "StaticGameData.hh"
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ static const string ACCOUNT_FILE_SIGNATURE =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static FileContentsCache player_files_cache(300 * 1000 * 1000);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PlayerStats::PlayerStats() noexcept
|
PlayerStats::PlayerStats() noexcept
|
||||||
: atp(0), mst(0), evp(0), hp(0), dfp(0), ata(0), lck(0) { }
|
: 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) {
|
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++) {
|
for (uint32_t x = 0; x < this->num_items; x++) {
|
||||||
this->items[x].data.id = 0x0F010000 + x;
|
this->items[x].data.id = 0x0F010000 + x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerBank::save(const string& filename) const {
|
void PlayerBank::save(const string& filename, bool save_to_filesystem) const {
|
||||||
save_file(filename, this, sizeof(*this));
|
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;
|
shared_ptr<SavedAccountDataBB> data;
|
||||||
try {
|
try {
|
||||||
data.reset(new SavedAccountDataBB(
|
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) {
|
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
|
||||||
throw runtime_error("account data header is incorrect");
|
throw runtime_error("account data header is incorrect");
|
||||||
}
|
}
|
||||||
|
player_data_log.info("Loaded account data file %s", filename.c_str());
|
||||||
|
|
||||||
} catch (const exception& e) {
|
} catch (const exception& e) {
|
||||||
player_data_log.info("No account data for %s; using default",
|
player_data_log.info("Cannot load account data for %s (%s); using default",
|
||||||
this->bb_username.c_str());
|
this->bb_username.c_str(), e.what());
|
||||||
|
player_files_cache.delete_key(filename);
|
||||||
data.reset(new SavedAccountDataBB(
|
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) {
|
if (data->signature != ACCOUNT_FILE_SIGNATURE) {
|
||||||
throw runtime_error("default account data header is incorrect");
|
throw runtime_error("default account data header is incorrect");
|
||||||
}
|
}
|
||||||
|
player_data_log.info("Loaded default account data file");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->account_data = data;
|
this->account_data = data;
|
||||||
player_data_log.info("Loaded account data file %s", filename.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientGameData::save_account_data() const {
|
void ClientGameData::save_account_data() const {
|
||||||
string filename = this->account_data_filename();
|
string filename = this->account_data_filename();
|
||||||
save_file(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
|
player_files_cache.replace(filename, this->account_data.get(), sizeof(SavedAccountDataBB));
|
||||||
player_data_log.info("Saved account data file %s", filename.c_str());
|
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() {
|
void ClientGameData::load_player_data() {
|
||||||
string filename = this->player_data_filename();
|
string filename = this->player_data_filename();
|
||||||
shared_ptr<SavedPlayerDataBB> data(new SavedPlayerDataBB(
|
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) {
|
if (data->signature != PLAYER_FILE_SIGNATURE) {
|
||||||
|
player_files_cache.delete_key(filename);
|
||||||
throw runtime_error("player data header is incorrect");
|
throw runtime_error("player data header is incorrect");
|
||||||
}
|
}
|
||||||
this->player_data = data;
|
this->player_data = data;
|
||||||
@@ -418,8 +436,13 @@ void ClientGameData::load_player_data() {
|
|||||||
|
|
||||||
void ClientGameData::save_player_data() const {
|
void ClientGameData::save_player_data() const {
|
||||||
string filename = this->player_data_filename();
|
string filename = this->player_data_filename();
|
||||||
save_file(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
|
player_files_cache.replace(filename, this->player_data.get(), sizeof(SavedPlayerDataBB));
|
||||||
player_data_log.info("Saved player data file %s", filename.c_str());
|
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) {
|
void ClientGameData::import_player(const PSOPlayerDataPC& pc) {
|
||||||
|
|||||||
+3
-2
@@ -71,7 +71,7 @@ struct PlayerBank {
|
|||||||
PlayerBankItem items[200];
|
PlayerBankItem items[200];
|
||||||
|
|
||||||
void load(const std::string& filename);
|
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,
|
bool switch_with_file(const std::string& save_filename,
|
||||||
const std::string& load_filename);
|
const std::string& load_filename);
|
||||||
@@ -407,8 +407,9 @@ public:
|
|||||||
size_t bb_player_index;
|
size_t bb_player_index;
|
||||||
PlayerInventoryItem identify_result;
|
PlayerInventoryItem identify_result;
|
||||||
std::vector<ItemData> shop_contents;
|
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();
|
~ClientGameData();
|
||||||
|
|
||||||
std::shared_ptr<SavedAccountDataBB> account(bool should_load = true);
|
std::shared_ptr<SavedAccountDataBB> account(bool should_load = true);
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ using namespace std;
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
extern FileContentsCache file_cache;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vector<MenuItem> quest_categories_menu({
|
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::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),
|
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;
|
return;
|
||||||
} else {
|
} else {
|
||||||
shared_ptr<License> l = LicenseManager::create_license_bb(
|
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);
|
s->license_manager->add(l);
|
||||||
c->set_license(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
|
uint16_t, uint32_t, const string& data) { // D7
|
||||||
string filename(data);
|
string filename(data);
|
||||||
strip_trailing_zeroes(filename);
|
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);
|
send_quest_file(c, "", filename, *contents, QuestFileType::GBA_DEMO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,117 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(
|
|||||||
return 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(
|
ReplaySession::ReplaySession(
|
||||||
shared_ptr<struct event_base> base,
|
shared_ptr<struct event_base> base,
|
||||||
FILE* input_log,
|
FILE* input_log,
|
||||||
@@ -79,6 +190,9 @@ ReplaySession::ReplaySession(
|
|||||||
parsing_command->mask += mask_bytes;
|
parsing_command->mask += mask_bytes;
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
if (parsing_command->type == Event::Type::RECEIVE) {
|
||||||
|
this->apply_default_mask(parsing_command);
|
||||||
|
}
|
||||||
parsing_command = nullptr;
|
parsing_command = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,6 +418,13 @@ void ReplaySession::on_command_received(
|
|||||||
switch (c->version) {
|
switch (c->version) {
|
||||||
case GameVersion::DC:
|
case GameVersion::DC:
|
||||||
throw runtime_error("DC encryption is not supported during replays");
|
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::PC:
|
||||||
case GameVersion::GC:
|
case GameVersion::GC:
|
||||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ private:
|
|||||||
Event::Type type, std::shared_ptr<Client> c);
|
Event::Type type, std::shared_ptr<Client> c);
|
||||||
void update_timeout_event();
|
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_timeout(evutil_socket_t fd, short events, void* ctx);
|
||||||
static void dispatch_on_command_received(
|
static void dispatch_on_command_received(
|
||||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||||
|
|||||||
+20
-7
@@ -344,6 +344,7 @@ static const vector<string> stream_file_entries = {
|
|||||||
"BattleParamEntry_ep4_on.dat",
|
"BattleParamEntry_ep4_on.dat",
|
||||||
"PlyLevelTbl.prs",
|
"PlyLevelTbl.prs",
|
||||||
};
|
};
|
||||||
|
static FileContentsCache bb_stream_files_cache(3600 * 1000 * 1000);
|
||||||
|
|
||||||
void send_stream_file_index_bb(shared_ptr<Client> c) {
|
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;
|
vector<S_StreamFileIndexEntry_BB_01EB> entries;
|
||||||
size_t offset = 0;
|
size_t offset = 0;
|
||||||
for (const string& filename : stream_file_entries) {
|
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();
|
auto& e = entries.emplace_back();
|
||||||
e.size = file_data->size();
|
e.size = cache_res.data->size();
|
||||||
// TODO: memoize the checksum somewhere; computing it can be slow
|
// Computing the checksum can be slow, so we cache it along with the file
|
||||||
e.checksum = crc32(file_data->data(), e.size);
|
// 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.offset = offset;
|
||||||
e.filename = filename;
|
e.filename = filename;
|
||||||
offset += e.size;
|
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) {
|
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;
|
size_t bytes = 0;
|
||||||
for (const auto& name : stream_file_entries) {
|
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;
|
string ret;
|
||||||
ret.reserve(bytes);
|
ret.reserve(bytes);
|
||||||
for (const auto& name : stream_file_entries) {
|
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;
|
return ret;
|
||||||
});
|
});
|
||||||
|
auto contents = cache_result.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;
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ void Server::on_listen_accept(struct evconnlistener* listener,
|
|||||||
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||||
shared_ptr<Client> c(new Client(
|
shared_ptr<Client> c(new Client(
|
||||||
bev, listening_socket->version, listening_socket->behavior));
|
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_command_received = Server::on_client_input;
|
||||||
c->channel.on_error = Server::on_client_error;
|
c->channel.on_error = Server::on_client_error;
|
||||||
c->channel.context_obj = this;
|
c->channel.context_obj = this;
|
||||||
@@ -100,6 +101,7 @@ void Server::connect_client(
|
|||||||
struct bufferevent* bev, uint32_t address, uint16_t client_port,
|
struct bufferevent* bev, uint32_t address, uint16_t client_port,
|
||||||
uint16_t server_port, GameVersion version, ServerBehavior initial_state) {
|
uint16_t server_port, GameVersion version, ServerBehavior initial_state) {
|
||||||
shared_ptr<Client> c(new Client(bev, version, 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_command_received = Server::on_client_input;
|
||||||
c->channel.on_error = Server::on_client_error;
|
c->channel.on_error = Server::on_client_error;
|
||||||
c->channel.context_obj = this;
|
c->channel.context_obj = this;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ ServerState::ServerState()
|
|||||||
: dns_server_port(0),
|
: dns_server_port(0),
|
||||||
ip_stack_debug(false),
|
ip_stack_debug(false),
|
||||||
allow_unregistered_users(false),
|
allow_unregistered_users(false),
|
||||||
|
allow_saving(true),
|
||||||
item_tracking_enabled(true),
|
item_tracking_enabled(true),
|
||||||
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
|
run_shell_behavior(RunShellBehavior::DEFAULT), next_lobby_id(1),
|
||||||
pre_lobby_event(0),
|
pre_lobby_event(0),
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ struct ServerState {
|
|||||||
std::vector<std::string> ip_stack_addresses;
|
std::vector<std::string> ip_stack_addresses;
|
||||||
bool ip_stack_debug;
|
bool ip_stack_debug;
|
||||||
bool allow_unregistered_users;
|
bool allow_unregistered_users;
|
||||||
|
bool allow_saving;
|
||||||
bool item_tracking_enabled;
|
bool item_tracking_enabled;
|
||||||
RunShellBehavior run_shell_behavior;
|
RunShellBehavior run_shell_behavior;
|
||||||
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>> bb_private_keys;
|
||||||
|
|||||||
Reference in New Issue
Block a user