eliminate using namespace
This commit is contained in:
+10
-13
@@ -9,10 +9,7 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
AFSArchive::AFSArchive(shared_ptr<const string> data)
|
||||
: data(data) {
|
||||
AFSArchive::AFSArchive(std::shared_ptr<const std::string> data) : data(data) {
|
||||
struct FileHeader {
|
||||
be_uint32_t magic;
|
||||
le_uint32_t num_files;
|
||||
@@ -26,7 +23,7 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
|
||||
phosg::StringReader r(*this->data);
|
||||
const auto& header = r.get<FileHeader>();
|
||||
if (header.magic != 0x41465300) { // 'AFS\0'
|
||||
throw runtime_error("file is not an AFS archive");
|
||||
throw std::runtime_error("file is not an AFS archive");
|
||||
}
|
||||
|
||||
while (this->entries.size() < header.num_files) {
|
||||
@@ -35,21 +32,21 @@ AFSArchive::AFSArchive(shared_ptr<const string> data)
|
||||
}
|
||||
}
|
||||
|
||||
pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
std::pair<const void*, size_t> AFSArchive::get(size_t index) const {
|
||||
const auto& entry = this->entries.at(index);
|
||||
if (entry.offset > this->data->size()) {
|
||||
throw out_of_range("entry begins beyond end of archive");
|
||||
throw std::out_of_range("entry begins beyond end of archive");
|
||||
}
|
||||
if (entry.offset + entry.size > this->data->size()) {
|
||||
throw out_of_range("entry extends beyond end of archive");
|
||||
throw std::out_of_range("entry extends beyond end of archive");
|
||||
}
|
||||
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||
}
|
||||
|
||||
string AFSArchive::get_copy(size_t index) const {
|
||||
std::string AFSArchive::get_copy(size_t index) const {
|
||||
auto ret = this->get(index);
|
||||
return string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||
return std::string(reinterpret_cast<const char*>(ret.first), ret.second);
|
||||
}
|
||||
|
||||
phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
||||
@@ -57,12 +54,12 @@ phosg::StringReader AFSArchive::get_reader(size_t index) const {
|
||||
return phosg::StringReader(ret.first, ret.second);
|
||||
}
|
||||
|
||||
string AFSArchive::generate(const vector<string>& files, bool big_endian) {
|
||||
std::string AFSArchive::generate(const std::vector<std::string>& files, bool big_endian) {
|
||||
return big_endian ? AFSArchive::generate_t<true>(files) : AFSArchive::generate_t<false>(files);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string AFSArchive::generate_t(const vector<string>& files) {
|
||||
std::string AFSArchive::generate_t(const std::vector<std::string>& files) {
|
||||
phosg::StringWriter w;
|
||||
w.put_u32b(0x41465300); // 'AFS\0'
|
||||
w.put<U32T<BE>>(files.size());
|
||||
|
||||
+188
-188
@@ -10,23 +10,21 @@
|
||||
|
||||
#include "Account.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<DCNTELicense>();
|
||||
std::shared_ptr<DCNTELicense> DCNTELicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = std::make_shared<DCNTELicense>();
|
||||
ret->serial_number = json.get_string("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
if (ret->serial_number.size() > 16) {
|
||||
throw runtime_error("serial number is too long");
|
||||
throw std::runtime_error("serial number is too long");
|
||||
}
|
||||
if (ret->serial_number.empty()) {
|
||||
throw runtime_error("serial number is too short");
|
||||
throw std::runtime_error("serial number is too short");
|
||||
}
|
||||
if (ret->access_key.size() > 16) {
|
||||
throw runtime_error("access key is too long");
|
||||
throw std::runtime_error("access key is too long");
|
||||
}
|
||||
if (ret->access_key.empty()) {
|
||||
throw runtime_error("access key is too short");
|
||||
throw std::runtime_error("access key is too short");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -35,15 +33,15 @@ phosg::JSON DCNTELicense::json() const {
|
||||
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
||||
}
|
||||
|
||||
shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<V1V2License>();
|
||||
std::shared_ptr<V1V2License> V1V2License::from_json(const phosg::JSON& json) {
|
||||
auto ret = std::make_shared<V1V2License>();
|
||||
ret->serial_number = json.get_int("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
if (ret->serial_number == 0) {
|
||||
throw runtime_error("serial number is zero");
|
||||
throw std::runtime_error("serial number is zero");
|
||||
}
|
||||
if (ret->access_key.size() != 8) {
|
||||
throw runtime_error("access key length is incorrect");
|
||||
throw std::runtime_error("access key length is incorrect");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -52,19 +50,19 @@ phosg::JSON V1V2License::json() const {
|
||||
return phosg::JSON::dict({{"SerialNumber", this->serial_number}, {"AccessKey", this->access_key}});
|
||||
}
|
||||
|
||||
shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<GCLicense>();
|
||||
std::shared_ptr<GCLicense> GCLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = std::make_shared<GCLicense>();
|
||||
ret->serial_number = json.get_int("SerialNumber");
|
||||
ret->access_key = json.get_string("AccessKey");
|
||||
ret->password = json.get_string("Password");
|
||||
if (ret->serial_number == 0) {
|
||||
throw runtime_error("serial number is zero");
|
||||
throw std::runtime_error("serial number is zero");
|
||||
}
|
||||
if (ret->access_key.size() != 12) {
|
||||
throw runtime_error("access key length is incorrect");
|
||||
throw std::runtime_error("access key length is incorrect");
|
||||
}
|
||||
if (ret->password.empty()) {
|
||||
throw runtime_error("password is too short");
|
||||
throw std::runtime_error("password is too short");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -77,19 +75,19 @@ phosg::JSON GCLicense::json() const {
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<XBLicense>();
|
||||
std::shared_ptr<XBLicense> XBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = std::make_shared<XBLicense>();
|
||||
ret->gamertag = json.get_string("GamerTag");
|
||||
ret->user_id = json.get_int("UserID");
|
||||
ret->account_id = json.get_int("AccountID");
|
||||
if (ret->gamertag.empty()) {
|
||||
throw runtime_error("gamertag is too short");
|
||||
throw std::runtime_error("gamertag is too short");
|
||||
}
|
||||
if (ret->user_id == 0) {
|
||||
throw runtime_error("user ID is zero");
|
||||
throw std::runtime_error("user ID is zero");
|
||||
}
|
||||
if (ret->account_id == 0) {
|
||||
throw runtime_error("account ID is zero");
|
||||
throw std::runtime_error("account ID is zero");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -98,21 +96,21 @@ phosg::JSON XBLicense::json() const {
|
||||
return phosg::JSON::dict({{"GamerTag", this->gamertag}, {"UserID", this->user_id}, {"AccountID", this->account_id}});
|
||||
}
|
||||
|
||||
shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = make_shared<BBLicense>();
|
||||
std::shared_ptr<BBLicense> BBLicense::from_json(const phosg::JSON& json) {
|
||||
auto ret = std::make_shared<BBLicense>();
|
||||
ret->username = json.get_string("UserName");
|
||||
ret->password = json.get_string("Password");
|
||||
if (ret->username.size() > 16) {
|
||||
throw runtime_error("username is too long");
|
||||
throw std::runtime_error("username is too long");
|
||||
}
|
||||
if (ret->username.empty()) {
|
||||
throw runtime_error("username is too short");
|
||||
throw std::runtime_error("username is too short");
|
||||
}
|
||||
if (ret->password.size() > 16) {
|
||||
throw runtime_error("password is too long");
|
||||
throw std::runtime_error("password is too long");
|
||||
}
|
||||
if (ret->password.empty()) {
|
||||
throw runtime_error("password is too short");
|
||||
throw std::runtime_error("password is too short");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -132,51 +130,51 @@ Account::Account(const phosg::JSON& json)
|
||||
uint64_t format_version = 0;
|
||||
try {
|
||||
format_version = json.get_int("FormatVersion");
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (format_version == 0) {
|
||||
// Original format - no account ID
|
||||
this->account_id = json.get_int("SerialNumber");
|
||||
string access_key = json.get_string("AccessKey", "");
|
||||
string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
|
||||
string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
|
||||
string gc_password = json.get_string("GCPassword", "");
|
||||
string xb_gamertag = json.get_string("XBGamerTag", "");
|
||||
std::string access_key = json.get_string("AccessKey", "");
|
||||
std::string dc_nte_serial_number = json.get_string("DCNTESerialNumber", "");
|
||||
std::string dc_nte_access_key = json.get_string("DCNTEAccessKey", "");
|
||||
std::string gc_password = json.get_string("GCPassword", "");
|
||||
std::string xb_gamertag = json.get_string("XBGamerTag", "");
|
||||
uint64_t xb_user_id = json.get_int("XBUserID", 0);
|
||||
uint64_t xb_account_id = json.get_int("XBAccountID", 0);
|
||||
string bb_username = json.get_string("BBUsername", "");
|
||||
string bb_password = json.get_string("BBPassword", "");
|
||||
std::string bb_username = json.get_string("BBUsername", "");
|
||||
std::string bb_password = json.get_string("BBPassword", "");
|
||||
if (access_key.size() == 12) {
|
||||
if (!gc_password.empty()) {
|
||||
auto lic = make_shared<GCLicense>();
|
||||
auto lic = std::make_shared<GCLicense>();
|
||||
lic->serial_number = this->account_id;
|
||||
lic->access_key = access_key;
|
||||
lic->password = gc_password;
|
||||
this->gc_licenses.emplace(lic->serial_number, lic);
|
||||
}
|
||||
} else if (access_key.size() >= 8) {
|
||||
auto lic = make_shared<V1V2License>();
|
||||
auto lic = std::make_shared<V1V2License>();
|
||||
lic->serial_number = this->account_id;
|
||||
lic->access_key = access_key.substr(0, 8);
|
||||
this->dc_licenses.emplace(lic->serial_number, lic);
|
||||
this->pc_licenses.emplace(lic->serial_number, lic);
|
||||
}
|
||||
if (!dc_nte_serial_number.empty() && !dc_nte_access_key.empty()) {
|
||||
auto lic = make_shared<DCNTELicense>();
|
||||
auto lic = std::make_shared<DCNTELicense>();
|
||||
lic->serial_number = dc_nte_serial_number;
|
||||
lic->access_key = dc_nte_access_key;
|
||||
this->dc_nte_licenses.emplace(lic->serial_number, lic);
|
||||
}
|
||||
if (!xb_gamertag.empty() && xb_user_id && xb_account_id) {
|
||||
auto lic = make_shared<XBLicense>();
|
||||
auto lic = std::make_shared<XBLicense>();
|
||||
lic->gamertag = xb_gamertag;
|
||||
lic->user_id = xb_user_id;
|
||||
lic->account_id = xb_account_id;
|
||||
this->xb_licenses.emplace(lic->user_id, lic);
|
||||
}
|
||||
if (!bb_username.empty() && !bb_password.empty()) {
|
||||
auto lic = make_shared<BBLicense>();
|
||||
auto lic = std::make_shared<BBLicense>();
|
||||
lic->username = bb_username;
|
||||
lic->password = bb_password;
|
||||
this->bb_licenses.emplace(lic->username, lic);
|
||||
@@ -223,7 +221,7 @@ Account::Account(const phosg::JSON& json)
|
||||
for (const auto& it : json.get_list("AutoPatchesEnabled")) {
|
||||
this->auto_patches_enabled.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,11 +277,11 @@ phosg::JSON Account::json() const {
|
||||
});
|
||||
}
|
||||
|
||||
string Account::str() const {
|
||||
std::string Account::str() const {
|
||||
std::string ret = std::format("Account: {:010}/{:08X}\n", this->account_id, this->account_id);
|
||||
|
||||
if (this->flags) {
|
||||
string flags_str = "";
|
||||
std::string flags_str = "";
|
||||
if (this->flags == static_cast<uint32_t>(Flag::ROOT)) {
|
||||
flags_str = "ROOT";
|
||||
} else if (this->flags == static_cast<uint32_t>(Flag::ADMINISTRATOR)) {
|
||||
@@ -334,7 +332,7 @@ string Account::str() const {
|
||||
}
|
||||
|
||||
if (this->user_flags) {
|
||||
string user_flags_str = "";
|
||||
std::string user_flags_str = "";
|
||||
if (this->check_user_flag(UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST)) {
|
||||
user_flags_str += "DISABLE_DROP_NOTIFICATION_BROADCAST,";
|
||||
}
|
||||
@@ -347,8 +345,7 @@ string Account::str() const {
|
||||
}
|
||||
|
||||
if (this->ban_end_time) {
|
||||
string time_str = phosg::format_time(this->ban_end_time);
|
||||
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, time_str);
|
||||
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, phosg::format_time(this->ban_end_time));
|
||||
}
|
||||
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
||||
ret += std::format(" Episode 3 meseta: {} (total earned: {})\n",
|
||||
@@ -399,20 +396,19 @@ string Account::str() const {
|
||||
void Account::save() const {
|
||||
if (!this->is_temporary) {
|
||||
auto json = this->json();
|
||||
string json_data = json.serialize(
|
||||
std::string json_data = json.serialize(
|
||||
phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS);
|
||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||
std::string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||
phosg::save_file(filename, json_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Account::delete_file() const {
|
||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||
remove(filename.c_str());
|
||||
std::filesystem::remove(std::format("system/licenses/{:010}.json", this->account_id));
|
||||
}
|
||||
|
||||
string Login::str() const {
|
||||
string ret = std::format("Account:{:08X}", this->account->account_id);
|
||||
std::string Login::str() const {
|
||||
std::string ret = std::format("Account:{:08X}", this->account->account_id);
|
||||
if (this->account_was_created) {
|
||||
ret += " (new)";
|
||||
}
|
||||
@@ -435,21 +431,21 @@ string Login::str() const {
|
||||
}
|
||||
|
||||
size_t AccountIndex::count() const {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->by_account_id.size();
|
||||
}
|
||||
|
||||
shared_ptr<Account> AccountIndex::from_account_id(uint32_t account_id) const {
|
||||
std::shared_ptr<Account> AccountIndex::from_account_id(uint32_t account_id) const {
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->by_account_id.at(account_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw missing_account();
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& serial_number, const string& access_key) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const std::string& serial_number, const std::string& access_key) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_dc_nte_serial_number.at(serial_number);
|
||||
login->dc_nte_license = login->account->dc_nte_licenses.at(serial_number);
|
||||
if (login->dc_nte_license->access_key != access_key) {
|
||||
@@ -461,30 +457,30 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials_locked(const string& ser
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
||||
const string& serial_number, const string& access_key, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
||||
const std::string& serial_number, const std::string& access_key, bool allow_create) {
|
||||
if (serial_number.empty()) {
|
||||
throw no_username();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_dc_nte_credentials_locked(serial_number, access_key);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = phosg::fnv1a32(serial_number) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<DCNTELicense>();
|
||||
auto lic = std::make_shared<DCNTELicense>();
|
||||
lic->serial_number = serial_number;
|
||||
lic->access_key = access_key;
|
||||
login->account->dc_nte_licenses.emplace(lic->serial_number, lic);
|
||||
@@ -496,9 +492,9 @@ shared_ptr<Login> AccountIndex::from_dc_nte_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
||||
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_dc_serial_number.at(serial_number);
|
||||
login->dc_license = login->account->dc_licenses.at(serial_number);
|
||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||
@@ -514,30 +510,30 @@ shared_ptr<Login> AccountIndex::from_dc_credentials_locked(
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_dc_credentials(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_dc_credentials(
|
||||
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
|
||||
if (serial_number == 0) {
|
||||
throw no_username();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_dc_credentials_locked(serial_number, access_key, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
auto lic = std::make_shared<V1V2License>();
|
||||
lic->serial_number = serial_number;
|
||||
lic->access_key = access_key;
|
||||
login->account->dc_licenses.emplace(lic->serial_number, lic);
|
||||
@@ -549,19 +545,19 @@ shared_ptr<Login> AccountIndex::from_dc_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_number, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_number, bool allow_create) {
|
||||
if (!allow_create) {
|
||||
throw missing_account();
|
||||
}
|
||||
if (guild_card_number == 0xFFFFFFFF) {
|
||||
guild_card_number = phosg::random_object<uint32_t>() & 0x7FFFFFFF;
|
||||
}
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = guild_card_number;
|
||||
login->account->is_temporary = true;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
auto lic = std::make_shared<V1V2License>();
|
||||
lic->serial_number = guild_card_number;
|
||||
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
||||
login->pc_license = lic;
|
||||
@@ -569,9 +565,9 @@ shared_ptr<Login> AccountIndex::from_pc_nte_credentials(uint32_t guild_card_numb
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
||||
uint32_t serial_number, const std::string& access_key, const std::string& character_name) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_pc_serial_number.at(serial_number);
|
||||
login->pc_license = login->account->pc_licenses.at(serial_number);
|
||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||
@@ -587,30 +583,30 @@ shared_ptr<Login> AccountIndex::from_pc_credentials_locked(
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_pc_credentials(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_pc_credentials(
|
||||
uint32_t serial_number, const std::string& access_key, const std::string& character_name, bool allow_create) {
|
||||
if (serial_number == 0) {
|
||||
throw no_username();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_pc_credentials_locked(serial_number, access_key, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
auto lic = std::make_shared<V1V2License>();
|
||||
lic->serial_number = serial_number;
|
||||
lic->access_key = access_key;
|
||||
login->account->pc_licenses.emplace(lic->serial_number, lic);
|
||||
@@ -622,9 +618,12 @@ shared_ptr<Login> AccountIndex::from_pc_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
||||
uint32_t serial_number, const string& access_key, const string* password, const string& character_name) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_gc_serial_number.at(serial_number);
|
||||
login->gc_license = login->account->gc_licenses.at(serial_number);
|
||||
bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT);
|
||||
@@ -643,34 +642,34 @@ shared_ptr<Login> AccountIndex::from_gc_credentials_locked(
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_gc_credentials(
|
||||
std::shared_ptr<Login> AccountIndex::from_gc_credentials(
|
||||
uint32_t serial_number,
|
||||
const string& access_key,
|
||||
const string* password,
|
||||
const string& character_name,
|
||||
const std::string& access_key,
|
||||
const std::string* password,
|
||||
const std::string& character_name,
|
||||
bool allow_create) {
|
||||
if (serial_number == 0) {
|
||||
throw no_username();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_gc_credentials_locked(serial_number, access_key, password, character_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create && password) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<GCLicense>();
|
||||
auto lic = std::make_shared<GCLicense>();
|
||||
lic->serial_number = serial_number;
|
||||
lic->access_key = access_key;
|
||||
lic->password = *password;
|
||||
@@ -683,8 +682,8 @@ shared_ptr<Login> AccountIndex::from_gc_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_xb_user_id.at(user_id);
|
||||
login->xb_license = login->account->xb_licenses.at(user_id);
|
||||
if (login->account->ban_end_time && (login->account->ban_end_time >= phosg::now())) {
|
||||
@@ -693,30 +692,30 @@ shared_ptr<Login> AccountIndex::from_xb_credentials_locked(uint64_t user_id) {
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_xb_credentials(
|
||||
const string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_xb_credentials(
|
||||
const std::string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) {
|
||||
if (user_id == 0 || account_id == 0) {
|
||||
throw incorrect_access_key();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_xb_credentials_locked(user_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_xb_credentials_locked(user_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = phosg::fnv1a32(gamertag) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<XBLicense>();
|
||||
auto lic = std::make_shared<XBLicense>();
|
||||
lic->gamertag = gamertag;
|
||||
lic->user_id = user_id;
|
||||
lic->account_id = account_id;
|
||||
@@ -729,8 +728,9 @@ shared_ptr<Login> AccountIndex::from_xb_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& username, const string* password) {
|
||||
auto login = make_shared<Login>();
|
||||
std::shared_ptr<Login> AccountIndex::from_bb_credentials_locked(
|
||||
const std::string& username, const std::string* password) {
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account = this->by_bb_username.at(username);
|
||||
login->bb_license = login->account->bb_licenses.at(username);
|
||||
if (password && (login->bb_license->password != *password)) {
|
||||
@@ -742,30 +742,30 @@ shared_ptr<Login> AccountIndex::from_bb_credentials_locked(const string& usernam
|
||||
return login;
|
||||
}
|
||||
|
||||
shared_ptr<Login> AccountIndex::from_bb_credentials(
|
||||
const string& username, const string* password, bool allow_create) {
|
||||
std::shared_ptr<Login> AccountIndex::from_bb_credentials(
|
||||
const std::string& username, const std::string* password, bool allow_create) {
|
||||
if (username.empty() || (password && password->empty())) {
|
||||
throw no_username();
|
||||
}
|
||||
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
std::shared_lock g(this->lock);
|
||||
return this->from_bb_credentials_locked(username, password);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
try {
|
||||
return this->from_bb_credentials_locked(username, password);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (allow_create && password) {
|
||||
auto login = make_shared<Login>();
|
||||
auto login = std::make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account = std::make_shared<Account>();
|
||||
login->account->account_id = phosg::fnv1a32(username) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<BBLicense>();
|
||||
auto lic = std::make_shared<BBLicense>();
|
||||
lic->username = username;
|
||||
lic->password = *password;
|
||||
login->account->bb_licenses.emplace(lic->username, lic);
|
||||
@@ -777,9 +777,9 @@ shared_ptr<Login> AccountIndex::from_bb_credentials(
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Account>> AccountIndex::all() const {
|
||||
shared_lock g(this->lock);
|
||||
vector<shared_ptr<Account>> ret;
|
||||
std::vector<std::shared_ptr<Account>> AccountIndex::all() const {
|
||||
std::shared_lock g(this->lock);
|
||||
std::vector<std::shared_ptr<Account>> ret;
|
||||
ret.reserve(this->by_account_id.size());
|
||||
for (const auto& it : this->by_account_id) {
|
||||
ret.emplace_back(it.second);
|
||||
@@ -787,44 +787,44 @@ vector<shared_ptr<Account>> AccountIndex::all() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AccountIndex::add(shared_ptr<Account> a) {
|
||||
unique_lock g(this->lock);
|
||||
void AccountIndex::add(std::shared_ptr<Account> a) {
|
||||
std::unique_lock g(this->lock);
|
||||
this->add_locked(a);
|
||||
}
|
||||
|
||||
void AccountIndex::add_locked(shared_ptr<Account> a) {
|
||||
void AccountIndex::add_locked(std::shared_ptr<Account> a) {
|
||||
if (this->force_all_temporary) {
|
||||
a->is_temporary = true;
|
||||
}
|
||||
|
||||
for (const auto& it : a->dc_nte_licenses) {
|
||||
if (this->by_dc_nte_serial_number.count(it.second->serial_number)) {
|
||||
throw runtime_error("account already exists with this DC NTE serial number");
|
||||
throw std::runtime_error("account already exists with this DC NTE serial number");
|
||||
}
|
||||
}
|
||||
for (const auto& it : a->dc_licenses) {
|
||||
if (this->by_dc_serial_number.count(it.second->serial_number)) {
|
||||
throw runtime_error("account already exists with this DC serial number");
|
||||
throw std::runtime_error("account already exists with this DC serial number");
|
||||
}
|
||||
}
|
||||
for (const auto& it : a->pc_licenses) {
|
||||
if (this->by_pc_serial_number.count(it.second->serial_number)) {
|
||||
throw runtime_error("account already exists with this PC NTE serial number");
|
||||
throw std::runtime_error("account already exists with this PC NTE serial number");
|
||||
}
|
||||
}
|
||||
for (const auto& it : a->gc_licenses) {
|
||||
if (this->by_gc_serial_number.count(it.second->serial_number)) {
|
||||
throw runtime_error("account already exists with this GC serial number");
|
||||
throw std::runtime_error("account already exists with this GC serial number");
|
||||
}
|
||||
}
|
||||
for (const auto& it : a->xb_licenses) {
|
||||
if (this->by_xb_user_id.count(it.second->user_id)) {
|
||||
throw runtime_error("account already exists with this XB user ID");
|
||||
throw std::runtime_error("account already exists with this XB user ID");
|
||||
}
|
||||
}
|
||||
for (const auto& it : a->bb_licenses) {
|
||||
if (this->by_bb_username.count(it.second->username)) {
|
||||
throw runtime_error("account already exists with this BB username");
|
||||
throw std::runtime_error("account already exists with this BB username");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,10 +854,10 @@ void AccountIndex::add_locked(shared_ptr<Account> a) {
|
||||
}
|
||||
|
||||
void AccountIndex::remove(uint32_t account_id) {
|
||||
unique_lock g(this->lock);
|
||||
std::unique_lock g(this->lock);
|
||||
auto acc_it = this->by_account_id.find(account_id);
|
||||
if (acc_it == this->by_account_id.end()) {
|
||||
throw out_of_range("account does not exist");
|
||||
throw std::out_of_range("account does not exist");
|
||||
}
|
||||
auto a = std::move(acc_it->second);
|
||||
this->by_account_id.erase(acc_it);
|
||||
@@ -882,135 +882,135 @@ void AccountIndex::remove(uint32_t account_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_dc_nte_license(shared_ptr<Account> account, shared_ptr<DCNTELicense> license) {
|
||||
void AccountIndex::add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license) {
|
||||
if (!this->by_dc_nte_serial_number.emplace(license->serial_number, account).second) {
|
||||
throw runtime_error("serial number already registered");
|
||||
throw std::runtime_error("serial number already registered");
|
||||
}
|
||||
if (!account->dc_nte_licenses.emplace(license->serial_number, license).second) {
|
||||
this->by_dc_nte_serial_number.erase(license->serial_number);
|
||||
throw logic_error("serial number registered in account but not in account index");
|
||||
throw std::logic_error("serial number registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_dc_license(shared_ptr<Account> account, shared_ptr<V1V2License> license) {
|
||||
void AccountIndex::add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license) {
|
||||
if (!this->by_dc_serial_number.emplace(license->serial_number, account).second) {
|
||||
throw runtime_error("serial number already registered");
|
||||
throw std::runtime_error("serial number already registered");
|
||||
}
|
||||
if (!account->dc_licenses.emplace(license->serial_number, license).second) {
|
||||
this->by_dc_serial_number.erase(license->serial_number);
|
||||
throw logic_error("serial number registered in account but not in account index");
|
||||
throw std::logic_error("serial number registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_pc_license(shared_ptr<Account> account, shared_ptr<V1V2License> license) {
|
||||
void AccountIndex::add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license) {
|
||||
if (!this->by_pc_serial_number.emplace(license->serial_number, account).second) {
|
||||
throw runtime_error("serial number already registered");
|
||||
throw std::runtime_error("serial number already registered");
|
||||
}
|
||||
if (!account->pc_licenses.emplace(license->serial_number, license).second) {
|
||||
this->by_pc_serial_number.erase(license->serial_number);
|
||||
throw logic_error("serial number registered in account but not in account index");
|
||||
throw std::logic_error("serial number registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_gc_license(shared_ptr<Account> account, shared_ptr<GCLicense> license) {
|
||||
void AccountIndex::add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license) {
|
||||
if (!this->by_gc_serial_number.emplace(license->serial_number, account).second) {
|
||||
throw runtime_error("serial number already registered");
|
||||
throw std::runtime_error("serial number already registered");
|
||||
}
|
||||
if (!account->gc_licenses.emplace(license->serial_number, license).second) {
|
||||
this->by_gc_serial_number.erase(license->serial_number);
|
||||
throw logic_error("serial number registered in account but not in account index");
|
||||
throw std::logic_error("serial number registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_xb_license(shared_ptr<Account> account, shared_ptr<XBLicense> license) {
|
||||
void AccountIndex::add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license) {
|
||||
if (!this->by_xb_user_id.emplace(license->user_id, account).second) {
|
||||
throw runtime_error("user ID already registered");
|
||||
throw std::runtime_error("user ID already registered");
|
||||
}
|
||||
if (!account->xb_licenses.emplace(license->user_id, license).second) {
|
||||
this->by_xb_user_id.erase(license->user_id);
|
||||
throw logic_error("user ID registered in account but not in account index");
|
||||
throw std::logic_error("user ID registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::add_bb_license(shared_ptr<Account> account, shared_ptr<BBLicense> license) {
|
||||
void AccountIndex::add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license) {
|
||||
if (!this->by_bb_username.emplace(license->username, account).second) {
|
||||
throw runtime_error("username already registered");
|
||||
throw std::runtime_error("username already registered");
|
||||
}
|
||||
if (!account->bb_licenses.emplace(license->username, license).second) {
|
||||
this->by_bb_username.erase(license->username);
|
||||
throw logic_error("username registered in account but not in account index");
|
||||
throw std::logic_error("username registered in account but not in account index");
|
||||
}
|
||||
}
|
||||
|
||||
void AccountIndex::remove_dc_nte_license(shared_ptr<Account> account, const string& serial_number) {
|
||||
void AccountIndex::remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number) {
|
||||
auto it = account->dc_nte_licenses.find(serial_number);
|
||||
if (it == account->dc_nte_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_dc_nte_serial_number.erase(it->second->serial_number)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->dc_nte_licenses.erase(it);
|
||||
}
|
||||
|
||||
void AccountIndex::remove_dc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
||||
void AccountIndex::remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||
auto it = account->dc_licenses.find(serial_number);
|
||||
if (it == account->dc_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_dc_serial_number.erase(it->second->serial_number)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->dc_licenses.erase(it);
|
||||
}
|
||||
|
||||
void AccountIndex::remove_pc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
||||
void AccountIndex::remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||
auto it = account->pc_licenses.find(serial_number);
|
||||
if (it == account->pc_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_pc_serial_number.erase(it->second->serial_number)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->pc_licenses.erase(it);
|
||||
}
|
||||
|
||||
void AccountIndex::remove_gc_license(shared_ptr<Account> account, uint32_t serial_number) {
|
||||
void AccountIndex::remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number) {
|
||||
auto it = account->gc_licenses.find(serial_number);
|
||||
if (it == account->gc_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_gc_serial_number.erase(it->second->serial_number)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->gc_licenses.erase(it);
|
||||
}
|
||||
|
||||
void AccountIndex::remove_xb_license(shared_ptr<Account> account, uint64_t user_id) {
|
||||
void AccountIndex::remove_xb_license(std::shared_ptr<Account> account, uint64_t user_id) {
|
||||
auto it = account->xb_licenses.find(user_id);
|
||||
if (it == account->xb_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_xb_user_id.erase(it->second->user_id)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->xb_licenses.erase(it);
|
||||
}
|
||||
|
||||
void AccountIndex::remove_bb_license(shared_ptr<Account> account, const string& username) {
|
||||
void AccountIndex::remove_bb_license(std::shared_ptr<Account> account, const std::string& username) {
|
||||
auto it = account->bb_licenses.find(username);
|
||||
if (it == account->bb_licenses.end()) {
|
||||
throw runtime_error("license not registered to account");
|
||||
throw std::runtime_error("license not registered to account");
|
||||
}
|
||||
if (!this->by_bb_username.erase(it->second->username)) {
|
||||
throw runtime_error("license registered in account but not in account index");
|
||||
throw std::runtime_error("license registered in account but not in account index");
|
||||
}
|
||||
account->bb_licenses.erase(it);
|
||||
}
|
||||
|
||||
shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||
shared_ptr<const Account> src_a, const string& variation_data) const {
|
||||
auto ret = make_shared<Account>(*src_a);
|
||||
std::shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||
std::shared_ptr<const Account> src_a, const std::string& variation_data) const {
|
||||
auto ret = std::make_shared<Account>(*src_a);
|
||||
ret->is_temporary = true;
|
||||
ret->account_id = phosg::fnv1a32(&src_a->account_id, sizeof(src_a->account_id));
|
||||
ret->account_id = phosg::fnv1a32(variation_data, ret->account_id);
|
||||
@@ -1023,12 +1023,12 @@ AccountIndex::AccountIndex(bool force_all_temporary) : force_all_temporary(force
|
||||
std::filesystem::create_directories("system/licenses");
|
||||
} else {
|
||||
for (const auto& item : std::filesystem::directory_iterator("system/licenses")) {
|
||||
string filename = item.path().filename().string();
|
||||
std::string filename = item.path().filename().string();
|
||||
if (filename.ends_with(".json")) {
|
||||
try {
|
||||
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + filename));
|
||||
this->add(make_shared<Account>(json));
|
||||
} catch (const exception& e) {
|
||||
this->add(std::make_shared<Account>(json));
|
||||
} catch (const std::exception& e) {
|
||||
phosg::log_error_f("Failed to index account {}", filename);
|
||||
throw;
|
||||
}
|
||||
|
||||
+104
-106
@@ -14,8 +14,6 @@
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class AddressTranslator {
|
||||
public:
|
||||
enum class ExpandMethod {
|
||||
@@ -63,7 +61,7 @@ public:
|
||||
case ExpandMethod::RAW_BOTH:
|
||||
return "RAW_BOTH";
|
||||
default:
|
||||
throw logic_error("invalid expand method");
|
||||
throw std::logic_error("invalid expand method");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +83,7 @@ public:
|
||||
case ExpandMethod::RAW_BOTH:
|
||||
return false;
|
||||
default:
|
||||
throw logic_error("invalid expand method");
|
||||
throw std::logic_error("invalid expand method");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,46 +105,46 @@ public:
|
||||
case ExpandMethod::RAW_BOTH:
|
||||
return false;
|
||||
default:
|
||||
throw logic_error("invalid expand method");
|
||||
throw std::logic_error("invalid expand method");
|
||||
}
|
||||
}
|
||||
|
||||
AddressTranslator(const string& directory)
|
||||
AddressTranslator(const std::string& directory)
|
||||
: log("[addr-trans] "),
|
||||
directory(directory) {
|
||||
while (this->directory.ends_with("/")) {
|
||||
this->directory.pop_back();
|
||||
}
|
||||
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
||||
string filename = item.path().filename().string();
|
||||
std::string filename = item.path().filename().string();
|
||||
if (filename.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
std::string name = filename.substr(0, filename.size() - 4);
|
||||
std::string path = directory + "/" + filename;
|
||||
|
||||
if (filename.ends_with(".dol")) {
|
||||
ResourceDASM::DOLFile dol(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
dol.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->ppc_mems.emplace(mem);
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".xbe")) {
|
||||
ResourceDASM::XBEFile xbe(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
xbe.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".exe")) {
|
||||
ResourceDASM::PEFile pe(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
pe.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".bin")) {
|
||||
string data = phosg::load_file(path);
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
std::string data = phosg::load_file(path);
|
||||
auto mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
mem->allocate_at(0x8C010000, data.size());
|
||||
mem->memcpy(0x8C010000, data.data(), data.size());
|
||||
this->mems.emplace(name, mem);
|
||||
@@ -156,10 +154,10 @@ public:
|
||||
}
|
||||
~AddressTranslator() = default;
|
||||
|
||||
const string& get_source_filename() const {
|
||||
const std::string& get_source_filename() const {
|
||||
return this->src_filename;
|
||||
}
|
||||
void set_source_file(const string& filename) {
|
||||
void set_source_file(const std::string& filename) {
|
||||
this->src_filename = filename;
|
||||
this->src_mem = this->mems.at(this->src_filename);
|
||||
}
|
||||
@@ -178,25 +176,25 @@ public:
|
||||
uint32_t opcode = r.get_u32b();
|
||||
if ((opcode & 0xFFFF0000) == 0x3DA00000) {
|
||||
if (r13_high_found) {
|
||||
throw runtime_error("multiple values for r13_high");
|
||||
throw std::runtime_error("multiple values for r13_high");
|
||||
}
|
||||
r13_high_found = true;
|
||||
r13 |= (opcode << 16);
|
||||
} else if ((opcode & 0xFFFF0000) == 0x3C400000) {
|
||||
if (r2_high_found) {
|
||||
throw runtime_error("multiple values for r2_high");
|
||||
throw std::runtime_error("multiple values for r2_high");
|
||||
}
|
||||
r2_high_found = true;
|
||||
r2 |= (opcode << 16);
|
||||
} else if ((opcode & 0xFFFF0000) == 0x61AD0000) {
|
||||
if (r13_low_found) {
|
||||
throw runtime_error("multiple values for r13_low");
|
||||
throw std::runtime_error("multiple values for r13_low");
|
||||
}
|
||||
r13_low_found = true;
|
||||
r13 |= (opcode & 0xFFFF);
|
||||
} else if ((opcode & 0xFFFF0000) == 0x60420000) {
|
||||
if (r2_low_found) {
|
||||
throw runtime_error("multiple values for r2_low");
|
||||
throw std::runtime_error("multiple values for r2_low");
|
||||
}
|
||||
r2_low_found = true;
|
||||
r2 |= (opcode & 0xFFFF);
|
||||
@@ -217,11 +215,11 @@ public:
|
||||
}
|
||||
|
||||
struct ParseDATConstructorTableSpec {
|
||||
string src_name;
|
||||
std::string src_name;
|
||||
uint32_t index_addr;
|
||||
size_t num_areas;
|
||||
bool has_names;
|
||||
vector<uint32_t> x86_constructor_calls;
|
||||
std::vector<uint32_t> x86_constructor_calls;
|
||||
|
||||
ParseDATConstructorTableSpec(const phosg::JSON& json) {
|
||||
this->src_name = json.at("SourceName").as_string();
|
||||
@@ -233,8 +231,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
|
||||
vector<ParseDATConstructorTableSpec> ret;
|
||||
static std::vector<ParseDATConstructorTableSpec> from_json_list(const phosg::JSON& json) {
|
||||
std::vector<ParseDATConstructorTableSpec> ret;
|
||||
for (const auto& z : json.as_list()) {
|
||||
ret.emplace_back(*z);
|
||||
}
|
||||
@@ -267,25 +265,25 @@ public:
|
||||
|
||||
// Returns {type: {constructor_addr: [(start_area, end_area), ...]}}
|
||||
template <typename EntryT>
|
||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> parse_dat_constructor_table_t(
|
||||
shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
|
||||
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> parse_dat_constructor_table_t(
|
||||
std::shared_ptr<const ResourceDASM::MemoryContext>& mem, const ParseDATConstructorTableSpec& spec) {
|
||||
if (!mem) {
|
||||
throw runtime_error("no file selected");
|
||||
throw std::runtime_error("no file selected");
|
||||
}
|
||||
|
||||
// On some of the x86 builds of the game (PCv2 and Xbox), the constructor tables aren't entirely static in the data
|
||||
// sections - some parts are written during static initialization instead. To handle this, we make a copy of the
|
||||
// immutable MemoryContext and run the static initialization functions using resource_dasm's emulator before
|
||||
// parsing the constructor table.
|
||||
shared_ptr<const ResourceDASM::MemoryContext> effective_mem = mem;
|
||||
std::shared_ptr<const ResourceDASM::MemoryContext> effective_mem = mem;
|
||||
if (!spec.x86_constructor_calls.empty()) {
|
||||
auto constructed_mem = make_shared<ResourceDASM::MemoryContext>(mem->duplicate());
|
||||
auto constructed_mem = std::make_shared<ResourceDASM::MemoryContext>(mem->duplicate());
|
||||
uint32_t esp = constructed_mem->allocate(0x1000) + 0x1000;
|
||||
for (uint32_t constructor_addr : spec.x86_constructor_calls) {
|
||||
ResourceDASM::X86Emulator emu(constructed_mem);
|
||||
|
||||
// Uncomment for debugging
|
||||
// auto debugger = make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
|
||||
// auto debugger = std::make_shared<ResourceDASM::EmulatorDebugger<ResourceDASM::X86Emulator>>();
|
||||
// debugger->bind(emu);
|
||||
// debugger->state.mode = ResourceDASM::DebuggerMode::TRACE;
|
||||
|
||||
@@ -295,7 +293,7 @@ public:
|
||||
constructed_mem->write_u32l(esp - 4, 0xFFFFFFFF); // Return addr
|
||||
try {
|
||||
emu.execute();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
if (regs.eip != 0xFFFFFFFF) {
|
||||
throw;
|
||||
}
|
||||
@@ -304,7 +302,7 @@ public:
|
||||
effective_mem = constructed_mem;
|
||||
}
|
||||
|
||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
||||
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||
|
||||
auto index_r = effective_mem->reader(spec.index_addr, spec.num_areas * sizeof(uint32_t));
|
||||
for (size_t area = 0; area < spec.num_areas; area++) {
|
||||
@@ -322,18 +320,18 @@ public:
|
||||
if (!group.empty() && (group.back().second == (area - 1))) {
|
||||
group.back().second = area;
|
||||
} else {
|
||||
group.emplace_back(make_pair(area, area));
|
||||
group.emplace_back(std::make_pair(area, area));
|
||||
}
|
||||
}
|
||||
if (entries_r.eof()) {
|
||||
throw runtime_error("did not find end-of-entries marker");
|
||||
throw std::runtime_error("did not find end-of-entries marker");
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
static uint64_t area_mask_for_ranges(const vector<pair<size_t, size_t>>& ranges) {
|
||||
static uint64_t area_mask_for_ranges(const std::vector<std::pair<size_t, size_t>>& ranges) {
|
||||
uint64_t ret = 0;
|
||||
for (const auto& [start, end] : ranges) {
|
||||
for (size_t z = start; z <= end; z++) {
|
||||
@@ -344,7 +342,7 @@ public:
|
||||
}
|
||||
|
||||
void parse_dat_constructor_table(const ParseDATConstructorTableSpec& spec) {
|
||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
||||
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||
auto spec_mem = this->mems.at(spec.src_name);
|
||||
if (this->ppc_mems.count(spec_mem)) {
|
||||
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
||||
@@ -374,10 +372,10 @@ public:
|
||||
}
|
||||
|
||||
void parse_dat_constructor_table_multi(
|
||||
const vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
|
||||
map<string, map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>>> all_tables;
|
||||
const std::vector<ParseDATConstructorTableSpec>& specs, bool is_enemies, bool print_area_masks) {
|
||||
std::map<std::string, std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>>> all_tables;
|
||||
for (const auto& spec : specs) {
|
||||
map<uint32_t, map<uint32_t, vector<pair<size_t, size_t>>>> table;
|
||||
std::map<uint32_t, std::map<uint32_t, std::vector<std::pair<size_t, size_t>>>> table;
|
||||
auto spec_mem = this->mems.at(spec.src_name);
|
||||
if (this->ppc_mems.count(spec_mem)) {
|
||||
table = this->parse_dat_constructor_table_t<DATConstructorTableEntry<true>>(spec_mem, spec);
|
||||
@@ -389,14 +387,14 @@ public:
|
||||
all_tables.emplace(spec.src_name, std::move(table));
|
||||
}
|
||||
|
||||
map<string, size_t> version_widths;
|
||||
map<uint32_t, map<string, string>> formatted_cells_for_type;
|
||||
std::map<std::string, size_t> version_widths;
|
||||
std::map<uint32_t, std::map<std::string, std::string>> formatted_cells_for_type;
|
||||
for (const auto& spec : specs) {
|
||||
const auto& table = all_tables.at(spec.src_name);
|
||||
size_t max_width = 0;
|
||||
|
||||
for (const auto& [type, constructor_to_area_ranges] : table) {
|
||||
string cell_data;
|
||||
std::string cell_data;
|
||||
for (const auto& [constructor, area_ranges] : constructor_to_area_ranges) {
|
||||
if (!cell_data.empty()) {
|
||||
cell_data.push_back(' ');
|
||||
@@ -417,14 +415,14 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
max_width = max<size_t>(max_width, cell_data.size());
|
||||
max_width = std::max<size_t>(max_width, cell_data.size());
|
||||
formatted_cells_for_type[type][spec.src_name] = std::move(cell_data);
|
||||
}
|
||||
version_widths[spec.src_name] = max_width;
|
||||
}
|
||||
|
||||
vector<string> formatted_lines;
|
||||
string header_line = "TYPE =>";
|
||||
std::vector<std::string> formatted_lines;
|
||||
std::string header_line = "TYPE =>";
|
||||
for (const auto& spec : specs) {
|
||||
size_t width = version_widths.at(spec.src_name);
|
||||
header_line.push_back(' ');
|
||||
@@ -436,7 +434,7 @@ public:
|
||||
header_line += " NAME";
|
||||
|
||||
for (const auto& [type, formatted_cells] : formatted_cells_for_type) {
|
||||
string line = std::format("{:04X} =>", type);
|
||||
std::string line = std::format("{:04X} =>", type);
|
||||
for (const auto& spec : specs) {
|
||||
size_t width = version_widths.at(spec.src_name);
|
||||
try {
|
||||
@@ -446,7 +444,7 @@ public:
|
||||
if (width > cell_data.size()) {
|
||||
line.resize(line.size() + (width - cell_data.size()), ' ');
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
line.resize(line.size() + (width + 1), ' ');
|
||||
}
|
||||
}
|
||||
@@ -466,21 +464,21 @@ public:
|
||||
}
|
||||
|
||||
uint32_t find_match(
|
||||
shared_ptr<const ResourceDASM::MemoryContext> dest_mem,
|
||||
std::shared_ptr<const ResourceDASM::MemoryContext> dest_mem,
|
||||
uint32_t src_addr,
|
||||
uint32_t src_size,
|
||||
ExpandMethod expand_method) const {
|
||||
bool is_ppc = this->is_ppc_expand_method(expand_method);
|
||||
bool is_ppc_data = this->is_ppc_data_expand_method(expand_method);
|
||||
if (!this->src_mem) {
|
||||
throw runtime_error("no source file selected");
|
||||
throw std::runtime_error("no source file selected");
|
||||
}
|
||||
|
||||
if (src_size == 0) {
|
||||
src_size = is_ppc ? 4 : 1;
|
||||
}
|
||||
|
||||
pair<uint32_t, uint32_t> src_section = make_pair(0, 0);
|
||||
std::pair<uint32_t, uint32_t> src_section = std::make_pair(0, 0);
|
||||
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
||||
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
||||
src_section = sec;
|
||||
@@ -488,7 +486,7 @@ public:
|
||||
}
|
||||
}
|
||||
if (!src_section.second) {
|
||||
throw runtime_error("source address not within any section");
|
||||
throw std::runtime_error("source address not within any section");
|
||||
}
|
||||
|
||||
const char* method_token = this->name_for_expand_method(expand_method);
|
||||
@@ -570,7 +568,7 @@ public:
|
||||
if (num_matches == 1) {
|
||||
return last_match_address;
|
||||
} else if (num_matches == 0) {
|
||||
throw runtime_error("did not find exactly one match");
|
||||
throw std::runtime_error("did not find exactly one match");
|
||||
}
|
||||
bool can_expand_backward = false;
|
||||
bool can_expand_forward = false;
|
||||
@@ -614,10 +612,10 @@ public:
|
||||
can_expand_forward = (src_bytes_available_after > match_bytes_after);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid expand method");
|
||||
throw std::logic_error("invalid expand method");
|
||||
}
|
||||
if (!can_expand_backward && !can_expand_forward) {
|
||||
throw runtime_error("no further expansion is allowed");
|
||||
throw std::runtime_error("no further expansion is allowed");
|
||||
}
|
||||
if (can_expand_backward) {
|
||||
match_bytes_before += (is_ppc ? 4 : 1);
|
||||
@@ -626,7 +624,7 @@ public:
|
||||
match_bytes_after += (is_ppc ? 4 : 1);
|
||||
}
|
||||
}
|
||||
throw runtime_error("scan field too long; too many matches");
|
||||
throw std::runtime_error("scan field too long; too many matches");
|
||||
}
|
||||
|
||||
enum class MatchType {
|
||||
@@ -637,18 +635,18 @@ public:
|
||||
|
||||
void find_all_matches(uint32_t src_addr, uint32_t src_size, MatchType type) const {
|
||||
if (!this->src_mem) {
|
||||
throw runtime_error("no source file selected");
|
||||
throw std::runtime_error("no source file selected");
|
||||
}
|
||||
|
||||
map<string, uint32_t> results;
|
||||
std::map<std::string, uint32_t> results;
|
||||
for (const auto& it : this->mems) {
|
||||
if (it.second == this->src_mem) {
|
||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||
results.emplace(it.first, src_addr);
|
||||
|
||||
} else {
|
||||
vector<future<uint32_t>> futures;
|
||||
static const vector<ExpandMethod> ppc_methods = {
|
||||
std::vector<std::future<uint32_t>> futures;
|
||||
static const std::vector<ExpandMethod> ppc_methods = {
|
||||
ExpandMethod::PPC_TEXT_FORWARD,
|
||||
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
||||
ExpandMethod::PPC_TEXT_BACKWARD,
|
||||
@@ -660,7 +658,7 @@ public:
|
||||
ExpandMethod::PPC_DATA_BACKWARD,
|
||||
ExpandMethod::PPC_DATA_BOTH,
|
||||
};
|
||||
static const vector<ExpandMethod> ppc_text_methods = {
|
||||
static const std::vector<ExpandMethod> ppc_text_methods = {
|
||||
ExpandMethod::PPC_TEXT_FORWARD,
|
||||
ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER,
|
||||
ExpandMethod::PPC_TEXT_BACKWARD,
|
||||
@@ -669,18 +667,18 @@ public:
|
||||
ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER,
|
||||
ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN,
|
||||
};
|
||||
static const vector<ExpandMethod> ppc_data_methods = {
|
||||
static const std::vector<ExpandMethod> ppc_data_methods = {
|
||||
ExpandMethod::PPC_DATA_FORWARD,
|
||||
ExpandMethod::PPC_DATA_BACKWARD,
|
||||
ExpandMethod::PPC_DATA_BOTH,
|
||||
};
|
||||
static const vector<ExpandMethod> raw_methods = {
|
||||
static const std::vector<ExpandMethod> raw_methods = {
|
||||
ExpandMethod::RAW_FORWARD,
|
||||
ExpandMethod::RAW_BACKWARD,
|
||||
ExpandMethod::RAW_BOTH,
|
||||
};
|
||||
|
||||
const vector<ExpandMethod>* methods;
|
||||
const std::vector<ExpandMethod>* methods;
|
||||
if (this->ppc_mems.count(it.second)) {
|
||||
if (type == MatchType::ANY) {
|
||||
methods = &ppc_methods;
|
||||
@@ -689,7 +687,7 @@ public:
|
||||
} else if (type == MatchType::DATA) {
|
||||
methods = &ppc_data_methods;
|
||||
} else {
|
||||
throw logic_error("invalid match type");
|
||||
throw std::logic_error("invalid match type");
|
||||
}
|
||||
} else {
|
||||
methods = &raw_methods;
|
||||
@@ -699,14 +697,14 @@ public:
|
||||
futures.emplace_back(async(&AddressTranslator::find_match, this, it.second, src_addr, src_size, methods->at(z)));
|
||||
}
|
||||
|
||||
unordered_set<uint32_t> match_addrs;
|
||||
std::unordered_set<uint32_t> match_addrs;
|
||||
for (size_t z = 0; z < futures.size(); z++) {
|
||||
const char* method_name = this->name_for_expand_method(methods->at(z));
|
||||
try {
|
||||
uint32_t ret = futures[z].get();
|
||||
log.info_f("({}) ({}) {:08X}", it.first, method_name, ret);
|
||||
match_addrs.emplace(ret);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
log.error_f("({}) ({}) failed: {}", it.first, method_name, e.what());
|
||||
}
|
||||
}
|
||||
@@ -726,12 +724,12 @@ public:
|
||||
}
|
||||
|
||||
uint32_t find_be_to_le_data_match(
|
||||
shared_ptr<const ResourceDASM::MemoryContext> dest_mem, uint32_t src_addr, uint32_t src_size) const {
|
||||
std::shared_ptr<const ResourceDASM::MemoryContext> dest_mem, uint32_t src_addr, uint32_t src_size) const {
|
||||
if (src_size == 0) {
|
||||
src_size = 4;
|
||||
}
|
||||
|
||||
pair<uint32_t, uint32_t> src_section = make_pair(0, 0);
|
||||
std::pair<uint32_t, uint32_t> src_section = std::make_pair(0, 0);
|
||||
for (const auto& sec : this->src_mem->allocated_blocks()) {
|
||||
if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) {
|
||||
src_section = sec;
|
||||
@@ -739,7 +737,7 @@ public:
|
||||
}
|
||||
}
|
||||
if (!src_section.second) {
|
||||
throw runtime_error("source address not within any section");
|
||||
throw std::runtime_error("source address not within any section");
|
||||
}
|
||||
|
||||
size_t src_offset = src_addr - src_section.first;
|
||||
@@ -782,12 +780,12 @@ public:
|
||||
if (num_matches == 1) {
|
||||
return last_match_address;
|
||||
} else if (num_matches == 0) {
|
||||
throw runtime_error("did not find exactly one match");
|
||||
throw std::runtime_error("did not find exactly one match");
|
||||
}
|
||||
bool can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4);
|
||||
bool can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4);
|
||||
if (!can_expand_backward && !can_expand_forward) {
|
||||
throw runtime_error("no further expansion is allowed");
|
||||
throw std::runtime_error("no further expansion is allowed");
|
||||
}
|
||||
if (can_expand_backward) {
|
||||
match_bytes_before += 4;
|
||||
@@ -796,15 +794,15 @@ public:
|
||||
match_bytes_after += 4;
|
||||
}
|
||||
}
|
||||
throw runtime_error("scan field too long; too many matches");
|
||||
throw std::runtime_error("scan field too long; too many matches");
|
||||
}
|
||||
|
||||
void find_all_be_to_le_data_matches(uint32_t src_addr, uint32_t src_size) const {
|
||||
if (!this->src_mem) {
|
||||
throw runtime_error("no source file selected");
|
||||
throw std::runtime_error("no source file selected");
|
||||
}
|
||||
|
||||
map<string, uint32_t> results;
|
||||
std::map<std::string, uint32_t> results;
|
||||
for (const auto& it : this->mems) {
|
||||
if (it.second == this->src_mem) {
|
||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||
@@ -815,7 +813,7 @@ public:
|
||||
try {
|
||||
ret = this->find_be_to_le_data_match(it.second, src_addr, src_size);
|
||||
log.info_f("({}) {:08X}", it.first, ret);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
log.error_f("({}) failed: {}", it.first, e.what());
|
||||
}
|
||||
|
||||
@@ -831,7 +829,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void find_data(const string& data) const {
|
||||
void find_data(const std::string& data) const {
|
||||
for (const auto& [name, mem] : this->mems) {
|
||||
for (const auto& [sec_addr, sec_size] : mem->allocated_blocks()) {
|
||||
uint32_t last_addr = sec_addr + sec_size - data.size();
|
||||
@@ -844,10 +842,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void handle_command(const string& command) {
|
||||
void handle_command(const std::string& command) {
|
||||
auto tokens = phosg::split(command, ' ');
|
||||
if (tokens.empty()) {
|
||||
throw runtime_error("no command given");
|
||||
throw std::runtime_error("no command given");
|
||||
}
|
||||
phosg::strip_trailing_whitespace(tokens[tokens.size() - 1]);
|
||||
|
||||
@@ -856,7 +854,7 @@ public:
|
||||
} else if (tokens[0] == "find") {
|
||||
this->find_data(phosg::parse_data_string(tokens.at(1)));
|
||||
} else if (tokens[0] == "only") {
|
||||
unordered_set<string> to_keep{tokens.begin() + 1, tokens.end()};
|
||||
std::unordered_set<std::string> to_keep{tokens.begin() + 1, tokens.end()};
|
||||
for (auto it = this->mems.begin(); it != this->mems.end();) {
|
||||
if (to_keep.count(it->first)) {
|
||||
it++;
|
||||
@@ -891,7 +889,7 @@ public:
|
||||
auto specs = ParseDATConstructorTableSpec::from_json_list(phosg::JSON::parse(phosg::load_file(tokens.at(1))));
|
||||
this->parse_dat_constructor_table_multi(specs, is_enemies, true);
|
||||
} else if (!tokens[0].empty()) {
|
||||
throw runtime_error("unknown command");
|
||||
throw std::runtime_error("unknown command");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -904,10 +902,10 @@ public:
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
string command = phosg::fgets(stdin);
|
||||
std::string command = phosg::fgets(stdin);
|
||||
try {
|
||||
this->handle_command(command);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.error_f("Failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -916,14 +914,14 @@ public:
|
||||
|
||||
private:
|
||||
phosg::PrefixedLogger log;
|
||||
string directory;
|
||||
unordered_map<string, shared_ptr<const ResourceDASM::MemoryContext>> mems;
|
||||
unordered_set<shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
|
||||
string src_filename;
|
||||
shared_ptr<const ResourceDASM::MemoryContext> src_mem;
|
||||
std::string directory;
|
||||
std::unordered_map<std::string, std::shared_ptr<const ResourceDASM::MemoryContext>> mems;
|
||||
std::unordered_set<std::shared_ptr<const ResourceDASM::MemoryContext>> ppc_mems;
|
||||
std::string src_filename;
|
||||
std::shared_ptr<const ResourceDASM::MemoryContext> src_mem;
|
||||
};
|
||||
|
||||
void run_address_translator(const string& directory, const string& use_filename, const string& command) {
|
||||
void run_address_translator(const std::string& directory, const std::string& use_filename, const std::string& command) {
|
||||
AddressTranslator trans(directory);
|
||||
if (!use_filename.empty()) {
|
||||
trans.set_source_file(use_filename);
|
||||
@@ -936,32 +934,32 @@ void run_address_translator(const string& directory, const string& use_filename,
|
||||
}
|
||||
}
|
||||
|
||||
vector<DiffEntry> diff_dol_files(const string& a_filename, const string& b_filename) {
|
||||
std::vector<DiffEntry> diff_dol_files(const std::string& a_filename, const std::string& b_filename) {
|
||||
ResourceDASM::DOLFile a(a_filename.c_str());
|
||||
ResourceDASM::DOLFile b(b_filename.c_str());
|
||||
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
a.load_into(a_mem);
|
||||
b.load_into(b_mem);
|
||||
|
||||
uint32_t min_addr = 0xFFFFFFFF;
|
||||
uint32_t max_addr = 0x00000000;
|
||||
for (const auto& sec : a.sections) {
|
||||
min_addr = min<uint32_t>(min_addr, sec.address);
|
||||
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||
min_addr = std::min<uint32_t>(min_addr, sec.address);
|
||||
max_addr = std::max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||
}
|
||||
for (const auto& sec : b.sections) {
|
||||
min_addr = min<uint32_t>(min_addr, sec.address);
|
||||
max_addr = max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||
min_addr = std::min<uint32_t>(min_addr, sec.address);
|
||||
max_addr = std::max<uint32_t>(max_addr, sec.address + sec.data.size());
|
||||
}
|
||||
|
||||
vector<DiffEntry> ret;
|
||||
std::vector<DiffEntry> ret;
|
||||
for (uint32_t addr = min_addr; addr < max_addr; addr += 4) {
|
||||
bool a_exists = a_mem->exists(addr, 4);
|
||||
bool b_exists = b_mem->exists(addr, 4);
|
||||
if (a_exists && b_exists) {
|
||||
string a_value = a_mem->read(addr, 4);
|
||||
string b_value = b_mem->read(addr, 4);
|
||||
std::string a_value = a_mem->read(addr, 4);
|
||||
std::string b_value = b_mem->read(addr, 4);
|
||||
if (a_value != b_value) {
|
||||
if (!ret.empty() && (ret.back().address + ret.back().b_data.size() == addr)) {
|
||||
ret.back().a_data += a_value;
|
||||
@@ -975,26 +973,26 @@ vector<DiffEntry> diff_dol_files(const string& a_filename, const string& b_filen
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<DiffEntry> diff_xbe_files(const string& a_filename, const string& b_filename) {
|
||||
std::vector<DiffEntry> diff_xbe_files(const std::string& a_filename, const std::string& b_filename) {
|
||||
ResourceDASM::XBEFile a(a_filename.c_str());
|
||||
ResourceDASM::XBEFile b(b_filename.c_str());
|
||||
auto a_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto b_mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
auto a_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
auto b_mem = std::make_shared<ResourceDASM::MemoryContext>();
|
||||
a.load_into(a_mem);
|
||||
b.load_into(b_mem);
|
||||
|
||||
uint32_t min_addr = 0xFFFFFFFF;
|
||||
uint32_t max_addr = 0x00000000;
|
||||
for (const auto& sec : a.sections) {
|
||||
min_addr = min<uint32_t>(min_addr, sec.addr);
|
||||
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||
min_addr = std::min<uint32_t>(min_addr, sec.addr);
|
||||
max_addr = std::max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||
}
|
||||
for (const auto& sec : b.sections) {
|
||||
min_addr = min<uint32_t>(min_addr, sec.addr);
|
||||
max_addr = max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||
min_addr = std::min<uint32_t>(min_addr, sec.addr);
|
||||
max_addr = std::max<uint32_t>(max_addr, sec.addr + sec.size);
|
||||
}
|
||||
|
||||
vector<DiffEntry> ret;
|
||||
std::vector<DiffEntry> ret;
|
||||
for (uint32_t addr = min_addr; addr < max_addr; addr++) {
|
||||
bool a_exists = a_mem->exists(addr, 1);
|
||||
bool b_exists = b_mem->exists(addr, 1);
|
||||
|
||||
+19
-21
@@ -14,9 +14,7 @@
|
||||
#include "Revision.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const unordered_map<int, const char*> explanation_for_response_code{
|
||||
static const std::unordered_map<int, const char*> explanation_for_response_code{
|
||||
{100, "Continue"},
|
||||
{101, "Switching Protocols"},
|
||||
{102, "Processing"},
|
||||
@@ -92,7 +90,7 @@ const std::string* HTTPRequest::get_header(const std::string& name) const {
|
||||
if (its.first == its.second) {
|
||||
return nullptr;
|
||||
}
|
||||
const string* ret = &its.first->second;
|
||||
const std::string* ret = &its.first->second;
|
||||
its.first++;
|
||||
if (its.first != its.second) {
|
||||
throw std::out_of_range("Header appears multiple times: " + name);
|
||||
@@ -105,7 +103,7 @@ const std::string* HTTPRequest::get_query_param(const std::string& name) const {
|
||||
if (its.first == its.second) {
|
||||
return nullptr;
|
||||
}
|
||||
const string* ret = &its.first->second;
|
||||
const std::string* ret = &its.first->second;
|
||||
its.first++;
|
||||
if (its.first != its.second) {
|
||||
throw std::out_of_range("Query parameter appears multiple times: " + name);
|
||||
@@ -113,7 +111,7 @@ const std::string* HTTPRequest::get_query_param(const std::string& name) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void url_decode_inplace(string& s) {
|
||||
static void url_decode_inplace(std::string& s) {
|
||||
size_t write_offset = 0, read_offset = 0;
|
||||
for (; read_offset < s.size(); write_offset++) {
|
||||
if ((s[read_offset] == '%') && (read_offset < s.size() - 2)) {
|
||||
@@ -139,7 +137,7 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
||||
std::string request_line = co_await this->r.read_line("\r\n", max_line_size);
|
||||
auto line_tokens = phosg::split(request_line, ' ');
|
||||
if (line_tokens.size() != 3) {
|
||||
throw runtime_error("invalid HTTP request line");
|
||||
throw std::runtime_error("invalid HTTP request line");
|
||||
}
|
||||
const auto& method_token = line_tokens[0];
|
||||
if (method_token == "GET") {
|
||||
@@ -169,14 +167,14 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
||||
req.http_version = std::move(line_tokens[2]);
|
||||
|
||||
size_t fragment_start_offset = line_tokens[1].find('#');
|
||||
if (fragment_start_offset != string::npos) {
|
||||
if (fragment_start_offset != std::string::npos) {
|
||||
req.fragment = line_tokens[1].substr(fragment_start_offset + 1);
|
||||
line_tokens[1].resize(fragment_start_offset);
|
||||
}
|
||||
|
||||
size_t query_start_offset = line_tokens[1].find('?');
|
||||
string query;
|
||||
if (query_start_offset != string::npos) {
|
||||
std::string query;
|
||||
if (query_start_offset != std::string::npos) {
|
||||
query = line_tokens[1].substr(query_start_offset + 1);
|
||||
line_tokens[1].resize(query_start_offset);
|
||||
}
|
||||
@@ -189,12 +187,12 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
||||
auto query_tokens = phosg::split(query, '&');
|
||||
for (auto& token : query_tokens) {
|
||||
size_t equals_pos = token.find('=');
|
||||
if (equals_pos == string::npos) {
|
||||
if (equals_pos == std::string::npos) {
|
||||
url_decode_inplace(token);
|
||||
req.query_params.emplace(std::move(token), "");
|
||||
} else {
|
||||
string key = token.substr(0, equals_pos);
|
||||
string value = token.substr(equals_pos + 1);
|
||||
std::string key = token.substr(0, equals_pos);
|
||||
std::string value = token.substr(equals_pos + 1);
|
||||
url_decode_inplace(key);
|
||||
url_decode_inplace(value);
|
||||
req.query_params.emplace(std::move(key), std::move(value));
|
||||
@@ -217,11 +215,11 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
||||
}
|
||||
} else {
|
||||
size_t colon_pos = line.find(':');
|
||||
if (colon_pos == string::npos) {
|
||||
throw runtime_error("malformed header line");
|
||||
if (colon_pos == std::string::npos) {
|
||||
throw std::runtime_error("malformed header line");
|
||||
}
|
||||
string key = line.substr(0, colon_pos);
|
||||
string value = line.substr(colon_pos + 1);
|
||||
std::string key = line.substr(0, colon_pos);
|
||||
std::string value = line.substr(colon_pos + 1);
|
||||
phosg::strip_whitespace(key);
|
||||
phosg::strip_whitespace(value);
|
||||
prev_header_it = req.headers.emplace(phosg::tolower(key), std::move(value));
|
||||
@@ -230,7 +228,7 @@ asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size,
|
||||
|
||||
auto transfer_encoding_header = req.get_header("transfer-encoding");
|
||||
if (transfer_encoding_header && phosg::tolower(*transfer_encoding_header) == "chunked") {
|
||||
deque<string> chunks;
|
||||
std::deque<std::string> chunks;
|
||||
size_t total_data_bytes = 0;
|
||||
for (;;) {
|
||||
auto line = co_await this->r.read_line("\r\n", 0x20);
|
||||
@@ -306,7 +304,7 @@ asio::awaitable<WebSocketMessage> HTTPClient::recv_websocket_message(size_t max_
|
||||
}
|
||||
|
||||
if (payload_size > max_data_size) {
|
||||
throw runtime_error("Incoming WebSocket message exceeds size limit");
|
||||
throw std::runtime_error("Incoming WebSocket message exceeds size limit");
|
||||
}
|
||||
|
||||
// Read the masking key if present
|
||||
@@ -380,7 +378,7 @@ asio::awaitable<WebSocketMessage> HTTPClient::recv_websocket_message(size_t max_
|
||||
}
|
||||
}
|
||||
|
||||
throw logic_error("failed to receive websocket message");
|
||||
throw std::logic_error("failed to receive websocket message");
|
||||
}
|
||||
|
||||
asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_t size, uint8_t opcode) {
|
||||
@@ -396,7 +394,7 @@ asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_
|
||||
w.put_u8(size);
|
||||
}
|
||||
|
||||
array<asio::const_buffer, 2> bufs = {asio::const_buffer(w.data(), w.size()), asio::const_buffer(data, size)};
|
||||
std::array<asio::const_buffer, 2> bufs = {asio::const_buffer(w.data(), w.size()), asio::const_buffer(data, size)};
|
||||
co_await asio::async_write(this->r.get_socket(), bufs, asio::use_awaitable);
|
||||
}
|
||||
|
||||
|
||||
+19
-21
@@ -7,15 +7,12 @@
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
AsyncEvent::AsyncEvent(asio::any_io_executor ex)
|
||||
: executor(ex), is_set(false) {}
|
||||
AsyncEvent::AsyncEvent(asio::any_io_executor ex) : executor(ex), is_set(false) {}
|
||||
|
||||
void AsyncEvent::set() {
|
||||
std::vector<std::unique_ptr<asio::detail::awaitable_handler<asio::any_io_executor>>> waiters_to_resume;
|
||||
{
|
||||
lock_guard g(this->lock);
|
||||
std::lock_guard g(this->lock);
|
||||
this->is_set = true;
|
||||
this->waiters.swap(waiters_to_resume);
|
||||
}
|
||||
@@ -28,7 +25,7 @@ void AsyncEvent::set() {
|
||||
}
|
||||
|
||||
void AsyncEvent::clear() {
|
||||
lock_guard g(this->lock);
|
||||
std::lock_guard g(this->lock);
|
||||
this->is_set = false;
|
||||
}
|
||||
|
||||
@@ -36,11 +33,12 @@ asio::awaitable<void> AsyncEvent::wait() {
|
||||
auto token = asio::use_awaitable_t<>{};
|
||||
co_await asio::async_initiate<asio::use_awaitable_t<>, void()>(
|
||||
[this](auto&& handler) -> void {
|
||||
lock_guard g(this->lock);
|
||||
std::lock_guard g(this->lock);
|
||||
if (this->is_set) {
|
||||
handler();
|
||||
} else {
|
||||
this->waiters.emplace_back(make_unique<asio::detail::awaitable_handler<asio::any_io_executor>>(std::move(handler)));
|
||||
this->waiters.emplace_back(
|
||||
std::make_unique<asio::detail::awaitable_handler<asio::any_io_executor>>(std::move(handler)));
|
||||
}
|
||||
},
|
||||
token);
|
||||
@@ -49,17 +47,17 @@ asio::awaitable<void> AsyncEvent::wait() {
|
||||
AsyncSocketReader::AsyncSocketReader(asio::ip::tcp::socket&& sock)
|
||||
: sock(std::move(sock)) {}
|
||||
|
||||
asio::awaitable<string> AsyncSocketReader::read_line(const char* delimiter, size_t max_length) {
|
||||
asio::awaitable<std::string> AsyncSocketReader::read_line(const char* delimiter, size_t max_length) {
|
||||
size_t delimiter_size = strlen(delimiter);
|
||||
if (delimiter_size == 0) {
|
||||
throw logic_error("delimiter is empty");
|
||||
throw std::logic_error("delimiter is empty");
|
||||
}
|
||||
size_t delimiter_backup_bytes = delimiter_size - 1;
|
||||
|
||||
size_t delimiter_pos = this->pending_data.find(delimiter);
|
||||
while ((delimiter_pos == string::npos) && (!max_length || (this->pending_data.size() < max_length))) {
|
||||
while ((delimiter_pos == std::string::npos) && (!max_length || (this->pending_data.size() < max_length))) {
|
||||
size_t pre_size = this->pending_data.size();
|
||||
this->pending_data.resize(min(max_length, this->pending_data.size() + 0x400));
|
||||
this->pending_data.resize(std::min(max_length, this->pending_data.size() + 0x400));
|
||||
|
||||
auto buf = asio::buffer(this->pending_data.data() + pre_size, this->pending_data.size() - pre_size);
|
||||
size_t bytes_read = co_await this->sock.async_read_some(buf, asio::use_awaitable);
|
||||
@@ -69,18 +67,18 @@ asio::awaitable<string> AsyncSocketReader::read_line(const char* delimiter, size
|
||||
(delimiter_backup_bytes > pre_size) ? 0 : (pre_size - delimiter_backup_bytes));
|
||||
}
|
||||
|
||||
if (delimiter_pos == string::npos) {
|
||||
throw runtime_error("line exceeds max length");
|
||||
if (delimiter_pos == std::string::npos) {
|
||||
throw std::runtime_error("line exceeds max length");
|
||||
}
|
||||
|
||||
// TODO: It's not great that we copy the data here. There's probably a more idiomatic and efficient way to do this.
|
||||
string ret = this->pending_data.substr(0, delimiter_pos);
|
||||
std::string ret = this->pending_data.substr(0, delimiter_pos);
|
||||
this->pending_data = this->pending_data.substr(delimiter_pos + delimiter_size);
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
asio::awaitable<string> AsyncSocketReader::read_data(size_t size) {
|
||||
string ret;
|
||||
asio::awaitable<std::string> AsyncSocketReader::read_data(size_t size) {
|
||||
std::string ret;
|
||||
if (this->pending_data.size() == size) {
|
||||
this->pending_data.swap(ret);
|
||||
} else if (this->pending_data.size() > size) {
|
||||
@@ -111,7 +109,7 @@ asio::awaitable<void> AsyncSocketReader::read_data_into(void* data, size_t size)
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWriteCollector::add(string&& data) {
|
||||
void AsyncWriteCollector::add(std::string&& data) {
|
||||
const auto& item = this->owned_data.emplace_back(std::move(data));
|
||||
bufs.emplace_back(asio::buffer(item.data(), item.size()));
|
||||
}
|
||||
@@ -121,14 +119,14 @@ void AsyncWriteCollector::add_reference(const void* data, size_t size) {
|
||||
}
|
||||
|
||||
asio::awaitable<void> AsyncWriteCollector::write(asio::ip::tcp::socket& sock) {
|
||||
deque<string> local_owned_data;
|
||||
std::deque<std::string> local_owned_data;
|
||||
local_owned_data.swap(this->owned_data);
|
||||
vector<asio::const_buffer> local_bufs;
|
||||
std::vector<asio::const_buffer> local_bufs;
|
||||
local_bufs.swap(this->bufs);
|
||||
co_await asio::async_write(sock, local_bufs, asio::use_awaitable);
|
||||
}
|
||||
|
||||
asio::awaitable<void> async_sleep(chrono::steady_clock::duration duration) {
|
||||
asio::awaitable<void> async_sleep(std::chrono::steady_clock::duration duration) {
|
||||
asio::steady_timer timer(co_await asio::this_coro::executor, duration);
|
||||
co_await timer.async_wait(asio::use_awaitable);
|
||||
}
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ asio::awaitable<std::invoke_result_t<FnT, ArgTs...>> call_on_thread_pool(asio::t
|
||||
using ReturnT = std::invoke_result_t<FnT, ArgTs...>;
|
||||
auto bound = std::bind(std::forward<FnT>(f), std::forward<ArgTs>(args)...);
|
||||
|
||||
// We have to use a shared_ptr here in case call_on_thread_pool is canceled (in that case, the posted callback will
|
||||
// We have to use a std::shared_ptr here in case call_on_thread_pool is canceled (in that case, the posted callback will
|
||||
// try to use promise after the call_on_thread_pool coroutine has been destroyed)
|
||||
auto promise = std::make_shared<AsyncPromise<ReturnT>>();
|
||||
asio::post(pool, [bound = std::move(bound), promise]() mutable {
|
||||
|
||||
+18
-21
@@ -7,8 +7,6 @@
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <bool BE>
|
||||
struct BMLHeaderT {
|
||||
parray<uint8_t, 0x04> unknown_a1;
|
||||
@@ -42,13 +40,13 @@ void BMLArchive::load_t() {
|
||||
const auto& entry = r.get<BMLHeaderEntryT<BE>>();
|
||||
|
||||
if (offset + entry.compressed_size > this->data->size()) {
|
||||
throw runtime_error("BML data entry extends beyond end of data");
|
||||
throw std::runtime_error("BML data entry extends beyond end of data");
|
||||
}
|
||||
size_t data_offset = offset;
|
||||
offset = (offset + entry.compressed_size + 0x1F) & (~0x1F);
|
||||
|
||||
if (offset + entry.compressed_gvm_size > this->data->size()) {
|
||||
throw runtime_error("BML GVM entry extends beyond end of data");
|
||||
throw std::runtime_error("BML GVM entry extends beyond end of data");
|
||||
}
|
||||
size_t gvm_offset = offset;
|
||||
offset = (offset + entry.compressed_gvm_size + 0x1F) & (~0x1F);
|
||||
@@ -57,8 +55,7 @@ void BMLArchive::load_t() {
|
||||
}
|
||||
}
|
||||
|
||||
BMLArchive::BMLArchive(shared_ptr<const string> data, bool big_endian)
|
||||
: data(data) {
|
||||
BMLArchive::BMLArchive(std::shared_ptr<const std::string> data, bool big_endian) : data(data) {
|
||||
if (big_endian) {
|
||||
this->load_t<true>();
|
||||
} else {
|
||||
@@ -66,42 +63,42 @@ BMLArchive::BMLArchive(shared_ptr<const string> data, bool big_endian)
|
||||
}
|
||||
}
|
||||
|
||||
const unordered_map<string, BMLArchive::Entry> BMLArchive::all_entries() const {
|
||||
const std::unordered_map<std::string, BMLArchive::Entry> BMLArchive::all_entries() const {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
pair<const void*, size_t> BMLArchive::get(const std::string& name) const {
|
||||
std::pair<const void*, size_t> BMLArchive::get(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
pair<const void*, size_t> BMLArchive::get_gvm(const std::string& name) const {
|
||||
std::pair<const void*, size_t> BMLArchive::get_gvm(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
return std::make_pair(this->data->data() + entry.gvm_offset, entry.gvm_size);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
string BMLArchive::get_copy(const string& name) const {
|
||||
std::string BMLArchive::get_copy(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return this->data->substr(entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
phosg::StringReader BMLArchive::get_reader(const string& name) const {
|
||||
phosg::StringReader BMLArchive::get_reader(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("BML does not contain file: " + name);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("BML does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-12
@@ -7,8 +7,6 @@
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
BattleParamsIndex::AttackData BattleParamsIndex::AttackData::from_json(const phosg::JSON& json) {
|
||||
return AttackData{
|
||||
json.get_int("MinATP"),
|
||||
@@ -202,7 +200,7 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const {
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
const auto& e = this->stats[static_cast<size_t>(difficulty)][z];
|
||||
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
||||
string names_str;
|
||||
std::string names_str;
|
||||
for (auto type : enemy_types_for_battle_param_stats_index(episode, z)) {
|
||||
if (!names_str.empty()) {
|
||||
names_str += ", ";
|
||||
@@ -290,17 +288,17 @@ const BattleParamsIndex::Table& JSONBattleParamsIndex::get_table(bool solo, Epis
|
||||
case Episode::EP4:
|
||||
return this->tables[!!solo][2];
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
throw std::invalid_argument("invalid episode");
|
||||
}
|
||||
}
|
||||
|
||||
BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
||||
shared_ptr<const string> data_on_ep1,
|
||||
shared_ptr<const string> data_on_ep2,
|
||||
shared_ptr<const string> data_on_ep4,
|
||||
shared_ptr<const string> data_off_ep1,
|
||||
shared_ptr<const string> data_off_ep2,
|
||||
shared_ptr<const string> data_off_ep4) {
|
||||
std::shared_ptr<const std::string> data_on_ep1,
|
||||
std::shared_ptr<const std::string> data_on_ep2,
|
||||
std::shared_ptr<const std::string> data_on_ep4,
|
||||
std::shared_ptr<const std::string> data_off_ep1,
|
||||
std::shared_ptr<const std::string> data_off_ep2,
|
||||
std::shared_ptr<const std::string> data_off_ep4) {
|
||||
this->files[0][0].data = data_on_ep1;
|
||||
this->files[0][1].data = data_on_ep2;
|
||||
this->files[0][2].data = data_on_ep4;
|
||||
@@ -312,7 +310,7 @@ BinaryBattleParamsIndex::BinaryBattleParamsIndex(
|
||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||
auto& file = this->files[is_solo][episode];
|
||||
if (file.data->size() < sizeof(Table)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"battle params table size is incorrect (expected {:X} bytes, have {:X} bytes; is_solo={}, episode={})",
|
||||
sizeof(Table), file.data->size(), is_solo, episode));
|
||||
}
|
||||
@@ -330,6 +328,6 @@ const BattleParamsIndex::Table& BinaryBattleParamsIndex::get_table(bool solo, Ep
|
||||
case Episode::EP4:
|
||||
return *this->files[!!solo][2].table;
|
||||
default:
|
||||
throw invalid_argument("invalid episode");
|
||||
throw std::invalid_argument("invalid episode");
|
||||
}
|
||||
}
|
||||
|
||||
+20
-22
@@ -12,14 +12,12 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
Channel::Channel(
|
||||
Version version,
|
||||
Language language,
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color,
|
||||
bool censor_received_credentials,
|
||||
@@ -49,7 +47,7 @@ void Channel::send(
|
||||
size += b.second;
|
||||
}
|
||||
|
||||
string send_data;
|
||||
std::string send_data;
|
||||
size_t logical_size;
|
||||
size_t send_data_size = 0;
|
||||
switch (this->version) {
|
||||
@@ -113,12 +111,12 @@ void Channel::send(
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in send_command");
|
||||
throw std::logic_error("unimplemented game version in send_command");
|
||||
}
|
||||
|
||||
// All versions of PSO I've seen (so far) have a receive buffer 0x7C00 bytes in size
|
||||
if (send_data_size > 0x7C00) {
|
||||
throw runtime_error("outbound command too large");
|
||||
throw std::runtime_error("outbound command too large");
|
||||
}
|
||||
|
||||
send_data.reserve(send_data_size);
|
||||
@@ -164,10 +162,10 @@ void Channel::send(
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
||||
this->send(cmd, flag, {make_pair(data, size)}, silent);
|
||||
this->send(cmd, flag, {std::make_pair(data, size)}, silent);
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const string& data, bool silent) {
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const std::string& data, bool silent) {
|
||||
this->send(cmd, flag, data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
@@ -182,7 +180,7 @@ void Channel::send(const void* data, size_t size, bool silent) {
|
||||
silent);
|
||||
}
|
||||
|
||||
void Channel::send(const string& data, bool silent) {
|
||||
void Channel::send(const std::string& data, bool silent) {
|
||||
this->send(data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
@@ -196,7 +194,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
||||
|
||||
size_t command_logical_size = header.size(version);
|
||||
if (command_logical_size < header_size) {
|
||||
throw runtime_error("header size field is smaller than header");
|
||||
throw std::runtime_error("header size field is smaller than header");
|
||||
}
|
||||
|
||||
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this is not reflected in the size field. This
|
||||
@@ -205,7 +203,7 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
||||
? ((command_logical_size + 7) & ~7)
|
||||
: command_logical_size;
|
||||
|
||||
string command_data(command_physical_size - header_size, '\0');
|
||||
std::string command_data(command_physical_size - header_size, '\0');
|
||||
co_await this->recv_raw(command_data.data(), command_data.size());
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
@@ -267,17 +265,17 @@ asio::awaitable<Channel::Message> Channel::recv() {
|
||||
};
|
||||
}
|
||||
|
||||
shared_ptr<SocketChannel> SocketChannel::create(
|
||||
std::shared_ptr<SocketChannel> SocketChannel::create(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
Language language,
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color,
|
||||
bool censor_received_credentials,
|
||||
bool censor_sent_credentials) {
|
||||
shared_ptr<SocketChannel> ret(new SocketChannel(
|
||||
std::shared_ptr<SocketChannel> ret(new SocketChannel(
|
||||
io_context,
|
||||
std::move(sock),
|
||||
version,
|
||||
@@ -296,7 +294,7 @@ SocketChannel::SocketChannel(
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
Language language,
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color,
|
||||
bool censor_received_credentials,
|
||||
@@ -320,7 +318,7 @@ void SocketChannel::disconnect() {
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
|
||||
void SocketChannel::send_raw(string&& data) {
|
||||
void SocketChannel::send_raw(std::string&& data) {
|
||||
if (this->sock && !this->should_disconnect) {
|
||||
this->outbound_data.emplace_back(std::move(data));
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
@@ -329,7 +327,7 @@ void SocketChannel::send_raw(string&& data) {
|
||||
|
||||
asio::awaitable<void> SocketChannel::recv_raw(void* data, size_t size) {
|
||||
if (!this->sock || this->should_disconnect) {
|
||||
throw runtime_error("Cannot receive on closed channel");
|
||||
throw std::runtime_error("Cannot receive on closed channel");
|
||||
}
|
||||
co_await asio::async_read(*this->sock, asio::buffer(data, size), asio::use_awaitable);
|
||||
}
|
||||
@@ -339,11 +337,11 @@ asio::awaitable<void> SocketChannel::send_task() {
|
||||
auto this_sh = this->shared_from_this();
|
||||
|
||||
while (this->sock->is_open()) {
|
||||
deque<string> to_send;
|
||||
std::deque<std::string> to_send;
|
||||
to_send.swap(this->outbound_data);
|
||||
|
||||
if (!to_send.empty()) {
|
||||
vector<asio::const_buffer> bufs;
|
||||
std::vector<asio::const_buffer> bufs;
|
||||
bufs.reserve(to_send.size());
|
||||
for (const auto& it : to_send) {
|
||||
bufs.emplace_back(asio::buffer(it.data(), it.size()));
|
||||
@@ -376,7 +374,7 @@ PeerChannel::PeerChannel(
|
||||
|
||||
void PeerChannel::link_peers(std::shared_ptr<PeerChannel> peer1, std::shared_ptr<PeerChannel> peer2) {
|
||||
if (peer1->connected() || peer2->connected()) {
|
||||
throw logic_error("Cannot link already-connected peer channels");
|
||||
throw std::logic_error("Cannot link already-connected peer channels");
|
||||
}
|
||||
peer1->peer = peer2;
|
||||
peer2->peer = peer1;
|
||||
@@ -400,7 +398,7 @@ void PeerChannel::disconnect() {
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
|
||||
void PeerChannel::send_raw(string&& data) {
|
||||
void PeerChannel::send_raw(std::string&& data) {
|
||||
auto peer = this->peer.lock();
|
||||
if (peer) {
|
||||
peer->inbound_data.emplace_back(std::move(data));
|
||||
@@ -428,7 +426,7 @@ asio::awaitable<void> PeerChannel::recv_raw(void* data, size_t size) {
|
||||
this->inbound_data.pop_front();
|
||||
}
|
||||
} else if (!this->peer.lock()) {
|
||||
throw runtime_error("Channel peer has disconnected");
|
||||
throw std::runtime_error("Channel peer has disconnected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+147
-153
@@ -23,13 +23,11 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Tools
|
||||
|
||||
string str_for_flag_ranges(const vector<bool>& flags) {
|
||||
string ret;
|
||||
std::string str_for_flag_ranges(const std::vector<bool>& flags) {
|
||||
std::string ret;
|
||||
auto add_result = [&](size_t start, size_t end) {
|
||||
if (!ret.empty()) {
|
||||
ret.push_back(',');
|
||||
@@ -174,19 +172,19 @@ struct ChatCommandDefinition {
|
||||
std::vector<const char*> names;
|
||||
Handler handler;
|
||||
|
||||
static unordered_map<string, const ChatCommandDefinition*> all_defs;
|
||||
static std::unordered_map<std::string, const ChatCommandDefinition*> all_defs;
|
||||
|
||||
ChatCommandDefinition(std::initializer_list<const char*> names, Handler handler)
|
||||
: names(names), handler(handler) {
|
||||
for (const char* name : this->names) {
|
||||
if (!this->all_defs.emplace(name, this).second) {
|
||||
throw logic_error("duplicate command definition: " + string(name));
|
||||
throw std::logic_error("duplicate command definition: " + std::string(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unordered_map<string, const ChatCommandDefinition*> ChatCommandDefinition::all_defs;
|
||||
std::unordered_map<std::string, const ChatCommandDefinition*> ChatCommandDefinition::all_defs;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// All commands (in alphabetical order)
|
||||
@@ -332,7 +330,7 @@ ChatCommandDefinition cc_auction(
|
||||
co_return;
|
||||
});
|
||||
|
||||
static string name_for_client(shared_ptr<Client> c) {
|
||||
static std::string name_for_client(std::shared_ptr<Client> c) {
|
||||
auto player = c->character_file(false);
|
||||
if (player.get()) {
|
||||
return escape_player_name(player->disp.name.decode(player->inventory.language));
|
||||
@@ -355,11 +353,11 @@ ChatCommandDefinition cc_ban(
|
||||
auto l = a.c->require_lobby();
|
||||
|
||||
size_t space_pos = a.text.find(' ');
|
||||
if (space_pos == string::npos) {
|
||||
if (space_pos == std::string::npos) {
|
||||
throw precondition_failed("$C6Incorrect arguments");
|
||||
}
|
||||
|
||||
string identifier = a.text.substr(space_pos + 1);
|
||||
std::string identifier = a.text.substr(space_pos + 1);
|
||||
auto target = s->find_client(&identifier);
|
||||
if (!target->login) {
|
||||
// This should be impossible, but I'll bet it's not actually
|
||||
@@ -372,7 +370,7 @@ ChatCommandDefinition cc_ban(
|
||||
if (a.c == target) {
|
||||
// This shouldn't be possible because you need BAN_USER to get here, but the target can't have BAN_USER if we
|
||||
// get here, so if a.c and target are the same, one of the preceding conditions must be false.
|
||||
throw logic_error("client attempts to ban themself");
|
||||
throw std::logic_error("client attempts to ban themself");
|
||||
}
|
||||
|
||||
uint64_t usecs = stoull(a.text, nullptr, 0) * 1000000;
|
||||
@@ -397,9 +395,8 @@ ChatCommandDefinition cc_ban(
|
||||
target->login->account->ban_end_time = phosg::now() + usecs;
|
||||
target->login->account->save();
|
||||
send_message_box(target, "$C6You have been banned.");
|
||||
string target_name = name_for_client(target);
|
||||
send_text_message_fmt(a.c, "$C6{} banned", name_for_client(target));
|
||||
target->channel->disconnect();
|
||||
send_text_message_fmt(a.c, "$C6{} banned", target_name);
|
||||
co_return;
|
||||
});
|
||||
|
||||
@@ -409,10 +406,10 @@ ChatCommandDefinition cc_bank(
|
||||
a.check_is_proxy(false);
|
||||
a.check_version(Version::BB_V4);
|
||||
if (a.c->check_flag(Client::Flag::AT_BANK_COUNTER)) {
|
||||
throw runtime_error("cannot change banks while at the bank counter");
|
||||
throw std::runtime_error("cannot change banks while at the bank counter");
|
||||
}
|
||||
if (a.c->has_overlay()) {
|
||||
throw runtime_error("cannot change banks while Battle or Challenge is in progress");
|
||||
throw std::runtime_error("cannot change banks while Battle or Challenge is in progress");
|
||||
}
|
||||
|
||||
ssize_t new_char_index = a.text.empty() ? (a.c->bb_character_index + 1) : stol(a.text, nullptr, 0);
|
||||
@@ -450,17 +447,17 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
throw precondition_failed("$C6Episode 3 players\ncannot be converted\nto BB format");
|
||||
}
|
||||
|
||||
shared_ptr<Account> dest_account;
|
||||
shared_ptr<BBLicense> dest_bb_license;
|
||||
std::shared_ptr<Account> dest_account;
|
||||
std::shared_ptr<BBLicense> dest_bb_license;
|
||||
size_t dest_character_index = 0;
|
||||
if (is_bb_conversion) {
|
||||
vector<string> tokens = phosg::split(a.text, ' ');
|
||||
std::vector<std::string> tokens = phosg::split(a.text, ' ');
|
||||
if (tokens.size() != 3) {
|
||||
throw precondition_failed("$C6Incorrect argument\ncount");
|
||||
}
|
||||
|
||||
// username/password are tokens[0] and [1]
|
||||
dest_character_index = stoull(tokens[2]) - 1;
|
||||
dest_character_index = std::stoull(tokens[2]) - 1;
|
||||
if (dest_character_index >= 127) {
|
||||
throw precondition_failed("$C6Player index must\nbe in range 1-127");
|
||||
}
|
||||
@@ -469,7 +466,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
auto dest_login = s->account_index->from_bb_credentials(tokens[0], &tokens[1], false);
|
||||
dest_account = dest_login->account;
|
||||
dest_bb_license = dest_login->bb_license;
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
throw precondition_failed("$C6Login failed: {}", e.what());
|
||||
}
|
||||
|
||||
@@ -490,7 +487,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
ch = co_await send_get_player_info(a.c, true);
|
||||
}
|
||||
|
||||
string filename = dest_bb_license
|
||||
std::string filename = dest_bb_license
|
||||
? Client::character_filename(dest_bb_license->username, dest_character_index)
|
||||
: Client::backup_character_filename(dest_account->account_id, dest_character_index, is_ep3(a.c->version()));
|
||||
|
||||
@@ -500,7 +497,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
try {
|
||||
Client::save_ep3_character_file(filename, *ch.ep3_character);
|
||||
send_text_message(a.c, "$C7Character data saved\n(full save file)");
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
send_text_message_fmt(a.c, "$C6Character data could\nnot be saved:\n{}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -508,7 +505,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
try {
|
||||
Client::save_character_file(filename, a.c->system_file(), ch.character);
|
||||
send_text_message(a.c, "$C7Character data saved\n(full save file)");
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
send_text_message_fmt(a.c, "$C6Character data could\nnot be saved:\n{}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -553,7 +550,7 @@ static asio::awaitable<void> server_command_bbchar_savechar(const Args& a, bool
|
||||
try {
|
||||
Client::save_character_file(filename, a.c->system_file(), bb_player);
|
||||
send_text_message(a.c, "$C7Character data saved\n(basic only)");
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
send_text_message_fmt(a.c, "$C6Character data could\nnot be saved:\n{}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -608,16 +605,16 @@ ChatCommandDefinition cc_checkchar(
|
||||
if (a.text.empty()) {
|
||||
bool is_ep3 = ::is_ep3(a.c->version());
|
||||
|
||||
vector<bool> flags;
|
||||
std::vector<bool> flags;
|
||||
flags.emplace_back(false);
|
||||
for (size_t z = 0; z < s->num_backup_character_slots; z++) {
|
||||
string filename = a.c->backup_character_filename(a.c->login->account->account_id, z, is_ep3);
|
||||
std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, z, is_ep3);
|
||||
flags.emplace_back(std::filesystem::is_regular_file(filename));
|
||||
}
|
||||
string used_str = str_for_flag_ranges(flags);
|
||||
std::string used_str = str_for_flag_ranges(flags);
|
||||
flags.flip();
|
||||
flags[0] = false;
|
||||
string free_str = str_for_flag_ranges(flags);
|
||||
std::string free_str = str_for_flag_ranges(flags);
|
||||
send_text_message_fmt(a.c, "Used: {}\nFree: {}", used_str, free_str);
|
||||
|
||||
} else {
|
||||
@@ -628,7 +625,7 @@ ChatCommandDefinition cc_checkchar(
|
||||
|
||||
try {
|
||||
if (is_ep3(a.c->version())) {
|
||||
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, true);
|
||||
std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, true);
|
||||
auto ch = phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename);
|
||||
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nCLv: on {}.{}, off {}.{}",
|
||||
index + 1, ch.disp.visual.name.decode(),
|
||||
@@ -636,7 +633,7 @@ ChatCommandDefinition cc_checkchar(
|
||||
(ch.ep3_config.online_clv_exp / 100) + 1, ch.ep3_config.online_clv_exp % 100,
|
||||
(ch.ep3_config.offline_clv_exp / 100) + 1, ch.ep3_config.offline_clv_exp % 100);
|
||||
} else {
|
||||
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false);
|
||||
std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, false);
|
||||
auto ch = PSOCHARFile::load_shared(filename, false).character_file;
|
||||
send_text_message_fmt(a.c, "Slot {}: $C6{}$C7\n{} {}\nLevel {}",
|
||||
index + 1, ch->disp.name.decode(),
|
||||
@@ -673,7 +670,7 @@ ChatCommandDefinition cc_deletechar(
|
||||
throw precondition_failed("$C6Player index must\nbe in range 1-16");
|
||||
}
|
||||
|
||||
string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, is_ep3(a.c->version()));
|
||||
std::string filename = a.c->backup_character_filename(a.c->login->account->account_id, index, is_ep3(a.c->version()));
|
||||
if (std::filesystem::is_regular_file(filename)) {
|
||||
std::filesystem::remove(filename);
|
||||
send_text_message_fmt(a.c, "Character in slot\n{} deleted", index + 1);
|
||||
@@ -694,7 +691,7 @@ ChatCommandDefinition cc_dicerange(
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -706,7 +703,7 @@ ChatCommandDefinition cc_dicerange(
|
||||
throw precondition_failed("$C6Cannot override\ndice ranges in a\ntournament");
|
||||
}
|
||||
|
||||
auto parse_dice_range = +[](const string& spec) -> uint8_t {
|
||||
auto parse_dice_range = +[](const std::string& spec) -> uint8_t {
|
||||
auto tokens = phosg::split(spec, '-');
|
||||
if (tokens.size() == 1) {
|
||||
uint8_t v = stoull(spec);
|
||||
@@ -714,7 +711,7 @@ ChatCommandDefinition cc_dicerange(
|
||||
} else if (tokens.size() == 2) {
|
||||
return (stoull(tokens[0]) << 4) | (stoull(tokens[1]) & 0x0F);
|
||||
} else {
|
||||
throw runtime_error("invalid dice spec format");
|
||||
throw std::runtime_error("invalid dice spec format");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -891,33 +888,33 @@ ChatCommandDefinition cc_edit(
|
||||
(s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) ||
|
||||
a.c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE));
|
||||
|
||||
string encoded_args = phosg::tolower(a.text);
|
||||
vector<string> tokens = phosg::split(encoded_args, ' ');
|
||||
std::string encoded_args = phosg::tolower(a.text);
|
||||
std::vector<std::string> tokens = phosg::split(encoded_args, ' ');
|
||||
|
||||
using MatType = PSOBBCharacterFile::MaterialType;
|
||||
|
||||
try {
|
||||
auto p = a.c->character_file();
|
||||
if (tokens.at(0) == "atp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.atp = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.atp = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "mst" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.mst = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.mst = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "evp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.evp = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.evp = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "hp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.hp = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.hp = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "dfp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.dfp = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.dfp = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "ata" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.ata = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.ata = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "lck" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.char_stats.lck = stoul(tokens.at(1));
|
||||
p->disp.stats.char_stats.lck = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "meseta" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.meseta = stoul(tokens.at(1));
|
||||
p->disp.stats.meseta = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "exp" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.exp = stoul(tokens.at(1));
|
||||
p->disp.stats.exp = std::stoul(tokens.at(1));
|
||||
} else if (tokens.at(0) == "level" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
p->disp.stats.level = stoul(tokens.at(1)) - 1;
|
||||
p->disp.stats.level = std::stoul(tokens.at(1)) - 1;
|
||||
p->recompute_stats(s->level_table(a.c->version()), true);
|
||||
} else if (((tokens.at(0) == "material") || (tokens.at(0) == "mat")) && !is_v1_or_v2(a.c->version()) && (cheats_allowed || !s->cheat_flags.reset_materials)) {
|
||||
if (tokens.at(1) == "reset") {
|
||||
@@ -958,10 +955,10 @@ ChatCommandDefinition cc_edit(
|
||||
}
|
||||
p->recompute_stats(s->level_table(a.c->version()), false);
|
||||
} else if (tokens.at(0) == "namecolor") {
|
||||
p->disp.visual.name_color = stoul(tokens.at(1), nullptr, 16);
|
||||
p->disp.visual.name_color = std::stoul(tokens.at(1), nullptr, 16);
|
||||
} else if (tokens.at(0) == "language" || tokens.at(0) == "lang") {
|
||||
if (tokens.at(1).size() != 1) {
|
||||
throw runtime_error("invalid language");
|
||||
throw std::runtime_error("invalid language");
|
||||
}
|
||||
Language new_language = language_for_char(tokens.at(1).at(0));
|
||||
a.c->channel->language = new_language;
|
||||
@@ -982,7 +979,7 @@ ChatCommandDefinition cc_edit(
|
||||
p->disp.visual.section_id = secid;
|
||||
}
|
||||
} else if (tokens.at(0) == "name") {
|
||||
vector<string> orig_tokens = phosg::split(a.text, ' ', 1);
|
||||
std::vector<std::string> orig_tokens = phosg::split(a.text, ' ', 1);
|
||||
p->disp.name.encode(orig_tokens.at(1), p->inventory.language);
|
||||
} else if (tokens.at(0) == "npc") {
|
||||
if (tokens.at(1) == "none") {
|
||||
@@ -999,7 +996,7 @@ ChatCommandDefinition cc_edit(
|
||||
p->disp.visual.validation_flags |= 0x02;
|
||||
}
|
||||
} else if (tokens.at(0) == "tech" && (cheats_allowed || !s->cheat_flags.edit_stats)) {
|
||||
uint8_t level = stoul(tokens.at(2)) - 1;
|
||||
uint8_t level = std::stoul(tokens.at(2)) - 1;
|
||||
if (tokens.at(1) == "all") {
|
||||
for (size_t x = 0; x < 0x14; x++) {
|
||||
p->set_technique_level(x, level);
|
||||
@@ -1011,14 +1008,14 @@ ChatCommandDefinition cc_edit(
|
||||
}
|
||||
try {
|
||||
p->set_technique_level(tech_id, level);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("$C6Invalid technique");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw precondition_failed("$C6Unknown field");
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("$C6Not enough arguments");
|
||||
}
|
||||
|
||||
@@ -1111,10 +1108,10 @@ ChatCommandDefinition cc_exit(
|
||||
a.c->check_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE)) {
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
auto s = a.c->require_server_state();
|
||||
shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
try {
|
||||
fn = s->client_functions->get("ExitAnywhere", a.c->specific_version);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (fn) {
|
||||
co_await send_function_call(a.c, fn);
|
||||
@@ -1182,7 +1179,7 @@ ChatCommandDefinition cc_inftime(
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -1258,7 +1255,7 @@ ChatCommandDefinition cc_item(
|
||||
}
|
||||
}
|
||||
|
||||
string name = s->describe_item(a.c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||
std::string name = s->describe_item(a.c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||
if (was_enqueued) {
|
||||
send_text_message(a.c, "$C7Next item:\n" + name);
|
||||
} else {
|
||||
@@ -1307,13 +1304,12 @@ ChatCommandDefinition cc_kick(
|
||||
if (a.c == target) {
|
||||
// This shouldn't be possible because you need KICK_USER to get here, but the target can't have KICK_USER if we
|
||||
// get here, so if a.c and target are the same, one of the preceding conditions must be false.
|
||||
throw logic_error("client attempts to kick themself off");
|
||||
throw std::logic_error("client attempts to kick themself off");
|
||||
}
|
||||
|
||||
send_message_box(target, "$C6You have been kicked off the server.");
|
||||
string target_name = name_for_client(target);
|
||||
send_text_message_fmt(a.c, "$C6{} kicked off", name_for_client(target));
|
||||
target->channel->disconnect();
|
||||
send_text_message_fmt(a.c, "$C6{} kicked off", target_name);
|
||||
co_return;
|
||||
});
|
||||
|
||||
@@ -1323,7 +1319,7 @@ ChatCommandDefinition cc_killcount(
|
||||
a.check_is_proxy(false);
|
||||
|
||||
auto p = a.c->character_file();
|
||||
vector<size_t> item_indexes;
|
||||
std::vector<size_t> item_indexes;
|
||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
||||
const auto& item = p->inventory.items[z];
|
||||
if (item.is_equipped() && item.data.has_kill_count()) {
|
||||
@@ -1348,7 +1344,7 @@ ChatCommandDefinition cc_killcount(
|
||||
auto s = a.c->require_server_state();
|
||||
for (size_t z : item_indexes) {
|
||||
const auto& item = p->inventory.items[z];
|
||||
string name = s->describe_item(
|
||||
std::string name = s->describe_item(
|
||||
a.c->version(), item.data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES | ItemNameIndex::Flag::NAME_ONLY);
|
||||
send_text_message_fmt(a.c, "{}$C7: {} kills", name, item.data.get_kill_count());
|
||||
}
|
||||
@@ -1360,7 +1356,7 @@ ChatCommandDefinition cc_lobby_info(
|
||||
{"$li"},
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
if (a.c->proxy_session) {
|
||||
string msg;
|
||||
std::string msg;
|
||||
// On non-masked-GC sessions (BB), there is no remote Guild Card number, so we don't show it. (The user can see
|
||||
// it in the pause menu, unlike in masked-GC sessions like GC.)
|
||||
if (a.c->proxy_session->remote_guild_card_number >= 0) {
|
||||
@@ -1384,7 +1380,7 @@ ChatCommandDefinition cc_lobby_info(
|
||||
}
|
||||
}
|
||||
|
||||
vector<const char*> cheats_tokens;
|
||||
std::vector<const char*> cheats_tokens;
|
||||
if (a.c->check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
|
||||
cheats_tokens.emplace_back("HP");
|
||||
}
|
||||
@@ -1396,7 +1392,7 @@ ChatCommandDefinition cc_lobby_info(
|
||||
msg += phosg::join(cheats_tokens, ",");
|
||||
}
|
||||
|
||||
vector<const char*> behaviors_tokens;
|
||||
std::vector<const char*> behaviors_tokens;
|
||||
if (a.c->check_flag(Client::Flag::SWITCH_ASSIST_ENABLED)) {
|
||||
behaviors_tokens.emplace_back("SWA");
|
||||
}
|
||||
@@ -1419,7 +1415,7 @@ ChatCommandDefinition cc_lobby_info(
|
||||
send_text_message(a.c->channel, msg);
|
||||
|
||||
} else { // Not proxy session
|
||||
vector<string> lines;
|
||||
std::vector<std::string> lines;
|
||||
|
||||
auto l = a.c->lobby.lock();
|
||||
if (!l) {
|
||||
@@ -1476,7 +1472,7 @@ ChatCommandDefinition cc_lobby_info(
|
||||
lines.emplace_back(std::format("$C7Lobby ID: $C6{:08X}$C7", l->lobby_id));
|
||||
}
|
||||
|
||||
string slots_str = "Slots: ";
|
||||
std::string slots_str = "Slots: ";
|
||||
for (size_t z = 0; z < l->clients.size(); z++) {
|
||||
if (!l->clients[z]) {
|
||||
slots_str += std::format("$C0{:X}$C7", z);
|
||||
@@ -1542,7 +1538,7 @@ ChatCommandDefinition cc_loadchar(
|
||||
throw precondition_failed("$C6Player index must\nbe in range 1-{}", s->num_backup_character_slots);
|
||||
}
|
||||
|
||||
shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
|
||||
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_char;
|
||||
if (is_ep3(a.c->version())) {
|
||||
ep3_char = a.c->load_ep3_backup_character(a.c->login->account->account_id, index);
|
||||
} else {
|
||||
@@ -1578,7 +1574,7 @@ ChatCommandDefinition cc_loadchar(
|
||||
send_player_leave_notification(l, a.c->lobby_client_id);
|
||||
s->send_lobby_join_notifications(l, a.c);
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
a.c->log.warning_f("Failed to set extended player info: {}", e.what());
|
||||
throw precondition_failed("Failed to set\nplayer info:\n{}", e.what());
|
||||
}
|
||||
@@ -1600,14 +1596,14 @@ ChatCommandDefinition cc_loadchar(
|
||||
co_await send_set_extended_player_info(*ep3_char);
|
||||
} else if (a.c->version() == Version::XB_V3) {
|
||||
if (!a.c->login || !a.c->login->xb_license) {
|
||||
throw runtime_error("XB client is not logged in");
|
||||
throw std::runtime_error("XB client is not logged in");
|
||||
}
|
||||
PSOXBCharacterFile::Character xb_char = *a.c->character_file();
|
||||
xb_char.guild_card.xb_user_id_high = (a.c->login->xb_license->user_id >> 32) & 0xFFFFFFFF;
|
||||
xb_char.guild_card.xb_user_id_low = a.c->login->xb_license->user_id & 0xFFFFFFFF;
|
||||
co_await send_set_extended_player_info(xb_char);
|
||||
} else {
|
||||
throw logic_error("unimplemented extended player info version");
|
||||
throw std::logic_error("unimplemented extended player info version");
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -1627,10 +1623,10 @@ ChatCommandDefinition cc_makeobj(
|
||||
|
||||
auto tokens = phosg::split(a.text, ' ');
|
||||
if (tokens.size() < 1) {
|
||||
throw runtime_error("not enough arguments");
|
||||
throw std::runtime_error("not enough arguments");
|
||||
}
|
||||
|
||||
uint32_t base_type_high = stoul(tokens[0], nullptr, 0) << 16;
|
||||
uint32_t base_type_high = std::stoul(tokens[0], nullptr, 0) << 16;
|
||||
VectorXYZF pos = a.c->pos;
|
||||
VectorXYZI angle{0, 0, 0};
|
||||
VectorXYZF param123{0, 0, 0};
|
||||
@@ -1638,7 +1634,7 @@ ChatCommandDefinition cc_makeobj(
|
||||
for (size_t z = 1; z < tokens.size(); z++) {
|
||||
auto subtokens = phosg::split(tokens[z], ':');
|
||||
if (subtokens.size() != 2 || subtokens[0].size() != 1) {
|
||||
throw runtime_error("invalid argument: " + tokens[z]);
|
||||
throw std::runtime_error("invalid argument: " + tokens[z]);
|
||||
}
|
||||
switch (tolower(subtokens[0].front())) {
|
||||
case 'X':
|
||||
@@ -1684,11 +1680,11 @@ ChatCommandDefinition cc_makeobj(
|
||||
param456.z = stol(subtokens[1], nullptr, 0);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid argument: " + tokens[z]);
|
||||
throw std::runtime_error("invalid argument: " + tokens[z]);
|
||||
}
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> label_writes{
|
||||
std::unordered_map<std::string, uint32_t> label_writes{
|
||||
{"base_type_high", base_type_high},
|
||||
{"floor_low", a.c->floor},
|
||||
{"pos_x", std::bit_cast<uint32_t>(pos.x.load())},
|
||||
@@ -1809,8 +1805,7 @@ ChatCommandDefinition cc_password(
|
||||
|
||||
} else {
|
||||
l->password = a.text;
|
||||
string escaped = remove_color(l->password);
|
||||
send_text_message_fmt(l, "$C6Game password:\n{}", escaped);
|
||||
send_text_message_fmt(l, "$C6Game password:\n{}", remove_color(l->password));
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
@@ -1820,15 +1815,15 @@ ChatCommandDefinition cc_patch(
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
auto tokens = phosg::split(a.text, ' ');
|
||||
if (tokens.empty()) {
|
||||
throw runtime_error("not enough arguments");
|
||||
throw std::runtime_error("not enough arguments");
|
||||
}
|
||||
|
||||
string patch_name = std::move(tokens[0]);
|
||||
unordered_map<string, uint32_t> label_writes;
|
||||
std::string patch_name = std::move(tokens[0]);
|
||||
std::unordered_map<std::string, uint32_t> label_writes;
|
||||
for (size_t z = 0; z < tokens.size() - 1; z++) {
|
||||
const auto& token = tokens[z + 1];
|
||||
size_t equals_pos = token.find('=');
|
||||
string key, value;
|
||||
std::string key, value;
|
||||
if (equals_pos == std::string::npos) {
|
||||
key = std::format("arg{}", z);
|
||||
value = token;
|
||||
@@ -1837,9 +1832,9 @@ ChatCommandDefinition cc_patch(
|
||||
value = token.substr(equals_pos + 1);
|
||||
}
|
||||
if (value.contains('.')) { // float
|
||||
label_writes.emplace(std::move(key), std::bit_cast<uint32_t>(stof(value, nullptr)));
|
||||
label_writes.emplace(std::move(key), std::bit_cast<uint32_t>(std::stof(value, nullptr)));
|
||||
} else { // int
|
||||
label_writes.emplace(std::move(key), stoul(value, nullptr, 0));
|
||||
label_writes.emplace(std::move(key), std::stoul(value, nullptr, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1870,7 +1865,7 @@ ChatCommandDefinition cc_patch(
|
||||
ret.return_value.load(), ret.return_value.load(), std::bit_cast<float>(ret.return_value.load()));
|
||||
}
|
||||
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
send_text_message(a.c, "$C6Invalid function");
|
||||
}
|
||||
co_return;
|
||||
@@ -1911,10 +1906,10 @@ ChatCommandDefinition cc_ping(
|
||||
co_return;
|
||||
});
|
||||
|
||||
static string file_path_for_recording(const std::string& args, uint32_t account_id, bool compressed) {
|
||||
static std::string file_path_for_recording(const std::string& args, uint32_t account_id, bool compressed) {
|
||||
for (char ch : args) {
|
||||
if (ch <= 0x20 || ch > 0x7E || ch == '/') {
|
||||
throw runtime_error("invalid recording name");
|
||||
throw std::runtime_error("invalid recording name");
|
||||
}
|
||||
}
|
||||
return std::format("system/ep3/battle-records/{:010}_{}.mzr{}", account_id, args, compressed ? "" : "d");
|
||||
@@ -1932,13 +1927,13 @@ ChatCommandDefinition cc_playrec(
|
||||
} else if (!l->is_game()) {
|
||||
|
||||
auto s = a.c->require_server_state();
|
||||
string filename = a.text;
|
||||
std::string filename = a.text;
|
||||
bool start_battle_player_immediately = (filename.at(0) != '!');
|
||||
if (!start_battle_player_immediately) {
|
||||
filename = filename.substr(1);
|
||||
}
|
||||
|
||||
string data;
|
||||
std::string data;
|
||||
try {
|
||||
data = phosg::load_file(file_path_for_recording(filename, a.c->login->account->account_id, false));
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
@@ -1949,8 +1944,8 @@ ChatCommandDefinition cc_playrec(
|
||||
throw precondition_failed("$C4The recording does\nnot exist");
|
||||
}
|
||||
}
|
||||
auto record = make_shared<Episode3::BattleRecord>(data);
|
||||
auto battle_player = make_shared<Episode3::BattleRecordPlayer>(s->io_context, record);
|
||||
auto record = std::make_shared<Episode3::BattleRecord>(data);
|
||||
auto battle_player = std::make_shared<Episode3::BattleRecordPlayer>(s->io_context, record);
|
||||
auto game = create_game_generic(
|
||||
s, a.c, filename, "", Episode::EP3, GameMode::NORMAL, Difficulty::NORMAL, false, nullptr, battle_player);
|
||||
if (game) {
|
||||
@@ -1975,7 +1970,7 @@ ChatCommandDefinition cc_qcall(
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->is_game() && l->quest) {
|
||||
send_quest_function_call(a.c, stoul(a.text, nullptr, 0));
|
||||
send_quest_function_call(a.c, std::stoul(a.text, nullptr, 0));
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
@@ -1986,7 +1981,7 @@ ChatCommandDefinition cc_qcheck(
|
||||
a.check_is_proxy(false);
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
uint16_t flag_num = stoul(a.text, nullptr, 0);
|
||||
uint16_t flag_num = std::stoul(a.text, nullptr, 0);
|
||||
|
||||
if (l->is_game()) {
|
||||
if (!l->quest_flags_known || l->quest_flags_known->get(l->difficulty, flag_num)) {
|
||||
@@ -2009,7 +2004,7 @@ ChatCommandDefinition cc_qcheck(
|
||||
|
||||
static void command_qset_qclear(const Args& a, bool should_set) {
|
||||
a.check_is_game(true);
|
||||
uint16_t flag_num = stoul(a.text, nullptr, 0);
|
||||
uint16_t flag_num = std::stoul(a.text, nullptr, 0);
|
||||
|
||||
if (!a.c->proxy_session) {
|
||||
a.check_debug_enabled();
|
||||
@@ -2072,11 +2067,11 @@ ChatCommandDefinition cc_qfread(
|
||||
const auto& def = s->quest_counter_fields.at(a.text);
|
||||
counter_index = def.first;
|
||||
mask = def.second;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("$C4Invalid field name");
|
||||
}
|
||||
if (mask == 0) {
|
||||
throw runtime_error("invalid quest counter definition");
|
||||
throw std::runtime_error("invalid quest counter definition");
|
||||
}
|
||||
|
||||
uint32_t counter_value = a.c->character_file()->quest_counters.at(counter_index) & mask;
|
||||
@@ -2098,7 +2093,7 @@ ChatCommandDefinition cc_qgread(
|
||||
{"$qgread"},
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
a.check_is_proxy(false);
|
||||
uint8_t counter_num = stoul(a.text, nullptr, 0);
|
||||
uint8_t counter_num = std::stoul(a.text, nullptr, 0);
|
||||
const auto& counters = a.c->character_file()->quest_counters;
|
||||
if (counter_num >= counters.size()) {
|
||||
throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size());
|
||||
@@ -2126,8 +2121,8 @@ ChatCommandDefinition cc_qgwrite(
|
||||
throw precondition_failed("$C6Incorrect number\nof arguments");
|
||||
}
|
||||
|
||||
uint8_t counter_num = stoul(tokens[0], nullptr, 0);
|
||||
uint32_t value = stoul(tokens[1], nullptr, 0);
|
||||
uint8_t counter_num = std::stoul(tokens[0], nullptr, 0);
|
||||
uint32_t value = std::stoul(tokens[1], nullptr, 0);
|
||||
auto& counters = a.c->character_file()->quest_counters;
|
||||
if (counter_num >= counters.size()) {
|
||||
throw precondition_failed("$C7Counter ID must be\nless than {}", counters.size());
|
||||
@@ -2158,10 +2153,10 @@ static void command_qsync_qsyncall(const Args& a, bool send_to_lobby) {
|
||||
|
||||
G_SyncQuestRegister_6x77 cmd;
|
||||
cmd.header = {0x77, 0x03, 0x0000};
|
||||
cmd.register_number = stoul(tokens[0].substr(1), nullptr, 0);
|
||||
cmd.register_number = std::stoul(tokens[0].substr(1), nullptr, 0);
|
||||
cmd.unused = 0;
|
||||
if (tokens[0][0] == 'r') {
|
||||
cmd.value.as_int = stoul(tokens[1], nullptr, 0);
|
||||
cmd.value.as_int = std::stoul(tokens[1], nullptr, 0);
|
||||
} else if (tokens[0][0] == 'f') {
|
||||
cmd.value.as_float = stof(tokens[1]);
|
||||
} else {
|
||||
@@ -2287,24 +2282,24 @@ ChatCommandDefinition cc_readmem(
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
a.check_debug_enabled();
|
||||
|
||||
uint32_t addr = stoul(a.text, nullptr, 16);
|
||||
uint32_t addr = std::stoul(a.text, nullptr, 16);
|
||||
if (!console_address_in_range(a.c->version(), addr)) {
|
||||
throw precondition_failed("$C4Address out of\nrange");
|
||||
}
|
||||
|
||||
co_await prepare_client_for_patches(a.c);
|
||||
|
||||
shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
std::shared_ptr<const ClientFunctionIndex::Function> fn;
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
fn = s->client_functions->get("ReadMemoryWord", a.c->specific_version);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("Invalid patch name");
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> label_writes{{"address", addr}};
|
||||
std::unordered_map<std::string, uint32_t> label_writes{{"address", addr}};
|
||||
auto res = co_await send_function_call(a.c, fn, label_writes);
|
||||
string data_str;
|
||||
std::string data_str;
|
||||
if (is_big_endian(a.c->version())) {
|
||||
be_uint32_t v = res.return_value.load();
|
||||
data_str = phosg::format_data_string(&v, sizeof(v));
|
||||
@@ -2362,9 +2357,9 @@ ChatCommandDefinition cc_saverec(
|
||||
if (!a.c->ep3_prev_battle_record) {
|
||||
throw precondition_failed("$C4No finished\nrecording is\npresent");
|
||||
}
|
||||
string file_path = file_path_for_recording(a.text, a.c->login->account->account_id, false);
|
||||
string data = a.c->ep3_prev_battle_record->serialize();
|
||||
phosg::save_file(file_path, data);
|
||||
phosg::save_file(
|
||||
file_path_for_recording(a.text, a.c->login->account->account_id, false),
|
||||
a.c->ep3_prev_battle_record->serialize());
|
||||
send_text_message(a.c, "$C7Recording saved");
|
||||
a.c->ep3_prev_battle_record.reset();
|
||||
co_return;
|
||||
@@ -2374,7 +2369,7 @@ static asio::awaitable<void> command_send_command(const Args& a, bool to_client,
|
||||
if (!a.c->proxy_session) {
|
||||
a.check_debug_enabled();
|
||||
}
|
||||
string data = phosg::parse_data_string(a.text);
|
||||
std::string data = phosg::parse_data_string(a.text);
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
if (to_client) {
|
||||
if (send_protected) {
|
||||
@@ -2452,7 +2447,7 @@ ChatCommandDefinition cc_setassist(
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -2465,10 +2460,10 @@ ChatCommandDefinition cc_setassist(
|
||||
}
|
||||
|
||||
size_t client_id;
|
||||
string card_name;
|
||||
std::string card_name;
|
||||
if (isdigit(a.text[0])) {
|
||||
auto tokens = phosg::split(a.text, ' ', 1);
|
||||
client_id = stoul(tokens.at(0), nullptr, 0) - 1;
|
||||
client_id = std::stoul(tokens.at(0), nullptr, 0) - 1;
|
||||
card_name = tokens.at(1);
|
||||
} else {
|
||||
client_id = a.c->lobby_client_id;
|
||||
@@ -2478,10 +2473,10 @@ ChatCommandDefinition cc_setassist(
|
||||
throw precondition_failed("$C6Invalid client ID");
|
||||
}
|
||||
|
||||
shared_ptr<const Episode3::CardIndex::CardEntry> ce;
|
||||
std::shared_ptr<const Episode3::CardIndex::CardEntry> ce;
|
||||
try {
|
||||
ce = l->ep3_server->options.card_index->definition_for_name_normalized(card_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("$C6Card not found");
|
||||
}
|
||||
if (ce->def.type != Episode3::CardType::ASSIST) {
|
||||
@@ -2495,7 +2490,7 @@ ChatCommandDefinition cc_server_info(
|
||||
{"$si"},
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
auto s = a.c->require_server_state();
|
||||
string uptime_str = phosg::format_duration(phosg::now() - s->creation_time);
|
||||
std::string uptime_str = phosg::format_duration(phosg::now() - s->creation_time);
|
||||
send_text_message_fmt(a.c,
|
||||
"Uptime: $C6{}$C7\nLobbies: $C6{}$C7\nClients: $C6{}$C7(g) $C6{}$C7(p)",
|
||||
uptime_str,
|
||||
@@ -2523,7 +2518,7 @@ ChatCommandDefinition cc_silence(
|
||||
}
|
||||
|
||||
target->can_chat = !target->can_chat;
|
||||
string target_name = name_for_client(target);
|
||||
std::string target_name = name_for_client(target);
|
||||
send_text_message_fmt(a.c, "$C6{} {}silenced", target_name, target->can_chat ? "un" : "");
|
||||
co_return;
|
||||
});
|
||||
@@ -2538,7 +2533,7 @@ ChatCommandDefinition cc_song(
|
||||
song = -song;
|
||||
send_ep3_change_music(a.c->proxy_session->server_channel, song);
|
||||
}
|
||||
send_ep3_change_music(a.c->channel, stoul(a.text, nullptr, 0));
|
||||
send_ep3_change_music(a.c->channel, std::stoul(a.text, nullptr, 0));
|
||||
co_return;
|
||||
});
|
||||
|
||||
@@ -2546,7 +2541,7 @@ ChatCommandDefinition cc_sound(
|
||||
{"$sound"},
|
||||
+[](const Args& a) -> asio::awaitable<void> {
|
||||
bool echo_to_all = (!a.text.empty() && a.text[0] == '!');
|
||||
uint32_t sound_id = stoul(echo_to_all ? a.text.substr(1) : a.text, nullptr, 16);
|
||||
uint32_t sound_id = std::stoul(echo_to_all ? a.text.substr(1) : a.text, nullptr, 16);
|
||||
|
||||
auto l = a.c->require_lobby();
|
||||
uint8_t area = l->is_game() ? l->area_for_floor(a.c->version(), a.c->floor) : 0x0F;
|
||||
@@ -2571,7 +2566,7 @@ ChatCommandDefinition cc_spec(
|
||||
a.check_is_ep3(true);
|
||||
auto l = a.c->require_lobby();
|
||||
if (!l->is_ep3()) {
|
||||
throw logic_error("Episode 3 client in non-Episode 3 game");
|
||||
throw std::logic_error("Episode 3 client in non-Episode 3 game");
|
||||
}
|
||||
|
||||
// In non-tournament games, only the leader can do this; in a tournament match, the players don't have control
|
||||
@@ -2613,7 +2608,7 @@ ChatCommandDefinition cc_stat(
|
||||
a.check_is_ep3(true);
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -2627,7 +2622,7 @@ ChatCommandDefinition cc_stat(
|
||||
}
|
||||
uint8_t team_id = ps->get_team_id();
|
||||
if (team_id > 1) {
|
||||
throw logic_error("team ID is incorrect");
|
||||
throw std::logic_error("team ID is incorrect");
|
||||
}
|
||||
|
||||
if (a.text == "rank") {
|
||||
@@ -2636,8 +2631,8 @@ ChatCommandDefinition cc_stat(
|
||||
const char* rank_name = ps->stats.name_for_rank(rank);
|
||||
send_text_message_fmt(a.c, "$C7Score: {:g}\nRank: {} ({})", score, rank, rank_name);
|
||||
} else if (a.text == "duration") {
|
||||
string s = phosg::format_duration(phosg::now() - l->ep3_server->battle_start_usecs);
|
||||
send_text_message_fmt(a.c, "$C7Duration: {}", s);
|
||||
send_text_message_fmt(a.c, "$C7Duration: {}",
|
||||
phosg::format_duration(phosg::now() - l->ep3_server->battle_start_usecs));
|
||||
} else if (a.text == "fcs-destroyed") {
|
||||
send_text_message_fmt(a.c, "$C7Team FCs destroyed:\n{}", l->ep3_server->team_num_ally_fcs_destroyed[team_id]);
|
||||
} else if (a.text == "cards-destroyed") {
|
||||
@@ -2694,7 +2689,7 @@ ChatCommandDefinition cc_surrender(
|
||||
a.check_is_ep3(true);
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -2706,7 +2701,7 @@ ChatCommandDefinition cc_surrender(
|
||||
if (!ps || !ps->is_alive()) {
|
||||
throw precondition_failed("$C6Defeated players\ncannot surrender");
|
||||
}
|
||||
string name = remove_color(a.c->character_file()->disp.name.decode(a.c->language()));
|
||||
std::string name = remove_color(a.c->character_file()->disp.name.decode(a.c->language()));
|
||||
send_text_message_fmt(l, "$C6{} has\nsurrendered", name);
|
||||
for (const auto& watcher_l : l->watcher_lobbies) {
|
||||
send_text_message_fmt(watcher_l, "$C6{} has\nsurrendered", name);
|
||||
@@ -2736,10 +2731,10 @@ static void command_swset_swclear(const Args& a, bool should_set) {
|
||||
uint8_t floor, flag_num;
|
||||
if (tokens.size() == 1) {
|
||||
floor = a.c->floor;
|
||||
flag_num = stoul(tokens[0], nullptr, 0);
|
||||
flag_num = std::stoul(tokens[0], nullptr, 0);
|
||||
} else if (tokens.size() == 2) {
|
||||
floor = stoul(tokens[0], nullptr, 0);
|
||||
flag_num = stoul(tokens[1], nullptr, 0);
|
||||
floor = std::stoul(tokens[0], nullptr, 0);
|
||||
flag_num = std::stoul(tokens[1], nullptr, 0);
|
||||
} else {
|
||||
throw precondition_failed("$C4Incorrect parameters");
|
||||
}
|
||||
@@ -2859,7 +2854,7 @@ ChatCommandDefinition cc_unset(
|
||||
a.check_cheats_enabled_in_game(s->cheat_flags.ep3_unset_field_character);
|
||||
auto l = a.c->require_lobby();
|
||||
if (l->episode != Episode::EP3) {
|
||||
throw logic_error("non-Ep3 client in Ep3 game");
|
||||
throw std::logic_error("non-Ep3 client in Ep3 game");
|
||||
}
|
||||
if (!l->ep3_server) {
|
||||
throw precondition_failed("$C6Episode 3 server\nis not initialized");
|
||||
@@ -2886,8 +2881,8 @@ ChatCommandDefinition cc_variations(
|
||||
auto s = a.c->require_server_state();
|
||||
a.check_cheats_enabled_in_game(s->cheat_flags.override_variations);
|
||||
|
||||
a.c->override_variations = make_unique<Variations>();
|
||||
for (size_t z = 0; z < min<size_t>(a.c->override_variations->entries.size() * 2, a.text.size()); z++) {
|
||||
a.c->override_variations = std::make_unique<Variations>();
|
||||
for (size_t z = 0; z < std::min<size_t>(a.c->override_variations->entries.size() * 2, a.text.size()); z++) {
|
||||
auto& entry = a.c->override_variations->entries.at(z / 2);
|
||||
if (z & 1) {
|
||||
entry.entities = a.text[z] - '0';
|
||||
@@ -2905,7 +2900,7 @@ static void command_warp(const Args& a, bool is_warpall) {
|
||||
auto s = a.c->require_server_state();
|
||||
a.check_cheats_enabled_or_allowed(s->cheat_flags.warp);
|
||||
|
||||
uint32_t floor = stoul(a.text, nullptr, 0);
|
||||
uint32_t floor = std::stoul(a.text, nullptr, 0);
|
||||
if (!is_warpall && (a.c->floor == floor)) {
|
||||
return;
|
||||
}
|
||||
@@ -2959,7 +2954,7 @@ ChatCommandDefinition cc_what(
|
||||
co_return;
|
||||
}
|
||||
|
||||
shared_ptr<const Lobby::FloorItem> nearest_fi;
|
||||
std::shared_ptr<const Lobby::FloorItem> nearest_fi;
|
||||
float min_dist2 = 0.0f;
|
||||
for (const auto& it : l->floor_item_managers.at(a.c->floor).items) {
|
||||
if (!it.second->visible_to_client(a.c->lobby_client_id)) {
|
||||
@@ -2976,9 +2971,8 @@ ChatCommandDefinition cc_what(
|
||||
throw precondition_failed("$C4No items are near you");
|
||||
} else {
|
||||
auto s = a.c->require_server_state();
|
||||
string name = s->describe_item(
|
||||
a.c->version(), nearest_fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||
send_text_message(a.c, name);
|
||||
send_text_message(
|
||||
a.c, s->describe_item(a.c->version(), nearest_fi->data, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES));
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
@@ -3005,8 +2999,8 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en
|
||||
|
||||
double min_dist2 = -1.0;
|
||||
VectorXYZF nearest_worldspace_pos;
|
||||
shared_ptr<const MapState::ObjectState> nearest_obj;
|
||||
shared_ptr<const MapState::EnemyState> nearest_ene;
|
||||
std::shared_ptr<const MapState::ObjectState> nearest_obj;
|
||||
std::shared_ptr<const MapState::EnemyState> nearest_ene;
|
||||
|
||||
auto check_entity = [&](auto& nearest_entity, auto entity, const auto& def) -> void {
|
||||
VectorXYZF worldspace_pos;
|
||||
@@ -3015,7 +3009,7 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en
|
||||
const auto& room = s->room_layout_index->get_room(area, layout_var, def.set_entry->room);
|
||||
// This is the order in which the game does the rotations; not sure why
|
||||
worldspace_pos = def.set_entry->pos.rotate_x(room.angle.x).rotate_z(room.angle.z).rotate_y(room.angle.y) + room.position;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
a.c->log.warning_f("Can't find definition for room {:02X}:{:02X}:{:08X}", area, layout_var, def.set_entry->room);
|
||||
worldspace_pos = def.set_entry->pos;
|
||||
}
|
||||
@@ -3056,7 +3050,7 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en
|
||||
// we print that if it's set, and print the object if not.
|
||||
if (nearest_ene) {
|
||||
const auto* set_entry = nearest_ene->super_ene->version(a.c->version()).set_entry;
|
||||
string type_name = MapFile::name_for_enemy_type(set_entry->base_type, a.c->version(), area);
|
||||
std::string type_name = MapFile::name_for_enemy_type(set_entry->base_type, a.c->version(), area);
|
||||
uint8_t area = l->area_for_floor(a.c->version(), a.c->floor);
|
||||
send_text_message_fmt(a.c, "$C5E-{:03X}\n$C6{}\n$C2{}\n$C7X:{:.2f} Z:{:.2f}",
|
||||
nearest_ene->e_id, phosg::name_for_enum(nearest_ene->type(a.c->version(), area, l->difficulty, l->event)),
|
||||
@@ -3120,7 +3114,7 @@ ChatCommandDefinition cc_where(
|
||||
if (!a.c->proxy_session && l && l->is_game()) {
|
||||
for (auto lc : l->clients) {
|
||||
if (lc && (lc != a.c)) {
|
||||
string name = lc->character_file()->disp.name.decode(lc->language());
|
||||
std::string name = lc->character_file()->disp.name.decode(lc->language());
|
||||
send_text_message_fmt(a.c, "$C6{}$C7 {:X}:{}",
|
||||
name, lc->floor, FloorDefinition::get(l->episode, lc->floor).short_name);
|
||||
}
|
||||
@@ -3139,7 +3133,7 @@ ChatCommandDefinition cc_writemem(
|
||||
throw precondition_failed("Incorrect arguments");
|
||||
}
|
||||
|
||||
uint32_t addr = stoul(tokens[0], nullptr, 16);
|
||||
uint32_t addr = std::stoul(tokens[0], nullptr, 16);
|
||||
if (!console_address_in_range(a.c->version(), addr)) {
|
||||
throw precondition_failed("$C4Address out of\nrange");
|
||||
}
|
||||
@@ -3152,9 +3146,9 @@ ChatCommandDefinition cc_writemem(
|
||||
try {
|
||||
auto s = a.c->require_server_state();
|
||||
auto fn = s->client_functions->get("WriteMemory", a.c->specific_version);
|
||||
unordered_map<string, uint32_t> label_writes{{"dest_addr", addr}, {"size", data.size()}};
|
||||
std::unordered_map<std::string, uint32_t> label_writes{{"dest_addr", addr}, {"size", data.size()}};
|
||||
co_await send_function_call(a.c, fn, label_writes, data.data(), data.size());
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("Invalid patch name");
|
||||
}
|
||||
co_return;
|
||||
@@ -3177,12 +3171,12 @@ ChatCommandDefinition cc_nativecall(
|
||||
throw precondition_failed("Incorrect arguments");
|
||||
}
|
||||
|
||||
uint32_t addr = stoul(tokens[0], nullptr, 16);
|
||||
uint32_t addr = std::stoul(tokens[0], nullptr, 16);
|
||||
if (!console_address_in_range(a.c->version(), addr)) {
|
||||
throw precondition_failed("$C4Function address\nout of range");
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> label_writes{{"call_addr", addr}};
|
||||
std::unordered_map<std::string, uint32_t> label_writes{{"call_addr", addr}};
|
||||
for (size_t z = 0; z < tokens.size() - 1; z++) {
|
||||
label_writes.emplace(std::format("arg{}", z), stoull(tokens[z + 1], nullptr, 16));
|
||||
}
|
||||
@@ -3193,7 +3187,7 @@ ChatCommandDefinition cc_nativecall(
|
||||
auto s = a.c->require_server_state();
|
||||
auto fn = s->client_functions->get("CallNativeFunction", a.c->specific_version);
|
||||
co_await send_function_call(a.c, fn, label_writes);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw precondition_failed("Invalid patch name");
|
||||
}
|
||||
co_return;
|
||||
@@ -3203,12 +3197,12 @@ ChatCommandDefinition cc_nativecall(
|
||||
// Dispatch methods
|
||||
|
||||
struct SplitCommand {
|
||||
string name;
|
||||
string args;
|
||||
std::string name;
|
||||
std::string args;
|
||||
|
||||
SplitCommand(const string& text) {
|
||||
SplitCommand(const std::string& text) {
|
||||
size_t space_pos = text.find(' ');
|
||||
if (space_pos != string::npos) {
|
||||
if (space_pos != std::string::npos) {
|
||||
this->name = text.substr(0, space_pos);
|
||||
this->args = text.substr(space_pos + 1);
|
||||
} else {
|
||||
@@ -3231,7 +3225,7 @@ asio::awaitable<void> on_chat_command(std::shared_ptr<Client> c, const std::stri
|
||||
const ChatCommandDefinition* def = nullptr;
|
||||
try {
|
||||
def = ChatCommandDefinition::all_defs.at(cmd.name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (!def) {
|
||||
send_text_message(c, "$C6Unknown command");
|
||||
@@ -3242,7 +3236,7 @@ asio::awaitable<void> on_chat_command(std::shared_ptr<Client> c, const std::stri
|
||||
co_await def->handler(Args{cmd.args, check_permissions, c});
|
||||
} catch (const precondition_failed& e) {
|
||||
send_text_message(c, e.what());
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
send_text_message(c, "$C6Failed:\n" + remove_color(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -5,9 +5,7 @@
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
const std::vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
ChoiceSearchCategory{
|
||||
.id = 0x0001,
|
||||
.name = "Level",
|
||||
@@ -24,7 +22,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
{0x0009, "Level 121-160"},
|
||||
{0x000A, "Level 161-200"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client> searcher_c, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
.client_matches = +[](std::shared_ptr<Client> searcher_c, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
@@ -75,7 +73,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
{0x0008, "FOnewm"},
|
||||
{0x0009, "FOnewearl"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
switch (choice_id) {
|
||||
case 0x0000:
|
||||
return true;
|
||||
@@ -102,7 +100,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
{0x0005, "GC Episode 3"},
|
||||
{0x0006, "BB"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
if (choice_id == 0x0000) {
|
||||
return true;
|
||||
}
|
||||
@@ -142,7 +140,7 @@ const vector<ChoiceSearchCategory> CHOICE_SEARCH_CATEGORIES({
|
||||
{0x0005, "Battle"},
|
||||
{0x0006, "Challenge"},
|
||||
},
|
||||
.client_matches = +[](shared_ptr<Client>, shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
.client_matches = +[](std::shared_ptr<Client>, std::shared_ptr<Client> target_c, uint16_t choice_id) -> bool {
|
||||
uint16_t target_choice_id = target_c->character_file()->choice_search_config.get_setting(0x0204);
|
||||
return (choice_id == 0) || (target_choice_id == 0) || (choice_id == target_choice_id);
|
||||
},
|
||||
|
||||
+118
-123
@@ -16,11 +16,9 @@
|
||||
#include "Server.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
|
||||
|
||||
static atomic<uint64_t> next_id(1);
|
||||
static std::atomic<uint64_t> next_client_id(1);
|
||||
|
||||
void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
||||
this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||
@@ -82,7 +80,7 @@ void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
||||
this->set_flag(Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
throw std::logic_error("invalid game version");
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -151,7 +149,7 @@ void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
||||
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error(std::format("unknown sub_version {:X}", sub_version));
|
||||
throw std::runtime_error(std::format("unknown sub_version {:X}", sub_version));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +174,11 @@ void Client::set_drop_notification_mode(ItemDropNotificationMode new_mode) {
|
||||
}
|
||||
|
||||
Client::Client(
|
||||
shared_ptr<GameServer> server,
|
||||
shared_ptr<Channel> channel,
|
||||
std::shared_ptr<GameServer> server,
|
||||
std::shared_ptr<Channel> channel,
|
||||
ServerBehavior server_behavior)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
id(next_client_id++),
|
||||
log(std::format("[C-{:X}] ", this->id), client_log.min_level),
|
||||
channel(channel),
|
||||
server_behavior(server_behavior),
|
||||
@@ -239,13 +237,12 @@ Client::~Client() {
|
||||
}
|
||||
|
||||
void Client::update_channel_name() {
|
||||
string default_name = this->channel->default_name();
|
||||
std::string default_name = this->channel->default_name();
|
||||
|
||||
auto player = this->character_file(false, false);
|
||||
if (player) {
|
||||
string name_str = player->disp.name.decode(this->language());
|
||||
size_t level = player->disp.stats.level + 1;
|
||||
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}", this->id, name_str, level, default_name);
|
||||
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}",
|
||||
this->id, player->disp.name.decode(this->language()), player->disp.stats.level + 1, default_name);
|
||||
} else {
|
||||
this->channel->name = std::format("C-{:X} @ {}", this->id, default_name);
|
||||
}
|
||||
@@ -278,7 +275,7 @@ void Client::reschedule_ping_and_timeout_timers() {
|
||||
// The game doesn't use this timestamp; we only use it for debugging purposes
|
||||
be_uint64_t timestamp = phosg::now();
|
||||
this->channel->send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.warning_f("Failed to send ping: {}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -306,25 +303,25 @@ void Client::convert_account_to_temporary_if_nte() {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ServerState> Client::require_server_state() const {
|
||||
std::shared_ptr<ServerState> Client::require_server_state() const {
|
||||
auto server = this->server.lock();
|
||||
if (!server) {
|
||||
throw logic_error("server is deleted");
|
||||
throw std::logic_error("server is deleted");
|
||||
}
|
||||
return server->get_state();
|
||||
}
|
||||
|
||||
shared_ptr<Lobby> Client::require_lobby() const {
|
||||
std::shared_ptr<Lobby> Client::require_lobby() const {
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
throw runtime_error("client not in any lobby");
|
||||
throw std::runtime_error("client not in any lobby");
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
std::shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
if (!this->login) {
|
||||
throw logic_error("Client::team called on client with no account");
|
||||
throw std::logic_error("Client::team called on client with no account");
|
||||
}
|
||||
|
||||
if (this->login->account->bb_team_id == 0) {
|
||||
@@ -352,7 +349,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
// The team membership is valid, but the player name may be different; update the team membership if needed
|
||||
if (p) {
|
||||
auto& m = member_it->second;
|
||||
string name = p->disp.name.decode(this->language());
|
||||
std::string name = p->disp.name.decode(this->language());
|
||||
if (m.name != name) {
|
||||
this->log.info_f("Updating player name in team config");
|
||||
s->team_index->update_member_name(this->login->account->account_id, name);
|
||||
@@ -363,8 +360,8 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
}
|
||||
|
||||
bool Client::evaluate_quest_availability_expression(
|
||||
shared_ptr<const IntegralExpression> expr,
|
||||
shared_ptr<const Lobby> game,
|
||||
std::shared_ptr<const IntegralExpression> expr,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
Difficulty difficulty,
|
||||
size_t num_players,
|
||||
@@ -376,7 +373,7 @@ bool Client::evaluate_quest_availability_expression(
|
||||
return true;
|
||||
}
|
||||
if (game && !game->quest_flag_values) {
|
||||
throw logic_error("quest flags are missing from game");
|
||||
throw std::logic_error("quest flags are missing from game");
|
||||
}
|
||||
auto p = this->character_file();
|
||||
IntegralExpression::Env env = {
|
||||
@@ -389,15 +386,14 @@ bool Client::evaluate_quest_availability_expression(
|
||||
};
|
||||
int64_t ret = expr->evaluate(env);
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
string expr_str = expr->str();
|
||||
this->log.info_f("Evaluated integral expression {} => {}", expr_str, ret ? "TRUE" : "FALSE");
|
||||
this->log.info_f("Evaluated integral expression {} => {}", expr->str(), ret ? "TRUE" : "FALSE");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Client::can_see_quest(
|
||||
shared_ptr<const Quest> q,
|
||||
shared_ptr<const Lobby> game,
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
Difficulty difficulty,
|
||||
size_t num_players,
|
||||
@@ -410,8 +406,8 @@ bool Client::can_see_quest(
|
||||
}
|
||||
|
||||
bool Client::can_play_quest(
|
||||
shared_ptr<const Quest> q,
|
||||
shared_ptr<const Lobby> game,
|
||||
std::shared_ptr<const Quest> q,
|
||||
std::shared_ptr<const Lobby> game,
|
||||
uint8_t event,
|
||||
Difficulty difficulty,
|
||||
size_t num_players,
|
||||
@@ -436,7 +432,7 @@ bool Client::can_use_chat_commands() const {
|
||||
return this->require_server_state()->enable_chat_commands;
|
||||
}
|
||||
|
||||
void Client::set_login(shared_ptr<Login> login) {
|
||||
void Client::set_login(std::shared_ptr<Login> login) {
|
||||
this->login = login;
|
||||
|
||||
auto s = this->require_server_state();
|
||||
@@ -458,116 +454,116 @@ void Client::set_login(shared_ptr<Login> login) {
|
||||
|
||||
// System file
|
||||
|
||||
string Client::system_filename(const string& bb_username) {
|
||||
std::string Client::system_filename(const std::string& bb_username) {
|
||||
return std::format("system/players/system_{}.psosys", bb_username);
|
||||
}
|
||||
|
||||
string Client::system_filename() const {
|
||||
std::string Client::system_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have system data");
|
||||
throw std::logic_error("non-BB players do not have system data");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
return this->system_filename(this->login->bb_license->username);
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
|
||||
std::shared_ptr<PSOBBBaseSystemFile> Client::system_file(bool allow_load) {
|
||||
if (!this->system_data && allow_load) {
|
||||
this->load_all_files();
|
||||
}
|
||||
return this->system_data;
|
||||
}
|
||||
|
||||
shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool throw_if_missing) const {
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> Client::system_file(bool throw_if_missing) const {
|
||||
if (!this->system_data.get() && throw_if_missing) {
|
||||
throw runtime_error("system file is not loaded");
|
||||
throw std::runtime_error("system file is not loaded");
|
||||
}
|
||||
return this->system_data;
|
||||
}
|
||||
|
||||
void Client::save_system_file() const {
|
||||
if (!this->system_data) {
|
||||
throw logic_error("no system file loaded");
|
||||
throw std::logic_error("no system file loaded");
|
||||
}
|
||||
string filename = this->system_filename();
|
||||
std::string filename = this->system_filename();
|
||||
phosg::save_object_file(filename, *this->system_data);
|
||||
this->log.info_f("Saved system file {}", filename);
|
||||
}
|
||||
|
||||
// Guild Card file
|
||||
|
||||
string Client::guild_card_filename(const string& bb_username) {
|
||||
std::string Client::guild_card_filename(const std::string& bb_username) {
|
||||
return std::format("system/players/guild_cards_{}.psocard", bb_username);
|
||||
}
|
||||
|
||||
string Client::guild_card_filename() const {
|
||||
std::string Client::guild_card_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved Guild Card files");
|
||||
throw std::logic_error("non-BB players do not have saved Guild Card files");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
return this->guild_card_filename(this->login->bb_license->username);
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
|
||||
std::shared_ptr<PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) {
|
||||
if (!this->guild_card_data && allow_load) {
|
||||
this->load_all_files();
|
||||
}
|
||||
return this->guild_card_data;
|
||||
}
|
||||
|
||||
shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
|
||||
std::shared_ptr<const PSOBBGuildCardFile> Client::guild_card_file(bool allow_load) const {
|
||||
if (!this->guild_card_data && allow_load) {
|
||||
throw runtime_error("account data is not loaded");
|
||||
throw std::runtime_error("account data is not loaded");
|
||||
}
|
||||
return this->guild_card_data;
|
||||
}
|
||||
|
||||
void Client::save_guild_card_file() const {
|
||||
if (!this->guild_card_data.get()) {
|
||||
throw logic_error("no Guild Card file loaded");
|
||||
throw std::logic_error("no Guild Card file loaded");
|
||||
}
|
||||
string filename = this->guild_card_filename();
|
||||
std::string filename = this->guild_card_filename();
|
||||
phosg::save_object_file(filename, *this->guild_card_data);
|
||||
this->log.info_f("Saved Guild Card file {}", filename);
|
||||
}
|
||||
|
||||
// Character file
|
||||
|
||||
string Client::character_filename(const std::string& bb_username, ssize_t index) {
|
||||
std::string Client::character_filename(const std::string& bb_username, ssize_t index) {
|
||||
if (bb_username.empty()) {
|
||||
throw logic_error("non-BB players do not have saved character filenames");
|
||||
throw std::logic_error("non-BB players do not have saved character filenames");
|
||||
}
|
||||
if (index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
throw std::logic_error("character index is not set");
|
||||
}
|
||||
return std::format("system/players/player_{}_{}.psochar", bb_username, index);
|
||||
}
|
||||
|
||||
string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
|
||||
std::string Client::backup_character_filename(uint32_t account_id, size_t index, bool is_ep3) {
|
||||
return std::format("system/players/backup_player_{}_{}.{}",
|
||||
account_id, index, is_ep3 ? "pso3char" : "psochar");
|
||||
}
|
||||
|
||||
string Client::character_filename() const {
|
||||
std::string Client::character_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved character filenames");
|
||||
throw std::logic_error("non-BB players do not have saved character filenames");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
return this->character_filename(this->login->bb_license->username, this->bb_character_index);
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allow_overlay) {
|
||||
std::shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allow_overlay) {
|
||||
if (this->overlay_character_data && allow_overlay) {
|
||||
return this->overlay_character_data;
|
||||
}
|
||||
if (!this->character_data && allow_load) {
|
||||
if ((this->version() == Version::BB_V4) && (this->bb_character_index < 0)) {
|
||||
throw runtime_error("character index not specified");
|
||||
throw std::runtime_error("character index not specified");
|
||||
}
|
||||
this->load_all_files();
|
||||
if (!this->character_data) {
|
||||
@@ -577,35 +573,35 @@ shared_ptr<PSOBBCharacterFile> Client::character_file(bool allow_load, bool allo
|
||||
return this->character_data;
|
||||
}
|
||||
|
||||
shared_ptr<const PSOBBCharacterFile> Client::character_file(bool throw_if_missing, bool allow_overlay) const {
|
||||
std::shared_ptr<const PSOBBCharacterFile> Client::character_file(bool throw_if_missing, bool allow_overlay) const {
|
||||
if (allow_overlay && this->overlay_character_data) {
|
||||
return this->overlay_character_data;
|
||||
}
|
||||
if (!this->character_data && throw_if_missing) {
|
||||
throw runtime_error("character data is not loaded");
|
||||
throw std::runtime_error("character data is not loaded");
|
||||
}
|
||||
return this->character_data;
|
||||
}
|
||||
|
||||
void Client::save_character_file(
|
||||
const string& filename,
|
||||
shared_ptr<const PSOBBBaseSystemFile> system,
|
||||
shared_ptr<const PSOBBCharacterFile> character) {
|
||||
const std::string& filename,
|
||||
std::shared_ptr<const PSOBBBaseSystemFile> system,
|
||||
std::shared_ptr<const PSOBBCharacterFile> character) {
|
||||
PSOCHARFile::save(filename, system, character);
|
||||
}
|
||||
|
||||
void Client::save_ep3_character_file(
|
||||
const string& filename,
|
||||
const std::string& filename,
|
||||
const PSOGCEp3CharacterFile::Character& character) {
|
||||
phosg::save_file(filename, &character, sizeof(character));
|
||||
}
|
||||
|
||||
void Client::save_character_file() {
|
||||
if (!this->system_data.get()) {
|
||||
throw logic_error("no system file loaded");
|
||||
throw std::logic_error("no system file loaded");
|
||||
}
|
||||
if (!this->character_data.get()) {
|
||||
throw logic_error("no character file loaded");
|
||||
throw std::logic_error("no character file loaded");
|
||||
}
|
||||
if (this->should_update_play_time) {
|
||||
// This is slightly inaccurate, since fractions of a second are truncated off each time we save. I'm lazy, so
|
||||
@@ -630,7 +626,7 @@ void Client::create_character_file(
|
||||
uint32_t guild_card_number,
|
||||
Language language,
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
shared_ptr<const LevelTable> level_table) {
|
||||
std::shared_ptr<const LevelTable> level_table) {
|
||||
this->log.info_f("Creating new character file");
|
||||
this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table);
|
||||
this->save_character_file();
|
||||
@@ -639,8 +635,8 @@ void Client::create_character_file(
|
||||
std::filesystem::remove(this->bank_filename());
|
||||
}
|
||||
|
||||
void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_ptr<const LevelTable> level_table) {
|
||||
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
|
||||
void Client::create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table) {
|
||||
this->overlay_character_data = std::make_shared<PSOBBCharacterFile>(*this->character_file(true, false));
|
||||
|
||||
if (rules->weapon_and_armor_mode != BattleRules::WeaponAndArmorMode::ALLOW) {
|
||||
this->overlay_character_data->inventory.remove_all_items_of_type(0);
|
||||
@@ -657,7 +653,7 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
|
||||
this->overlay_character_data->inventory.hp_from_materials = 0;
|
||||
this->overlay_character_data->inventory.tp_from_materials = 0;
|
||||
|
||||
uint32_t target_level = clamp<uint32_t>(rules->char_level, 0, 199);
|
||||
uint32_t target_level = std::clamp<uint32_t>(rules->char_level, 0, 199);
|
||||
uint8_t char_class = this->overlay_character_data->disp.visual.char_class;
|
||||
auto& stats = this->overlay_character_data->disp.stats;
|
||||
|
||||
@@ -689,11 +685,11 @@ void Client::create_battle_overlay(shared_ptr<const BattleRules> rules, shared_p
|
||||
}
|
||||
|
||||
void Client::create_challenge_overlay(
|
||||
Version version, size_t template_index, shared_ptr<const LevelTable> level_table) {
|
||||
Version version, size_t template_index, std::shared_ptr<const LevelTable> level_table) {
|
||||
auto p = this->character_file(true, false);
|
||||
const auto& tpl = get_challenge_template_definition(version, p->disp.visual.class_flags, template_index);
|
||||
|
||||
this->overlay_character_data = make_shared<PSOBBCharacterFile>(*p);
|
||||
this->overlay_character_data = std::make_shared<PSOBBCharacterFile>(*p);
|
||||
auto overlay = this->overlay_character_data;
|
||||
|
||||
for (size_t z = 0; z < overlay->inventory.items.size(); z++) {
|
||||
@@ -738,9 +734,9 @@ void Client::create_challenge_overlay(
|
||||
|
||||
// Bank file
|
||||
|
||||
string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
||||
std::string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
||||
if (bb_username.empty()) {
|
||||
throw logic_error("non-BB players do not have saved bank files");
|
||||
throw std::logic_error("non-BB players do not have saved bank files");
|
||||
}
|
||||
if (index < 0) {
|
||||
return std::format("system/players/shared_bank_{}.psobank", bb_username);
|
||||
@@ -749,19 +745,19 @@ string Client::bank_filename(const std::string& bb_username, ssize_t index) {
|
||||
}
|
||||
}
|
||||
|
||||
string Client::bank_filename() const {
|
||||
std::string Client::bank_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved bank filenames");
|
||||
throw std::logic_error("non-BB players do not have saved bank filenames");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
return this->bank_filename(this->login->bb_license->username, this->bb_bank_character_index);
|
||||
}
|
||||
|
||||
std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved bank files");
|
||||
throw std::logic_error("non-BB players do not have saved bank files");
|
||||
}
|
||||
if (this->has_overlay()) {
|
||||
throw std::runtime_error("bank is inaccessible when overlay is present");
|
||||
@@ -771,7 +767,7 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
||||
// If there's a psobank file, load it and ignore the character file bank
|
||||
auto filename = this->bank_filename();
|
||||
auto f = phosg::fopen_unique(filename, "rb");
|
||||
this->bank_data = make_shared<PlayerBank>();
|
||||
this->bank_data = std::make_shared<PlayerBank>();
|
||||
this->bank_data->load(f.get());
|
||||
this->log.info_f("Loaded bank data from {}", filename);
|
||||
} catch (const phosg::cannot_open_file&) {
|
||||
@@ -783,15 +779,16 @@ std::shared_ptr<PlayerBank> Client::bank_file(bool allow_load) {
|
||||
this->log.info_f("Using bank data from loaded character");
|
||||
} else if (this->bb_bank_character_index >= 0) {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
string filename = this->character_filename(this->login->bb_license->username, this->bb_bank_character_index);
|
||||
std::string filename = this->character_filename(
|
||||
this->login->bb_license->username, this->bb_bank_character_index);
|
||||
auto character = PSOCHARFile::load_shared(filename, false).character_file;
|
||||
this->bank_data = std::make_shared<PlayerBank>(character->bank);
|
||||
this->log.info_f("Using bank data from {}", filename);
|
||||
} else {
|
||||
// The shared bank doesn't exist; make a new one
|
||||
this->bank_data = make_shared<PlayerBank>();
|
||||
this->bank_data = std::make_shared<PlayerBank>();
|
||||
this->log.info_f("Created new shared bank");
|
||||
}
|
||||
}
|
||||
@@ -811,14 +808,14 @@ std::shared_ptr<const PlayerBank> Client::bank_file(bool throw_if_missing) const
|
||||
return this->bank_data;
|
||||
}
|
||||
|
||||
void Client::save_bank_file(const string& filename, const PlayerBank& bank) {
|
||||
void Client::save_bank_file(const std::string& filename, const PlayerBank& bank) {
|
||||
auto f = phosg::fopen_unique(filename, "wb");
|
||||
bank.save(f.get());
|
||||
}
|
||||
|
||||
void Client::save_bank_file() const {
|
||||
if (!this->bank_data) {
|
||||
throw logic_error("no bank file loaded");
|
||||
throw std::logic_error("no bank file loaded");
|
||||
}
|
||||
auto filename = this->bank_filename();
|
||||
this->save_bank_file(filename, *this->bank_data);
|
||||
@@ -840,30 +837,28 @@ void Client::change_bank(ssize_t index) {
|
||||
|
||||
// Legacy files
|
||||
|
||||
string Client::legacy_account_filename() const {
|
||||
std::string Client::legacy_account_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved account data");
|
||||
throw std::logic_error("non-BB players do not have saved account data");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
return std::format("system/players/account_{}.nsa", this->login->bb_license->username);
|
||||
}
|
||||
|
||||
string Client::legacy_player_filename() const {
|
||||
std::string Client::legacy_player_filename() const {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("non-BB players do not have saved player files");
|
||||
throw std::logic_error("non-BB players do not have saved player files");
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
throw std::logic_error("client is not logged in");
|
||||
}
|
||||
if (this->bb_character_index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
throw std::logic_error("character index is not set");
|
||||
}
|
||||
return std::format(
|
||||
"system/players/player_{}_{}.nsc",
|
||||
this->login->bb_license->username,
|
||||
static_cast<ssize_t>(this->bb_character_index + 1));
|
||||
return std::format("system/players/player_{}_{}.nsc",
|
||||
this->login->bb_license->username, static_cast<ssize_t>(this->bb_character_index + 1));
|
||||
}
|
||||
|
||||
void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_senders) {
|
||||
@@ -877,20 +872,20 @@ void Client::import_blocked_senders(const parray<le_uint32_t, 30>& blocked_sende
|
||||
|
||||
void Client::load_all_files() {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
||||
this->bank_data = make_shared<PlayerBank>();
|
||||
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
|
||||
this->character_data = std::make_shared<PSOBBCharacterFile>();
|
||||
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>();
|
||||
this->bank_data = std::make_shared<PlayerBank>();
|
||||
return;
|
||||
}
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("cannot load BB player data until client is logged in");
|
||||
throw std::logic_error("cannot load BB player data until client is logged in");
|
||||
}
|
||||
|
||||
if (!this->system_data) {
|
||||
string sys_filename = this->system_filename();
|
||||
std::string sys_filename = this->system_filename();
|
||||
if (std::filesystem::is_regular_file(sys_filename)) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||
this->system_data = std::make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||
this->log.info_f("Loaded system data from {}", sys_filename);
|
||||
} else {
|
||||
this->log.info_f("System file is missing: {}", sys_filename);
|
||||
@@ -898,7 +893,7 @@ void Client::load_all_files() {
|
||||
}
|
||||
|
||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||
string char_filename = this->character_filename();
|
||||
std::string char_filename = this->character_filename();
|
||||
if (std::filesystem::is_regular_file(char_filename)) {
|
||||
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
|
||||
this->character_data = psochar.character_file;
|
||||
@@ -907,7 +902,7 @@ void Client::load_all_files() {
|
||||
// If there was no .psosys file, use the system file from the .psochar file instead
|
||||
if (!this->system_data) {
|
||||
if (!psochar.system_file) {
|
||||
throw logic_error("account system data not present, and also not loaded from psochar file");
|
||||
throw std::logic_error("account system data not present, and also not loaded from psochar file");
|
||||
}
|
||||
this->system_data = psochar.system_file;
|
||||
this->log.info_f("Loaded system data from {}", char_filename);
|
||||
@@ -922,9 +917,9 @@ void Client::load_all_files() {
|
||||
}
|
||||
|
||||
if (!this->guild_card_data) {
|
||||
string card_filename = this->guild_card_filename();
|
||||
std::string card_filename = this->guild_card_filename();
|
||||
if (std::filesystem::is_regular_file(card_filename)) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||
this->guild_card_data->delete_duplicates();
|
||||
this->log.info_f("Loaded Guild Card data from {}", card_filename);
|
||||
} else {
|
||||
@@ -934,25 +929,25 @@ void Client::load_all_files() {
|
||||
|
||||
// If any of the above files are still missing, try to load from .nsa/.nsc files instead
|
||||
if (!this->system_data || (!this->character_data && (this->bb_character_index >= 0)) || !this->guild_card_data) {
|
||||
string nsa_filename = this->legacy_account_filename();
|
||||
shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
||||
std::string nsa_filename = this->legacy_account_filename();
|
||||
std::shared_ptr<LegacySavedAccountDataBB> nsa_data;
|
||||
if (std::filesystem::is_regular_file(nsa_filename)) {
|
||||
nsa_data = make_shared<LegacySavedAccountDataBB>(phosg::load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||
nsa_data = std::make_shared<LegacySavedAccountDataBB>(phosg::load_object_file<LegacySavedAccountDataBB>(nsa_filename));
|
||||
if (!nsa_data->signature.eq(LegacySavedAccountDataBB::SIGNATURE)) {
|
||||
throw runtime_error("account data header is incorrect");
|
||||
throw std::runtime_error("account data header is incorrect");
|
||||
}
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
||||
this->system_data = std::make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
||||
this->log.info_f("Loaded legacy system data from {}", nsa_filename);
|
||||
}
|
||||
if (!this->guild_card_data) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
|
||||
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>(nsa_data->guild_card_file);
|
||||
this->log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||
string nsc_filename = this->legacy_player_filename();
|
||||
std::string nsc_filename = this->legacy_player_filename();
|
||||
if (std::filesystem::is_regular_file(nsc_filename)) {
|
||||
auto nsc_data = phosg::load_object_file<LegacySavedPlayerDataBB>(nsc_filename);
|
||||
if (nsc_data.signature == LegacySavedPlayerDataBB::SIGNATURE_V0) {
|
||||
@@ -962,10 +957,10 @@ void Client::load_all_files() {
|
||||
nsc_data.battle_records.disconnect_count = 0;
|
||||
nsc_data.battle_records.unknown_a1.clear(0);
|
||||
} else if (nsc_data.signature != LegacySavedPlayerDataBB::SIGNATURE_V1) {
|
||||
throw runtime_error("legacy player data has incorrect signature");
|
||||
throw std::runtime_error("legacy player data has incorrect signature");
|
||||
}
|
||||
|
||||
this->character_data = make_shared<PSOBBCharacterFile>();
|
||||
this->character_data = std::make_shared<PSOBBCharacterFile>();
|
||||
this->character_data->inventory = nsc_data.inventory;
|
||||
this->character_data->disp = nsc_data.disp;
|
||||
this->character_data->play_time_seconds = 0;
|
||||
@@ -1001,7 +996,7 @@ void Client::load_all_files() {
|
||||
// The system and Guild Card files can be auto-created if they can't be loaded. After this, system_data and
|
||||
// guild_card_data are always non-null, but character_data may still be null
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>();
|
||||
this->system_data = std::make_shared<PSOBBBaseSystemFile>();
|
||||
auto s = this->require_server_state();
|
||||
if (s->bb_default_keyboard_config) {
|
||||
this->system_data->key_config = *s->bb_default_keyboard_config;
|
||||
@@ -1012,7 +1007,7 @@ void Client::load_all_files() {
|
||||
this->log.info_f("Created new system data");
|
||||
}
|
||||
if (!this->guild_card_data) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
||||
this->guild_card_data = std::make_shared<PSOBBGuildCardFile>();
|
||||
this->log.info_f("Created new Guild Card data");
|
||||
}
|
||||
|
||||
@@ -1041,7 +1036,7 @@ void Client::load_all_files() {
|
||||
}
|
||||
}
|
||||
|
||||
void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> charfile) {
|
||||
void Client::update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> charfile) {
|
||||
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
|
||||
|
||||
Language lang = this->language();
|
||||
@@ -1050,7 +1045,7 @@ void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> cha
|
||||
charfile->guild_card.language = lang;
|
||||
}
|
||||
|
||||
void Client::update_bank_data_after_load(shared_ptr<PlayerBank> bank) {
|
||||
void Client::update_bank_data_after_load(std::shared_ptr<PlayerBank> bank) {
|
||||
auto s = this->require_server_state();
|
||||
auto limits = s->item_stack_limits(this->version());
|
||||
for (auto& item : bank->items) {
|
||||
@@ -1083,17 +1078,17 @@ void Client::save_all() {
|
||||
}
|
||||
|
||||
void Client::load_backup_character(uint32_t account_id, size_t index) {
|
||||
string filename = this->backup_character_filename(account_id, index, false);
|
||||
std::string filename = this->backup_character_filename(account_id, index, false);
|
||||
this->character_data = PSOCHARFile::load_shared(filename, false).character_file;
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->v1_v2_last_reported_disp.reset();
|
||||
}
|
||||
|
||||
shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
|
||||
string filename = this->backup_character_filename(account_id, index, true);
|
||||
auto ch = make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
||||
std::shared_ptr<PSOGCEp3CharacterFile::Character> Client::load_ep3_backup_character(uint32_t account_id, size_t index) {
|
||||
std::string filename = this->backup_character_filename(account_id, index, true);
|
||||
auto ch = std::make_shared<PSOGCEp3CharacterFile::Character>(phosg::load_object_file<PSOGCEp3CharacterFile::Character>(filename));
|
||||
this->character_data = PSOBBCharacterFile::create_from_file(*ch);
|
||||
this->ep3_config = make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
||||
this->ep3_config = std::make_shared<Episode3::PlayerConfig>(ch->ep3_config);
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->v1_v2_last_reported_disp.reset();
|
||||
return ch;
|
||||
|
||||
+1
-2
@@ -10,7 +10,6 @@
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
#include "Episode3/Tournament.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
@@ -27,7 +26,7 @@ struct Lobby;
|
||||
class Parsed6x70Data;
|
||||
|
||||
struct GetPlayerInfoResult {
|
||||
// Exactly one of the following two shared_ptrs is not null
|
||||
// Exactly one of the following two std::shared_ptrs is not null
|
||||
std::shared_ptr<PSOBBCharacterFile> character;
|
||||
std::shared_ptr<PSOGCEp3CharacterFile::Character> ep3_character;
|
||||
bool is_full_info; // True if the client sent 30; false if it was 61 or 98
|
||||
|
||||
+40
-42
@@ -18,8 +18,6 @@
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using Arch = ClientFunctionIndex::Function::Architecture;
|
||||
|
||||
const char* name_for_architecture(Arch arch) {
|
||||
@@ -31,7 +29,7 @@ const char* name_for_architecture(Arch arch) {
|
||||
case Arch::X86:
|
||||
return "x86";
|
||||
default:
|
||||
throw logic_error("invalid architecture");
|
||||
throw std::logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +42,7 @@ uint32_t specific_version_for_architecture(Arch arch) {
|
||||
case Arch::X86:
|
||||
return SPECIFIC_VERSION_X86_INDETERMINATE;
|
||||
default:
|
||||
throw logic_error("invalid architecture");
|
||||
throw std::logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +85,8 @@ const T& get_with_sv_fallback(
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string ClientFunctionIndex::Function::generate_client_command_t(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
std::string ClientFunctionIndex::Function::generate_client_command_t(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
@@ -102,11 +100,11 @@ string ClientFunctionIndex::Function::generate_client_command_t(
|
||||
|
||||
phosg::StringWriter w;
|
||||
if (!label_writes.empty()) {
|
||||
string modified_code = this->code;
|
||||
std::string modified_code = this->code;
|
||||
for (const auto& it : label_writes) {
|
||||
size_t offset = this->label_offsets.at(it.first);
|
||||
if (offset > modified_code.size() - 4) {
|
||||
throw runtime_error("label out of range");
|
||||
throw std::runtime_error("label out of range");
|
||||
}
|
||||
*reinterpret_cast<U32T<FooterT::IsBE>*>(modified_code.data() + offset) = it.second;
|
||||
}
|
||||
@@ -143,8 +141,8 @@ string ClientFunctionIndex::Function::generate_client_command_t(
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
string ClientFunctionIndex::Function::generate_client_command(
|
||||
const unordered_map<string, uint32_t>& label_writes,
|
||||
std::string ClientFunctionIndex::Function::generate_client_command(
|
||||
const std::unordered_map<std::string, uint32_t>& label_writes,
|
||||
const void* suffix_data,
|
||||
size_t suffix_size,
|
||||
uint32_t override_relocations_offset) const {
|
||||
@@ -153,11 +151,11 @@ string ClientFunctionIndex::Function::generate_client_command(
|
||||
} else if ((this->arch == Architecture::X86) || (this->arch == Architecture::SH4)) {
|
||||
return this->generate_client_command_t<false>(label_writes, suffix_data, suffix_size, override_relocations_offset);
|
||||
} else {
|
||||
throw logic_error("invalid architecture");
|
||||
throw std::logic_error("invalid architecture");
|
||||
}
|
||||
}
|
||||
|
||||
static unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||
static std::unordered_map<uint32_t, std::string> preprocess_function_code(const std::string& text) {
|
||||
std::unordered_set<uint32_t> all_specific_versions;
|
||||
struct Line {
|
||||
std::string text;
|
||||
@@ -170,7 +168,7 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
||||
auto& line = lines.emplace_back();
|
||||
line.text = std::move(line_text);
|
||||
|
||||
string stripped_line = line.text;
|
||||
std::string stripped_line = line.text;
|
||||
phosg::strip_whitespace(stripped_line);
|
||||
|
||||
if (stripped_line == ".all_versions") {
|
||||
@@ -190,7 +188,7 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
||||
|
||||
static const std::string empty_str = "";
|
||||
|
||||
unordered_map<uint32_t, std::string> ret;
|
||||
std::unordered_map<uint32_t, std::string> ret;
|
||||
for (uint32_t specific_version : all_specific_versions) {
|
||||
std::deque<std::string> version_lines;
|
||||
bool include_current_line = true;
|
||||
@@ -220,15 +218,15 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
||||
} else {
|
||||
std::string line_text = line.text;
|
||||
size_t vers_offset = line_text.find("<VERS ");
|
||||
while (vers_offset != string::npos) {
|
||||
while (vers_offset != std::string::npos) {
|
||||
size_t end_offset = line_text.find('>', vers_offset + 6);
|
||||
if (end_offset == string::npos) {
|
||||
throw runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
|
||||
if (end_offset == std::string::npos) {
|
||||
throw std::runtime_error(std::format("(version {}) (line {}) unterminated <VERS> replacement",
|
||||
str_for_specific_version(specific_version), line_znum + 1));
|
||||
}
|
||||
auto tokens = phosg::split(line_text.substr(vers_offset + 6, end_offset - vers_offset - 6), ' ');
|
||||
if (current_vers_index >= tokens.size()) {
|
||||
throw runtime_error(std::format("(version {}) (line {}) invalid <VERS> replacement",
|
||||
throw std::runtime_error(std::format("(version {}) (line {}) invalid <VERS> replacement",
|
||||
str_for_specific_version(specific_version), line_znum + 1));
|
||||
}
|
||||
line_text = line_text.substr(0, vers_offset) + tokens[current_vers_index] + line_text.substr(end_offset + 1);
|
||||
@@ -243,12 +241,12 @@ static unordered_map<uint32_t, std::string> preprocess_function_code(const std::
|
||||
return ret;
|
||||
}
|
||||
|
||||
ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_any_failure) {
|
||||
map<string, string> source_files;
|
||||
ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise_on_any_failure) {
|
||||
std::map<std::string, std::string> source_files;
|
||||
std::function<void(const std::string&)> add_directory = [&](const std::string& dir) -> void {
|
||||
for (const auto& item : std::filesystem::directory_iterator(dir)) {
|
||||
string item_name = item.path().filename().string();
|
||||
string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
|
||||
std::string item_name = item.path().filename().string();
|
||||
std::string item_path = dir.ends_with("/") ? (dir + item_name) : (dir + "/" + item_name);
|
||||
if (std::filesystem::is_directory(item_path)) {
|
||||
add_directory(item_path);
|
||||
} else if (item_path.ends_with(".s") && std::filesystem::is_regular_file(item_path)) {
|
||||
@@ -268,7 +266,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
};
|
||||
add_directory(root_dir);
|
||||
|
||||
unordered_map<string, string> include_cache;
|
||||
std::unordered_map<std::string, std::string> include_cache;
|
||||
uint32_t last_menu_item_id = 0;
|
||||
for (const auto& [source_filename, source] : source_files) {
|
||||
if (!source_filename.ends_with(".s")) {
|
||||
@@ -288,15 +286,15 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
}
|
||||
|
||||
for (const auto& [specific_version, source] : preprocessed) {
|
||||
shared_ptr<Function> fn = make_shared<Function>();
|
||||
std::shared_ptr<Function> fn = std::make_shared<Function>();
|
||||
fn->short_name = source_filename.substr(0, source_filename.size() - 2);
|
||||
fn->specific_version = specific_version;
|
||||
fn->menu_item_id = ++last_menu_item_id;
|
||||
fn->arch = architecture_for_specific_version(fn->specific_version);
|
||||
|
||||
try {
|
||||
unordered_set<string> get_include_stack;
|
||||
function<string(const string&, uint32_t)> get_include_for_sv = [&include_cache, &source_files, &get_include_stack, &get_include_for_sv](const string& name, uint32_t specific_version) -> string {
|
||||
std::unordered_set<std::string> get_include_stack;
|
||||
std::function<std::string(const std::string&, uint32_t)> get_include_for_sv = [&include_cache, &source_files, &get_include_stack, &get_include_for_sv](const std::string& name, uint32_t specific_version) -> std::string {
|
||||
try {
|
||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||
} catch (const std::out_of_range&) {
|
||||
@@ -309,7 +307,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
auto it = source_files.find(name + ".inc.s");
|
||||
if (it != source_files.end()) {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("Mutual recursion between includes: " + name);
|
||||
throw std::runtime_error("Mutual recursion between includes: " + name);
|
||||
}
|
||||
for (const auto& [include_specific_version, include_source] : preprocess_function_code(it->second)) {
|
||||
ResourceDASM::EmulatorBase::AssembleResult ret;
|
||||
@@ -325,7 +323,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
ret = ResourceDASM::SH4Emulator::assemble(include_source, get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown architecture");
|
||||
throw std::runtime_error("unknown architecture");
|
||||
}
|
||||
if (client_functions_log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
client_functions_log.debug_f("({}) Compiled include {}-{}",
|
||||
@@ -347,7 +345,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
return get_with_sv_fallback(include_cache, name, specific_version);
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"Data not found for include {} ({})", name, str_for_specific_version(specific_version)));
|
||||
};
|
||||
|
||||
@@ -365,7 +363,7 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
assembled = ResourceDASM::SH4Emulator::assemble(source, get_include);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid architecture");
|
||||
throw std::runtime_error("invalid architecture");
|
||||
}
|
||||
|
||||
fn->code = std::move(assembled.code);
|
||||
@@ -396,17 +394,17 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
} else if (key == "show_return_value") {
|
||||
fn->show_return_value = true;
|
||||
} else {
|
||||
throw runtime_error("unknown metadata key: " + key);
|
||||
throw std::runtime_error("unknown metadata key: " + key);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fn->entrypoint_offset_offset = fn->label_offsets.at("entry_ptr");
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("code does not contain entry_ptr label");
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("code does not contain entry_ptr label");
|
||||
}
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
std::set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : fn->label_offsets) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
@@ -416,13 +414,13 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
for (const auto& it : reloc_indexes) {
|
||||
uint32_t delta = it - prev_index;
|
||||
if (delta > 0xFFFF) {
|
||||
throw runtime_error("relocation delta too far away");
|
||||
throw std::runtime_error("relocation delta too far away");
|
||||
}
|
||||
fn->relocation_deltas.emplace_back(delta);
|
||||
prev_index = it;
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (raise_on_any_failure) {
|
||||
throw;
|
||||
}
|
||||
@@ -448,18 +446,18 @@ ClientFunctionIndex::ClientFunctionIndex(const string& root_dir, bool raise_on_a
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Menu> ClientFunctionIndex::patch_switches_menu(
|
||||
std::shared_ptr<const Menu> ClientFunctionIndex::patch_switches_menu(
|
||||
uint32_t specific_version,
|
||||
const std::unordered_set<std::string>& server_auto_patches_enabled,
|
||||
const std::unordered_set<std::string>& client_auto_patches_enabled) const {
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
auto ret = std::make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
auto map_it = this->functions_by_specific_version.find(specific_version);
|
||||
if (map_it != this->functions_by_specific_version.end()) {
|
||||
for (auto [name, fn] : map_it->second) {
|
||||
if (fn->appears_in_patches_menu() && !server_auto_patches_enabled.count(fn->short_name)) {
|
||||
string item_text;
|
||||
std::string item_text;
|
||||
item_text.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-');
|
||||
item_text += fn->long_name.empty() ? fn->short_name : fn->long_name;
|
||||
ret->items.emplace_back(
|
||||
@@ -496,7 +494,7 @@ std::shared_ptr<const ClientFunctionIndex::Function> ClientFunctionIndex::get_by
|
||||
}
|
||||
|
||||
uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
static unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
||||
static std::unordered_map<uint32_t, uint32_t> checksum_to_specific_version;
|
||||
if (checksum_to_specific_version.empty()) {
|
||||
struct {
|
||||
char system_code = 'G';
|
||||
@@ -517,7 +515,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33000030 | (*game_code2 << 16) | (*region_code << 8) | version_code;
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
throw std::logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -529,7 +527,7 @@ uint32_t specific_version_for_gc_header_checksum(uint32_t header_checksum) {
|
||||
uint32_t checksum = phosg::crc32(&data, sizeof(data));
|
||||
uint32_t specific_version = 0x33004A54 | (*game_code2 << 16);
|
||||
if (!checksum_to_specific_version.emplace(checksum, specific_version).second) {
|
||||
throw logic_error("multiple specific_versions have same header checksum");
|
||||
throw std::logic_error("multiple specific_versions have same header checksum");
|
||||
}
|
||||
data.system_code = 'G';
|
||||
}
|
||||
|
||||
+42
-44
@@ -6,8 +6,6 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename IntT, size_t Count>
|
||||
phosg::JSON to_json(const parray<IntT, Count>& v) {
|
||||
auto ret = phosg::JSON::list();
|
||||
@@ -20,7 +18,7 @@ phosg::JSON to_json(const parray<IntT, Count>& v) {
|
||||
template <typename IntT, size_t Count>
|
||||
void from_json_into(const phosg::JSON& json, parray<IntT, Count>& ret) {
|
||||
if (json.size() != Count) {
|
||||
throw runtime_error("incorrect array length");
|
||||
throw std::runtime_error("incorrect array length");
|
||||
}
|
||||
for (size_t z = 0; z < Count; z++) {
|
||||
ret[z] = json.at(z).as_int();
|
||||
@@ -39,7 +37,7 @@ phosg::JSON to_json(const parray<CommonItemSet::Table::Range<IntT>, Count>& v) {
|
||||
template <typename IntT, size_t Count>
|
||||
void from_json_into(const phosg::JSON& json, parray<CommonItemSet::Table::Range<IntT>, Count>& ret) {
|
||||
if (json.size() != Count) {
|
||||
throw runtime_error("incorrect array length");
|
||||
throw std::runtime_error("incorrect array length");
|
||||
}
|
||||
for (size_t z = 0; z < Count; z++) {
|
||||
from_json_into(json.at(z), ret[z]);
|
||||
@@ -60,7 +58,7 @@ void from_json_into(const phosg::JSON& json, CommonItemSet::Table::Range<IntT>&
|
||||
} else {
|
||||
const auto& l = json.as_list();
|
||||
if (l.size() != 2) {
|
||||
throw runtime_error("incorrect range list length");
|
||||
throw std::runtime_error("incorrect range list length");
|
||||
}
|
||||
ret.min = l.at(0)->as_int();
|
||||
ret.max = l.at(1)->as_int();
|
||||
@@ -79,7 +77,7 @@ phosg::JSON to_json(const parray<parray<IntT, Count2>, Count1>& v) {
|
||||
template <typename IntT, size_t Count1, size_t Count2>
|
||||
void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1>& ret) {
|
||||
if (json.size() != Count1) {
|
||||
throw runtime_error("incorrect array length");
|
||||
throw std::runtime_error("incorrect array length");
|
||||
}
|
||||
for (size_t z = 0; z < Count1; z++) {
|
||||
from_json_into(json.at(z), ret[z]);
|
||||
@@ -89,7 +87,7 @@ void from_json_into(const phosg::JSON& json, parray<parray<IntT, Count2>, Count1
|
||||
template <typename IntT, size_t Count1, size_t Count2>
|
||||
void from_json_into(const phosg::JSON& json, parray<parray<CommonItemSet::Table::Range<IntT>, Count2>, Count1>& ret) {
|
||||
if (json.size() != Count1) {
|
||||
throw runtime_error("incorrect array length");
|
||||
throw std::runtime_error("incorrect array length");
|
||||
}
|
||||
for (size_t z = 0; z < Count1; z++) {
|
||||
from_json_into(json.at(z), ret[z]);
|
||||
@@ -139,7 +137,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
from_json_into(*dict.at(phosg::name_for_enum(enemy_type)), this->enemy_type_meseta_ranges[enemy_type]);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -151,7 +149,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
this->enemy_type_drop_probs[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -163,7 +161,7 @@ CommonItemSet::Table::Table(std::shared_ptr<const Table> prev_table, const phosg
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
this->enemy_type_item_classes[enemy_type] = dict.at(phosg::name_for_enum(enemy_type))->as_int();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -208,13 +206,13 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
def.rt_index, phosg::name_for_enum(enemy_type),
|
||||
meseta_range.min, meseta_range.max, drop_prob, item_class,
|
||||
name_for_common_item_class(item_class));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{:<23} ----- ----- ---- --:-------\n",
|
||||
def.rt_index, phosg::name_for_enum(enemy_type));
|
||||
}
|
||||
}
|
||||
|
||||
static const array<const char*, 12> base_weapon_type_names = {
|
||||
static const std::array<const char*, 12> base_weapon_type_names = {
|
||||
"SABER", "SWORD", "DAGGER", "PARTISAN", "SLICER", "HANDGUN", "RIFLE", "MECHGUN", "SHOT", "CANE", "ROD", "WAND"};
|
||||
phosg::fwrite_fmt(stream, "Base weapon config:\n");
|
||||
phosg::fwrite_fmt(stream, " TYPE PROB [SB AL] FLOORS\n");
|
||||
@@ -295,7 +293,7 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
fputc('\n', stream);
|
||||
}
|
||||
|
||||
static const array<const char*, 19> technique_names = {
|
||||
static const std::array<const char*, 19> technique_names = {
|
||||
"FOIE ",
|
||||
"GIFOIE ",
|
||||
"RAFOIE ",
|
||||
@@ -392,7 +390,7 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
}
|
||||
|
||||
auto format_enemy_range_table = [&](const std::unordered_map<EnemyType, Range<uint16_t>>& table) -> std::string {
|
||||
string ret = "";
|
||||
std::string ret = "";
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
const auto& range = table.at(enemy_type);
|
||||
@@ -400,13 +398,13 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
ret += ",";
|
||||
}
|
||||
ret += std::format("{}=[{},{}]", phosg::name_for_enum(enemy_type), range.min, range.max);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
auto format_enemy_u8_table = [&](const std::unordered_map<EnemyType, uint8_t>& table) -> std::string {
|
||||
string ret = "";
|
||||
std::string ret = "";
|
||||
for (auto enemy_type : phosg::EnumRange<EnemyType>()) {
|
||||
try {
|
||||
uint8_t value = table.at(enemy_type);
|
||||
@@ -414,7 +412,7 @@ void CommonItemSet::Table::print_diff(FILE* stream, const Table& other) const {
|
||||
ret += ",";
|
||||
}
|
||||
ret += std::format("{}={}", phosg::name_for_enum(enemy_type), value);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -620,7 +618,7 @@ phosg::JSON CommonItemSet::json() const {
|
||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||
ret.emplace(json_key, table->json(prev_table));
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -642,7 +640,7 @@ void CommonItemSet::print(FILE* stream) const {
|
||||
name_for_difficulty(difficulty),
|
||||
name_for_section_id(section_id));
|
||||
table->print(stream);
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,15 +653,15 @@ void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const {
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
for (const auto& difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
shared_ptr<const Table> this_table;
|
||||
shared_ptr<const Table> other_table;
|
||||
std::shared_ptr<const Table> this_table;
|
||||
std::shared_ptr<const Table> other_table;
|
||||
try {
|
||||
this_table = this->get_table(episode, mode, difficulty, section_id);
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
try {
|
||||
other_table = other.get_table(episode, mode, difficulty, section_id);
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const std::runtime_error&) {
|
||||
}
|
||||
|
||||
if (!this_table && !other_table) {
|
||||
@@ -789,17 +787,17 @@ std::string CommonItemSet::json_key_for_table(
|
||||
token_name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
}
|
||||
|
||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||
std::shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||
try {
|
||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, section_id));
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
||||
name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), section_id));
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const CommonItemSet::Table> CommonItemSet::get_prev_table(
|
||||
std::shared_ptr<const CommonItemSet::Table> CommonItemSet::get_prev_table(
|
||||
Episode episode, GameMode mode, Difficulty difficulty, uint8_t section_id) const {
|
||||
if (section_id != 0) {
|
||||
// All section IDs are based on the previous, except Viridia
|
||||
@@ -837,7 +835,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
auto entry = pt_afs.get(static_cast<size_t>(difficulty) * 10 + section_id);
|
||||
phosg::StringReader r(entry.first, entry.second);
|
||||
auto table = make_shared<Table>(r, false, false, Episode::EP1);
|
||||
auto table = std::make_shared<Table>(r, false, false, Episode::EP1);
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table);
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::BATTLE, difficulty, section_id), table);
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::SOLO, difficulty, section_id), table);
|
||||
@@ -861,7 +859,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
continue;
|
||||
}
|
||||
auto r = ct_afs.get_reader(static_cast<size_t>(difficulty) * 10);
|
||||
auto table = make_shared<Table>(r, false, false, Episode::EP1);
|
||||
auto table = std::make_shared<Table>(r, false, false, Episode::EP1);
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table);
|
||||
}
|
||||
@@ -872,7 +870,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet(
|
||||
GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gsl_data, bool is_big_endian) {
|
||||
GSLArchive gsl(gsl_data, is_big_endian);
|
||||
|
||||
auto filename_for_table = +[](Episode episode, Difficulty difficulty, uint8_t section_id, bool is_challenge) -> string {
|
||||
auto filename_for_table = +[](Episode episode, Difficulty difficulty, uint8_t section_id, bool is_challenge) -> std::string {
|
||||
const char* episode_token = "";
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
@@ -885,7 +883,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
episode_token = "s";
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid episode");
|
||||
throw std::runtime_error("invalid episode");
|
||||
}
|
||||
return std::format(
|
||||
"ItemPT{}{}{}{}.rel",
|
||||
@@ -901,7 +899,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
phosg::StringReader r;
|
||||
try {
|
||||
r = gsl.get_reader(filename_for_table(episode, difficulty, section_id, false));
|
||||
} catch (const exception&) {
|
||||
} catch (const std::exception&) {
|
||||
// Fall back to Episode 1 if Episode 4 data is missing
|
||||
if (episode == Episode::EP4) {
|
||||
auto ep1_table = this->tables.at(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id));
|
||||
@@ -913,7 +911,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
throw;
|
||||
}
|
||||
}
|
||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
||||
auto table = std::make_shared<Table>(r, is_big_endian, true, episode);
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::NORMAL, difficulty, section_id), table);
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::BATTLE, difficulty, section_id), table);
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::SOLO, difficulty, section_id), table);
|
||||
@@ -923,11 +921,11 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
try {
|
||||
auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true));
|
||||
auto table = make_shared<Table>(r, is_big_endian, true, episode);
|
||||
auto table = std::make_shared<Table>(r, is_big_endian, true, episode);
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
this->tables.emplace(this->key_for_table(episode, GameMode::CHALLENGE, difficulty, section_id), table);
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// GC NTE doesn't have Ep2 challenge; just skip adding the table
|
||||
}
|
||||
}
|
||||
@@ -943,9 +941,9 @@ JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& json) {
|
||||
auto prev_table = this->get_prev_table(episode, mode, difficulty, section_id);
|
||||
auto json_key = this->json_key_for_table(episode, mode, difficulty, section_id);
|
||||
auto key = this->key_for_table(episode, mode, difficulty, section_id);
|
||||
this->tables.emplace(key, make_shared<Table>(prev_table, json.at(json_key), episode));
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const out_of_range&) {
|
||||
this->tables.emplace(key, std::make_shared<Table>(prev_table, json.at(json_key), episode));
|
||||
} catch (const std::runtime_error&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,21 +983,21 @@ ToolRandomSet::ToolRandomSet(std::shared_ptr<const std::string> data) : RELFileS
|
||||
this->tech_disk_level_table_spec = &r.pget<TableSpec>(r.pget_u32b(specs_offset + 2 * sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
pair<const uint8_t*, size_t> ToolRandomSet::get_common_recovery_table(size_t index) const {
|
||||
std::pair<const uint8_t*, size_t> ToolRandomSet::get_common_recovery_table(size_t index) const {
|
||||
return this->get_table<uint8_t>(*this->common_recovery_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
std::pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
ToolRandomSet::get_rare_recovery_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(*this->rare_recovery_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
std::pair<const ToolRandomSet::WeightTableEntry8*, size_t>
|
||||
ToolRandomSet::get_tech_disk_table(size_t index) const {
|
||||
return this->get_table<WeightTableEntry8>(*this->tech_disk_table_spec, index);
|
||||
}
|
||||
|
||||
pair<const ToolRandomSet::TechDiskLevelEntry*, size_t>
|
||||
std::pair<const ToolRandomSet::TechDiskLevelEntry*, size_t>
|
||||
ToolRandomSet::get_tech_disk_level_table(size_t index) const {
|
||||
return this->get_table<TechDiskLevelEntry>(*this->tech_disk_level_table_spec, index);
|
||||
}
|
||||
@@ -1013,7 +1011,7 @@ std::pair<const WeaponRandomSet::WeightTableEntry8*, size_t>
|
||||
WeaponRandomSet::get_weapon_type_table(size_t index) const {
|
||||
const auto& spec = this->r.pget<TableSpec>(this->offsets->weapon_type_table + index * sizeof(TableSpec));
|
||||
const auto* data = &this->r.pget<WeightTableEntry8>(spec.offset, spec.entries_per_table * sizeof(WeightTableEntry8));
|
||||
return make_pair(data, spec.entries_per_table);
|
||||
return std::make_pair(data, spec.entries_per_table);
|
||||
}
|
||||
|
||||
const parray<WeaponRandomSet::WeightTableEntry32, 6>*
|
||||
@@ -1055,7 +1053,7 @@ const ProbabilityTable<uint8_t, 100>& TekkerAdjustmentSet::get_table(
|
||||
bool favored,
|
||||
uint8_t section_id) const {
|
||||
if (section_id >= 10) {
|
||||
throw runtime_error("invalid section ID");
|
||||
throw std::runtime_error("invalid section ID");
|
||||
}
|
||||
ProbabilityTable<uint8_t, 100>& table = favored ? tables_favored[section_id] : tables_default[section_id];
|
||||
if (table.count == 0) {
|
||||
|
||||
+45
-48
@@ -11,8 +11,6 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <>
|
||||
const char* phosg::name_for_enum<CompressPhase>(CompressPhase v) {
|
||||
switch (v) {
|
||||
@@ -34,13 +32,13 @@ struct WindowIndex {
|
||||
const uint8_t* data;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
set<size_t, function<bool(size_t, size_t)>> index;
|
||||
std::set<size_t, std::function<bool(size_t, size_t)>> index;
|
||||
|
||||
WindowIndex(const void* data, size_t size)
|
||||
: data(reinterpret_cast<const uint8_t*>(data)),
|
||||
size(size),
|
||||
offset(0),
|
||||
index(bind(&WindowIndex::set_comparator, this, placeholders::_1, placeholders::_2)) {}
|
||||
index(std::bind(&WindowIndex::set_comparator, this, std::placeholders::_1, std::placeholders::_2)) {}
|
||||
|
||||
void advance() {
|
||||
if (this->offset >= WindowLength) {
|
||||
@@ -69,7 +67,7 @@ struct WindowIndex {
|
||||
// strings far too much. We can solve this by instead storing the offset of each string as keys in a set and using a
|
||||
// custom comparator to treat them as references to binary strings within the data.
|
||||
bool set_comparator(size_t a, size_t b) const {
|
||||
size_t max_length = min<size_t>(MaxMatchLength, this->size - max<size_t>(a, b));
|
||||
size_t max_length = std::min<size_t>(MaxMatchLength, this->size - std::max<size_t>(a, b));
|
||||
size_t end_a = a + max_length;
|
||||
for (; a < end_a; a++, b++) {
|
||||
uint8_t data_a = static_cast<uint8_t>(this->data[a]);
|
||||
@@ -83,7 +81,7 @@ struct WindowIndex {
|
||||
return a < b; // Maximum-length match; order them by offset
|
||||
};
|
||||
|
||||
pair<size_t, size_t> get_best_match() const {
|
||||
std::pair<size_t, size_t> get_best_match() const {
|
||||
// Find the best match from the index. It's unlikely that we'll get an exact match, so check the entry before the
|
||||
// upper_bound result too. Note: We use upper_bound rather than lower_bound because in PRS, a backreference can be
|
||||
// encoded with fewer bits if it's close to the decompression offset, and this makes us pick the latest match by
|
||||
@@ -108,7 +106,7 @@ struct WindowIndex {
|
||||
match_size = new_match_size;
|
||||
}
|
||||
}
|
||||
return make_pair(match_offset, match_size);
|
||||
return std::make_pair(match_offset, match_size);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,7 +138,7 @@ struct LZSSInterleavedWriter {
|
||||
|
||||
void write_control(bool v) {
|
||||
if (this->next_control_bit == 0) {
|
||||
throw logic_error("write_control called with no space to write");
|
||||
throw std::logic_error("write_control called with no space to write");
|
||||
}
|
||||
if (v) {
|
||||
this->buf[0] |= this->next_control_bit;
|
||||
@@ -208,15 +206,15 @@ struct PRSPathNode {
|
||||
size_t to_offset = 0;
|
||||
};
|
||||
|
||||
string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
std::string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||
|
||||
vector<PRSPathNode> nodes;
|
||||
std::vector<PRSPathNode> nodes;
|
||||
nodes.resize(in_size + 1);
|
||||
nodes[0].bits_used = 18; // Stop command: 2 control bits and 2 data bytes
|
||||
|
||||
size_t copy_progress_max = 3 * in_size;
|
||||
atomic<size_t> copy_progress = 0;
|
||||
std::atomic<size_t> copy_progress = 0;
|
||||
|
||||
// Populate all possible short copies
|
||||
std::thread short_window_thread([&]() -> void {
|
||||
@@ -361,14 +359,14 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
switch (next_node.from_command_type) {
|
||||
case PRSPathNode::CommandType::LITERAL:
|
||||
if (copy_size != 1) {
|
||||
throw logic_error("incorrect size for LITERAL copy type");
|
||||
throw std::logic_error("incorrect size for LITERAL copy type");
|
||||
}
|
||||
w.write_control(true);
|
||||
w.write_data(in_data[offset]);
|
||||
break;
|
||||
case PRSPathNode::CommandType::SHORT_COPY: {
|
||||
if (copy_size < 2 || copy_size > 5) {
|
||||
throw logic_error("incorrect size for SHORT_COPY copy type");
|
||||
throw std::logic_error("incorrect size for SHORT_COPY copy type");
|
||||
}
|
||||
uint8_t encoded_size = copy_size - 2;
|
||||
w.write_control(false);
|
||||
@@ -383,7 +381,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
}
|
||||
case PRSPathNode::CommandType::LONG_COPY: {
|
||||
if (copy_size < 2 || copy_size > 9) {
|
||||
throw logic_error("incorrect size for LONG_COPY copy type");
|
||||
throw std::logic_error("incorrect size for LONG_COPY copy type");
|
||||
}
|
||||
w.write_control(false);
|
||||
w.flush_if_ready();
|
||||
@@ -395,7 +393,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
}
|
||||
case PRSPathNode::CommandType::EXTENDED_COPY: {
|
||||
if (copy_size < 1 || copy_size > 0x100) {
|
||||
throw logic_error("incorrect size for EXTENDED_COPY copy type");
|
||||
throw std::logic_error("incorrect size for EXTENDED_COPY copy type");
|
||||
}
|
||||
w.write_control(false);
|
||||
w.flush_if_ready();
|
||||
@@ -407,7 +405,7 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid copy type in shortest path");
|
||||
throw std::logic_error("invalid copy type in shortest path");
|
||||
}
|
||||
w.flush_if_ready();
|
||||
|
||||
@@ -424,11 +422,11 @@ string prs_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
return std::move(w.close());
|
||||
}
|
||||
|
||||
string prs_compress_optimal(const string& data, ProgressCallback progress_fn) {
|
||||
std::string prs_compress_optimal(const std::string& data, ProgressCallback progress_fn) {
|
||||
return prs_compress_optimal(data.data(), data.size(), progress_fn);
|
||||
}
|
||||
|
||||
string prs_compress_pessimal(const void* vdata, size_t size) {
|
||||
std::string prs_compress_pessimal(const void* vdata, size_t size) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(vdata);
|
||||
|
||||
// The worst possible encoding we can do is a literal byte when no byte with the same value is within the window, or
|
||||
@@ -479,7 +477,7 @@ PRSCompressor::PRSCompressor(
|
||||
|
||||
void PRSCompressor::add(const void* data, size_t size) {
|
||||
if (this->closed) {
|
||||
throw logic_error("compressor is closed");
|
||||
throw std::logic_error("compressor is closed");
|
||||
}
|
||||
|
||||
phosg::StringReader r(data, size);
|
||||
@@ -488,7 +486,7 @@ void PRSCompressor::add(const void* data, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
void PRSCompressor::add(const string& data) {
|
||||
void PRSCompressor::add(const std::string& data) {
|
||||
this->add(data.data(), data.size());
|
||||
}
|
||||
|
||||
@@ -573,7 +571,7 @@ void PRSCompressor::advance() {
|
||||
this->advance_extended_copy(backreference_offset, best_match_size);
|
||||
|
||||
} else {
|
||||
throw logic_error("invalid best match");
|
||||
throw std::logic_error("invalid best match");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -621,7 +619,7 @@ void PRSCompressor::advance_extended_copy(ssize_t offset, size_t size) {
|
||||
this->move_forward_data_to_reverse_log(size);
|
||||
}
|
||||
|
||||
string& PRSCompressor::close() {
|
||||
std::string& PRSCompressor::close() {
|
||||
if (!this->closed) {
|
||||
// Advance until all input is consumed
|
||||
while (this->reverse_log.end_offset() < this->input_bytes) {
|
||||
@@ -658,23 +656,23 @@ void PRSCompressor::flush_control() {
|
||||
this->output.pput_u8(this->control_byte_offset, this->pending_control_bits & 0xFF);
|
||||
} else {
|
||||
if (this->control_byte_offset != this->output.size() - 1) {
|
||||
throw logic_error("data written without control bits");
|
||||
throw std::logic_error("data written without control bits");
|
||||
}
|
||||
this->output.str().resize(this->output.str().size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
string prs_compress(const void* vdata, size_t size, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||
std::string prs_compress(const void* vdata, size_t size, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||
PRSCompressor prs(compression_level, progress_fn);
|
||||
prs.add(vdata, size);
|
||||
return std::move(prs.close());
|
||||
}
|
||||
|
||||
string prs_compress(const string& data, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||
std::string prs_compress(const std::string& data, ssize_t compression_level, ProgressCallback progress_fn) {
|
||||
return prs_compress(data.data(), data.size(), compression_level, progress_fn);
|
||||
}
|
||||
|
||||
string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
std::string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||
|
||||
LZSSInterleavedWriter w;
|
||||
@@ -779,12 +777,12 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
}
|
||||
case PRSPathNode::CommandType::NONE:
|
||||
default:
|
||||
throw logic_error("invalid command type");
|
||||
throw std::logic_error("invalid command type");
|
||||
}
|
||||
w.flush_if_ready();
|
||||
|
||||
if (bytes_consumed == 0) {
|
||||
throw logic_error("no input data was consumed");
|
||||
throw std::logic_error("no input data was consumed");
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < bytes_consumed; z++) {
|
||||
@@ -804,7 +802,7 @@ string prs_compress_indexed(const void* in_data_v, size_t in_size, ProgressCallb
|
||||
return std::move(w.close());
|
||||
}
|
||||
|
||||
string prs_compress_indexed(const string& data, ProgressCallback progress_fn) {
|
||||
std::string prs_compress_indexed(const std::string& data, ProgressCallback progress_fn) {
|
||||
return prs_compress_indexed(data.data(), data.size(), progress_fn);
|
||||
}
|
||||
|
||||
@@ -846,7 +844,7 @@ PRSDecompressResult prs_decompress_with_meta(
|
||||
if (allow_unterminated) {
|
||||
return {std::move(w.str()), r.where()};
|
||||
} else {
|
||||
throw runtime_error("maximum output size exceeded");
|
||||
throw std::runtime_error("maximum output size exceeded");
|
||||
}
|
||||
}
|
||||
w.put_u8(r.get_u8());
|
||||
@@ -882,14 +880,14 @@ PRSDecompressResult prs_decompress_with_meta(
|
||||
// support ranges that cover the current end of the output.
|
||||
size_t read_offset = w.size() + offset;
|
||||
if (read_offset >= w.size()) {
|
||||
throw runtime_error("backreference offset beyond beginning of output");
|
||||
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||
}
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
if (max_output_size && w.size() == max_output_size) {
|
||||
if (allow_unterminated) {
|
||||
return {std::move(w.str()), r.where()};
|
||||
} else {
|
||||
throw out_of_range("maximum output size exceeded");
|
||||
throw std::out_of_range("maximum output size exceeded");
|
||||
}
|
||||
}
|
||||
w.put_u8(w.str()[read_offset + z]);
|
||||
@@ -900,16 +898,16 @@ PRSDecompressResult prs_decompress_with_meta(
|
||||
return {std::move(w.str()), r.where()};
|
||||
}
|
||||
|
||||
PRSDecompressResult prs_decompress_with_meta(const string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
PRSDecompressResult prs_decompress_with_meta(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
return prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||
}
|
||||
|
||||
string prs_decompress(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
|
||||
std::string prs_decompress(const void* data, size_t size, size_t max_output_size, bool allow_unterminated) {
|
||||
auto ret = prs_decompress_with_meta(data, size, max_output_size, allow_unterminated);
|
||||
return std::move(ret.data);
|
||||
}
|
||||
|
||||
string prs_decompress(const string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
std::string prs_decompress(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
auto ret = prs_decompress_with_meta(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||
return std::move(ret.data);
|
||||
}
|
||||
@@ -945,7 +943,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
||||
|
||||
size_t read_offset = ret + offset;
|
||||
if (read_offset >= ret) {
|
||||
throw runtime_error("backreference offset beyond beginning of output");
|
||||
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||
}
|
||||
ret += count;
|
||||
}
|
||||
@@ -954,7 +952,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
||||
if (allow_unterminated) {
|
||||
return max_output_size;
|
||||
} else {
|
||||
throw out_of_range("maximum output size exceeded");
|
||||
throw std::out_of_range("maximum output size exceeded");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -962,7 +960,7 @@ size_t prs_decompress_size(const void* data, size_t size, size_t max_output_size
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t prs_decompress_size(const string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
size_t prs_decompress_size(const std::string& data, size_t max_output_size, bool allow_unterminated) {
|
||||
return prs_decompress_size(data.data(), data.size(), max_output_size, allow_unterminated);
|
||||
}
|
||||
|
||||
@@ -1015,7 +1013,7 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
}
|
||||
|
||||
if (read_offset >= output_bytes) {
|
||||
throw runtime_error("backreference offset beyond beginning of output");
|
||||
throw std::runtime_error("backreference offset beyond beginning of output");
|
||||
}
|
||||
output_bytes += count;
|
||||
}
|
||||
@@ -1043,11 +1041,10 @@ struct BC0PathNode {
|
||||
size_t to_offset = 0;
|
||||
};
|
||||
|
||||
string bc0_compress_optimal(
|
||||
const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
std::string bc0_compress_optimal(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||
|
||||
vector<BC0PathNode> nodes;
|
||||
std::vector<BC0PathNode> nodes;
|
||||
nodes.resize(in_size + 1);
|
||||
nodes[0].bits_used = 0;
|
||||
|
||||
@@ -1140,11 +1137,11 @@ string bc0_compress_optimal(
|
||||
return std::move(w.close());
|
||||
}
|
||||
|
||||
string bc0_compress(const string& data, ProgressCallback progress_fn) {
|
||||
std::string bc0_compress(const std::string& data, ProgressCallback progress_fn) {
|
||||
return bc0_compress(data.data(), data.size(), progress_fn);
|
||||
}
|
||||
|
||||
string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
std::string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback progress_fn) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||
|
||||
LZSSInterleavedWriter w;
|
||||
@@ -1180,7 +1177,7 @@ string bc0_compress(const void* in_data_v, size_t in_size, ProgressCallback prog
|
||||
return std::move(w.close());
|
||||
}
|
||||
|
||||
string bc0_encode(const void* in_data_v, size_t in_size) {
|
||||
std::string bc0_encode(const void* in_data_v, size_t in_size) {
|
||||
const uint8_t* in_data = reinterpret_cast<const uint8_t*>(in_data_v);
|
||||
|
||||
LZSSInterleavedWriter w;
|
||||
@@ -1197,11 +1194,11 @@ string bc0_encode(const void* in_data_v, size_t in_size) {
|
||||
// the output buffer. It is unlikely that this can be usefully exploited (e.g. for RCE) because the output pointer is
|
||||
// loaded from memory before every byte is written, so we cannot change the output pointer to any arbitrary address.
|
||||
|
||||
string bc0_decompress(const string& data) {
|
||||
std::string bc0_decompress(const std::string& data) {
|
||||
return bc0_decompress(data.data(), data.size());
|
||||
}
|
||||
|
||||
string bc0_decompress(const void* data, size_t size) {
|
||||
std::string bc0_decompress(const void* data, size_t size) {
|
||||
phosg::StringReader r(data, size);
|
||||
phosg::StringWriter w;
|
||||
|
||||
@@ -1265,7 +1262,7 @@ string bc0_decompress(const void* data, size_t size) {
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
void bc0_disassemble(FILE* stream, const string& data) {
|
||||
void bc0_disassemble(FILE* stream, const std::string& data) {
|
||||
bc0_disassemble(stream, data.data(), data.size());
|
||||
}
|
||||
|
||||
|
||||
+27
-28
@@ -9,8 +9,6 @@
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const uint32_t primes1[] = {
|
||||
0x65, 0x67, 0x6B, 0x6D, 0x71, 0x7F, 0x83, 0x89, 0x8B, 0x95, 0x97, 0x9D, 0xA3, 0xA7, 0xAD, 0xB3, 0xB5, 0xBF, 0xC1,
|
||||
0xC5, 0xC7, 0xD3, 0xDF, 0xE3, 0xE5, 0xE9, 0xEF, 0xF1, 0xFB, 0x101, 0x107, 0x10D, 0x10F, 0x115, 0x119, 0x11B, 0x125,
|
||||
@@ -735,10 +733,10 @@ static const uint32_t primes3[] = {
|
||||
static constexpr size_t num_primes3 = sizeof(primes3) / sizeof(primes3[0]);
|
||||
|
||||
static bool check_prime3(uint64_t prime3) {
|
||||
static vector<bool> primes3_set;
|
||||
static mutex primes3_init_mutex;
|
||||
static std::vector<bool> primes3_set;
|
||||
static std::mutex primes3_init_mutex;
|
||||
if (primes3_set.empty()) {
|
||||
lock_guard g(primes3_init_mutex);
|
||||
std::lock_guard g(primes3_init_mutex);
|
||||
if (primes3_set.empty()) {
|
||||
size_t primes3_set_size = primes3[num_primes3 - 1] - primes3[0] + 1;
|
||||
primes3_set.resize(primes3_set_size, false);
|
||||
@@ -790,7 +788,7 @@ static char replace_char_reverse(char ch) {
|
||||
|
||||
static constexpr uint64_t INVALID_PRODUCT = 0xFFFFFFFFFFFFFFFF;
|
||||
|
||||
static uint64_t decode_dc_serial_number_str(const string& s) {
|
||||
static uint64_t decode_dc_serial_number_str(const std::string& s) {
|
||||
if (s.size() != 8) {
|
||||
return INVALID_PRODUCT;
|
||||
}
|
||||
@@ -828,20 +826,20 @@ static uint32_t encode_dc_serial_number_int(uint32_t v) {
|
||||
(replace_nybble_reverse(v));
|
||||
}
|
||||
|
||||
static pair<size_t, size_t> compute_offset1_and_limit1(uint8_t domain, uint8_t subdomain) {
|
||||
static std::pair<size_t, size_t> compute_offset1_and_limit1(uint8_t domain, uint8_t subdomain) {
|
||||
if (domain > 2) {
|
||||
return make_pair(0, 0);
|
||||
return std::make_pair(0, 0);
|
||||
}
|
||||
size_t domain_base = domain * 30;
|
||||
if (subdomain != 0xFF) {
|
||||
size_t subdomain_base = domain_base + (subdomain % 3);
|
||||
return make_pair(subdomain_base, subdomain_base + 1);
|
||||
return std::make_pair(subdomain_base, subdomain_base + 1);
|
||||
} else {
|
||||
return make_pair(domain_base, domain_base + 3);
|
||||
return std::make_pair(domain_base, domain_base + 3);
|
||||
}
|
||||
}
|
||||
|
||||
bool dc_serial_number_is_valid_slow(const string& s, uint8_t domain, uint8_t subdomain) {
|
||||
bool dc_serial_number_is_valid_slow(const std::string& s, uint8_t domain, uint8_t subdomain) {
|
||||
uint64_t serial_number = decode_dc_serial_number_str(s);
|
||||
if (serial_number == INVALID_PRODUCT) {
|
||||
return false;
|
||||
@@ -890,7 +888,7 @@ bool decoded_dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t doma
|
||||
return false;
|
||||
}
|
||||
|
||||
bool dc_serial_number_is_valid_fast(const string& s, uint8_t domain, uint8_t subdomain) {
|
||||
bool dc_serial_number_is_valid_fast(const std::string& s, uint8_t domain, uint8_t subdomain) {
|
||||
uint64_t serial_number = decode_dc_serial_number_str(s);
|
||||
if (serial_number == INVALID_PRODUCT) {
|
||||
return false;
|
||||
@@ -902,7 +900,7 @@ bool dc_serial_number_is_valid_fast(uint32_t serial_number, uint8_t domain, uint
|
||||
return decoded_dc_serial_number_is_valid_fast(decode_dc_serial_number_int(serial_number), domain, subdomain);
|
||||
}
|
||||
|
||||
string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
std::string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
size_t offset1, limit1;
|
||||
if (domain == 0) {
|
||||
offset1 = 0x00;
|
||||
@@ -914,7 +912,7 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
offset1 = 0x3C;
|
||||
limit1 = 0x3F;
|
||||
} else {
|
||||
throw runtime_error("invalid domain");
|
||||
throw std::runtime_error("invalid domain");
|
||||
}
|
||||
|
||||
size_t det1 = (subdomain == 0xFF) ? phosg::random_object<uint32_t>() : subdomain;
|
||||
@@ -922,23 +920,23 @@ string generate_dc_serial_number(uint8_t domain, uint8_t subdomain) {
|
||||
size_t index2 = phosg::random_object<uint32_t>() % num_primes2;
|
||||
size_t index3 = phosg::random_object<uint32_t>() % num_primes3;
|
||||
uint32_t value = primes1[index1] * primes2[index2] * primes3[index3];
|
||||
string s = std::format("{:08X}", value);
|
||||
std::string s = std::format("{:08X}", value);
|
||||
|
||||
string ret;
|
||||
std::string ret;
|
||||
for (char ch : s) {
|
||||
ret.push_back(replace_char_reverse(ch));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
|
||||
std::unordered_map<uint32_t, std::string> generate_all_dc_serial_numbers(uint8_t domain, uint8_t subdomain) {
|
||||
DCSerialNumberIterator iter;
|
||||
|
||||
if (domain < 3) {
|
||||
iter.domain = domain;
|
||||
iter.end_domain = domain + 1;
|
||||
} else if (domain != 0xFF) {
|
||||
throw runtime_error("invalid domain");
|
||||
throw std::runtime_error("invalid domain");
|
||||
}
|
||||
|
||||
if (subdomain < 3) {
|
||||
@@ -946,11 +944,11 @@ unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, u
|
||||
iter.start_subdomain = subdomain;
|
||||
iter.end_subdomain = subdomain + 1;
|
||||
} else if (subdomain != 0xFF) {
|
||||
throw runtime_error("invalid subdomain");
|
||||
throw std::runtime_error("invalid subdomain");
|
||||
}
|
||||
|
||||
uint32_t serial_number;
|
||||
unordered_map<uint32_t, string> ret;
|
||||
std::unordered_map<uint32_t, std::string> ret;
|
||||
while ((serial_number = iter.next()) != 0) {
|
||||
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
|
||||
if (iter.index3 == 0) {
|
||||
@@ -1015,7 +1013,7 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
||||
size_t num_disagreements = 0;
|
||||
static constexpr size_t count = 0x1000;
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
string s = std::format("{:08X}", crypt.next());
|
||||
std::string s = std::format("{:08X}", crypt.next());
|
||||
|
||||
uint64_t start = phosg::now();
|
||||
bool is_valid_fast = dc_serial_number_is_valid_fast(s, 1, 0xFF);
|
||||
@@ -1041,7 +1039,8 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
||||
phosg::fwrite_fmt(stderr, "Disagreements: {}\n", num_disagreements);
|
||||
}
|
||||
|
||||
string decrypt_dp_address_jpn(const string& executable, const string& values, const string& indexes) {
|
||||
std::string decrypt_dp_address_jpn(
|
||||
const std::string& executable, const std::string& values, const std::string& indexes) {
|
||||
phosg::StringReader values_r(values);
|
||||
phosg::StringReader indexes_r(indexes);
|
||||
|
||||
@@ -1056,7 +1055,7 @@ string decrypt_dp_address_jpn(const string& executable, const string& values, co
|
||||
fixup_offset += (fixup_steps_r.get_u8() << 2);
|
||||
fixup_steps_r.skip(1);
|
||||
if (fixup_offset + 4 > decrypted.compressed_data.size()) {
|
||||
throw runtime_error("fixup beyond end of compressed data");
|
||||
throw std::runtime_error("fixup beyond end of compressed data");
|
||||
}
|
||||
*reinterpret_cast<le_uint32_t*>(decrypted.compressed_data.data() + fixup_offset) = fixup_values_r.get_u32l();
|
||||
}
|
||||
@@ -1064,10 +1063,10 @@ string decrypt_dp_address_jpn(const string& executable, const string& values, co
|
||||
return prs_decompress(decrypted.compressed_data);
|
||||
}
|
||||
|
||||
EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const string& indexes) {
|
||||
EncryptedDCv2Executables encrypt_dp_address_jpn(const std::string& executable, const std::string& indexes) {
|
||||
EncryptedDCv2Executables ret;
|
||||
|
||||
string compressed = prs_compress(executable);
|
||||
std::string compressed = prs_compress(executable);
|
||||
ret.executable = encrypt_pr2_data<false>(compressed, executable.size(), phosg::random_object<uint32_t>() & 0x7FFFFF7F);
|
||||
|
||||
phosg::StringReader indexes_r(indexes);
|
||||
@@ -1079,12 +1078,12 @@ EncryptedDCv2Executables encrypt_dp_address_jpn(const string& executable, const
|
||||
|
||||
std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_key) {
|
||||
if (data.size() & 3) {
|
||||
throw runtime_error("size is not a multiple of 4");
|
||||
throw std::runtime_error("size is not a multiple of 4");
|
||||
}
|
||||
|
||||
phosg::StringReader r(data);
|
||||
if (mask_key < 0) {
|
||||
unordered_map<uint32_t, size_t> key_freq;
|
||||
std::unordered_map<uint32_t, size_t> key_freq;
|
||||
while (!r.eof()) {
|
||||
key_freq[r.get_u32l()] += 1;
|
||||
}
|
||||
@@ -1096,7 +1095,7 @@ std::string crypt_dp_address_jpn_simple(const std::string& data, int64_t mask_ke
|
||||
}
|
||||
}
|
||||
if (mask_key < 0) {
|
||||
throw runtime_error("cannot determine mask key");
|
||||
throw std::runtime_error("cannot determine mask key");
|
||||
}
|
||||
phosg::log_info_f("Determined {:08X} to be the most likely mask key", mask_key);
|
||||
r.go(0);
|
||||
|
||||
+8
-10
@@ -14,9 +14,7 @@
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DNSServer::DNSServer(shared_ptr<ServerState> state)
|
||||
DNSServer::DNSServer(std::shared_ptr<ServerState> state)
|
||||
: state(state) {}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
@@ -25,15 +23,15 @@ void DNSServer::listen(const std::string& addr, int port) {
|
||||
}
|
||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||
asio::ip::udp::endpoint endpoint(asio_addr, port);
|
||||
auto sock = make_shared<asio::ip::udp::socket>(*this->state->io_context, endpoint);
|
||||
auto sock = std::make_shared<asio::ip::udp::socket>(*this->state->io_context, endpoint);
|
||||
this->sockets.emplace(sock);
|
||||
|
||||
asio::co_spawn(*this->state->io_context, this->dns_server_task(sock), asio::detached);
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
||||
std::string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t resolved_address) {
|
||||
if (size < 0x0C) {
|
||||
throw invalid_argument("query too small");
|
||||
throw std::invalid_argument("query too small");
|
||||
}
|
||||
|
||||
const char* data = reinterpret_cast<const char*>(vdata);
|
||||
@@ -41,7 +39,7 @@ string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t re
|
||||
|
||||
phosg::be_uint32_t be_resolved_address = resolved_address;
|
||||
|
||||
string response;
|
||||
std::string response;
|
||||
response.append(data, 2);
|
||||
response.append("\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00", 10);
|
||||
response.append(&data[12], name_len);
|
||||
@@ -50,13 +48,13 @@ string DNSServer::response_for_query(const void* vdata, size_t size, uint32_t re
|
||||
return response;
|
||||
}
|
||||
|
||||
string DNSServer::response_for_query(const string& query, uint32_t resolved_address) {
|
||||
std::string DNSServer::response_for_query(const std::string& query, uint32_t resolved_address) {
|
||||
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
||||
}
|
||||
|
||||
asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock) {
|
||||
for (;;) {
|
||||
string input(2048, 0);
|
||||
std::string input(2048, 0);
|
||||
asio::ip::udp::endpoint sender_ep;
|
||||
size_t bytes = co_await sock->async_receive_from(asio::buffer(input), sender_ep, asio::use_awaitable);
|
||||
uint32_t sender_addr = ipv4_addr_for_asio_addr(sender_ep.address());
|
||||
@@ -69,7 +67,7 @@ asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::
|
||||
uint32_t connect_address = is_local_address(sender_addr)
|
||||
? this->state->local_address
|
||||
: this->state->external_address;
|
||||
string response = this->response_for_query(input, connect_address);
|
||||
std::string response = this->response_for_query(input, connect_address);
|
||||
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-13
@@ -18,15 +18,13 @@
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
DOLFileIndex::DOLFileIndex(const std::string& directory) {
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
client_functions_log.info_f("DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
auto menu = make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
||||
auto menu = std::make_shared<Menu>(MenuID::PROGRAMS, "Programs");
|
||||
this->menu = menu;
|
||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
@@ -43,17 +41,17 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!is_dol && !is_compressed_dol) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
||||
std::string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4));
|
||||
|
||||
try {
|
||||
auto dol = make_shared<File>();
|
||||
auto dol = std::make_shared<File>();
|
||||
dol->menu_item_id = next_menu_item_id++;
|
||||
dol->name = name;
|
||||
|
||||
string path = directory + "/" + filename;
|
||||
string file_data = phosg::load_file(path);
|
||||
std::string path = directory + "/" + filename;
|
||||
std::string file_data = phosg::load_file(path);
|
||||
|
||||
string description;
|
||||
std::string description;
|
||||
if (is_compressed_dol) {
|
||||
size_t decompressed_size = prs_decompress_size(file_data);
|
||||
|
||||
@@ -66,8 +64,8 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string compressed_size_str = phosg::format_size(file_data.size());
|
||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
std::string compressed_size_str = phosg::format_size(file_data.size());
|
||||
std::string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
client_functions_log.debug_f("Loaded compressed DOL file {} ({} -> {})",
|
||||
dol->name, compressed_size_str, decompressed_size_str);
|
||||
description = std::format("$C6{}$C7\n{}\n{} (orig)", dol->name, compressed_size_str, decompressed_size_str);
|
||||
@@ -82,7 +80,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
}
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string size_str = phosg::format_size(dol->data.size());
|
||||
std::string size_str = phosg::format_size(dol->data.size());
|
||||
client_functions_log.debug_f("Loaded DOL file {} ({})", filename, size_str);
|
||||
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
||||
}
|
||||
@@ -90,7 +88,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
this->item_id_to_file.emplace_back(dol);
|
||||
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
client_functions_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
+42
-44
@@ -22,12 +22,10 @@
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string random_name() {
|
||||
string ret;
|
||||
static std::string random_name() {
|
||||
std::string ret;
|
||||
size_t length = (phosg::random_object<size_t>() % 12) + 4;
|
||||
static const string alphabet = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-+<>:\"\',.";
|
||||
static const std::string alphabet = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-+<>:\"\',.";
|
||||
while (ret.size() < length) {
|
||||
ret.push_back(alphabet[phosg::random_object<size_t>() % alphabet.size()]);
|
||||
}
|
||||
@@ -87,40 +85,40 @@ DownloadSession::DownloadSession(
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
if (this->serial_number2 == 0 || this->serial_number == 0 || this->access_key.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
throw std::runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::PC_V2:
|
||||
if (this->serial_number == 0 || this->access_key.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
throw std::runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::GC_V3:
|
||||
if (this->serial_number == 0 || this->access_key.empty() || this->password.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
throw std::runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
if (this->xb_gamertag.empty() || this->xb_user_id == 0 || this->xb_account_id == 0) {
|
||||
throw runtime_error("missing credentials");
|
||||
throw std::runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
if (this->username.empty() || this->password.empty()) {
|
||||
throw runtime_error("missing credentials");
|
||||
throw std::runtime_error("missing credentials");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
this->character->inventory.language = language;
|
||||
}
|
||||
|
||||
asio::awaitable<void> DownloadSession::run() {
|
||||
string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||
std::string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||
this->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||
this->channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
std::move(sock),
|
||||
@@ -214,7 +212,7 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
||||
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +264,7 @@ void DownloadSession::send_61_98(bool is_98) {
|
||||
this->channel->send(command, 0x04, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,16 +276,16 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
switch (msg.command) {
|
||||
case 0x03: {
|
||||
if (this->version != Version::BB_V4) {
|
||||
throw runtime_error("BB server sent non-BB encryption command");
|
||||
throw std::runtime_error("BB server sent non-BB encryption command");
|
||||
}
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
throw std::runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||
this->channel->crypt_in = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||
this->channel->crypt_out = make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||
this->channel->crypt_in = std::make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.server_key[0], sizeof(cmd.server_key));
|
||||
this->channel->crypt_out = std::make_shared<PSOBBEncryption>(*this->bb_key_file, &cmd.client_key[0], sizeof(cmd.client_key));
|
||||
this->log.info_f("Enabled BB encryption");
|
||||
throw runtime_error("not yet implemented"); // Send 93
|
||||
throw std::runtime_error("not yet implemented"); // Send 93
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -297,17 +295,17 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
case 0x9B: {
|
||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
||||
if (uses_v3_encryption(this->version)) {
|
||||
this->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info_f("Enabled V3 encryption (server key {:08X}, client key {:08X})",
|
||||
cmd.server_key, cmd.client_key);
|
||||
} else if (!uses_v4_encryption(this->version)) {
|
||||
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info_f("Enabled V2 encryption (server key {:08X}, client key {:08X})",
|
||||
cmd.server_key, cmd.client_key);
|
||||
} else {
|
||||
throw runtime_error("BB server sent non-BB encryption command");
|
||||
throw std::runtime_error("BB server sent non-BB encryption command");
|
||||
}
|
||||
|
||||
if (msg.command == 0x02) {
|
||||
@@ -346,7 +344,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,14 +379,14 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
this->channel->send(0x9C, 0x00, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
} else if (msg.flag == 0 || msg.flag == 2) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
throw runtime_error("login failed");
|
||||
throw std::runtime_error("login failed");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -396,14 +394,14 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
case 0x92:
|
||||
case 0x9C:
|
||||
if (msg.flag == 0) {
|
||||
throw runtime_error("server rejected login credentials");
|
||||
throw std::runtime_error("server rejected login credentials");
|
||||
}
|
||||
this->send_93_9D_9E(true);
|
||||
break;
|
||||
|
||||
case 0x9F: {
|
||||
if (is_v1_or_v2(this->version)) {
|
||||
throw runtime_error("invalid command");
|
||||
throw std::runtime_error("invalid command");
|
||||
}
|
||||
this->channel->send(0x9F, 0x00, this->client_config);
|
||||
break;
|
||||
@@ -477,11 +475,11 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
if (this->interactive) {
|
||||
while (item_index == 0 || item_index > msg.flag) {
|
||||
this->log.info_f("Choose response index:");
|
||||
string input = phosg::fgets(stdin);
|
||||
item_index = stoul(input, nullptr, 0);
|
||||
std::string input = phosg::fgets(stdin);
|
||||
item_index = std::stoul(input, nullptr, 0);
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("unhandled menu selection");
|
||||
throw std::runtime_error("unhandled menu selection");
|
||||
}
|
||||
}
|
||||
ret.menu_id = items[item_index].menu_id;
|
||||
@@ -521,9 +519,9 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
const auto& cmd = msg.check_size_t<S_Reconnect_19>(sizeof(S_Reconnect_19), 0xFFFF);
|
||||
|
||||
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
||||
string netloc_str = str_for_endpoint(new_ep);
|
||||
std::string netloc_str = str_for_endpoint(new_ep);
|
||||
this->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
this->channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
std::move(sock),
|
||||
@@ -649,12 +647,12 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
case 0xA6: {
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto& cmd = msg.check_size_t<CmdT>(0xFFFF);
|
||||
string internal_name = cmd.filename.decode();
|
||||
string filtered_name;
|
||||
std::string internal_name = cmd.filename.decode();
|
||||
std::string filtered_name;
|
||||
for (char ch : internal_name) {
|
||||
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
|
||||
}
|
||||
string local_filename = std::format(
|
||||
std::string local_filename = std::format(
|
||||
"{}/{:016X}_{}_{}_{:c}_{}",
|
||||
this->output_dir,
|
||||
this->current_request,
|
||||
@@ -677,7 +675,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
case 0x13:
|
||||
case 0xA7: {
|
||||
const auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
||||
string internal_filename = cmd.filename.decode();
|
||||
std::string internal_filename = cmd.filename.decode();
|
||||
|
||||
if (!is_v1_or_v2(this->version)) {
|
||||
C_WriteFileConfirmation_V3_BB_13_A7 ret;
|
||||
@@ -692,8 +690,8 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
}
|
||||
auto& f = this->open_files.at(cmd.filename.decode());
|
||||
size_t block_offset = msg.flag * 0x400;
|
||||
size_t allowed_block_size = (block_offset < f.total_size) ? min<size_t>(f.total_size - block_offset, 0x400) : 0;
|
||||
size_t data_size = min<size_t>(cmd.data_size, allowed_block_size);
|
||||
size_t allowed_block_size = (block_offset < f.total_size) ? std::min<size_t>(f.total_size - block_offset, 0x400) : 0;
|
||||
size_t data_size = std::min<size_t>(cmd.data_size, allowed_block_size);
|
||||
size_t block_end_offset = block_offset + data_size;
|
||||
if (block_end_offset > f.data.size()) {
|
||||
f.data.resize(block_end_offset);
|
||||
@@ -720,7 +718,7 @@ asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
}
|
||||
case 0xAC: {
|
||||
if (is_v1_or_v2(this->version)) {
|
||||
throw runtime_error("unsupported version");
|
||||
throw std::runtime_error("unsupported version");
|
||||
}
|
||||
this->on_request_complete();
|
||||
break;
|
||||
@@ -745,7 +743,7 @@ void DownloadSession::send_next_request() {
|
||||
this->log.info_f("{:016X}: {}", it.first, it.second);
|
||||
}
|
||||
this->log.info_f("Choose item to expand by ID (q to quit; s to skip to next config):");
|
||||
string input = phosg::fgets(stdin);
|
||||
std::string input = phosg::fgets(stdin);
|
||||
if (input.empty() || (input == "q\n")) {
|
||||
this->channel->disconnect();
|
||||
return;
|
||||
@@ -753,7 +751,7 @@ void DownloadSession::send_next_request() {
|
||||
this->pending_requests.clear();
|
||||
this->on_request_complete();
|
||||
} else {
|
||||
this->current_request = stoull(input, nullptr, 16);
|
||||
this->current_request = std::stoull(input, nullptr, 16);
|
||||
this->done_requests.emplace(this->current_request);
|
||||
this->pending_requests.erase(this->current_request);
|
||||
}
|
||||
|
||||
+12
-14
@@ -8,8 +8,6 @@
|
||||
#include "PSOEncryption.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static constexpr uint8_t EP1 = EnemyTypeDefinition::Flag::VALID_EP1;
|
||||
static constexpr uint8_t EP2 = EnemyTypeDefinition::Flag::VALID_EP2;
|
||||
static constexpr uint8_t EP4 = EnemyTypeDefinition::Flag::VALID_EP4;
|
||||
@@ -17,7 +15,7 @@ static constexpr uint8_t RARE = EnemyTypeDefinition::Flag::IS_RARE;
|
||||
static constexpr uint8_t BOSS = EnemyTypeDefinition::Flag::IS_BOSS;
|
||||
|
||||
static constexpr uint8_t NONE = 0xFF;
|
||||
static const vector<EnemyTypeDefinition> type_defs{
|
||||
static const std::vector<EnemyTypeDefinition> type_defs{
|
||||
// clang-format off
|
||||
// TYPE FLAGS RT OLDRT BP-STATS BP-ATTACK BP-RESIST BP-MOVEMENT ENUM NAME IN-GAME NAME ULTIMATE NAME
|
||||
{EnemyType::UNKNOWN, 0, NONE, NONE, {}, {}, {}, {}, "UNKNOWN", "__UNKNOWN__", nullptr},
|
||||
@@ -167,19 +165,19 @@ const char* phosg::name_for_enum<EnemyType>(EnemyType type) {
|
||||
|
||||
template <>
|
||||
EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
||||
static unordered_map<string, EnemyType> index;
|
||||
static std::unordered_map<std::string, EnemyType> index;
|
||||
if (index.empty()) {
|
||||
for (const auto& def : type_defs) {
|
||||
if (!index.emplace(def.enum_name, def.type).second) {
|
||||
throw logic_error(std::format("duplicate enemy enum name: {}", def.enum_name));
|
||||
throw std::logic_error(std::format("duplicate enemy enum name: {}", def.enum_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
return index.at(name);
|
||||
}
|
||||
|
||||
const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
|
||||
static array<vector<vector<EnemyType>>, 5> data;
|
||||
const std::vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8_t rt_index) {
|
||||
static std::array<std::vector<std::vector<EnemyType>>, 5> data;
|
||||
auto& ret = data.at(static_cast<size_t>(episode));
|
||||
if (ret.empty()) {
|
||||
for (const auto& def : type_defs) {
|
||||
@@ -196,8 +194,8 @@ const vector<EnemyType>& enemy_types_for_rare_table_index(Episode episode, uint8
|
||||
}
|
||||
try {
|
||||
return ret.at(rt_index);
|
||||
} catch (const out_of_range&) {
|
||||
static const vector<EnemyType> empty_vec;
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::vector<EnemyType> empty_vec;
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
@@ -211,7 +209,7 @@ struct BPIndexCacheEntry {
|
||||
|
||||
static const BPIndexCacheEntry& get_bp_index_cache_entry(Episode episode, uint8_t bp_index) {
|
||||
static bool cache_populated = false;
|
||||
static array<vector<BPIndexCacheEntry>, 5> data;
|
||||
static std::array<std::vector<BPIndexCacheEntry>, 5> data;
|
||||
if (!cache_populated) {
|
||||
cache_populated = true;
|
||||
for (const auto& def : type_defs) {
|
||||
@@ -256,7 +254,7 @@ static const std::set<EnemyType> empty_vec;
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).stats;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
@@ -264,7 +262,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_stats_index(Episode epis
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).attack_data;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
@@ -272,7 +270,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_attack_data_index(Episod
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).resist_data;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
@@ -280,7 +278,7 @@ const std::set<EnemyType>& enemy_types_for_battle_param_resist_data_index(Episod
|
||||
const std::set<EnemyType>& enemy_types_for_battle_param_movement_data_index(Episode episode, uint8_t bp_index) {
|
||||
try {
|
||||
return get_bp_index_cache_entry(episode, bp_index).movement_data;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return empty_vec;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,20 @@
|
||||
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
const vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
||||
const std::vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
||||
// Note: This order matches the order that the cards are defined in the original
|
||||
// code. This is relevant for consistency of results when choosing a random card
|
||||
// (for God Whim).
|
||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
|
||||
static const std::vector<uint16_t> ALL_ASSIST_CARD_IDS_TRIAL = {
|
||||
0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0100, 0x0101, 0x0102,
|
||||
0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x010F, 0x0121,
|
||||
0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F, 0x0130, 0x0131, 0x0132,
|
||||
0x0133, 0x0134, 0x0135, 0x0136, 0x0137, 0x0138, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E, 0x013F, 0x0140,
|
||||
0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0148, 0x014A, 0x014B, 0x014C, 0x014D, 0x014E, 0x023F, 0x0240,
|
||||
0x0241, 0x0242};
|
||||
static const vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
|
||||
static const std::vector<uint16_t> ALL_ASSIST_CARD_IDS_FINAL = {
|
||||
0x0018, 0x0019, 0x001A, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
|
||||
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D,
|
||||
0x010E, 0x010F, 0x0121, 0x0125, 0x0126, 0x0127, 0x0128, 0x0129, 0x012A, 0x012B, 0x012C, 0x012D, 0x012E, 0x012F,
|
||||
@@ -28,12 +26,12 @@ const vector<uint16_t>& all_assist_card_ids(bool is_nte) {
|
||||
}
|
||||
|
||||
AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
|
||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect_final_only({
|
||||
static const std::unordered_map<uint16_t, AssistEffect> card_id_to_effect_final_only({
|
||||
{0x0018, /* 0x0049 */ AssistEffect::DICE_FEVER_PLUS},
|
||||
{0x0019, /* 0x004A */ AssistEffect::RICH_PLUS},
|
||||
{0x001A, /* 0x004B */ AssistEffect::CHARITY_PLUS},
|
||||
});
|
||||
static const unordered_map<uint16_t, AssistEffect> card_id_to_effect({
|
||||
static const std::unordered_map<uint16_t, AssistEffect> card_id_to_effect({
|
||||
{0x00F5, /* 0x0001 */ AssistEffect::DICE_HALF},
|
||||
{0x00F6, /* 0x0002 */ AssistEffect::DICE_PLUS_1},
|
||||
{0x00F7, /* 0x0003 */ AssistEffect::DICE_FEVER},
|
||||
@@ -109,18 +107,18 @@ AssistEffect assist_effect_number_for_card_id(uint16_t card_id, bool is_nte) {
|
||||
});
|
||||
try {
|
||||
return card_id_to_effect.at(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (!is_nte) {
|
||||
try {
|
||||
return card_id_to_effect_final_only.at(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
return AssistEffect::NONE;
|
||||
}
|
||||
|
||||
AssistServer::AssistServer(shared_ptr<Server> server)
|
||||
AssistServer::AssistServer(std::shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
assist_effects(AssistEffect::NONE),
|
||||
num_assist_cards_set(0),
|
||||
@@ -128,18 +126,18 @@ AssistServer::AssistServer(shared_ptr<Server> server)
|
||||
active_assist_effects(AssistEffect::NONE),
|
||||
num_active_assists(0) {}
|
||||
|
||||
shared_ptr<Server> AssistServer::server() {
|
||||
std::shared_ptr<Server> AssistServer::server() {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<const Server> AssistServer::server() const {
|
||||
std::shared_ptr<const Server> AssistServer::server() const {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -148,7 +146,7 @@ uint16_t AssistServer::card_id_for_card_ref(uint16_t card_ref) const {
|
||||
return this->server()->card_id_for_card_ref(card_ref);
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
|
||||
std::shared_ptr<const CardIndex::CardEntry> AssistServer::definition_for_card_id(
|
||||
uint16_t card_id) const {
|
||||
return this->server()->definition_for_card_id(card_id);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "../CommandFormats.hh"
|
||||
#include "../SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
void BattleRecord::PlayerEntry::print(FILE* stream) const {
|
||||
@@ -41,7 +39,7 @@ BattleRecord::Event::Event(phosg::StringReader& r) {
|
||||
this->data = r.read(r.get_u16l());
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown event type");
|
||||
throw std::logic_error("unknown event type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +49,7 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
||||
switch (this->type) {
|
||||
case Event::Type::PLAYER_JOIN:
|
||||
if (this->players.size() != 1) {
|
||||
throw logic_error("player join event does not contain 1 player entry");
|
||||
throw std::logic_error("player join event does not contain 1 player entry");
|
||||
}
|
||||
w.put(this->players[0]);
|
||||
break;
|
||||
@@ -75,13 +73,12 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
||||
w.write(this->data);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown event type");
|
||||
throw std::logic_error("unknown event type");
|
||||
}
|
||||
}
|
||||
|
||||
void BattleRecord::Event::print(FILE* stream) const {
|
||||
string time_str = phosg::format_time(this->timestamp);
|
||||
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, time_str);
|
||||
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, phosg::format_time(this->timestamp));
|
||||
switch (this->type) {
|
||||
case Type::PLAYER_JOIN:
|
||||
phosg::fwrite_fmt(stream, "PLAYER_JOIN {:02X}\n", this->players[0].lobby_data.client_id);
|
||||
@@ -121,7 +118,7 @@ void BattleRecord::Event::print(FILE* stream) const {
|
||||
phosg::print_data(stream, this->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown event type in battle record");
|
||||
throw std::runtime_error("unknown event type in battle record");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,11 +128,8 @@ BattleRecord::BattleRecord(uint32_t behavior_flags)
|
||||
battle_start_timestamp(0),
|
||||
battle_end_timestamp(0) {}
|
||||
|
||||
BattleRecord::BattleRecord(const string& data)
|
||||
: is_writable(false),
|
||||
behavior_flags(0),
|
||||
battle_start_timestamp(0),
|
||||
battle_end_timestamp(0) {
|
||||
BattleRecord::BattleRecord(const std::string& data)
|
||||
: is_writable(false), behavior_flags(0), battle_start_timestamp(0), battle_end_timestamp(0) {
|
||||
phosg::StringReader r(data);
|
||||
|
||||
uint64_t signature = r.get_u64l();
|
||||
@@ -145,7 +139,7 @@ BattleRecord::BattleRecord(const string& data)
|
||||
} else if (signature == this->SIGNATURE_V2) {
|
||||
has_random_stream = true;
|
||||
} else {
|
||||
throw runtime_error("incorrect battle record signature");
|
||||
throw std::runtime_error("incorrect battle record signature");
|
||||
}
|
||||
|
||||
this->battle_start_timestamp = r.get_u64l();
|
||||
@@ -159,7 +153,7 @@ BattleRecord::BattleRecord(const string& data)
|
||||
}
|
||||
}
|
||||
|
||||
string BattleRecord::serialize() const {
|
||||
std::string BattleRecord::serialize() const {
|
||||
phosg::StringWriter w;
|
||||
w.put_u64l(this->SIGNATURE_V2);
|
||||
w.put_u64l(this->battle_start_timestamp);
|
||||
@@ -179,10 +173,10 @@ void BattleRecord::add_player(
|
||||
const PlayerDispDataDCPCV3& disp,
|
||||
uint32_t level) {
|
||||
if (!this->is_writable) {
|
||||
throw logic_error("cannot write to battle record");
|
||||
throw std::logic_error("cannot write to battle record");
|
||||
}
|
||||
if (this->battle_start_timestamp != 0) {
|
||||
throw runtime_error("cannot add player during battle");
|
||||
throw std::runtime_error("cannot add player during battle");
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::PLAYER_JOIN;
|
||||
@@ -196,7 +190,7 @@ void BattleRecord::add_player(
|
||||
|
||||
void BattleRecord::delete_player(uint8_t client_id) {
|
||||
if (!this->is_writable) {
|
||||
throw logic_error("cannot write to battle record");
|
||||
throw std::logic_error("cannot write to battle record");
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::PLAYER_LEAVE;
|
||||
@@ -206,7 +200,7 @@ void BattleRecord::delete_player(uint8_t client_id) {
|
||||
|
||||
void BattleRecord::add_command(Event::Type type, const void* data, size_t size) {
|
||||
if (!this->is_writable) {
|
||||
throw logic_error("cannot write to battle record");
|
||||
throw std::logic_error("cannot write to battle record");
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = type;
|
||||
@@ -214,9 +208,9 @@ void BattleRecord::add_command(Event::Type type, const void* data, size_t size)
|
||||
ev.data.assign(reinterpret_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
void BattleRecord::add_command(Event::Type type, string&& data) {
|
||||
void BattleRecord::add_command(Event::Type type, std::string&& data) {
|
||||
if (!this->is_writable) {
|
||||
throw logic_error("cannot write to battle record");
|
||||
throw std::logic_error("cannot write to battle record");
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = type;
|
||||
@@ -224,10 +218,9 @@ void BattleRecord::add_command(Event::Type type, string&& data) {
|
||||
ev.data = std::move(data);
|
||||
}
|
||||
|
||||
void BattleRecord::add_chat_message(
|
||||
uint32_t guild_card_number, string&& data) {
|
||||
void BattleRecord::add_chat_message(uint32_t guild_card_number, std::string&& data) {
|
||||
if (!this->is_writable) {
|
||||
throw logic_error("cannot write to battle record");
|
||||
throw std::logic_error("cannot write to battle record");
|
||||
}
|
||||
Event& ev = this->events.emplace_back();
|
||||
ev.type = Event::Type::CHAT_MESSAGE;
|
||||
@@ -240,7 +233,7 @@ void BattleRecord::add_random_data(const void* data, size_t size) {
|
||||
this->random_stream.append(reinterpret_cast<const char*>(data), size);
|
||||
}
|
||||
|
||||
const string& BattleRecord::get_random_stream() const {
|
||||
const std::string& BattleRecord::get_random_stream() const {
|
||||
return this->random_stream;
|
||||
}
|
||||
|
||||
@@ -259,7 +252,7 @@ bool BattleRecord::is_map_definition_event(const Event& ev) {
|
||||
|
||||
void BattleRecord::set_battle_start_timestamp() {
|
||||
if (this->battle_start_timestamp != 0) {
|
||||
throw logic_error("battle start timestamp is already set");
|
||||
throw std::logic_error("battle start timestamp is already set");
|
||||
}
|
||||
this->battle_start_timestamp = phosg::now();
|
||||
|
||||
@@ -271,30 +264,30 @@ void BattleRecord::set_battle_start_timestamp() {
|
||||
for (auto& ev : this->events) {
|
||||
if (ev.type == Event::Type::PLAYER_JOIN) {
|
||||
if (ev.players.size() != 1) {
|
||||
throw logic_error("player join event does not contain 1 player entry");
|
||||
throw std::logic_error("player join event does not contain 1 player entry");
|
||||
}
|
||||
auto& player = ev.players[0];
|
||||
if (player.lobby_data.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
players[player.lobby_data.client_id] = player;
|
||||
players_present[player.lobby_data.client_id] = true;
|
||||
|
||||
} else if (ev.type == Event::Type::PLAYER_LEAVE) {
|
||||
if (ev.leaving_client_id >= 4) {
|
||||
throw logic_error("invalid client ID");
|
||||
throw std::logic_error("invalid client ID");
|
||||
}
|
||||
players_present[ev.leaving_client_id] = false;
|
||||
|
||||
} else if (ev.type == Event::Type::SET_INITIAL_PLAYERS) {
|
||||
throw logic_error("BattleRecord::set_battle_start_timestamp called twice");
|
||||
throw std::logic_error("BattleRecord::set_battle_start_timestamp called twice");
|
||||
|
||||
} else if (this->is_map_definition_event(ev)) {
|
||||
num_map_events++;
|
||||
}
|
||||
}
|
||||
|
||||
deque<Event> new_events;
|
||||
std::deque<Event> new_events;
|
||||
|
||||
// Generate the initial players event
|
||||
Event initial_ev;
|
||||
@@ -335,32 +328,31 @@ void BattleRecord::set_battle_end_timestamp() {
|
||||
}
|
||||
|
||||
void BattleRecord::print(FILE* stream) const {
|
||||
string start_str = phosg::format_time(this->battle_start_timestamp);
|
||||
string end_str = phosg::format_time(this->battle_end_timestamp);
|
||||
phosg::fwrite_fmt(stream, "BattleRecord {} behavior_flags={:08X} start={:016X} ({}) end={:016X} ({}); {} events\n",
|
||||
this->is_writable ? "writable" : "read-only",
|
||||
this->behavior_flags,
|
||||
this->battle_start_timestamp,
|
||||
start_str,
|
||||
phosg::format_time(this->battle_start_timestamp),
|
||||
this->battle_end_timestamp,
|
||||
end_str, this->events.size());
|
||||
phosg::format_time(this->battle_end_timestamp),
|
||||
this->events.size());
|
||||
for (const auto& event : this->events) {
|
||||
event.print(stream);
|
||||
}
|
||||
}
|
||||
|
||||
BattleRecordPlayer::BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, shared_ptr<const BattleRecord> rec)
|
||||
BattleRecordPlayer::BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, std::shared_ptr<const BattleRecord> rec)
|
||||
: io_context(io_context),
|
||||
record(rec),
|
||||
event_it(this->record->events.begin()),
|
||||
play_start_timestamp(0),
|
||||
next_command_timer(*this->io_context) {}
|
||||
|
||||
shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
|
||||
std::shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
|
||||
return this->record;
|
||||
}
|
||||
|
||||
void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
|
||||
void BattleRecordPlayer::set_lobby(std::shared_ptr<Lobby> l) {
|
||||
this->lobby = l;
|
||||
}
|
||||
|
||||
@@ -406,7 +398,7 @@ asio::awaitable<void> BattleRecordPlayer::play_task() {
|
||||
switch (ev.type) {
|
||||
case BattleRecord::Event::Type::PLAYER_JOIN:
|
||||
// Technically we can support this, but it should never happen
|
||||
throw runtime_error("player join event during battle replay");
|
||||
throw std::runtime_error("player join event during battle replay");
|
||||
case BattleRecord::Event::Type::PLAYER_LEAVE:
|
||||
send_player_leave_notification(l, ev.leaving_client_id);
|
||||
break;
|
||||
|
||||
+52
-59
@@ -3,11 +3,9 @@
|
||||
#include "../CommandFormats.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, shared_ptr<Server> server)
|
||||
Card::Card(uint16_t card_id, uint16_t card_ref, uint16_t client_id, std::shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
w_player_state(server->get_player_state(client_id)),
|
||||
client_id(client_id),
|
||||
@@ -39,7 +37,7 @@ void Card::init() {
|
||||
// Arkz-side. This could break things later on in the battle, and even if it
|
||||
// doesn't, it certainly isn't behavior that the player would expect, so we
|
||||
// prevent it instead.
|
||||
throw runtime_error("card definition is missing");
|
||||
throw std::runtime_error("card definition is missing");
|
||||
}
|
||||
this->sc_card_ref = ps->get_sc_card_ref();
|
||||
this->sc_def_entry = s->definition_for_card_id(ps->get_sc_card_id());
|
||||
@@ -56,7 +54,7 @@ void Card::init() {
|
||||
} else {
|
||||
int16_t rules_char_hp = s->map_and_rules->rules.char_hp;
|
||||
int16_t base_char_hp = (rules_char_hp == 0) ? 15 : rules_char_hp;
|
||||
int16_t hp = clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
||||
int16_t hp = std::clamp<int16_t>(base_char_hp + this->def_entry->def.hp.stat, 1, 99);
|
||||
this->max_hp = hp;
|
||||
this->current_hp = hp;
|
||||
}
|
||||
@@ -78,34 +76,34 @@ void Card::init() {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Server> Card::server() {
|
||||
std::shared_ptr<Server> Card::server() {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<const Server> Card::server() const {
|
||||
std::shared_ptr<const Server> Card::server() const {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<PlayerState> Card::player_state() {
|
||||
std::shared_ptr<PlayerState> Card::player_state() {
|
||||
auto s = this->w_player_state.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<const PlayerState> Card::player_state() const {
|
||||
std::shared_ptr<const PlayerState> Card::player_state() const {
|
||||
auto s = this->w_player_state.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -158,7 +156,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
int16_t existing_cond_value = 0;
|
||||
auto& cond = this->action_chain.conditions[cond_index];
|
||||
if ((eff.type == ConditionType::MV_BONUS) && (cond.type == ConditionType::MV_BONUS)) {
|
||||
existing_cond_value = clamp<int16_t>(cond.value, -99, 99);
|
||||
existing_cond_value = std::clamp<int16_t>(cond.value, -99, 99);
|
||||
log.debug_f("MV_BONUS combines => existing_cond_value = {}", existing_cond_value);
|
||||
}
|
||||
|
||||
@@ -176,7 +174,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
|
||||
switch (eff.arg1.at(0)) {
|
||||
case 'a': {
|
||||
string s = eff.arg1.decode();
|
||||
std::string s = eff.arg1.decode();
|
||||
cond.a_arg_value = atoi(s.c_str() + 1);
|
||||
break;
|
||||
}
|
||||
@@ -190,13 +188,12 @@ ssize_t Card::apply_abnormal_condition(
|
||||
cond.remaining_turns = 102;
|
||||
break;
|
||||
case 't': {
|
||||
string s = eff.arg1.decode();
|
||||
std::string s = eff.arg1.decode();
|
||||
cond.remaining_turns = atoi(s.c_str() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
string cond_str = cond.str(s);
|
||||
log.debug_f("wrote condition {} => {}", cond_index, cond_str);
|
||||
log.debug_f("wrote condition {} => {}", cond_index, cond.str(s));
|
||||
|
||||
if (!is_nte) {
|
||||
s->card_special->update_condition_orders(this->shared_from_this());
|
||||
@@ -204,8 +201,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
if (this->action_chain.conditions[z].type == ConditionType::NONE) {
|
||||
continue;
|
||||
}
|
||||
string cond_str = cond.str(s);
|
||||
log.debug_f("sorted conditions: [{}] => {}", z, cond_str);
|
||||
log.debug_f("sorted conditions: [{}] => {}", z, cond.str(s));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +209,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
}
|
||||
|
||||
void Card::apply_ap_and_tp_adjust_assists_to_attack(
|
||||
shared_ptr<const Card> attacker_card,
|
||||
std::shared_ptr<const Card> attacker_card,
|
||||
int16_t* inout_attacker_ap,
|
||||
int16_t* in_defense_power,
|
||||
int16_t* inout_attacker_tp) const {
|
||||
@@ -226,12 +222,12 @@ void Card::apply_ap_and_tp_adjust_assists_to_attack(
|
||||
switch (s->assist_server->get_active_assist_by_index(z)) {
|
||||
case AssistEffect::POWERLESS_RAIN:
|
||||
if (is_nte) {
|
||||
*inout_attacker_ap = max<int16_t>(*inout_attacker_ap - 2, 0);
|
||||
*inout_attacker_ap = std::max<int16_t>(*inout_attacker_ap - 2, 0);
|
||||
}
|
||||
break;
|
||||
case AssistEffect::BRAVE_WIND:
|
||||
if (is_nte) {
|
||||
*inout_attacker_ap = max<int16_t>(*inout_attacker_ap + 2, 0);
|
||||
*inout_attacker_ap = std::max<int16_t>(*inout_attacker_ap + 2, 0);
|
||||
}
|
||||
break;
|
||||
case AssistEffect::SILENT_COLOSSEUM:
|
||||
@@ -284,7 +280,7 @@ bool Card::check_card_flag(uint32_t flags) const {
|
||||
|
||||
void Card::commit_attack(
|
||||
int16_t damage,
|
||||
shared_ptr<Card> attacker_card,
|
||||
std::shared_ptr<Card> attacker_card,
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06* cmd,
|
||||
size_t strike_number,
|
||||
int16_t* out_effective_damage) {
|
||||
@@ -302,7 +298,7 @@ void Card::commit_attack(
|
||||
auto eff = s->assist_server->get_active_assist_by_index(z);
|
||||
if ((eff == AssistEffect::RANSOM) && (attacker_card->action_chain.chain.attack_medium == AttackMedium::PHYSICAL)) {
|
||||
uint8_t team_id = this->player_state()->get_team_id();
|
||||
int16_t exp_amount = clamp<int16_t>(s->team_exp[team_id], 0, effective_damage);
|
||||
int16_t exp_amount = std::clamp<int16_t>(s->team_exp[team_id], 0, effective_damage);
|
||||
s->team_exp[team_id] -= exp_amount;
|
||||
effective_damage -= exp_amount;
|
||||
if (!is_nte) {
|
||||
@@ -326,7 +322,7 @@ void Card::commit_attack(
|
||||
this->player_state()->stats.damage_taken += effective_damage;
|
||||
log.debug_f("updated stats");
|
||||
|
||||
this->current_hp = clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
|
||||
this->current_hp = std::clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
|
||||
log.debug_f("hp set to {}", this->current_hp);
|
||||
|
||||
if ((effective_damage > 0) && (attacker_ps->stats.max_attack_damage < effective_damage)) {
|
||||
@@ -366,7 +362,7 @@ void Card::commit_attack(
|
||||
}
|
||||
}
|
||||
|
||||
int16_t Card::compute_defense_power_for_attacker_card(shared_ptr<const Card> attacker_card) {
|
||||
int16_t Card::compute_defense_power_for_attacker_card(std::shared_ptr<const Card> attacker_card) {
|
||||
if (!attacker_card) {
|
||||
return 0;
|
||||
}
|
||||
@@ -392,7 +388,7 @@ int16_t Card::compute_defense_power_for_attacker_card(shared_ptr<const Card> att
|
||||
return this->action_metadata.defense_power + this->action_metadata.defense_bonus;
|
||||
}
|
||||
|
||||
void Card::destroy_set_card(shared_ptr<Card> attacker_card) {
|
||||
void Card::destroy_set_card(std::shared_ptr<Card> attacker_card) {
|
||||
auto s = this->server();
|
||||
auto ps = this->player_state();
|
||||
|
||||
@@ -490,7 +486,7 @@ int32_t Card::error_code_for_move_to_location(const Location& loc) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
void Card::execute_attack(std::shared_ptr<Card> attacker_card) {
|
||||
if (!attacker_card) {
|
||||
return;
|
||||
}
|
||||
@@ -518,7 +514,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
if (attacker_card->action_chain.chain.attack_medium == AttackMedium::UNKNOWN_03) {
|
||||
// Probably Resta
|
||||
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
||||
this->current_hp = min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
|
||||
this->current_hp = std::min<int16_t>(this->current_hp + attacker_card->action_chain.chain.effective_tp, this->max_hp);
|
||||
}
|
||||
this->propagate_shared_hp_if_needed();
|
||||
cmd.effect.tp = attacker_card->action_chain.chain.effective_tp;
|
||||
@@ -545,7 +541,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
log.debug_f("assist adjusts ap={}, defense={}", attack_ap, defense_power);
|
||||
|
||||
int16_t raw_damage = attack_ap - defense_power;
|
||||
int16_t preliminary_damage = max<int16_t>(raw_damage, 0) - attack_tp;
|
||||
int16_t preliminary_damage = std::max<int16_t>(raw_damage, 0) - attack_tp;
|
||||
this->last_attack_preliminary_damage = preliminary_damage;
|
||||
log.debug_f("raw_damage={}, preliminary_damange={}", raw_damage, preliminary_damage);
|
||||
|
||||
@@ -571,8 +567,8 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
}
|
||||
}
|
||||
|
||||
cmd.effect.current_hp = is_nte ? attack_ap : min<int16_t>(attack_ap, 99);
|
||||
cmd.effect.ap = is_nte ? defense_power : min<int16_t>(defense_power, 99);
|
||||
cmd.effect.current_hp = is_nte ? attack_ap : std::min<int16_t>(attack_ap, 99);
|
||||
cmd.effect.ap = is_nte ? defense_power : std::min<int16_t>(defense_power, 99);
|
||||
cmd.effect.tp = attack_tp;
|
||||
|
||||
auto ps = this->player_state();
|
||||
@@ -583,7 +579,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
for (size_t strike_num = 0; strike_num < attacker_card->action_chain.chain.strike_count; strike_num++) {
|
||||
int16_t final_effective_damage = 0;
|
||||
target->commit_attack(preliminary_damage, attacker_card, &cmd, strike_num, &final_effective_damage);
|
||||
ps->stats.action_card_negated_damage += max<int16_t>(0, this->current_defense_power - final_effective_damage);
|
||||
ps->stats.action_card_negated_damage += std::max<int16_t>(0, this->current_defense_power - final_effective_damage);
|
||||
}
|
||||
} else {
|
||||
log.debug_f("flag 2 set; committing zero-damage attack");
|
||||
@@ -618,7 +614,7 @@ const Condition* Card::find_condition(ConditionType cond_type) const {
|
||||
return const_cast<Card*>(this)->find_condition(cond_type);
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
|
||||
std::shared_ptr<const CardIndex::CardEntry> Card::get_definition() const {
|
||||
return this->def_entry;
|
||||
}
|
||||
|
||||
@@ -839,10 +835,10 @@ void Card::set_current_and_max_hp(int16_t hp) {
|
||||
void Card::set_current_hp(
|
||||
uint32_t new_hp, bool propagate_shared_hp, bool enforce_max_hp) {
|
||||
if (!enforce_max_hp) {
|
||||
new_hp = max<int16_t>(new_hp, 0);
|
||||
this->max_hp = max<int16_t>(this->max_hp, new_hp);
|
||||
new_hp = std::max<int16_t>(new_hp, 0);
|
||||
this->max_hp = std::max<int16_t>(this->max_hp, new_hp);
|
||||
} else {
|
||||
new_hp = clamp<int16_t>(new_hp, 0, this->max_hp);
|
||||
new_hp = std::clamp<int16_t>(new_hp, 0, this->max_hp);
|
||||
}
|
||||
|
||||
this->current_hp = new_hp;
|
||||
@@ -955,11 +951,11 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
}
|
||||
|
||||
if (!this->action_chain.check_flag(0x10)) {
|
||||
this->action_chain.chain.effective_ap = is_nte ? effective_ap : min<int16_t>(effective_ap, 99);
|
||||
this->action_chain.chain.effective_ap = is_nte ? effective_ap : std::min<int16_t>(effective_ap, 99);
|
||||
log.debug_f("set chain effective_ap = {}", this->action_chain.chain.effective_ap);
|
||||
}
|
||||
if (!this->action_chain.check_flag(0x20)) {
|
||||
this->action_chain.chain.effective_tp = is_nte ? effective_tp : min<int16_t>(effective_tp, 99);
|
||||
this->action_chain.chain.effective_tp = is_nte ? effective_tp : std::min<int16_t>(effective_tp, 99);
|
||||
log.debug_f("set chain effective_tp = {}", this->action_chain.chain.effective_tp);
|
||||
}
|
||||
|
||||
@@ -1099,7 +1095,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
|
||||
this->action_chain.chain.damage = is_nte
|
||||
? (damage * this->action_chain.chain.damage_multiplier)
|
||||
: min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99);
|
||||
: std::min<int16_t>(damage * this->action_chain.chain.damage_multiplier, 99);
|
||||
log.debug_f("overall chain damage = {} (base) * {} (mult) = {}",
|
||||
damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
|
||||
|
||||
@@ -1108,7 +1104,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, this_sh, this_sh, 2, nullptr);
|
||||
log.debug_f("applied action conditions (2)");
|
||||
if (!is_nte && this->action_chain.check_flag(0x100)) {
|
||||
this->action_chain.chain.damage = min<int16_t>(this->action_chain.chain.damage + 5, 99);
|
||||
this->action_chain.chain.damage = std::min<int16_t>(this->action_chain.chain.damage + 5, 99);
|
||||
log.debug_f("(has flag 0x100) chain damage = {}", this->action_chain.chain.damage);
|
||||
}
|
||||
} else {
|
||||
@@ -1141,8 +1137,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string chain_str = this->action_chain.str(s);
|
||||
log.debug_f("result computed as {}", chain_str);
|
||||
log.debug_f("result computed as {}", this->action_chain.str(s));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1208,20 +1203,19 @@ void Card::move_phase_before() {
|
||||
this->server()->card_special->move_phase_before_for_card(this->shared_from_this());
|
||||
}
|
||||
|
||||
void Card::unknown_80236374(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
void Card::unknown_80236374(std::shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(std::format("unknown_80236374(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
||||
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug_f("as = {}", as_str);
|
||||
log.debug_f("as = {}", as->str(s));
|
||||
} else {
|
||||
log.debug_f("as = null");
|
||||
}
|
||||
}
|
||||
|
||||
auto check_card = [&](shared_ptr<Card> card) -> void {
|
||||
auto check_card = [&](std::shared_ptr<Card> card) -> void {
|
||||
if (card) {
|
||||
if (!card->unknown_80236554(other_card, as)) {
|
||||
log.debug_f("check_card @{:04X} #{:04X} => false", card->get_card_ref(), card->get_card_id());
|
||||
@@ -1263,7 +1257,7 @@ void Card::unknown_802379DC(const ActionState& pa) {
|
||||
pa.defense_card_ref, this->shared_from_this(), pa.original_attacker_card_ref);
|
||||
this->server()->card_special->unknown_8024A9D8(pa, 0xFFFF);
|
||||
for (size_t z = 0; z < this->action_metadata.target_card_ref_count; z++) {
|
||||
shared_ptr<Card> card = this->server()->card_for_set_card_ref(this->action_metadata.target_card_refs[z]);
|
||||
std::shared_ptr<Card> card = this->server()->card_for_set_card_ref(this->action_metadata.target_card_refs[z]);
|
||||
if (card) {
|
||||
card->action_chain.set_action_subphase_from_card(this->shared_from_this());
|
||||
card->send_6xB4x4E_4C_4D_if_needed();
|
||||
@@ -1302,7 +1296,7 @@ void Card::unknown_80237A90(const ActionState& pa, uint16_t action_card_ref) {
|
||||
if (this->action_chain.chain.target_card_ref_count == 0) {
|
||||
for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) {
|
||||
this->action_chain.add_target_card_ref(pa.target_card_refs[z]);
|
||||
shared_ptr<Card> sc_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||
std::shared_ptr<Card> sc_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||
if (sc_card) {
|
||||
sc_card->action_metadata.add_target_card_ref(this->card_ref);
|
||||
sc_card->card_flags |= 8;
|
||||
@@ -1357,7 +1351,7 @@ bool Card::is_guard_item() const {
|
||||
return (this->def_entry->def.card_class() == CardClass::GUARD_ITEM);
|
||||
}
|
||||
|
||||
bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
bool Card::unknown_80236554(std::shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(other_card
|
||||
? std::format("unknown_80236554(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
||||
@@ -1395,7 +1389,7 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
}
|
||||
}
|
||||
|
||||
this->action_metadata.attack_bonus = max<int16_t>(attack_bonus, 0);
|
||||
this->action_metadata.attack_bonus = std::max<int16_t>(attack_bonus, 0);
|
||||
log.debug_f("attack_bonus = {}", this->action_metadata.attack_bonus);
|
||||
this->last_attack_preliminary_damage = 0;
|
||||
this->last_attack_final_damage = 0;
|
||||
@@ -1421,17 +1415,17 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Card::execute_attack_on_all_valid_targets(shared_ptr<Card> attacker_card) {
|
||||
void Card::execute_attack_on_all_valid_targets(std::shared_ptr<Card> attacker_card) {
|
||||
auto s = this->server();
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = s->player_states[client_id];
|
||||
if (ps) {
|
||||
shared_ptr<Card> card = ps->get_sc_card();
|
||||
std::shared_ptr<Card> card = ps->get_sc_card();
|
||||
if (card) {
|
||||
card->execute_attack(attacker_card);
|
||||
}
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
shared_ptr<Card> card = ps->get_set_card(set_index);
|
||||
std::shared_ptr<Card> card = ps->get_set_card(set_index);
|
||||
if (card) {
|
||||
card->execute_attack(attacker_card);
|
||||
}
|
||||
@@ -1476,7 +1470,7 @@ void Card::apply_attack_result() {
|
||||
temp_chain.chain.target_card_ref_count++;
|
||||
} else if ((target_card->get_definition()->def.type == CardType::ITEM) && !this->action_chain.check_flag(0x02)) {
|
||||
auto target_ps = target_card->player_state();
|
||||
shared_ptr<Card> candidate_card;
|
||||
std::shared_ptr<Card> candidate_card;
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
auto set_card = target_ps->get_set_card(set_index);
|
||||
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
||||
@@ -1555,12 +1549,11 @@ void Card::apply_attack_result() {
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string as_str = as.str(s);
|
||||
log.debug_f("as constructed as {}", as_str);
|
||||
log.debug_f("as constructed as {}", as.str(s));
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < this->action_chain.chain.target_card_ref_count; z++) {
|
||||
shared_ptr<Card> card = s->card_for_set_card_ref(this->action_chain.chain.target_card_refs[z]);
|
||||
std::shared_ptr<Card> card = s->card_for_set_card_ref(this->action_chain.chain.target_card_refs[z]);
|
||||
if (card) {
|
||||
card->current_defense_power = card->action_metadata.attack_bonus;
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
|
||||
+223
-236
File diff suppressed because it is too large
Load Diff
+234
-245
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
NameEntry::NameEntry() {
|
||||
@@ -198,7 +196,7 @@ void DeckState::redraw_initial_hand(bool is_nte) {
|
||||
|
||||
auto s = this->server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is missing");
|
||||
throw std::runtime_error("server is missing");
|
||||
}
|
||||
|
||||
// Shuffle the deck, except the first 5 cards (which are about to be drawn).
|
||||
@@ -260,7 +258,7 @@ void DeckState::shuffle() {
|
||||
if (this->shuffle_enabled) {
|
||||
auto s = this->server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is missing");
|
||||
throw std::runtime_error("server is missing");
|
||||
}
|
||||
|
||||
size_t max = this->num_drawable_cards();
|
||||
@@ -305,17 +303,17 @@ void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index)
|
||||
this->loop_enabled ? "true" : "false");
|
||||
for (size_t z = 0; z < 31; z++) {
|
||||
const auto& e = this->entries[z];
|
||||
shared_ptr<const CardIndex::CardEntry> ce;
|
||||
std::shared_ptr<const CardIndex::CardEntry> ce;
|
||||
if (card_index) {
|
||||
try {
|
||||
ce = card_index->definition_for_id(e.card_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode(Language::ENGLISH);
|
||||
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} \"{}\" {}\n",
|
||||
z, e.deck_index, this->card_refs[z], e.card_id, name, name_for_card_state(e.state));
|
||||
z, e.deck_index, this->card_refs[z], e.card_id, ce->def.en_name.decode(Language::ENGLISH),
|
||||
name_for_card_state(e.state));
|
||||
} else {
|
||||
phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} {}\n",
|
||||
z, e.deck_index, this->card_refs[z], e.card_id, name_for_card_state(e.state));
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#include "MapState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
MapState::MapState() {
|
||||
|
||||
+47
-53
@@ -2,11 +2,9 @@
|
||||
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
PlayerState::PlayerState(uint8_t client_id, shared_ptr<Server> server)
|
||||
PlayerState::PlayerState(uint8_t client_id, std::shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
client_id(client_id),
|
||||
num_hand_redraws_allowed(1),
|
||||
@@ -46,10 +44,10 @@ void PlayerState::init() {
|
||||
if (s->player_states.at(this->client_id).get() != this) {
|
||||
// Note: The original code handles this, but we don't. This appears not to
|
||||
// ever happen, so we didn't bother implementing it.
|
||||
throw logic_error("replacing a player state object is not permitted");
|
||||
throw std::logic_error("replacing a player state object is not permitted");
|
||||
}
|
||||
|
||||
this->deck_state = make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s);
|
||||
this->deck_state = std::make_shared<DeckState>(this->client_id, s->deck_entries[client_id]->card_ids, s);
|
||||
if (s->map_and_rules->rules.disable_deck_shuffle) {
|
||||
this->deck_state->disable_shuffle();
|
||||
}
|
||||
@@ -62,7 +60,7 @@ void PlayerState::init() {
|
||||
this->team_id = s->deck_entries[this->client_id]->team_id;
|
||||
auto sc_ce = s->definition_for_card_ref(this->sc_card_ref);
|
||||
if (!sc_ce) {
|
||||
throw runtime_error("SC card definition is missing");
|
||||
throw std::runtime_error("SC card definition is missing");
|
||||
}
|
||||
if (sc_ce->def.type == CardType::HUNTERS_SC) {
|
||||
this->sc_card_type = CardType::HUNTERS_SC;
|
||||
@@ -72,13 +70,13 @@ void PlayerState::init() {
|
||||
// In the original code, sc_card_type gets left as 0xFFFFFFFF (yes, it's a
|
||||
// uint32_t). This probably breaks some things later on, so we instead
|
||||
// prevent it upfront.
|
||||
throw runtime_error("SC card is not a Hunters or Arkz SC");
|
||||
throw std::runtime_error("SC card is not a Hunters or Arkz SC");
|
||||
}
|
||||
|
||||
this->hand_and_equip = make_shared<HandAndEquipState>();
|
||||
this->card_short_statuses = make_shared<parray<CardShortStatus, 0x10>>();
|
||||
this->set_card_action_chains = make_shared<parray<ActionChainWithConds, 9>>();
|
||||
this->set_card_action_metadatas = make_shared<parray<ActionMetadata, 9>>();
|
||||
this->hand_and_equip = std::make_shared<HandAndEquipState>();
|
||||
this->card_short_statuses = std::make_shared<parray<CardShortStatus, 0x10>>();
|
||||
this->set_card_action_chains = std::make_shared<parray<ActionChainWithConds, 9>>();
|
||||
this->set_card_action_metadatas = std::make_shared<parray<ActionMetadata, 9>>();
|
||||
|
||||
this->hand_and_equip->clear_FF();
|
||||
for (size_t z = 0; z < 0x10; z++) {
|
||||
@@ -89,7 +87,7 @@ void PlayerState::init() {
|
||||
this->set_card_action_metadatas->at(z).clear_FF();
|
||||
}
|
||||
|
||||
this->sc_card = make_shared<Card>(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s);
|
||||
this->sc_card = std::make_shared<Card>(this->deck_state->sc_card_id(), this->sc_card_ref, this->client_id, s);
|
||||
this->sc_card->init();
|
||||
this->draw_initial_hand();
|
||||
if (s->options.is_nte()) {
|
||||
@@ -114,18 +112,18 @@ void PlayerState::init() {
|
||||
this->god_whim_can_use_hidden_cards = (s->deck_entries[this->client_id]->god_whim_flag != 3);
|
||||
}
|
||||
|
||||
shared_ptr<Server> PlayerState::server() {
|
||||
std::shared_ptr<Server> PlayerState::server() {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<const Server> PlayerState::server() const {
|
||||
std::shared_ptr<const Server> PlayerState::server() const {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -151,7 +149,7 @@ bool PlayerState::draw_cards_allowed() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter_ps) {
|
||||
void PlayerState::apply_assist_card_effect_on_set(std::shared_ptr<PlayerState> setter_ps) {
|
||||
auto s = this->server();
|
||||
|
||||
uint16_t assist_card_id = this->set_assist_card_id;
|
||||
@@ -244,7 +242,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
||||
case AssistEffect::LEGACY: {
|
||||
uint16_t total_cost = 0;
|
||||
for (ssize_t z = 7; z >= 0; z--) {
|
||||
shared_ptr<const Card> card = this->set_cards[z];
|
||||
std::shared_ptr<const Card> card = this->set_cards[z];
|
||||
if (card) {
|
||||
auto ce = card->get_definition();
|
||||
uint8_t card_cost = ce->def.self_cost;
|
||||
@@ -258,7 +256,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
||||
if (!is_nte) {
|
||||
this->on_cards_destroyed();
|
||||
}
|
||||
this->atk_points = min<uint8_t>(9, this->atk_points + (total_cost >> 1));
|
||||
this->atk_points = std::min<uint8_t>(9, this->atk_points + (total_cost >> 1));
|
||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
if (!is_nte) {
|
||||
s->send_6xB4x05();
|
||||
@@ -353,7 +351,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
||||
if (is_nte
|
||||
? (other_ps->assist_remaining_turns != 90 && other_ps->assist_remaining_turns != 99)
|
||||
: (other_ps->assist_remaining_turns < 10)) {
|
||||
other_ps->assist_remaining_turns = min<uint8_t>(9, other_ps->assist_remaining_turns << 1);
|
||||
other_ps->assist_remaining_turns = std::min<uint8_t>(9, other_ps->assist_remaining_turns << 1);
|
||||
}
|
||||
|
||||
for (ssize_t set_index = is_nte ? 0 : -1; set_index < 8; set_index++) {
|
||||
@@ -369,7 +367,7 @@ void PlayerState::apply_assist_card_effect_on_set(shared_ptr<PlayerState> setter
|
||||
cond.remaining_turns <<= 1;
|
||||
}
|
||||
} else if (cond.remaining_turns < 10) {
|
||||
cond.remaining_turns = min<uint8_t>(9, cond.remaining_turns << 1);
|
||||
cond.remaining_turns = std::min<uint8_t>(9, cond.remaining_turns << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,7 +478,7 @@ void PlayerState::apply_dice_effects() {
|
||||
}
|
||||
|
||||
for (size_t die_index = 0; die_index < 2; die_index++) {
|
||||
this->dice_results[die_index] = min<uint8_t>(this->dice_results[die_index], 9);
|
||||
this->dice_results[die_index] = std::min<uint8_t>(this->dice_results[die_index], 9);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -818,15 +816,14 @@ int32_t PlayerState::error_code_for_client_setting_card(
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint16_t> PlayerState::get_all_cards_within_range(
|
||||
std::vector<uint16_t> PlayerState::get_all_cards_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range, const Location& loc, uint8_t target_team_id) const {
|
||||
auto s = this->server();
|
||||
|
||||
auto log = s->log_stack("get_all_cards_within_range: ");
|
||||
string loc_str = loc.str();
|
||||
log.debug_f("loc={}, target_team_id={:02X}", loc_str, target_team_id);
|
||||
log.debug_f("loc={}, target_team_id={:02X}", loc.str(), target_team_id);
|
||||
|
||||
vector<uint16_t> ret;
|
||||
std::vector<uint16_t> ret;
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto other_ps = s->player_states[client_id];
|
||||
if (other_ps && ((target_team_id == 0xFF) || (target_team_id == other_ps->get_team_id()))) {
|
||||
@@ -845,7 +842,7 @@ void PlayerState::get_short_status_for_card_index_in_hand(size_t hand_index, Car
|
||||
stat->card_ref = this->card_refs[hand_index - 1];
|
||||
}
|
||||
|
||||
shared_ptr<DeckState> PlayerState::get_deck() {
|
||||
std::shared_ptr<DeckState> PlayerState::get_deck() {
|
||||
return this->deck_state;
|
||||
}
|
||||
|
||||
@@ -871,11 +868,11 @@ uint16_t PlayerState::get_sc_card_id() const {
|
||||
return this->sc_card_id;
|
||||
}
|
||||
|
||||
shared_ptr<Card> PlayerState::get_sc_card() {
|
||||
std::shared_ptr<Card> PlayerState::get_sc_card() {
|
||||
return this->sc_card;
|
||||
}
|
||||
|
||||
shared_ptr<const Card> PlayerState::get_sc_card() const {
|
||||
std::shared_ptr<const Card> PlayerState::get_sc_card() const {
|
||||
return this->sc_card;
|
||||
}
|
||||
|
||||
@@ -887,11 +884,11 @@ CardType PlayerState::get_sc_card_type() const {
|
||||
return this->sc_card_type;
|
||||
}
|
||||
|
||||
shared_ptr<Card> PlayerState::get_set_card(size_t set_index) {
|
||||
std::shared_ptr<Card> PlayerState::get_set_card(size_t set_index) {
|
||||
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<const Card> PlayerState::get_set_card(size_t set_index) const {
|
||||
std::shared_ptr<const Card> PlayerState::get_set_card(size_t set_index) const {
|
||||
return (set_index < 8) ? this->set_cards[set_index] : nullptr;
|
||||
}
|
||||
|
||||
@@ -964,7 +961,7 @@ uint16_t PlayerState::pop_from_discard_log(uint16_t) {
|
||||
bool PlayerState::move_card_to_location_by_card_index(size_t card_index, const Location& new_loc) {
|
||||
auto s = this->server();
|
||||
|
||||
shared_ptr<Card> card;
|
||||
std::shared_ptr<Card> card;
|
||||
if (card_index == 0) {
|
||||
card = this->sc_card;
|
||||
} else {
|
||||
@@ -1010,7 +1007,7 @@ void PlayerState::move_null_hand_refs_to_end() {
|
||||
void PlayerState::on_cards_destroyed() {
|
||||
auto s = this->server();
|
||||
|
||||
unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
|
||||
std::unordered_multimap<uint16_t, bool> card_refs_map; // {card_ref: should_return_to_hand}
|
||||
for (size_t z = 0; z < 8; z++) {
|
||||
auto card = this->set_cards[z];
|
||||
if (!card || !(card->card_flags & 2)) {
|
||||
@@ -1333,7 +1330,7 @@ bool PlayerState::set_card_from_hand(
|
||||
}
|
||||
this->card_refs[card_index + 1] = card_ref;
|
||||
// Note: NTE doesn't call the destructor on the existing card, if there is one. Is that a bug?
|
||||
this->set_cards[card_index - 7] = make_shared<Card>(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s);
|
||||
this->set_cards[card_index - 7] = std::make_shared<Card>(s->card_id_for_card_ref(card_ref), card_ref, this->client_id, s);
|
||||
auto new_card = this->set_cards[card_index - 7];
|
||||
new_card->init();
|
||||
|
||||
@@ -1433,7 +1430,7 @@ void PlayerState::set_initial_location() {
|
||||
|
||||
static const uint8_t start_tile_defs_offset_for_team_size[4] = {0, 0, 1, 3};
|
||||
if (num_team_players >= 4) {
|
||||
throw logic_error("too many players on team");
|
||||
throw std::logic_error("too many players on team");
|
||||
}
|
||||
size_t start_tile_def_index = start_tile_defs_offset_for_team_size[num_team_players] + player_index_within_team;
|
||||
uint8_t player_start_tile = mr->map.start_tile_definitions[this->team_id][start_tile_def_index];
|
||||
@@ -1455,11 +1452,11 @@ void PlayerState::set_initial_location() {
|
||||
}
|
||||
}
|
||||
if (!start_tile_found) {
|
||||
throw runtime_error("player start location not set");
|
||||
throw std::runtime_error("player start location not set");
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(shared_ptr<const Card> card) {
|
||||
void PlayerState::set_map_occupied_bit_for_card_on_warp_tile(std::shared_ptr<const Card> card) {
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
@@ -1524,7 +1521,7 @@ bool PlayerState::subtract_or_check_atk_or_def_points_for_action(const ActionSta
|
||||
|
||||
void PlayerState::subtract_atk_points(uint8_t cost) {
|
||||
this->atk_points -= cost;
|
||||
this->atk_points2 = min<uint8_t>(this->atk_points, this->atk_points2_max);
|
||||
this->atk_points2 = std::min<uint8_t>(this->atk_points, this->atk_points2_max);
|
||||
}
|
||||
|
||||
G_UpdateHand_Ep3_6xB4x02 PlayerState::prepare_6xB4x02() const {
|
||||
@@ -1571,7 +1568,7 @@ void PlayerState::set_random_assist_card_from_hand_for_free() {
|
||||
auto s = this->server();
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
vector<uint16_t> candidate_card_refs;
|
||||
std::vector<uint16_t> candidate_card_refs;
|
||||
for (size_t hand_index = 0; hand_index < 6; hand_index++) {
|
||||
uint16_t card_ref = this->card_refs[hand_index];
|
||||
auto ce = s->definition_for_card_ref(card_ref);
|
||||
@@ -1636,11 +1633,11 @@ void PlayerState::send_6xB4x04_if_needed(bool always_send) {
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players(
|
||||
std::vector<uint16_t> PlayerState::get_card_refs_within_range_from_all_players(
|
||||
const parray<uint8_t, 9 * 9>& range, const Location& loc, CardType type) const {
|
||||
auto s = this->server();
|
||||
|
||||
vector<uint16_t> ret;
|
||||
std::vector<uint16_t> ret;
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto other_ps = s->player_states[client_id];
|
||||
if (other_ps && ((other_ps->get_sc_card_type() == type) || (type == CardType::ITEM))) {
|
||||
@@ -1697,7 +1694,7 @@ void PlayerState::handle_before_turn_assist_effects() {
|
||||
break;
|
||||
case AssistEffect::ATK_DICE_2:
|
||||
// Note: This behavior doesn't match the card description. Is it supposed to add 2 or multiply by 2?
|
||||
this->atk_points = min<int16_t>(this->atk_points + 2, 9);
|
||||
this->atk_points = std::min<int16_t>(this->atk_points + 2, 9);
|
||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
break;
|
||||
case AssistEffect::SKIP_TURN:
|
||||
@@ -1756,8 +1753,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
size_t z = 0;
|
||||
do {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug_f("on action card ref {}", ref_str);
|
||||
log.debug_f("on action card ref {}", s->debug_str_for_card_ref(pa.action_card_refs[z]));
|
||||
}
|
||||
card->unknown_80237A90(pa, pa.action_card_refs[z]);
|
||||
card->unknown_802379BC(pa.action_card_refs[z]);
|
||||
@@ -1793,8 +1789,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||
if (target_card) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.target_card_refs[z]);
|
||||
log.debug_f("on target card ref {}", ref_str);
|
||||
log.debug_f("on target card ref {}", s->debug_str_for_card_ref(pa.target_card_refs[z]));
|
||||
}
|
||||
target_card->unknown_802379DC(pa);
|
||||
if (!is_nte) {
|
||||
@@ -1823,8 +1818,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
}
|
||||
for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug_f("discarding {} from hand", ref_str);
|
||||
log.debug_f("discarding {} from hand", s->debug_str_for_card_ref(pa.action_card_refs[z]));
|
||||
}
|
||||
this->discard_ref_from_hand(pa.action_card_refs[z]);
|
||||
}
|
||||
@@ -1866,7 +1860,7 @@ void PlayerState::dice_phase_before() {
|
||||
this->send_set_card_updates();
|
||||
}
|
||||
|
||||
void PlayerState::handle_homesick_assist_effect_from_bomb(shared_ptr<Card> card) {
|
||||
void PlayerState::handle_homesick_assist_effect_from_bomb(std::shared_ptr<Card> card) {
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
@@ -1998,13 +1992,13 @@ void PlayerState::roll_main_dice_or_apply_after_effects() {
|
||||
}
|
||||
this->atk_points += s->team_dice_bonus[this->team_id];
|
||||
this->def_points += s->team_dice_bonus[this->team_id];
|
||||
this->atk_points = clamp<uint8_t>(this->atk_points, 1, 9);
|
||||
this->def_points = clamp<uint8_t>(this->def_points, 1, 9);
|
||||
this->atk_points = std::clamp<uint8_t>(this->atk_points, 1, 9);
|
||||
this->def_points = std::clamp<uint8_t>(this->def_points, 1, 9);
|
||||
if (!s->options.is_nte()) {
|
||||
this->atk_bonuses = this->atk_points - atk_before_bonuses;
|
||||
this->def_bonuses = this->def_points - def_before_bonuses;
|
||||
}
|
||||
this->atk_points2 = min<uint8_t>(this->atk_points2_max, this->atk_points);
|
||||
this->atk_points2 = std::min<uint8_t>(this->atk_points2_max, this->atk_points);
|
||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
}
|
||||
|
||||
@@ -2036,7 +2030,7 @@ void PlayerState::compute_team_dice_bonus_after_draw_phase() {
|
||||
uint8_t current_team_turn = s->get_current_team_turn();
|
||||
uint8_t dice_boost = s->get_team_exp(current_team_turn) / (s->team_client_count[current_team_turn] * 12);
|
||||
s->card_special->adjust_dice_boost_if_team_has_condition_52(current_team_turn, &dice_boost, 0);
|
||||
s->team_dice_bonus[current_team_turn] = clamp<int16_t>(dice_boost, 0, 8);
|
||||
s->team_dice_bonus[current_team_turn] = std::clamp<int16_t>(dice_boost, 0, 8);
|
||||
this->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
Condition::Condition() {
|
||||
@@ -61,7 +59,7 @@ void Condition::clear_FF() {
|
||||
this->unknown_a8 = 0xFF;
|
||||
}
|
||||
|
||||
std::string Condition::str(shared_ptr<const Server> s) const {
|
||||
std::string Condition::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
|
||||
"def_eff_index={}, ref={}, value={}, giver_ref={} "
|
||||
@@ -98,7 +96,7 @@ void EffectResult::clear() {
|
||||
this->dice_roll_value = 0;
|
||||
}
|
||||
|
||||
std::string EffectResult::str(shared_ptr<const Server> s) const {
|
||||
std::string EffectResult::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"EffectResult[att_ref={}, target_ref={}, value={}, cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, cond_index={}, dice={}]",
|
||||
s->debug_str_for_card_ref(this->attacker_card_ref),
|
||||
@@ -130,7 +128,7 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
std::string CardShortStatus::str(shared_ptr<const Server> s) const {
|
||||
std::string CardShortStatus::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, u1={:04X}, max_hp={}, u2={}]",
|
||||
s->debug_str_for_card_ref(this->card_ref),
|
||||
@@ -178,7 +176,7 @@ void ActionState::clear() {
|
||||
this->unused2 = 0xFFFF;
|
||||
}
|
||||
|
||||
std::string ActionState::str(shared_ptr<const Server> s) const {
|
||||
std::string ActionState::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, def_ref={}, target_refs={}, action_refs={}, orig_attacker_ref={}]",
|
||||
this->client_id,
|
||||
@@ -222,7 +220,7 @@ bool ActionChain::operator!=(const ActionChain& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
std::string ActionChain::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"ActionChain[eff_ap={}, eff_tp={}, ap_bonus={}, damage={}, acting_ref={}, unknown_ref_a3={}, attack_action_refs={}, "
|
||||
"attack_action_ref_count={}, medium={}, target_ref_count={}, subphase={}, strikes={}, damage_mult={}, attack_num={}, "
|
||||
@@ -309,8 +307,8 @@ bool ActionChainWithConds::operator!=(const ActionChainWithConds& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
std::string ActionChainWithConds::str(shared_ptr<const Server> s) const {
|
||||
string ret = "ActionChainWithConds[chain=";
|
||||
std::string ActionChainWithConds::str(std::shared_ptr<const Server> s) const {
|
||||
std::string ret = "ActionChainWithConds[chain=";
|
||||
ret += this->chain.str(s);
|
||||
ret += ", conds=[";
|
||||
for (size_t z = 0; z < this->conditions.size(); z++) {
|
||||
@@ -383,7 +381,7 @@ void ActionChainWithConds::set_flags(uint32_t flags) {
|
||||
this->chain.flags |= flags;
|
||||
}
|
||||
|
||||
void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, shared_ptr<Server> server) {
|
||||
void ActionChainWithConds::add_attack_action_card_ref(uint16_t card_ref, std::shared_ptr<Server> server) {
|
||||
if (card_ref != 0xFFFF) {
|
||||
this->chain.attack_action_card_refs[this->chain.attack_action_card_ref_count++] = card_ref;
|
||||
}
|
||||
@@ -397,7 +395,7 @@ void ActionChainWithConds::add_target_card_ref(uint16_t card_ref) {
|
||||
}
|
||||
}
|
||||
|
||||
void ActionChainWithConds::compute_attack_medium(shared_ptr<Server> server) {
|
||||
void ActionChainWithConds::compute_attack_medium(std::shared_ptr<Server> server) {
|
||||
this->chain.attack_medium = AttackMedium::PHYSICAL;
|
||||
for (size_t z = 0; z < this->chain.attack_action_card_ref_count; z++) {
|
||||
uint16_t card_ref = this->chain.attack_action_card_refs[z];
|
||||
@@ -437,7 +435,7 @@ bool ActionChainWithConds::get_condition_value(
|
||||
return any_found;
|
||||
}
|
||||
|
||||
void ActionChainWithConds::set_action_subphase_from_card(shared_ptr<const Card> card) {
|
||||
void ActionChainWithConds::set_action_subphase_from_card(std::shared_ptr<const Card> card) {
|
||||
this->chain.action_subphase = card->server()->get_current_action_subphase();
|
||||
}
|
||||
|
||||
@@ -545,7 +543,7 @@ bool ActionMetadata::operator!=(const ActionMetadata& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
||||
std::string ActionMetadata::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"ActionMetadata[ref={}, target_ref_count={}, def_ref_count={}, subphase={}, def_power={}, def_bonus={}, "
|
||||
"att_bonus={}, flags={:08X}, target_refs={}, defense_refs={}, original_attacker_refs={}]",
|
||||
@@ -621,7 +619,7 @@ void ActionMetadata::add_target_card_ref(uint16_t card_ref) {
|
||||
}
|
||||
|
||||
void ActionMetadata::add_defense_card_ref(
|
||||
uint16_t defense_card_ref, shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
|
||||
uint16_t defense_card_ref, std::shared_ptr<Card> card, uint16_t original_attacker_card_ref) {
|
||||
if ((defense_card_ref != 0xFFFF) && (this->defense_card_ref_count < 8)) {
|
||||
this->defense_card_refs[this->defense_card_ref_count] = defense_card_ref;
|
||||
this->original_attacker_card_refs[this->defense_card_ref_count] = original_attacker_card_ref;
|
||||
@@ -634,7 +632,7 @@ HandAndEquipState::HandAndEquipState() {
|
||||
this->clear();
|
||||
}
|
||||
|
||||
std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
|
||||
std::string HandAndEquipState::str(std::shared_ptr<const Server> s) const {
|
||||
return std::format(
|
||||
"HandAndEquipState[dice=[{}, {}], atk={}, def={}, atk2={}, a1={}, total_set_cost={}, is_cpu={}, assist_flags={:08X}, "
|
||||
"hand_refs={}, assist_ref={}, set_refs={}, sc_ref={}, hand_refs2={}, set_refs2={}, assist_ref2={}, assist_set_num={}, assist_card_id={}, "
|
||||
@@ -777,7 +775,7 @@ uint8_t PlayerBattleStats::rank_for_score(float score) {
|
||||
|
||||
const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
|
||||
if (rank >= RANK_THRESHOLD_COUNT + 1) {
|
||||
throw invalid_argument("invalid rank");
|
||||
throw std::invalid_argument("invalid rank");
|
||||
}
|
||||
return RANK_NAMES[rank];
|
||||
}
|
||||
@@ -842,12 +840,12 @@ static bool is_card_within_range(
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<uint16_t> get_card_refs_within_range(
|
||||
std::vector<uint16_t> get_card_refs_within_range(
|
||||
const parray<uint8_t, 9 * 9>& range,
|
||||
const Location& loc,
|
||||
const parray<CardShortStatus, 0x10>& short_statuses,
|
||||
phosg::PrefixedLogger* log) {
|
||||
vector<uint16_t> ret;
|
||||
std::vector<uint16_t> ret;
|
||||
if (is_card_within_range(range, loc, short_statuses[0], log)) {
|
||||
if (log) {
|
||||
log->debug_f("get_card_refs_within_range: sc card @{:04X} within range", short_statuses[0].card_ref);
|
||||
|
||||
+57
-64
@@ -5,20 +5,17 @@
|
||||
#include "DataIndexes.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
void compute_effective_range(
|
||||
parray<uint8_t, 9 * 9>& ret,
|
||||
shared_ptr<const CardIndex> card_index,
|
||||
std::shared_ptr<const CardIndex> card_index,
|
||||
uint16_t card_id,
|
||||
const Location& loc,
|
||||
shared_ptr<const MapAndRulesState> map_and_rules,
|
||||
std::shared_ptr<const MapAndRulesState> map_and_rules,
|
||||
phosg::PrefixedLogger* log) {
|
||||
if (log && log->should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string loc_str = loc.str();
|
||||
log->debug_f("compute_effective_range: card_id=#{:04X}, loc={}", card_id, loc_str);
|
||||
log->debug_f("compute_effective_range: card_id=#{:04X}, loc={}", card_id, loc.str());
|
||||
log->debug_f("compute_effective_range: map_and_rules->map:");
|
||||
map_and_rules->map.print(stderr);
|
||||
}
|
||||
@@ -28,10 +25,10 @@ void compute_effective_range(
|
||||
if (card_id == 0xFFFE) { // Heavy Fog: one tile directly in front
|
||||
range_def[3] = 0x00000100;
|
||||
} else {
|
||||
shared_ptr<const CardIndex::CardEntry> ce;
|
||||
std::shared_ptr<const CardIndex::CardEntry> ce;
|
||||
try {
|
||||
ce = card_index->definition_for_id(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return;
|
||||
}
|
||||
for (size_t z = 0; z < 6; z++) {
|
||||
@@ -95,7 +92,7 @@ void compute_effective_range(
|
||||
up_y = 9 - y - 1;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid direction");
|
||||
throw std::logic_error("invalid direction");
|
||||
}
|
||||
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
|
||||
if (log) {
|
||||
@@ -117,9 +114,9 @@ void compute_effective_range(
|
||||
}
|
||||
|
||||
bool card_linkage_is_valid(
|
||||
shared_ptr<const CardIndex::CardEntry> right_ce,
|
||||
shared_ptr<const CardIndex::CardEntry> left_ce,
|
||||
shared_ptr<const CardIndex::CardEntry> sc_ce,
|
||||
std::shared_ptr<const CardIndex::CardEntry> right_ce,
|
||||
std::shared_ptr<const CardIndex::CardEntry> left_ce,
|
||||
std::shared_ptr<const CardIndex::CardEntry> sc_ce,
|
||||
bool has_permission_effect) {
|
||||
if (!right_ce) {
|
||||
return false;
|
||||
@@ -173,31 +170,27 @@ bool card_linkage_is_valid(
|
||||
return false;
|
||||
}
|
||||
|
||||
RulerServer::RulerServer(shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
team_id_for_client_id(0xFF),
|
||||
error_code1(0),
|
||||
error_code2(0),
|
||||
error_code3(0) {}
|
||||
RulerServer::RulerServer(std::shared_ptr<Server> server)
|
||||
: w_server(server), team_id_for_client_id(0xFF), error_code1(0), error_code2(0), error_code3(0) {}
|
||||
|
||||
shared_ptr<Server> RulerServer::server() {
|
||||
std::shared_ptr<Server> RulerServer::server() {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<const Server> RulerServer::server() const {
|
||||
std::shared_ptr<const Server> RulerServer::server() const {
|
||||
auto s = this->w_server.lock();
|
||||
if (!s) {
|
||||
throw runtime_error("server is deleted");
|
||||
throw std::runtime_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) {
|
||||
return const_cast<ActionChainWithConds*>(as_const(*this).action_chain_with_conds_for_card_ref(card_ref));
|
||||
return const_cast<ActionChainWithConds*>(std::as_const(*this).action_chain_with_conds_for_card_ref(card_ref));
|
||||
}
|
||||
|
||||
const ActionChainWithConds* RulerServer::action_chain_with_conds_for_card_ref(uint16_t card_ref) const {
|
||||
@@ -398,7 +391,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
|
||||
last_action_card_index = z;
|
||||
}
|
||||
|
||||
auto check_chain = [&]() -> optional<bool> {
|
||||
auto check_chain = [&]() -> std::optional<bool> {
|
||||
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.attacker_card_ref);
|
||||
if (chain) {
|
||||
for (ssize_t cond_index = 8; cond_index >= 0; cond_index--) {
|
||||
@@ -415,7 +408,7 @@ bool RulerServer::attack_action_has_pierce_and_not_rampage(const ActionState& pa
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullopt;
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (is_nte) {
|
||||
@@ -767,7 +760,7 @@ bool RulerServer::check_move_path_and_get_cost(
|
||||
if (max_dist < 1) {
|
||||
return false;
|
||||
}
|
||||
max_dist = min<uint8_t>(max_dist, 9);
|
||||
max_dist = std::min<uint8_t>(max_dist, 9);
|
||||
|
||||
const auto* short_status = this->short_status_for_card_ref(card_ref);
|
||||
if (!short_status) {
|
||||
@@ -1030,7 +1023,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
}
|
||||
break;
|
||||
case CriterionCode::HUNTER_NON_ANDROID_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0002, // Kranz
|
||||
0x0003, // Ino'lis
|
||||
@@ -1059,7 +1052,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_HU_CLASS_MALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0113, // Teifu
|
||||
0x02AA, // H-HUmar
|
||||
@@ -1070,7 +1063,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_FEMALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0003, // Ino'lis
|
||||
0x0004, // Sil'fer
|
||||
0x0006, // Kylria
|
||||
@@ -1094,7 +1087,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_NON_RA_CLASS_HUMAN_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0003, // Ino'lis
|
||||
0x0004, // Sil'fer
|
||||
@@ -1117,7 +1110,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_HU_CLASS_ANDROID_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0110, // Saligun
|
||||
0x0113, // Teifu
|
||||
0x02AC, // H-HUcast
|
||||
@@ -1128,7 +1121,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_NON_RA_CLASS_NON_NEWMAN_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0003, // Ino'lis
|
||||
0x0110, // Saligun
|
||||
@@ -1148,7 +1141,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_NON_NEWMAN_NON_FORCE_MALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0002, // Kranz
|
||||
0x0005, // Guykild
|
||||
@@ -1166,7 +1159,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_HUNEWEARL_CLASS_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0004, // Sil'fer
|
||||
0x02AB, // H-HUnewearl
|
||||
0x02CF, // H-HUnewearl
|
||||
@@ -1174,7 +1167,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_RA_CLASS_MALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0002, // Kranz
|
||||
0x0005, // Guykild
|
||||
0x02AE, // H-RAmar
|
||||
@@ -1185,7 +1178,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_RA_CLASS_FEMALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0006, // Kylria
|
||||
0x0114, // Stella
|
||||
0x02AF, // H-RAmarl
|
||||
@@ -1196,7 +1189,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_RA_OR_FO_CLASS_FEMALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0003, // Ino'lis
|
||||
0x0006, // Kylria
|
||||
0x0112, // Viviana
|
||||
@@ -1213,7 +1206,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_HU_OR_RA_CLASS_HUMAN_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0001, // Orland
|
||||
0x0002, // Kranz
|
||||
0x0004, // Sil'fer
|
||||
@@ -1230,7 +1223,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_RA_CLASS_ANDROID_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0005, // Guykild
|
||||
0x0114, // Stella
|
||||
0x02B0, // H-RAcast
|
||||
@@ -1241,7 +1234,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_FO_CLASS_FEMALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0003, // Ino'lis
|
||||
0x0112, // Viviana
|
||||
0x02B3, // H-FOmarl
|
||||
@@ -1252,7 +1245,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_HUMAN_FEMALE_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0003, // Ino'lis
|
||||
0x0004, // Sil'fer
|
||||
0x0006, // Kylria
|
||||
@@ -1269,7 +1262,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
return ret && card_ids.count(card_id2);
|
||||
}
|
||||
case CriterionCode::HUNTER_ANDROID_SC: {
|
||||
static const unordered_set<uint16_t> card_ids = {
|
||||
static const std::unordered_set<uint16_t> card_ids = {
|
||||
0x0005, // Guykild
|
||||
0x0110, // Saligun
|
||||
0x0113, // Teifu
|
||||
@@ -1433,14 +1426,14 @@ uint16_t RulerServer::compute_attack_or_defense_costs(
|
||||
final_cost = 0;
|
||||
}
|
||||
} else {
|
||||
final_cost = max<int16_t>(final_cost, this->hand_and_equip_states[pa.client_id]->atk_points);
|
||||
final_cost = std::max<int16_t>(final_cost, this->hand_and_equip_states[pa.client_id]->atk_points);
|
||||
}
|
||||
}
|
||||
|
||||
if (out_ally_cost) {
|
||||
*out_ally_cost = total_ally_cost;
|
||||
}
|
||||
return max<int16_t>(final_cost, total_cost + assist_cost_bias);
|
||||
return std::max<int16_t>(final_cost, total_cost + assist_cost_bias);
|
||||
}
|
||||
|
||||
bool RulerServer::compute_effective_range_and_target_mode_for_attack(
|
||||
@@ -1627,7 +1620,7 @@ bool RulerServer::defense_card_can_apply_to_attack(
|
||||
bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionState& pa) const {
|
||||
auto ce = this->definition_for_card_ref(pa.action_card_refs[0]);
|
||||
if (!ce) {
|
||||
throw runtime_error("defense card definition is missing");
|
||||
throw std::runtime_error("defense card definition is missing");
|
||||
}
|
||||
const auto* chain = this->action_chain_with_conds_for_card_ref(pa.original_attacker_card_ref);
|
||||
if (chain->chain.attack_action_card_ref_count < 1) {
|
||||
@@ -1646,7 +1639,7 @@ bool RulerServer::defense_card_matches_any_attack_card_top_color(const ActionSta
|
||||
return false;
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
|
||||
std::shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_ref(uint16_t card_ref) const {
|
||||
uint16_t card_id = this->card_id_for_card_ref(card_ref);
|
||||
if (card_id == 0xFFFF) {
|
||||
return nullptr;
|
||||
@@ -1841,7 +1834,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
||||
return -0x7E;
|
||||
}
|
||||
} else {
|
||||
int16_t diff = max<int16_t>(summon_area_size - summon_cost, 0);
|
||||
int16_t diff = std::max<int16_t>(summon_area_size - summon_cost, 0);
|
||||
if (x_offset > 0) {
|
||||
if (loc->x < summon_area_loc.x) {
|
||||
return -0x7E;
|
||||
@@ -1860,7 +1853,7 @@ int32_t RulerServer::error_code_for_client_setting_card(
|
||||
return -0x7E;
|
||||
}
|
||||
} else {
|
||||
int16_t diff = max<int16_t>(summon_area_size - summon_cost, 0);
|
||||
int16_t diff = std::max<int16_t>(summon_area_size - summon_cost, 0);
|
||||
if (y_offset > 0) {
|
||||
if (loc->y < summon_area_loc.y) {
|
||||
return -0x7E;
|
||||
@@ -1974,7 +1967,7 @@ bool RulerServer::flood_fill_move_path(
|
||||
Direction dirs[3] = {direction, turn_left(direction), turn_right(direction)};
|
||||
for (size_t dir_index = 0; dir_index < 3; dir_index++) {
|
||||
if (static_cast<uint8_t>(dirs[dir_index]) > 3) {
|
||||
throw logic_error("invalid direction");
|
||||
throw std::logic_error("invalid direction");
|
||||
}
|
||||
ret |= this->flood_fill_move_path(
|
||||
chain,
|
||||
@@ -2017,7 +2010,7 @@ uint16_t RulerServer::get_ally_sc_card_ref(uint16_t card_ref) const {
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
|
||||
std::shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint32_t card_id) const {
|
||||
return this->server()->definition_for_card_id(card_id);
|
||||
}
|
||||
|
||||
@@ -2130,11 +2123,11 @@ bool RulerServer::get_creature_summon_area(uint8_t client_id, Location* out_loc,
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
|
||||
std::shared_ptr<HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) {
|
||||
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
|
||||
std::shared_ptr<const HandAndEquipState> RulerServer::get_hand_and_equip_state_for_client_id(uint8_t client_id) const {
|
||||
return (client_id < 4) ? this->hand_and_equip_states[client_id] : nullptr;
|
||||
}
|
||||
|
||||
@@ -2171,7 +2164,7 @@ ssize_t RulerServer::get_path_cost(
|
||||
path_length *= cond.value;
|
||||
}
|
||||
}
|
||||
return clamp<ssize_t>(path_length + cost_penalty, 0, 99);
|
||||
return std::clamp<ssize_t>(path_length + cost_penalty, 0, 99);
|
||||
}
|
||||
|
||||
ActionType RulerServer::get_pending_action_type(const ActionState& pa) const {
|
||||
@@ -2440,9 +2433,9 @@ bool RulerServer::is_defense_valid(const ActionState& pa) {
|
||||
}
|
||||
|
||||
void RulerServer::link_objects(
|
||||
shared_ptr<MapAndRulesState> map_and_rules,
|
||||
shared_ptr<StateFlags> state_flags,
|
||||
shared_ptr<AssistServer> assist_server) {
|
||||
std::shared_ptr<MapAndRulesState> map_and_rules,
|
||||
std::shared_ptr<StateFlags> state_flags,
|
||||
std::shared_ptr<AssistServer> assist_server) {
|
||||
this->map_and_rules = map_and_rules;
|
||||
this->state_flags = state_flags;
|
||||
this->assist_server = assist_server;
|
||||
@@ -2491,7 +2484,7 @@ size_t RulerServer::max_move_distance_for_card_ref(uint32_t card_ref) const {
|
||||
if (this->find_condition_on_card_ref(card_ref, ConditionType::SET_MV, &cond, nullptr, true)) {
|
||||
ret = cond.value;
|
||||
}
|
||||
ret = max<ssize_t>(0, ret);
|
||||
ret = std::max<ssize_t>(0, ret);
|
||||
|
||||
size_t num_assists = this->assist_server->compute_num_assist_effects_for_client(client_id);
|
||||
bool has_stamina_effect = false;
|
||||
@@ -2505,7 +2498,7 @@ size_t RulerServer::max_move_distance_for_card_ref(uint32_t card_ref) const {
|
||||
}
|
||||
}
|
||||
|
||||
return has_stamina_effect ? 9 : min<ssize_t>(9, ret);
|
||||
return has_stamina_effect ? 9 : std::min<ssize_t>(9, ret);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2566,11 +2559,11 @@ void RulerServer::offsets_for_direction(
|
||||
|
||||
void RulerServer::register_player(
|
||||
uint8_t client_id,
|
||||
shared_ptr<HandAndEquipState> hes,
|
||||
shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
|
||||
shared_ptr<DeckEntry> deck_entry,
|
||||
shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
|
||||
shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
|
||||
std::shared_ptr<HandAndEquipState> hes,
|
||||
std::shared_ptr<parray<CardShortStatus, 0x10>> short_statuses,
|
||||
std::shared_ptr<DeckEntry> deck_entry,
|
||||
std::shared_ptr<parray<ActionChainWithConds, 9>> set_card_action_chains,
|
||||
std::shared_ptr<parray<ActionMetadata, 9>> set_card_action_metadatas) {
|
||||
this->hand_and_equip_states[client_id] = hes;
|
||||
this->short_statuses[client_id] = short_statuses;
|
||||
this->deck_entries[client_id] = deck_entry;
|
||||
@@ -2656,7 +2649,7 @@ int32_t RulerServer::set_cost_for_card(uint8_t client_id, uint16_t card_ref) con
|
||||
// In NTE, Land Price is apparently 2x rather than 1.5x
|
||||
ret = is_nte ? (ret << 1) : (ret + (ret >> 1));
|
||||
} else if (eff == AssistEffect::DEFLATION) {
|
||||
ret = max<int32_t>(0, ret - 1);
|
||||
ret = std::max<int32_t>(0, ret - 1);
|
||||
} else if (eff == AssistEffect::INFLATION) {
|
||||
ret++;
|
||||
}
|
||||
|
||||
+117
-119
@@ -7,11 +7,9 @@
|
||||
#include "../Revision.hh"
|
||||
#include "../SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
// This is (obviously) not the original string. The original string is:
|
||||
// This is (obviously) not the original std::string. The original std::string is:
|
||||
// NTE: "03/05/29 18:00 by K.Toya"
|
||||
// Final: "[V1][FINAL2.0] 03/09/13 15:30 by K.Toya"
|
||||
static const char* VERSION_SIGNATURE = "newserv Ep3 based on [V1][FINAL2.0] 03/09/13 15:30 by K.Toya";
|
||||
@@ -27,7 +25,7 @@ void Server::PresenceEntry::clear() {
|
||||
this->is_cpu_player = 0;
|
||||
}
|
||||
|
||||
Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
Server::Server(std::shared_ptr<Lobby> lobby, Options&& options)
|
||||
: lobby(lobby),
|
||||
battle_record(lobby ? lobby->battle_record : nullptr),
|
||||
has_lobby(lobby != nullptr),
|
||||
@@ -81,7 +79,7 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
|
||||
Server::~Server() noexcept(false) {
|
||||
if (this->logger_stack.size() != 1) {
|
||||
throw logic_error(std::format("incorrect logger stack size: expected 1, received {}", this->logger_stack.size()));
|
||||
throw std::logic_error(std::format("incorrect logger stack size: expected 1, received {}", this->logger_stack.size()));
|
||||
}
|
||||
delete this->logger_stack.back();
|
||||
}
|
||||
@@ -89,31 +87,31 @@ Server::~Server() noexcept(false) {
|
||||
void Server::init() {
|
||||
this->log().info_f("Creating server with random seed {:08X}", this->options.rand_crypt->seed());
|
||||
|
||||
this->map_and_rules = make_shared<MapAndRulesState>();
|
||||
this->map_and_rules = std::make_shared<MapAndRulesState>();
|
||||
this->num_clients_present = 0;
|
||||
this->overlay_state.clear();
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
this->presence_entries[z].clear();
|
||||
this->deck_entries[z] = make_shared<DeckEntry>();
|
||||
this->deck_entries[z] = std::make_shared<DeckEntry>();
|
||||
this->name_entries[z].clear();
|
||||
this->name_entries_valid[z] = false;
|
||||
}
|
||||
|
||||
this->card_special = make_shared<CardSpecial>(this->shared_from_this());
|
||||
this->card_special = std::make_shared<CardSpecial>(this->shared_from_this());
|
||||
|
||||
// Note: The original implementation calls the default PSOV2Encryption constructor for random_crypt, which just uses
|
||||
// 0 as the seed. It then re-seeds the generator later. We instead expect the caller to provide a seeded generator,
|
||||
// and we don't re-seed it at all.
|
||||
// this->random_crypt = make_shared<PSOV2Encryption>(0);
|
||||
// this->random_crypt = std::make_shared<PSOV2Encryption>(0);
|
||||
|
||||
this->state_flags = make_shared<StateFlags>();
|
||||
this->state_flags = std::make_shared<StateFlags>();
|
||||
|
||||
this->clear_player_flags_after_dice_phase();
|
||||
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||
|
||||
this->assist_server = make_shared<AssistServer>(this->shared_from_this());
|
||||
this->ruler_server = make_shared<RulerServer>(this->shared_from_this());
|
||||
this->assist_server = std::make_shared<AssistServer>(this->shared_from_this());
|
||||
this->ruler_server = std::make_shared<RulerServer>(this->shared_from_this());
|
||||
this->ruler_server->link_objects(this->map_and_rules, this->state_flags, this->assist_server);
|
||||
|
||||
this->send_6xB4x46();
|
||||
@@ -135,7 +133,7 @@ Server::StackLogger::StackLogger(StackLogger&& other)
|
||||
: PrefixedLogger(std::move(other)),
|
||||
server(other.server) {
|
||||
if (this->server->logger_stack.back() != &other) {
|
||||
throw logic_error("cannot move StackLogger unless it is the last one");
|
||||
throw std::logic_error("cannot move StackLogger unless it is the last one");
|
||||
}
|
||||
this->server->logger_stack.back() = this;
|
||||
}
|
||||
@@ -144,7 +142,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
|
||||
this->PrefixedLogger::operator=(std::move(other));
|
||||
this->server = other.server;
|
||||
if (this->server->logger_stack.back() != &other) {
|
||||
throw logic_error("cannot move StackLogger unless it is the last one");
|
||||
throw std::logic_error("cannot move StackLogger unless it is the last one");
|
||||
}
|
||||
this->server->logger_stack.back() = this;
|
||||
return *this;
|
||||
@@ -152,7 +150,7 @@ Server::StackLogger& Server::StackLogger::operator=(StackLogger&& other) {
|
||||
|
||||
Server::StackLogger::~StackLogger() noexcept(false) {
|
||||
if (this->server->logger_stack.back() != this) {
|
||||
throw logic_error("incorrect logger stack unwind order");
|
||||
throw std::logic_error("incorrect logger stack unwind order");
|
||||
}
|
||||
this->server->logger_stack.pop_back();
|
||||
}
|
||||
@@ -171,8 +169,7 @@ std::string Server::debug_str_for_card_ref(uint16_t card_ref) const {
|
||||
}
|
||||
auto ce = this->definition_for_card_ref(card_ref);
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode();
|
||||
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, name);
|
||||
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, ce->def.en_name.decode());
|
||||
} else {
|
||||
return std::format("@{:04X} (missing)", card_ref);
|
||||
}
|
||||
@@ -184,8 +181,7 @@ std::string Server::debug_str_for_card_id(uint16_t card_id) const {
|
||||
}
|
||||
auto ce = this->definition_for_card_id(card_id);
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode();
|
||||
return std::format("#{:04X} ({})", card_id, name);
|
||||
return std::format("#{:04X} ({})", card_id, ce->def.en_name.decode());
|
||||
} else {
|
||||
return std::format("#{:04X} (missing)", card_id);
|
||||
}
|
||||
@@ -209,17 +205,17 @@ int8_t Server::get_winner_team_id() const {
|
||||
}
|
||||
|
||||
if (!team_player_counts[0] || !team_player_counts[1]) {
|
||||
throw logic_error("at least one team has no players");
|
||||
throw std::logic_error("at least one team has no players");
|
||||
}
|
||||
if (team_win_flag_counts[0] && team_win_flag_counts[1]) {
|
||||
throw logic_error("both teams have winning players");
|
||||
throw std::logic_error("both teams have winning players");
|
||||
}
|
||||
for (int8_t z = 0; z < 2; z++) {
|
||||
if (!team_win_flag_counts[z]) {
|
||||
continue;
|
||||
}
|
||||
if (team_win_flag_counts[z] != team_player_counts[z]) {
|
||||
throw logic_error("only some players on team have won");
|
||||
throw std::logic_error("only some players on team have won");
|
||||
}
|
||||
return z;
|
||||
}
|
||||
@@ -236,10 +232,10 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
if (this->has_lobby) {
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
throw runtime_error("lobby is deleted");
|
||||
throw std::runtime_error("lobby is deleted");
|
||||
}
|
||||
|
||||
string masked_data;
|
||||
std::string masked_data;
|
||||
if (enable_masking &&
|
||||
!this->options.is_nte() &&
|
||||
!(this->options.behavior_flags & BehaviorFlag::DISABLE_MASKING) &&
|
||||
@@ -267,7 +263,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
|
||||
void Server::send_6xB4x46() const {
|
||||
// Note: This function is not part of the original implementation; it was factored out from its callsites in this
|
||||
// file and the strings were changed.
|
||||
// file and the std::strings were changed.
|
||||
|
||||
// NTE doesn't have the date_str2 field, but we send it anyway to make debugging easier.
|
||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||
@@ -275,12 +271,13 @@ void Server::send_6xB4x46() const {
|
||||
cmd.date_str1.encode(
|
||||
std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()),
|
||||
Language::ENGLISH);
|
||||
string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
||||
std::string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
||||
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH);
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> map, Language language, bool is_nte) {
|
||||
std::string Server::prepare_6xB6x41_map_definition(
|
||||
std::shared_ptr<const MapIndex::Map> map, Language language, bool is_nte) {
|
||||
auto vm = map->version(language);
|
||||
|
||||
const auto& compressed = vm->compressed(is_nte);
|
||||
@@ -295,7 +292,8 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
|
||||
|
||||
void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) const {
|
||||
if (this->last_chosen_map) {
|
||||
string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte());
|
||||
std::string data = this->prepare_6xB6x41_map_definition(
|
||||
this->last_chosen_map, ch->language, this->options.is_nte());
|
||||
this->log().info_f(
|
||||
"Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number);
|
||||
ch->send(0x6C, 0x00, data);
|
||||
@@ -366,11 +364,12 @@ void Server::add_team_exp(uint8_t team_id, int32_t exp) {
|
||||
}
|
||||
}
|
||||
|
||||
this->team_exp[team_id] = clamp<int16_t>(this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
||||
this->team_exp[team_id] = std::clamp<int16_t>(
|
||||
this->team_exp[team_id] + exp, 0, this->team_client_count[team_id] * 96);
|
||||
|
||||
uint8_t dice_boost = this->team_exp[team_id] / (this->team_client_count[team_id] * 12);
|
||||
this->card_special->adjust_dice_boost_if_team_has_condition_52(team_id, &dice_boost, 0);
|
||||
this->team_dice_bonus[team_id] = min<uint8_t>(dice_boost, 8);
|
||||
this->team_dice_bonus[team_id] = std::min<uint8_t>(dice_boost, 8);
|
||||
}
|
||||
|
||||
bool Server::advance_battle_phase() {
|
||||
@@ -396,7 +395,7 @@ bool Server::advance_battle_phase() {
|
||||
this->dice_phase_before();
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid battle phase");
|
||||
throw std::logic_error("invalid battle phase");
|
||||
}
|
||||
return this->check_for_battle_end();
|
||||
}
|
||||
@@ -414,15 +413,15 @@ void Server::draw_phase_before() {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
|
||||
std::shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_ref(uint16_t card_ref) const {
|
||||
try {
|
||||
return this->options.card_index->definition_for_id(this->card_id_for_card_ref(card_ref));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||
std::shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||
if (card_ref == 0xFFFF) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -447,7 +446,7 @@ shared_ptr<Card> Server::card_for_set_card_ref(uint16_t card_ref) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||
std::shared_ptr<const Card> Server::card_for_set_card_ref(uint16_t card_ref) const {
|
||||
return const_cast<Server*>(this)->card_for_set_card_ref(card_ref);
|
||||
}
|
||||
|
||||
@@ -584,7 +583,7 @@ bool Server::check_for_battle_end() {
|
||||
void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
throw runtime_error("player does not exist");
|
||||
throw std::runtime_error("player does not exist");
|
||||
}
|
||||
if (card_id == 0xFFFF) {
|
||||
ps->discard_set_assist_card();
|
||||
@@ -599,12 +598,12 @@ void Server::force_replace_assist_card(uint8_t client_id, uint16_t card_id) {
|
||||
void Server::force_destroy_field_character(uint8_t client_id, size_t visible_index) {
|
||||
auto ps = this->player_states.at(client_id);
|
||||
if (!ps) {
|
||||
throw runtime_error("player does not exist");
|
||||
throw std::runtime_error("player does not exist");
|
||||
}
|
||||
|
||||
// TODO: Is it possible for there to be gaps in the set cards array? If not, we could just do a direct array lookup
|
||||
// here instead of this loop
|
||||
shared_ptr<Card> set_card = nullptr;
|
||||
std::shared_ptr<Card> set_card = nullptr;
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
if (!ps->set_cards[set_index]) {
|
||||
continue;
|
||||
@@ -681,7 +680,7 @@ void Server::compute_all_map_occupied_bits() {
|
||||
}
|
||||
|
||||
void Server::compute_team_dice_bonus(uint8_t team_id) {
|
||||
this->team_dice_bonus[team_id] = clamp<int16_t>(
|
||||
this->team_dice_bonus[team_id] = std::clamp<int16_t>(
|
||||
this->team_exp[team_id] / (this->team_client_count[team_id] * 12), 0, 8);
|
||||
}
|
||||
|
||||
@@ -700,10 +699,10 @@ void Server::copy_player_states_to_prev_states() {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
|
||||
std::shared_ptr<const CardIndex::CardEntry> Server::definition_for_card_id(uint16_t card_id) const {
|
||||
try {
|
||||
return this->options.card_index->definition_for_id(card_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -731,7 +730,7 @@ void Server::determine_first_team_turn() {
|
||||
this->team_client_count[0] = this->map_and_rules->num_team0_players;
|
||||
this->team_client_count[1] = this->map_and_rules->num_players - this->team_client_count[0];
|
||||
if (this->team_client_count[0] == 0 || this->team_client_count[1] == 0) {
|
||||
throw runtime_error("one or both teams have no players");
|
||||
throw std::runtime_error("one or both teams have no players");
|
||||
}
|
||||
this->first_team_turn = 0xFF;
|
||||
while (this->first_team_turn == 0xFF) {
|
||||
@@ -927,7 +926,7 @@ void Server::end_action_phase() {
|
||||
bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
||||
auto log = this->log_stack("enqueue_attack_or_defense: ");
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string s = pa->str(this->shared_from_this());
|
||||
std::string s = pa->str(this->shared_from_this());
|
||||
log.debug_f("input: {}", s);
|
||||
}
|
||||
|
||||
@@ -977,8 +976,8 @@ bool Server::enqueue_attack_or_defense(uint8_t client_id, ActionState* pa) {
|
||||
size_t attack_index = this->num_pending_attacks++;
|
||||
this->pending_attacks[attack_index] = *pa;
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string pa_str = this->pending_attacks[attack_index].str(this->shared_from_this());
|
||||
log.debug_f("set pending attack {}: {}", attack_index, pa_str);
|
||||
log.debug_f("set pending attack {}: {}",
|
||||
attack_index, this->pending_attacks[attack_index].str(this->shared_from_this()));
|
||||
}
|
||||
ps->set_action_cards_for_action_state(*pa);
|
||||
log.debug_f("set action cards");
|
||||
@@ -1013,14 +1012,14 @@ uint8_t Server::get_current_team_turn() const {
|
||||
return this->current_team_turn1;
|
||||
}
|
||||
|
||||
shared_ptr<PlayerState> Server::get_player_state(uint8_t client_id) {
|
||||
std::shared_ptr<PlayerState> Server::get_player_state(uint8_t client_id) {
|
||||
if (client_id >= 4) {
|
||||
return nullptr;
|
||||
}
|
||||
return this->player_states[client_id];
|
||||
}
|
||||
|
||||
shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const {
|
||||
std::shared_ptr<const PlayerState> Server::get_player_state(uint8_t client_id) const {
|
||||
if (client_id >= 4) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1094,19 +1093,19 @@ void Server::move_phase_after() {
|
||||
continue;
|
||||
}
|
||||
|
||||
static const array<vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
|
||||
static const std::array<std::vector<uint16_t>, 5> DEFAULT_TRAP_CARD_IDS = {
|
||||
// Red: Dice Fever, Heavy Fog, Muscular, Immortality, Snail Pace
|
||||
vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
||||
std::vector<uint16_t>{0x00F7, 0x010F, 0x012E, 0x013B, 0x013C},
|
||||
// Blue: Gold Rush, Charity, Requiem
|
||||
vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||
std::vector<uint16_t>{0x0131, 0x012B, 0x0133},
|
||||
// Purple: Powerless Rain, Trash 1, Empty Hand, Skip Draw
|
||||
vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||
std::vector<uint16_t>{0x00FA, 0x0125, 0x0126, 0x0137},
|
||||
// Green: Brave Wind, Homesick, Fly
|
||||
vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||
std::vector<uint16_t>{0x00FB, 0x014E, 0x0107},
|
||||
// Yellow: Dice+1, Battle Royale, Reverse Card, Giant Garden, Fix
|
||||
vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
std::vector<uint16_t>{0x00F6, 0x0242, 0x014B, 0x0145, 0x012D}};
|
||||
|
||||
const vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
|
||||
const std::vector<uint16_t>* trap_card_ids = &this->options.trap_card_ids.at(trap_type);
|
||||
if (trap_card_ids->empty()) {
|
||||
trap_card_ids = &DEFAULT_TRAP_CARD_IDS.at(trap_type);
|
||||
}
|
||||
@@ -1201,7 +1200,7 @@ int8_t Server::send_6xB4x33_remove_ally_atk_if_needed(const ActionState& pa) {
|
||||
bool has_ally_cost = false;
|
||||
uint8_t ally_cost = 0;
|
||||
uint8_t setter_client_id = 0xFF;
|
||||
shared_ptr<PlayerState> setter_ps = nullptr;
|
||||
std::shared_ptr<PlayerState> setter_ps = nullptr;
|
||||
cmd.card_ref = 0xFFFF;
|
||||
for (size_t z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
auto ce = this->definition_for_card_ref(pa.action_card_refs[z]);
|
||||
@@ -1509,7 +1508,7 @@ void Server::setup_and_start_battle() {
|
||||
this->name_entries[z].clear();
|
||||
}
|
||||
} else {
|
||||
this->player_states[z] = make_shared<PlayerState>(z, this->shared_from_this());
|
||||
this->player_states[z] = std::make_shared<PlayerState>(z, this->shared_from_this());
|
||||
this->player_states[z]->init();
|
||||
}
|
||||
}
|
||||
@@ -1523,7 +1522,7 @@ void Server::setup_and_start_battle() {
|
||||
}
|
||||
auto card = ps->get_sc_card();
|
||||
if (card) {
|
||||
team_hp[ps->get_team_id()] = min<int16_t>(team_hp[ps->get_team_id()], card->get_current_hp());
|
||||
team_hp[ps->get_team_id()] = std::min<int16_t>(team_hp[ps->get_team_id()], card->get_current_hp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1739,7 +1738,7 @@ bool Server::update_registration_phase() {
|
||||
return true;
|
||||
}
|
||||
|
||||
const unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
||||
const std::unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
||||
{0x0B, &Server::handle_CAx0B_redraw_initial_hand},
|
||||
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_phase},
|
||||
{0x0D, &Server::handle_CAx0D_end_non_action_phase},
|
||||
@@ -1765,23 +1764,23 @@ const unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
||||
{0x49, &Server::handle_CAx49_card_counts},
|
||||
});
|
||||
|
||||
void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& data) {
|
||||
void Server::on_server_data_input(std::shared_ptr<Client> sender_c, const std::string& data) {
|
||||
auto header = check_size_t<G_CardBattleCommandHeader>(data, 0xFFFF);
|
||||
size_t expected_size = header.size * 4;
|
||||
if (expected_size < data.size()) {
|
||||
phosg::print_data(stderr, data);
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"command is incomplete: expected {:X} bytes, received {:X} bytes", expected_size, data.size()));
|
||||
}
|
||||
if (header.subcommand != 0xB3) {
|
||||
throw runtime_error("server data command is not 6xB3");
|
||||
throw std::runtime_error("server data command is not 6xB3");
|
||||
}
|
||||
|
||||
handler_t handler = nullptr;
|
||||
try {
|
||||
handler = this->subcommand_handlers.at(header.subsubcommand);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("unknown CAx subsubcommand");
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("unknown CAx subsubcommand");
|
||||
}
|
||||
|
||||
if (this->battle_record && this->battle_record->writable()) {
|
||||
@@ -1791,17 +1790,17 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
if ((sender_c && (sender_c->version() == Version::GC_EP3_NTE)) || !header.mask_key) {
|
||||
(this->*handler)(sender_c, data);
|
||||
} else {
|
||||
string unmasked_data = data;
|
||||
std::string unmasked_data = data;
|
||||
set_mask_for_ep3_game_command(unmasked_data.data(), unmasked_data.size(), 0);
|
||||
(this->*handler)(sender_c, unmasked_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx0B_redraw_initial_hand(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0B_redraw_initial_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_RedrawInitialHand_Ep3_CAx0B>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "REDRAW");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -1829,11 +1828,11 @@ void Server::handle_CAx0B_redraw_initial_hand(shared_ptr<Client>, const string&
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0C_end_redraw_initial_hand_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndInitialRedrawPhase_Ep3_CAx0C>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "HAND READY");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -1887,11 +1886,11 @@ void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, cons
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0D_end_non_action_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndNonAttackPhase_Ep3_CAx0D>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END PHASE");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
if (!this->options.is_nte()) {
|
||||
@@ -1909,11 +1908,11 @@ void Server::handle_CAx0D_end_non_action_phase(shared_ptr<Client>, const string&
|
||||
this->send(out_cmd_fin);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0E_discard_card_from_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_DiscardCardFromHand_Ep3_CAx0E>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "DISCARD");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -1948,11 +1947,11 @@ void Server::handle_CAx0E_discard_card_from_hand(shared_ptr<Client>, const strin
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0F_set_card_from_hand(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetCardFromHand_Ep3_CAx0F>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "SET FC");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -1994,11 +1993,11 @@ void Server::handle_CAx0F_set_card_from_hand(shared_ptr<Client>, const string& d
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code1);
|
||||
}
|
||||
|
||||
void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx10_move_fc_to_location(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MoveFieldCharacter_Ep3_CAx10>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "MOVE");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -2034,11 +2033,11 @@ void Server::handle_CAx10_move_fc_to_location(shared_ptr<Client>, const string&
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code2);
|
||||
}
|
||||
|
||||
void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx11_enqueue_attack_or_defense(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EnqueueAttackOrDefense_Ep3_CAx11>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "ENQUEUE ACT");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -2072,11 +2071,11 @@ void Server::handle_CAx11_enqueue_attack_or_defense(shared_ptr<Client>, const st
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, this->ruler_server->error_code3);
|
||||
}
|
||||
|
||||
void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx12_end_attack_list(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndAttackList_Ep3_CAx12>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END ATK LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
int32_t error_code = 0;
|
||||
@@ -2097,7 +2096,7 @@ void Server::handle_CAx12_end_attack_list(shared_ptr<Client>, const string& data
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const string& data) {
|
||||
void Server::handle_CAx13_update_map_during_setup_t(std::shared_ptr<Client> c, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<CmdT>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "UPDATE MAP");
|
||||
|
||||
@@ -2157,7 +2156,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr<Client> c, const
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client> c, const string& data) {
|
||||
void Server::handle_CAx13_update_map_during_setup(std::shared_ptr<Client> c, const std::string& data) {
|
||||
if (this->options.is_nte()) {
|
||||
this->handle_CAx13_update_map_during_setup_t<G_SetMapState_Ep3NTE_CAx13>(c, data);
|
||||
} else {
|
||||
@@ -2165,7 +2164,7 @@ void Server::handle_CAx13_update_map_during_setup(shared_ptr<Client> c, const st
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx14_update_deck_during_setup(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerDeck_Ep3_CAx14>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "UPDATE DECK");
|
||||
|
||||
@@ -2188,7 +2187,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
if (verify_error) {
|
||||
throw runtime_error(std::format("invalid deck: -0x{:X}", verify_error));
|
||||
throw std::runtime_error(std::format("invalid deck: -0x{:X}", verify_error));
|
||||
}
|
||||
if (!this->options.is_nte() && !(this->options.behavior_flags & BehaviorFlag::SKIP_D1_D2_REPLACE)) {
|
||||
this->ruler_server->replace_D1_D2_rank_cards_with_Attack(entry.card_ids);
|
||||
@@ -2211,7 +2210,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx15_unused_hard_reset_server_state(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_HardResetServerState_Ep3_CAx15>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "HARD RESET");
|
||||
|
||||
@@ -2224,10 +2223,10 @@ void Server::handle_CAx15_unused_hard_reset_server_state(shared_ptr<Client>, con
|
||||
// this->send_all_state_updates();
|
||||
// this->update_registration_phase();
|
||||
// this->setup_and_start_battle();
|
||||
throw runtime_error("hard reset command received");
|
||||
throw std::runtime_error("hard reset command received");
|
||||
}
|
||||
|
||||
void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx1B_update_player_name(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_SetPlayerName_Ep3_CAx1B>(data);
|
||||
this->send_debug_command_received_message(in_cmd.entry.client_id, in_cmd.header.subsubcommand, "UPDATE NAME");
|
||||
|
||||
@@ -2258,7 +2257,7 @@ void Server::handle_CAx1B_update_player_name(shared_ptr<Client>, const string& d
|
||||
this->send(out_cmd);
|
||||
}
|
||||
|
||||
void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx1D_start_battle(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_StartBattle_Ep3_CAx1D>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "START BATTLE");
|
||||
|
||||
@@ -2304,7 +2303,7 @@ void Server::handle_CAx1D_start_battle(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx21_end_battle(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndBattle_Ep3_CAx21>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "END BATTLE");
|
||||
if (this->setup_phase == SetupPhase::BATTLE_ENDED) {
|
||||
@@ -2318,11 +2317,11 @@ void Server::handle_CAx21_end_battle(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx28_end_defense_list(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndDefenseList_Ep3_CAx28>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END DEF LIST");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd_ack;
|
||||
@@ -2369,13 +2368,13 @@ void Server::handle_CAx28_end_defense_list(shared_ptr<Client>, const string& dat
|
||||
this->send(out_cmd_fin);
|
||||
}
|
||||
|
||||
void Server::handle_CAx2B_legacy_set_card(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx2B_legacy_set_card(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_ExecLegacyCard_Ep3_CAx2B>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "EXEC LEGACY");
|
||||
// Sega's original implementation does nothing here, so we do nothing as well.
|
||||
}
|
||||
|
||||
void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx34_subtract_ally_atk_points(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_PhotonBlastRequest_Ep3_CAx34>(data);
|
||||
|
||||
uint8_t card_ref_client_id = client_id_for_card_ref(in_cmd.card_ref);
|
||||
@@ -2450,11 +2449,11 @@ void Server::handle_CAx34_subtract_ally_atk_points(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_AdvanceFromStartingRollsPhase_Ep3_CAx37>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "CHOOSE ORDER");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
auto ps = this->player_states[in_cmd.client_id];
|
||||
@@ -2479,19 +2478,19 @@ void Server::handle_CAx37_client_ready_to_advance_from_starter_roll_phase(shared
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx3A_time_limit_expired(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx3A_time_limit_expired(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_OverallTimeLimitExpired_Ep3_CAx3A>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "TIME EXPIRED");
|
||||
// We don't need to do anything here because the overall time limit is tracked server-side instead.
|
||||
}
|
||||
|
||||
void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const string& data) {
|
||||
void Server::handle_CAx40_map_list_request(std::shared_ptr<Client> sender_c, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_MapListRequest_Ep3_CAx40>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.subsubcommand, "MAP LIST");
|
||||
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
throw runtime_error("lobby is deleted");
|
||||
throw std::runtime_error("lobby is deleted");
|
||||
}
|
||||
|
||||
size_t num_players = l ? l->count_clients() : 1;
|
||||
@@ -2512,13 +2511,13 @@ void Server::handle_CAx40_map_list_request(shared_ptr<Client> sender_c, const st
|
||||
|
||||
void Server::send_6xB6x41_to_all_clients() const {
|
||||
if (!this->last_chosen_map) {
|
||||
throw logic_error("cannot send 6xB4x41 without a map chosen");
|
||||
throw std::logic_error("cannot send 6xB4x41 without a map chosen");
|
||||
}
|
||||
|
||||
auto l = this->lobby.lock();
|
||||
if (l) {
|
||||
vector<string> map_commands_by_language;
|
||||
auto send_to_client = [&](shared_ptr<Client> c) -> void {
|
||||
std::vector<std::string> map_commands_by_language;
|
||||
auto send_to_client = [&](std::shared_ptr<Client> c) -> void {
|
||||
if (!c) {
|
||||
return;
|
||||
}
|
||||
@@ -2545,7 +2544,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
if (this->battle_record && this->battle_record->writable()) {
|
||||
// TODO: It's not great that we just pick the first one; ideally we'd put all of them in the recording and send
|
||||
// the appropriate one to the client in the playback lobby
|
||||
for (string& data : map_commands_by_language) {
|
||||
for (std::string& data : map_commands_by_language) {
|
||||
if (!data.empty()) {
|
||||
this->battle_record->add_command(BattleRecord::Event::Type::BATTLE_COMMAND, std::move(data));
|
||||
break;
|
||||
@@ -2559,7 +2558,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx41_map_request(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& cmd = check_size_t<G_MapDataRequest_Ep3_CAx41>(data);
|
||||
this->send_debug_command_received_message(cmd.header.subsubcommand, "MAP DATA");
|
||||
if (!this->options.tournament || (this->options.tournament->get_map()->map_number == cmd.map_number)) {
|
||||
@@ -2568,11 +2567,11 @@ void Server::handle_CAx41_map_request(shared_ptr<Client>, const string& data) {
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx48_end_turn(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_EndTurn_Ep3_CAx48>(data);
|
||||
this->send_debug_command_received_message(in_cmd.client_id, in_cmd.header.subsubcommand, "END TURN");
|
||||
if (in_cmd.client_id >= 4) {
|
||||
throw runtime_error("invalid client ID");
|
||||
throw std::runtime_error("invalid client ID");
|
||||
}
|
||||
|
||||
auto ps = this->get_player_state(in_cmd.client_id);
|
||||
@@ -2585,7 +2584,7 @@ void Server::handle_CAx48_end_turn(shared_ptr<Client>, const string& data) {
|
||||
this->send(out_cmd);
|
||||
}
|
||||
|
||||
void Server::handle_CAx49_card_counts(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx49_card_counts(std::shared_ptr<Client>, const std::string& data) {
|
||||
const auto& in_cmd = check_size_t<G_CardCounts_Ep3_CAx49>(data);
|
||||
this->send_debug_command_received_message(in_cmd.header.sender_client_id, in_cmd.header.subsubcommand, "CARD COUNTS");
|
||||
|
||||
@@ -2612,7 +2611,7 @@ void Server::compute_losing_team_id_and_add_winner_flags(uint32_t flags) {
|
||||
uint32_t winner_flags = flags | AssistFlag::HAS_WON_BATTLE | AssistFlag::WINNER_DECIDED_BY_DEFEAT;
|
||||
|
||||
int8_t losing_team_id = -1;
|
||||
array<uint32_t, 2> team_counts = {0, 0};
|
||||
std::array<uint32_t, 2> team_counts{0, 0};
|
||||
|
||||
if (!is_nte) {
|
||||
// First, check which team has more dead SCs
|
||||
@@ -2747,8 +2746,7 @@ void Server::unknown_8023EEF4() {
|
||||
ActionState as = this->pending_attacks_with_cards[this->unknown_a14];
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
log.debug_f("card @{:04X} #{:04X} can attack", card->get_card_ref(), card->get_card_id());
|
||||
string as_str = as.str(this->shared_from_this());
|
||||
log.debug_f("as: {}", as_str);
|
||||
log.debug_f("as: {}", as.str(this->shared_from_this()));
|
||||
}
|
||||
if (is_nte) {
|
||||
this->replace_targets_due_to_destruction_nte(&as);
|
||||
@@ -2756,8 +2754,7 @@ void Server::unknown_8023EEF4() {
|
||||
this->replace_targets_due_to_destruction_or_conditions(&as);
|
||||
}
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string as_str = as.str(this->shared_from_this());
|
||||
log.debug_f("as after target replacement: {}", as_str);
|
||||
log.debug_f("as after target replacement: {}", as.str(this->shared_from_this()));
|
||||
}
|
||||
if (this->any_target_exists_for_attack(as)) {
|
||||
log.debug_f("as is valid");
|
||||
@@ -2837,8 +2834,8 @@ void Server::execute_bomb_assist_effect() {
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
auto card = ps->get_set_card(set_index);
|
||||
if (card && !(card->card_flags & 2)) {
|
||||
max_hp = max<int16_t>(max_hp, card->get_current_hp());
|
||||
min_hp = min<int16_t>(min_hp, card->get_current_hp());
|
||||
max_hp = std::max<int16_t>(max_hp, card->get_current_hp());
|
||||
min_hp = std::min<int16_t>(min_hp, card->get_current_hp());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2875,7 +2872,7 @@ void Server::replace_targets_due_to_destruction_nte(ActionState* as) {
|
||||
continue;
|
||||
}
|
||||
auto ps = target_card->player_state();
|
||||
shared_ptr<Card> found_guard_item;
|
||||
std::shared_ptr<Card> found_guard_item;
|
||||
for (size_t z = 0; z < 8; z++) {
|
||||
auto set_card = ps->get_set_card(z);
|
||||
if (set_card && (set_card != target_card) && !(set_card->card_flags & 2) && set_card->is_guard_item()) {
|
||||
@@ -2915,7 +2912,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
|
||||
return;
|
||||
}
|
||||
|
||||
vector<uint16_t> phase1_replaced_card_refs;
|
||||
std::vector<uint16_t> phase1_replaced_card_refs;
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = this->get_player_state(client_id);
|
||||
if (!attacker_card->action_chain.check_flag(0x200 << client_id)) {
|
||||
@@ -3005,7 +3002,7 @@ void Server::replace_targets_due_to_destruction_or_conditions(ActionState* as) {
|
||||
}
|
||||
// as->target_card_refs[phase1_replaced_card_refs.size()] = 0xFFFF;
|
||||
|
||||
vector<uint16_t> phase2_replaced_card_refs;
|
||||
std::vector<uint16_t> phase2_replaced_card_refs;
|
||||
for (size_t z = 0; (z < 4 * 9) && (as->target_card_refs[z] != 0xFFFF); z++) {
|
||||
uint16_t target_card_ref = this->send_6xB4x06_if_card_ref_invalid(as->target_card_refs[z], 7);
|
||||
auto target_card = this->card_for_set_card_ref(target_card_ref);
|
||||
@@ -3105,13 +3102,14 @@ void Server::unknown_802402F4() {
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Card>> Server::const_cast_set_cards_v(const vector<shared_ptr<const Card>>& cards) {
|
||||
std::vector<std::shared_ptr<Card>> Server::const_cast_set_cards_v(
|
||||
const std::vector<std::shared_ptr<const Card>>& cards) {
|
||||
// TODO: This is dumb. Figure out a not-dumb way to do this.
|
||||
vector<shared_ptr<Card>> ret;
|
||||
std::vector<std::shared_ptr<Card>> ret;
|
||||
for (auto const_card : cards) {
|
||||
auto mutable_card = this->card_for_set_card_ref(const_card->get_card_ref());
|
||||
if (mutable_card.get() != const_card.get()) {
|
||||
throw logic_error("inconsistent set cards index");
|
||||
throw std::logic_error("inconsistent set cards index");
|
||||
}
|
||||
ret.emplace_back(mutable_card);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Episode3 {
|
||||
// RulerServer.hh/cc
|
||||
// Server.hh/cc
|
||||
|
||||
// Class ownership levels (classes may contain weak_ptrs but not shared_ptrs to classes at the same or higher level):
|
||||
// Class ownership levels (classes may contain weak_ptrs but not std::shared_ptrs to classes at the same or higher level):
|
||||
// - Server
|
||||
// - - RulerServer
|
||||
// - - - AssistServer
|
||||
|
||||
+99
-115
@@ -7,23 +7,18 @@
|
||||
#include "../SendCommands.hh"
|
||||
#include "../ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace Episode3 {
|
||||
|
||||
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_name)
|
||||
: account_id(account_id),
|
||||
player_name(player_name) {}
|
||||
Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const std::string& player_name)
|
||||
: account_id(account_id), player_name(player_name) {}
|
||||
|
||||
Tournament::PlayerEntry::PlayerEntry(shared_ptr<Client> c)
|
||||
Tournament::PlayerEntry::PlayerEntry(std::shared_ptr<Client> c)
|
||||
: account_id(c->login->account->account_id),
|
||||
client(c),
|
||||
player_name(c->character_file()->disp.name.decode(c->language())) {}
|
||||
|
||||
Tournament::PlayerEntry::PlayerEntry(
|
||||
shared_ptr<const COMDeckDefinition> com_deck)
|
||||
: account_id(0),
|
||||
com_deck(com_deck) {}
|
||||
Tournament::PlayerEntry::PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck)
|
||||
: account_id(0), com_deck(com_deck) {}
|
||||
|
||||
bool Tournament::PlayerEntry::is_com() const {
|
||||
return (this->com_deck != nullptr);
|
||||
@@ -33,8 +28,7 @@ bool Tournament::PlayerEntry::is_human() const {
|
||||
return (this->account_id != 0);
|
||||
}
|
||||
|
||||
Tournament::Team::Team(
|
||||
shared_ptr<Tournament> tournament, size_t index, size_t max_players)
|
||||
Tournament::Team::Team(std::shared_ptr<Tournament> tournament, size_t index, size_t max_players)
|
||||
: tournament(tournament),
|
||||
index(index),
|
||||
max_players(max_players),
|
||||
@@ -43,7 +37,7 @@ Tournament::Team::Team(
|
||||
num_rounds_cleared(0),
|
||||
is_active(true) {}
|
||||
|
||||
string Tournament::Team::str() const {
|
||||
std::string Tournament::Team::str() const {
|
||||
size_t num_human_players = 0;
|
||||
size_t num_com_players = 0;
|
||||
for (const auto& player : this->players) {
|
||||
@@ -51,7 +45,7 @@ string Tournament::Team::str() const {
|
||||
num_com_players += player.is_com();
|
||||
}
|
||||
|
||||
string ret = std::format("[Team/{} {} {}H/{}C/{}P name={} pass={} rounds={}",
|
||||
std::string ret = std::format("[Team/{} {} {}H/{}C/{}P name={} pass={} rounds={}",
|
||||
this->index, this->is_active ? "active" : "inactive",
|
||||
num_human_players, num_com_players, this->max_players, this->name,
|
||||
this->password, this->num_rounds_cleared);
|
||||
@@ -67,26 +61,27 @@ string Tournament::Team::str() const {
|
||||
return ret + "]";
|
||||
}
|
||||
|
||||
void Tournament::Team::register_player(shared_ptr<Client> c, const string& team_name, const string& password) {
|
||||
void Tournament::Team::register_player(
|
||||
std::shared_ptr<Client> c, const std::string& team_name, const std::string& password) {
|
||||
if (this->players.size() >= this->max_players) {
|
||||
throw runtime_error("team is full");
|
||||
throw std::runtime_error("team is full");
|
||||
}
|
||||
|
||||
if (!this->name.empty() && (password != this->password)) {
|
||||
throw runtime_error("incorrect password");
|
||||
throw std::runtime_error("incorrect password");
|
||||
}
|
||||
|
||||
auto tournament = this->tournament.lock();
|
||||
if (!tournament) {
|
||||
throw runtime_error("tournament has been deleted");
|
||||
throw std::runtime_error("tournament has been deleted");
|
||||
}
|
||||
if (!tournament->all_player_account_ids.emplace(c->login->account->account_id).second) {
|
||||
throw runtime_error("player already registered in same tournament");
|
||||
throw std::runtime_error("player already registered in same tournament");
|
||||
}
|
||||
|
||||
for (const auto& player : this->players) {
|
||||
if (player.is_human() && (player.account_id == c->login->account->account_id)) {
|
||||
throw logic_error("player already registered in team but not in tournament");
|
||||
throw std::logic_error("player already registered in team but not in tournament");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +120,7 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
|
||||
// Look through the pending matches to see if this team is involved in any of them
|
||||
for (auto match : tournament->pending_matches) {
|
||||
if (!match->preceding_a || !match->preceding_b) {
|
||||
throw logic_error("zero-round match is pending after tournament registration phase");
|
||||
throw std::logic_error("zero-round match is pending after tournament registration phase");
|
||||
}
|
||||
if (match->preceding_a->winner_team.get() == this) {
|
||||
match->set_winner_team(match->preceding_b->winner_team);
|
||||
@@ -139,7 +134,7 @@ bool Tournament::Team::unregister_player(uint32_t account_id) {
|
||||
} else {
|
||||
// If the tournament has not started yet, just remove the player from the team
|
||||
if (!tournament->all_player_account_ids.erase(account_id)) {
|
||||
throw logic_error("player removed from team but not from tournament");
|
||||
throw std::logic_error("player removed from team but not from tournament");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,27 +171,19 @@ size_t Tournament::Team::num_com_players() const {
|
||||
}
|
||||
|
||||
Tournament::Match::Match(
|
||||
shared_ptr<Tournament> tournament, shared_ptr<Match> preceding_a, shared_ptr<Match> preceding_b)
|
||||
: tournament(tournament),
|
||||
preceding_a(preceding_a),
|
||||
preceding_b(preceding_b),
|
||||
winner_team(nullptr),
|
||||
round_num(0) {
|
||||
std::shared_ptr<Tournament> tournament, std::shared_ptr<Match> preceding_a, std::shared_ptr<Match> preceding_b)
|
||||
: tournament(tournament), preceding_a(preceding_a), preceding_b(preceding_b), winner_team(nullptr), round_num(0) {
|
||||
if (this->preceding_a->round_num != this->preceding_b->round_num) {
|
||||
throw logic_error("preceding matches have different round numbers");
|
||||
throw std::logic_error("preceding matches have different round numbers");
|
||||
}
|
||||
this->round_num = this->preceding_a->round_num + 1;
|
||||
}
|
||||
|
||||
Tournament::Match::Match(shared_ptr<Tournament> tournament, shared_ptr<Team> winner_team)
|
||||
: tournament(tournament),
|
||||
preceding_a(nullptr),
|
||||
preceding_b(nullptr),
|
||||
winner_team(winner_team),
|
||||
round_num(0) {}
|
||||
Tournament::Match::Match(std::shared_ptr<Tournament> tournament, std::shared_ptr<Team> winner_team)
|
||||
: tournament(tournament), preceding_a(nullptr), preceding_b(nullptr), winner_team(winner_team), round_num(0) {}
|
||||
|
||||
string Tournament::Match::str() const {
|
||||
string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
||||
std::string Tournament::Match::str() const {
|
||||
std::string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
||||
return std::format("[Match round={} winner={}]", this->round_num, winner_str);
|
||||
}
|
||||
|
||||
@@ -262,12 +249,12 @@ void Tournament::Match::on_winner_team_set() {
|
||||
}
|
||||
}
|
||||
|
||||
void Tournament::Match::set_winner_team_without_triggers(shared_ptr<Team> team) {
|
||||
void Tournament::Match::set_winner_team_without_triggers(std::shared_ptr<Team> team) {
|
||||
if (!this->preceding_a || !this->preceding_b) {
|
||||
throw logic_error("set_winner_team called on zero-round match");
|
||||
throw std::logic_error("set_winner_team called on zero-round match");
|
||||
}
|
||||
if ((team != this->preceding_a->winner_team) && (team != this->preceding_b->winner_team)) {
|
||||
throw logic_error("winner team did not participate in match");
|
||||
throw std::logic_error("winner team did not participate in match");
|
||||
}
|
||||
|
||||
this->winner_team = team;
|
||||
@@ -280,29 +267,29 @@ void Tournament::Match::set_winner_team_without_triggers(shared_ptr<Team> team)
|
||||
}
|
||||
}
|
||||
|
||||
void Tournament::Match::set_winner_team(shared_ptr<Team> team) {
|
||||
void Tournament::Match::set_winner_team(std::shared_ptr<Team> team) {
|
||||
this->set_winner_team_without_triggers(team);
|
||||
this->on_winner_team_set();
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(shared_ptr<Team> team) const {
|
||||
std::shared_ptr<Tournament::Team> Tournament::Match::opponent_team_for_team(std::shared_ptr<Team> team) const {
|
||||
if (!this->preceding_a || !this->preceding_b) {
|
||||
throw logic_error("zero-round matches do not have opponents");
|
||||
throw std::logic_error("zero-round matches do not have opponents");
|
||||
}
|
||||
if (team == this->preceding_a->winner_team) {
|
||||
return this->preceding_b->winner_team;
|
||||
} else if (team == this->preceding_b->winner_team) {
|
||||
return this->preceding_a->winner_team;
|
||||
} else {
|
||||
throw logic_error("team is not registered for this match");
|
||||
throw std::logic_error("team is not registered for this match");
|
||||
}
|
||||
}
|
||||
|
||||
Tournament::Tournament(
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const string& name,
|
||||
shared_ptr<const MapIndex::Map> map,
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const std::string& name,
|
||||
std::shared_ptr<const MapIndex::Map> map,
|
||||
const Rules& rules,
|
||||
size_t num_teams,
|
||||
uint8_t flags)
|
||||
@@ -317,18 +304,20 @@ Tournament::Tournament(
|
||||
current_state(State::REGISTRATION),
|
||||
menu_item_id(0xFFFFFFFF) {
|
||||
if (this->num_teams < 4) {
|
||||
throw invalid_argument("team count must be 4 or more");
|
||||
throw std::invalid_argument("team count must be 4 or more");
|
||||
}
|
||||
if (this->num_teams > 32) {
|
||||
throw invalid_argument("team count must be 32 or fewer");
|
||||
throw std::invalid_argument("team count must be 32 or fewer");
|
||||
}
|
||||
if (this->num_teams & (this->num_teams - 1)) {
|
||||
throw invalid_argument("team count must be a power of 2");
|
||||
throw std::invalid_argument("team count must be a power of 2");
|
||||
}
|
||||
}
|
||||
|
||||
Tournament::Tournament(
|
||||
shared_ptr<const MapIndex> map_index, shared_ptr<const COMDeckIndex> com_deck_index, const phosg::JSON& json)
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const phosg::JSON& json)
|
||||
: log(std::format("[Tournament:{}] ", json.get_string("name"))),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
@@ -336,7 +325,7 @@ Tournament::Tournament(
|
||||
current_state(State::REGISTRATION) {}
|
||||
|
||||
void Tournament::init() {
|
||||
vector<size_t> team_index_to_rounds_cleared;
|
||||
std::vector<size_t> team_index_to_rounds_cleared;
|
||||
|
||||
bool is_registration_complete;
|
||||
if (!this->source_json.is_null()) {
|
||||
@@ -350,7 +339,7 @@ void Tournament::init() {
|
||||
is_registration_complete = this->source_json.get_bool("is_registration_complete");
|
||||
|
||||
for (const auto& team_json : this->source_json.get_list("teams")) {
|
||||
auto& team = this->teams.emplace_back(make_shared<Team>(
|
||||
auto& team = this->teams.emplace_back(std::make_shared<Team>(
|
||||
this->shared_from_this(), this->teams.size(), team_json->get_int("max_players")));
|
||||
team->name = team_json->get_string("name");
|
||||
team->password = team_json->get_string("password");
|
||||
@@ -367,7 +356,7 @@ void Tournament::init() {
|
||||
} else if (player_json->is_string()) {
|
||||
team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string()));
|
||||
} else {
|
||||
throw runtime_error("invalid player spec");
|
||||
throw std::runtime_error("invalid player spec");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -378,7 +367,7 @@ void Tournament::init() {
|
||||
} else {
|
||||
// Create empty teams
|
||||
while (this->teams.size() < this->num_teams) {
|
||||
auto t = make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
|
||||
auto t = std::make_shared<Team>(this->shared_from_this(), this->teams.size(), (this->flags & Flag::IS_2V2) ? 2 : 1);
|
||||
this->teams.emplace_back(t);
|
||||
}
|
||||
is_registration_complete = false;
|
||||
@@ -390,12 +379,12 @@ void Tournament::init() {
|
||||
this->create_bracket_matches();
|
||||
|
||||
// Start with all zero-round matches in the match queue
|
||||
unordered_set<shared_ptr<Match>> match_queue;
|
||||
std::unordered_set<std::shared_ptr<Match>> match_queue;
|
||||
for (auto match : this->zero_round_matches) {
|
||||
match_queue.emplace(match->following.lock());
|
||||
}
|
||||
if (match_queue.count(nullptr)) {
|
||||
throw logic_error("null match in match queue");
|
||||
throw std::logic_error("null match in match queue");
|
||||
}
|
||||
|
||||
// For each match in the queue, either resolve it from the previous state or
|
||||
@@ -406,12 +395,12 @@ void Tournament::init() {
|
||||
match_queue.erase(match_it);
|
||||
|
||||
if (!match->preceding_a->winner_team || !match->preceding_b->winner_team) {
|
||||
throw logic_error("preceding matches are not resolved");
|
||||
throw std::logic_error("preceding matches are not resolved");
|
||||
}
|
||||
size_t& a_rounds_cleared = team_index_to_rounds_cleared[match->preceding_a->winner_team->index];
|
||||
size_t& b_rounds_cleared = team_index_to_rounds_cleared[match->preceding_b->winner_team->index];
|
||||
if (a_rounds_cleared && b_rounds_cleared) {
|
||||
throw runtime_error("both teams won the same match");
|
||||
throw std::runtime_error("both teams won the same match");
|
||||
}
|
||||
if (!a_rounds_cleared && !b_rounds_cleared) {
|
||||
this->pending_matches.emplace(match); // Neither team has won yet
|
||||
@@ -434,7 +423,7 @@ void Tournament::init() {
|
||||
}
|
||||
|
||||
if (!this->final_match->winner_team == this->pending_matches.empty()) {
|
||||
throw logic_error("there must be pending matches if and only if the final match is not resolved");
|
||||
throw std::logic_error("there must be pending matches if and only if the final match is not resolved");
|
||||
}
|
||||
|
||||
// If all matches are resolved, then the tournament is complete
|
||||
@@ -449,19 +438,19 @@ void Tournament::init() {
|
||||
|
||||
void Tournament::create_bracket_matches() {
|
||||
if (this->teams.size() < 4) {
|
||||
throw logic_error("tournaments must have at least 4 teams");
|
||||
throw std::logic_error("tournaments must have at least 4 teams");
|
||||
}
|
||||
if (this->teams.size() > 32) {
|
||||
throw logic_error("tournaments must have at most 32 teams");
|
||||
throw std::logic_error("tournaments must have at most 32 teams");
|
||||
}
|
||||
if (this->teams.size() & (this->teams.size() - 1)) {
|
||||
throw logic_error("tournaments team count is not a power of 2");
|
||||
throw std::logic_error("tournaments team count is not a power of 2");
|
||||
}
|
||||
|
||||
// Create the zero-round matches, and make them all pending if registration is still open
|
||||
this->zero_round_matches.clear();
|
||||
for (const auto& team : this->teams) {
|
||||
auto m = make_shared<Match>(this->shared_from_this(), team);
|
||||
auto m = std::make_shared<Match>(this->shared_from_this(), team);
|
||||
this->zero_round_matches.emplace_back(m);
|
||||
if (this->current_state == State::REGISTRATION) {
|
||||
this->pending_matches.emplace(m);
|
||||
@@ -469,11 +458,11 @@ void Tournament::create_bracket_matches() {
|
||||
}
|
||||
|
||||
// Create the bracket matches
|
||||
vector<shared_ptr<Match>> current_round_matches = this->zero_round_matches;
|
||||
std::vector<std::shared_ptr<Match>> current_round_matches = this->zero_round_matches;
|
||||
while (current_round_matches.size() > 1) {
|
||||
vector<shared_ptr<Match>> next_round_matches;
|
||||
std::vector<std::shared_ptr<Match>> next_round_matches;
|
||||
for (size_t z = 0; z < current_round_matches.size(); z += 2) {
|
||||
auto m = make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
|
||||
auto m = std::make_shared<Match>(this->shared_from_this(), current_round_matches[z], current_round_matches[z + 1]);
|
||||
current_round_matches[z]->following = m;
|
||||
current_round_matches[z + 1]->following = m;
|
||||
next_round_matches.emplace_back(std::move(m));
|
||||
@@ -516,26 +505,26 @@ phosg::JSON Tournament::json() const {
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
||||
std::shared_ptr<Tournament::Team> Tournament::get_winner_team() const {
|
||||
if (this->current_state != State::COMPLETE) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!this->final_match) {
|
||||
throw logic_error("tournament is complete but final match is missing");
|
||||
throw std::logic_error("tournament is complete but final match is missing");
|
||||
}
|
||||
if (!this->final_match->winner_team) {
|
||||
throw logic_error("tournament is complete but winner is not set");
|
||||
throw std::logic_error("tournament is complete but winner is not set");
|
||||
}
|
||||
return this->final_match->winner_team;
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> team) const {
|
||||
std::shared_ptr<Tournament::Match> Tournament::next_match_for_team(std::shared_ptr<Team> team) const {
|
||||
if (this->current_state == Tournament::State::REGISTRATION) {
|
||||
return nullptr;
|
||||
}
|
||||
for (auto match : this->pending_matches) {
|
||||
if (!match->preceding_a || !match->preceding_b) {
|
||||
throw logic_error("zero-round match is pending after tournament registration phase");
|
||||
throw std::logic_error("zero-round match is pending after tournament registration phase");
|
||||
}
|
||||
if ((team == match->preceding_a->winner_team) || (team == match->preceding_b->winner_team)) {
|
||||
return match;
|
||||
@@ -544,11 +533,11 @@ shared_ptr<Tournament::Match> Tournament::next_match_for_team(shared_ptr<Team> t
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Match> Tournament::get_final_match() const {
|
||||
std::shared_ptr<Tournament::Match> Tournament::get_final_match() const {
|
||||
return this->final_match;
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
|
||||
std::shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id) const {
|
||||
if (!this->all_player_account_ids.count(account_id)) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -561,16 +550,16 @@ shared_ptr<Tournament::Team> Tournament::team_for_account_id(uint32_t account_id
|
||||
}
|
||||
}
|
||||
|
||||
throw logic_error("account ID registered in tournament but not in any team");
|
||||
throw std::logic_error("account ID registered in tournament but not in any team");
|
||||
}
|
||||
|
||||
const set<uint32_t>& Tournament::get_all_player_account_ids() const {
|
||||
const std::set<uint32_t>& Tournament::get_all_player_account_ids() const {
|
||||
return this->all_player_account_ids;
|
||||
}
|
||||
|
||||
void Tournament::start() {
|
||||
if (this->current_state != State::REGISTRATION) {
|
||||
throw runtime_error("tournament has already started");
|
||||
throw std::runtime_error("tournament has already started");
|
||||
}
|
||||
|
||||
bool has_com_teams = (this->flags & Flag::HAS_COM_TEAMS);
|
||||
@@ -584,7 +573,7 @@ void Tournament::start() {
|
||||
}
|
||||
}
|
||||
if (num_human_teams < (has_com_teams ? 1 : 2)) {
|
||||
throw runtime_error("not enough registrants to start tournament");
|
||||
throw std::runtime_error("not enough registrants to start tournament");
|
||||
}
|
||||
|
||||
if ((this->flags & Flag::SHUFFLE_ENTRIES) && (this->flags & Flag::RESIZE_ON_START)) {
|
||||
@@ -642,11 +631,11 @@ void Tournament::start() {
|
||||
}
|
||||
for (const auto& player : t->players) {
|
||||
if (player.is_com()) {
|
||||
throw logic_error("non-human player on team before tournament start");
|
||||
throw std::logic_error("non-human player on team before tournament start");
|
||||
}
|
||||
}
|
||||
if (this->com_deck_index->num_decks() < t->max_players - t->players.size()) {
|
||||
throw runtime_error("not enough COM decks to complete team");
|
||||
throw std::runtime_error("not enough COM decks to complete team");
|
||||
}
|
||||
// If we allow all-COM teams, or this is a 2v2 tournament and the team has only one human on it, add a COM
|
||||
if (has_com_teams || !t->players.empty()) {
|
||||
@@ -687,10 +676,10 @@ void Tournament::send_all_state_updates_on_deletion() const {
|
||||
}
|
||||
}
|
||||
|
||||
string Tournament::bracket_str() const {
|
||||
string ret = std::format("Tournament \"{}\"\n", this->name);
|
||||
std::string Tournament::bracket_str() const {
|
||||
std::string ret = std::format("Tournament \"{}\"\n", this->name);
|
||||
|
||||
function<void(shared_ptr<Match>, size_t)> add_match = [&](shared_ptr<Match> m, size_t indent_level) -> void {
|
||||
std::function<void(std::shared_ptr<Match>, size_t)> add_match = [&](std::shared_ptr<Match> m, size_t indent_level) -> void {
|
||||
ret.append(2 * indent_level, ' ');
|
||||
ret += m->str();
|
||||
if (this->pending_matches.count(m)) {
|
||||
@@ -707,13 +696,12 @@ string Tournament::bracket_str() const {
|
||||
|
||||
auto en_vm = this->map->version(Language::ENGLISH);
|
||||
if (en_vm) {
|
||||
string map_name = en_vm->map->name.decode(en_vm->language);
|
||||
std::string map_name = en_vm->map->name.decode(en_vm->language);
|
||||
ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name);
|
||||
} else {
|
||||
ret += std::format(" Map: {:08X}\n", this->map->map_number);
|
||||
}
|
||||
string rules_str = this->rules.str();
|
||||
ret += std::format(" Rules: {}\n", rules_str);
|
||||
ret += std::format(" Rules: {}\n", this->rules.str());
|
||||
ret += std::format(" Structure: {}, {} entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
||||
ret += std::format(" COM teams: {}\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
||||
ret += std::format(" Shuffle entries: {}\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
||||
@@ -739,14 +727,12 @@ string Tournament::bracket_str() const {
|
||||
if (this->current_state == State::REGISTRATION) {
|
||||
ret += " Teams:\n";
|
||||
for (const auto& team : this->teams) {
|
||||
string team_str = team->str();
|
||||
ret += std::format(" {}\n", team_str);
|
||||
ret += std::format(" {}\n", team->str());
|
||||
}
|
||||
} else {
|
||||
ret += " Pending matches:\n";
|
||||
for (const auto& match : this->pending_matches) {
|
||||
string match_str = match->str();
|
||||
ret += std::format(" {}\n", match_str);
|
||||
ret += std::format(" {}\n", match->str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,13 +741,11 @@ string Tournament::bracket_str() const {
|
||||
}
|
||||
|
||||
TournamentIndex::TournamentIndex(
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const string& state_filename,
|
||||
std::shared_ptr<const MapIndex> map_index,
|
||||
std::shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const std::string& state_filename,
|
||||
bool skip_load_state)
|
||||
: map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
state_filename(state_filename) {
|
||||
: map_index(map_index), com_deck_index(com_deck_index), state_filename(state_filename) {
|
||||
if (this->state_filename.empty() || skip_load_state) {
|
||||
return;
|
||||
}
|
||||
@@ -775,14 +759,14 @@ TournamentIndex::TournamentIndex(
|
||||
|
||||
if (json.is_list()) {
|
||||
if (json.size() > 0x20) {
|
||||
throw runtime_error("tournament phosg::JSON list length is incorrect");
|
||||
throw std::runtime_error("tournament phosg::JSON list length is incorrect");
|
||||
}
|
||||
for (size_t z = 0; z < min<size_t>(json.size(), 0x20); z++) {
|
||||
for (size_t z = 0; z < std::min<size_t>(json.size(), 0x20); z++) {
|
||||
if (!json.at(z).is_null()) {
|
||||
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, json.at(z));
|
||||
auto tourn = std::make_shared<Tournament>(this->map_index, this->com_deck_index, json.at(z));
|
||||
tourn->init();
|
||||
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
||||
throw runtime_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||
throw std::runtime_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||
}
|
||||
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
||||
this->menu_item_id_to_tournament.emplace_back(tourn);
|
||||
@@ -790,20 +774,20 @@ TournamentIndex::TournamentIndex(
|
||||
}
|
||||
} else if (json.is_dict()) {
|
||||
if (json.size() > 0x20) {
|
||||
throw runtime_error("tournament phosg::JSON dict length is incorrect");
|
||||
throw std::runtime_error("tournament phosg::JSON dict length is incorrect");
|
||||
}
|
||||
for (const auto& it : json.as_dict()) {
|
||||
auto tourn = make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
|
||||
auto tourn = std::make_shared<Tournament>(this->map_index, this->com_deck_index, *it.second);
|
||||
tourn->init();
|
||||
if (!this->name_to_tournament.emplace(tourn->get_name(), tourn).second) {
|
||||
// This is logic_error instead of runtime_error because phosg::JSON dicts already have unique keys
|
||||
throw logic_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||
throw std::logic_error("multiple tournaments have the same name: " + tourn->get_name());
|
||||
}
|
||||
tourn->set_menu_item_id(this->menu_item_id_to_tournament.size());
|
||||
this->menu_item_id_to_tournament.emplace_back(tourn);
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("tournament state root phosg::JSON is not a list or dict");
|
||||
throw std::runtime_error("tournament state root phosg::JSON is not a list or dict");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,20 +803,20 @@ void TournamentIndex::save() const {
|
||||
phosg::save_file(this->state_filename, json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS | phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
|
||||
shared_ptr<Tournament> TournamentIndex::create_tournament(
|
||||
const string& name,
|
||||
shared_ptr<const MapIndex::Map> map,
|
||||
std::shared_ptr<Tournament> TournamentIndex::create_tournament(
|
||||
const std::string& name,
|
||||
std::shared_ptr<const MapIndex::Map> map,
|
||||
const Rules& rules,
|
||||
size_t num_teams,
|
||||
uint8_t flags) {
|
||||
if (this->name_to_tournament.size() >= 0x20) {
|
||||
throw runtime_error("there can be at most 32 tournaments at a time");
|
||||
throw std::runtime_error("there can be at most 32 tournaments at a time");
|
||||
}
|
||||
|
||||
auto t = make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
|
||||
auto t = std::make_shared<Tournament>(this->map_index, this->com_deck_index, name, map, rules, num_teams, flags);
|
||||
t->init();
|
||||
if (!this->name_to_tournament.emplace(t->get_name(), t).second) {
|
||||
throw runtime_error("a tournament with the same name already exists");
|
||||
throw std::runtime_error("a tournament with the same name already exists");
|
||||
}
|
||||
|
||||
size_t z;
|
||||
@@ -852,7 +836,7 @@ shared_ptr<Tournament> TournamentIndex::create_tournament(
|
||||
return t;
|
||||
}
|
||||
|
||||
bool TournamentIndex::delete_tournament(const string& name) {
|
||||
bool TournamentIndex::delete_tournament(const std::string& name) {
|
||||
auto it = this->name_to_tournament.find(name);
|
||||
if (it == this->name_to_tournament.end()) {
|
||||
return false;
|
||||
@@ -869,7 +853,7 @@ bool TournamentIndex::delete_tournament(const string& name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t account_id) const {
|
||||
std::shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t account_id) const {
|
||||
for (const auto& it : this->name_to_tournament) {
|
||||
const auto& tourn = it.second;
|
||||
auto team = tourn->team_for_account_id(account_id);
|
||||
@@ -880,7 +864,7 @@ shared_ptr<Tournament::Team> TournamentIndex::team_for_account_id(uint32_t accou
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TournamentIndex::link_client(shared_ptr<Client> c) {
|
||||
void TournamentIndex::link_client(std::shared_ptr<Client> c) {
|
||||
if (!is_ep3(c->version())) {
|
||||
return;
|
||||
}
|
||||
@@ -898,7 +882,7 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw logic_error("tournament team found for player, but player not found on team");
|
||||
throw std::logic_error("tournament team found for player, but player not found on team");
|
||||
} else {
|
||||
c->ep3_tournament_team.reset();
|
||||
if (c->version() == Version::GC_EP3) {
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
#include "FileContentsCache.hh"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
FileContentsCache::FileContentsCache(uint64_t ttl_usecs) : ttl_usecs(ttl_usecs) {}
|
||||
|
||||
FileContentsCache::File::File(
|
||||
const string& name,
|
||||
string&& data,
|
||||
uint64_t load_time)
|
||||
: name(name),
|
||||
data(make_shared<string>(std::move(data))),
|
||||
load_time(load_time) {}
|
||||
|
||||
shared_ptr<const FileContentsCache::File> FileContentsCache::replace(
|
||||
const string& name, string&& data, uint64_t t) {
|
||||
if (t == 0) {
|
||||
t = phosg::now();
|
||||
}
|
||||
auto new_file = make_shared<File>(name, std::move(data), t);
|
||||
auto emplace_ret = this->name_to_file.emplace(name, new_file);
|
||||
if (!emplace_ret.second) {
|
||||
emplace_ret.first->second = new_file;
|
||||
}
|
||||
return new_file;
|
||||
}
|
||||
|
||||
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, std::move(s), t);
|
||||
}
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const std::string& name) {
|
||||
return this->get(name, phosg::load_file);
|
||||
}
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get_or_load(const char* name) {
|
||||
return this->get_or_load(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).file;
|
||||
}
|
||||
|
||||
shared_ptr<const FileContentsCache::File> 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 = phosg::now();
|
||||
try {
|
||||
auto& entry = this->name_to_file.at(name);
|
||||
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};
|
||||
}
|
||||
|
||||
FileContentsCache::GetResult FileContentsCache::get(const char* name,
|
||||
std::function<std::string(const std::string&)> generate) {
|
||||
return this->get(string(name), generate);
|
||||
}
|
||||
|
||||
shared_ptr<const string> ThreadSafeFileCache::get(
|
||||
const string& name, std::function<shared_ptr<const string>(const std::string&)> generate) {
|
||||
try {
|
||||
shared_lock g(this->lock);
|
||||
return this->name_to_file.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
unique_lock g(this->lock);
|
||||
auto it = this->name_to_file.find(name);
|
||||
if (it == this->name_to_file.end()) {
|
||||
it = this->name_to_file.emplace(name, generate(name)).first;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
class FileContentsCache {
|
||||
public:
|
||||
struct File {
|
||||
std::string name;
|
||||
std::shared_ptr<const std::string> data;
|
||||
uint64_t load_time;
|
||||
|
||||
File() = delete;
|
||||
File(const std::string& name, std::string&& contents, uint64_t load_time);
|
||||
File(const File&) = delete;
|
||||
File(File&&) = delete;
|
||||
File& operator=(const File&) = delete;
|
||||
File& operator=(File&&) = delete;
|
||||
~File() = 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;
|
||||
|
||||
template <typename NameT>
|
||||
bool delete_key(NameT key) {
|
||||
return this->name_to_file.erase(key);
|
||||
}
|
||||
|
||||
std::shared_ptr<const File> replace(const std::string& name, std::string&& data, uint64_t t = 0);
|
||||
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 File> file;
|
||||
bool generate_called;
|
||||
};
|
||||
|
||||
GetResult get_or_load(const std::string& name);
|
||||
GetResult get_or_load(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);
|
||||
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 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.file->data->size() != sizeof(T)) {
|
||||
throw std::runtime_error("cached string size is incorrect");
|
||||
}
|
||||
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.file->data->size() != sizeof(T)) {
|
||||
throw std::runtime_error("cached string size is incorrect");
|
||||
}
|
||||
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 = phosg::now();
|
||||
try {
|
||||
auto& f = this->name_to_file.at(name);
|
||||
if (f->data->size() != sizeof(T)) {
|
||||
throw std::runtime_error("cached string size is incorrect");
|
||||
}
|
||||
if (this->ttl_usecs && (t - f->load_time < this->ttl_usecs)) {
|
||||
return {*reinterpret_cast<const T*>(f->data->data()), f, false};
|
||||
}
|
||||
} catch (const std::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->data()), cached_value, false};
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::shared_ptr<File>> name_to_file;
|
||||
uint64_t ttl_usecs;
|
||||
};
|
||||
|
||||
class ThreadSafeFileCache {
|
||||
public:
|
||||
explicit ThreadSafeFileCache() = default;
|
||||
ThreadSafeFileCache(const ThreadSafeFileCache&) = delete;
|
||||
ThreadSafeFileCache(ThreadSafeFileCache&&) = delete;
|
||||
ThreadSafeFileCache& operator=(const ThreadSafeFileCache&) = delete;
|
||||
ThreadSafeFileCache& operator=(ThreadSafeFileCache&&) = delete;
|
||||
~ThreadSafeFileCache() = default;
|
||||
|
||||
// generate() is called while the lock is held for writing, so it will block other threads.
|
||||
std::shared_ptr<const std::string> get(
|
||||
const std::string& name, std::function<std::shared_ptr<const std::string>(const std::string&)> generate);
|
||||
|
||||
private:
|
||||
std::shared_mutex lock;
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> name_to_file;
|
||||
};
|
||||
+15
-17
@@ -7,8 +7,6 @@
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <bool BE>
|
||||
struct GSLHeaderEntryT {
|
||||
pstring<TextEncoding::ASCII, 0x20> filename;
|
||||
@@ -30,13 +28,13 @@ void GSLArchive::load_t() {
|
||||
}
|
||||
uint64_t offset = static_cast<uint64_t>(entry.offset) * 0x800;
|
||||
if (offset + entry.size > this->data->size()) {
|
||||
throw runtime_error("GSL entry extends beyond end of data");
|
||||
throw std::runtime_error("GSL entry extends beyond end of data");
|
||||
}
|
||||
this->entries.emplace(entry.filename.decode(), Entry{offset, entry.size});
|
||||
}
|
||||
}
|
||||
|
||||
GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian) : data(data) {
|
||||
GSLArchive::GSLArchive(std::shared_ptr<const std::string> data, bool big_endian) : data(data) {
|
||||
if (big_endian) {
|
||||
this->load_t<true>();
|
||||
} else {
|
||||
@@ -44,43 +42,43 @@ GSLArchive::GSLArchive(shared_ptr<const string> data, bool big_endian) : data(da
|
||||
}
|
||||
}
|
||||
|
||||
const unordered_map<string, GSLArchive::Entry> GSLArchive::all_entries() const {
|
||||
const std::unordered_map<std::string, GSLArchive::Entry> GSLArchive::all_entries() const {
|
||||
return this->entries;
|
||||
}
|
||||
|
||||
pair<const void*, size_t> GSLArchive::get(const std::string& name) const {
|
||||
std::pair<const void*, size_t> GSLArchive::get(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return make_pair(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("GSL does not contain file: " + name);
|
||||
return std::make_pair(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("GSL does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
string GSLArchive::get_copy(const string& name) const {
|
||||
std::string GSLArchive::get_copy(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return this->data->substr(entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("GSL does not contain file: " + name);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("GSL does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
phosg::StringReader GSLArchive::get_reader(const string& name) const {
|
||||
phosg::StringReader GSLArchive::get_reader(const std::string& name) const {
|
||||
try {
|
||||
const auto& entry = this->entries.at(name);
|
||||
return phosg::StringReader(this->data->data() + entry.offset, entry.size);
|
||||
} catch (const out_of_range&) {
|
||||
throw out_of_range("GSL does not contain file: " + name);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::out_of_range("GSL does not contain file: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
string GSLArchive::generate(const unordered_map<string, string>& files, bool big_endian) {
|
||||
std::string GSLArchive::generate(const std::unordered_map<std::string, std::string>& files, bool big_endian) {
|
||||
return big_endian ? GSLArchive::generate_t<true>(files) : GSLArchive::generate_t<false>(files);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string GSLArchive::generate_t(const unordered_map<string, string>& files) {
|
||||
std::string GSLArchive::generate_t(const std::unordered_map<std::string, std::string>& files) {
|
||||
phosg::StringWriter w;
|
||||
|
||||
// Make sure there's enough space for a blank header entry before any file's data pages begin
|
||||
|
||||
+24
-26
@@ -18,14 +18,11 @@
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
|
||||
GameServer::GameServer(shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
|
||||
GameServer::GameServer(std::shared_ptr<ServerState> state) : Server(state->io_context, "[GameServer] "), state(state) {}
|
||||
|
||||
void GameServer::listen(
|
||||
const std::string& name,
|
||||
const string& addr,
|
||||
const std::string& addr,
|
||||
uint16_t port,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
@@ -34,7 +31,7 @@ void GameServer::listen(
|
||||
}
|
||||
|
||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||
auto sock = make_shared<GameServerSocket>();
|
||||
auto sock = std::make_shared<GameServerSocket>();
|
||||
sock->name = name;
|
||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||
sock->version = version;
|
||||
@@ -42,8 +39,8 @@ void GameServer::listen(
|
||||
this->add_socket(std::move(sock));
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::connect_channel(shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
|
||||
auto c = make_shared<Client>(this->shared_from_this(), ch, initial_state);
|
||||
std::shared_ptr<Client> GameServer::connect_channel(std::shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state) {
|
||||
auto c = std::make_shared<Client>(this->shared_from_this(), ch, initial_state);
|
||||
|
||||
this->log.info_f("Client connected: C-{:X} via TSI-{}-{}-{}",
|
||||
c->id, port, phosg::name_for_enum(ch->version), phosg::name_for_enum(initial_state));
|
||||
@@ -52,31 +49,31 @@ shared_ptr<Client> GameServer::connect_channel(shared_ptr<Channel> ch, uint16_t
|
||||
return c;
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::get_client() const {
|
||||
std::shared_ptr<Client> GameServer::get_client() const {
|
||||
if (this->clients.empty()) {
|
||||
throw runtime_error("no clients on game server");
|
||||
throw std::runtime_error("no clients on game server");
|
||||
}
|
||||
if (this->clients.size() > 1) {
|
||||
throw runtime_error("multiple clients on game server");
|
||||
throw std::runtime_error("multiple clients on game server");
|
||||
}
|
||||
return *this->clients.begin();
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& ident) const {
|
||||
std::vector<std::shared_ptr<Client>> GameServer::get_clients_by_identifier(const std::string& ident) const {
|
||||
int64_t account_id_hex = -1;
|
||||
int64_t account_id_dec = -1;
|
||||
try {
|
||||
account_id_dec = stoul(ident, nullptr, 10);
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
try {
|
||||
account_id_hex = stoul(ident, nullptr, 16);
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
}
|
||||
|
||||
// TODO: It's kind of not great that we do a linear search here, but this is only used in the shell, so it should be
|
||||
// pretty rare.
|
||||
vector<shared_ptr<Client>> results;
|
||||
std::vector<std::shared_ptr<Client>> results;
|
||||
for (const auto& c : this->clients) {
|
||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||
results.emplace_back(c);
|
||||
@@ -114,8 +111,8 @@ vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const string& i
|
||||
return results;
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::create_client(
|
||||
shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
||||
std::shared_ptr<Client> GameServer::create_client(
|
||||
std::shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock) {
|
||||
uint32_t addr = ipv4_addr_for_asio_addr(client_sock.remote_endpoint().address());
|
||||
if (this->state->banned_ipv4_ranges->check(addr)) {
|
||||
if (client_sock.is_open()) {
|
||||
@@ -126,7 +123,7 @@ shared_ptr<Client> GameServer::create_client(
|
||||
|
||||
auto channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
make_unique<asio::ip::tcp::socket>(std::move(client_sock)),
|
||||
std::make_unique<asio::ip::tcp::socket>(std::move(client_sock)),
|
||||
listen_sock->version,
|
||||
Language::ENGLISH,
|
||||
"",
|
||||
@@ -134,27 +131,28 @@ shared_ptr<Client> GameServer::create_client(
|
||||
phosg::TerminalFormat::FG_GREEN,
|
||||
this->state->censor_credentials,
|
||||
false);
|
||||
auto c = make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior);
|
||||
auto c = std::make_shared<Client>(this->shared_from_this(), channel, listen_sock->behavior);
|
||||
this->log.info_f("Client connected: C-{:X} via {}", c->id, listen_sock->name);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
asio::awaitable<void> GameServer::handle_client_command(shared_ptr<Client> c, unique_ptr<Channel::Message> msg) {
|
||||
asio::awaitable<void> GameServer::handle_client_command(
|
||||
std::shared_ptr<Client> c, std::unique_ptr<Channel::Message> msg) {
|
||||
try {
|
||||
co_await on_command(c, std::move(msg));
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.warning_f("Error processing client command: {}", e.what());
|
||||
c->channel->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> GameServer::handle_client(shared_ptr<Client> c) {
|
||||
asio::awaitable<void> GameServer::handle_client(std::shared_ptr<Client> c) {
|
||||
auto g = phosg::on_close_scope(std::bind(&Client::cancel_pending_promises, c.get()));
|
||||
|
||||
try {
|
||||
co_await on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.warning_f("Error in client initialization: {}", e.what());
|
||||
c->channel->disconnect();
|
||||
}
|
||||
@@ -181,17 +179,17 @@ asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
|
||||
|
||||
try {
|
||||
co_await on_disconnect(c);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.warning_f("Error during client disconnect cleanup: {}", e.what());
|
||||
}
|
||||
|
||||
// Note: It's important to move the disconnect hooks out of the client here because the hooks could modify
|
||||
// c->disconnect_hooks while it's being iterated here, which would invalidate these iterators.
|
||||
unordered_map<string, function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||
std::unordered_map<std::string, std::function<void()>> hooks = std::move(c->disconnect_hooks);
|
||||
for (auto h_it : hooks) {
|
||||
try {
|
||||
h_it.second();
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
+56
-56
@@ -14,9 +14,7 @@
|
||||
#include "Server.hh"
|
||||
#include "ShellCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
HTTPServer::HTTPServer(std::shared_ptr<ServerState> state)
|
||||
: AsyncHTTPServer(state->io_context, "[HTTPServer] "), state(state) {
|
||||
using RouterRetT = std::variant<RawResponse, std::shared_ptr<const phosg::JSON>>;
|
||||
using RetT = asio::awaitable<RouterRetT>;
|
||||
@@ -32,15 +30,15 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
};
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/", [generate_server_version_json](ArgsT&&) -> RetT {
|
||||
co_return make_shared<phosg::JSON>(generate_server_version_json());
|
||||
co_return std::make_shared<phosg::JSON>(generate_server_version_json());
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::POST, "/y/shell-exec", [this](ArgsT&& args) -> RetT {
|
||||
auto command = args.post_data.get_string("command");
|
||||
try {
|
||||
auto dispatch_res = co_await ShellCommand::dispatch_str(this->state, command);
|
||||
co_return make_shared<phosg::JSON>(phosg::JSON::dict({{"result", phosg::join(dispatch_res, "\n")}}));
|
||||
} catch (const exception& e) {
|
||||
co_return std::make_shared<phosg::JSON>(phosg::JSON::dict({{"result", phosg::join(dispatch_res, "\n")}}));
|
||||
} catch (const std::exception& e) {
|
||||
throw HTTPError(400, e.what());
|
||||
}
|
||||
});
|
||||
@@ -55,7 +53,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/clients", [this](ArgsT&&) -> RetT {
|
||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
for (const auto& c : this->state->game_server->all_clients()) {
|
||||
auto item_name_index = this->state->item_name_index_opt(c->version());
|
||||
|
||||
@@ -215,7 +213,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
uint8_t minute = p->challenge_records.grave_time & 0xFF;
|
||||
client_json.emplace("ChallengeGraveTime", std::format("{:04}-{:02}-{:02} {:02}:{:02}:00", year, month, day, hour, minute));
|
||||
}
|
||||
string grave_enemy_types;
|
||||
std::string grave_enemy_types;
|
||||
if (p->challenge_records.grave_defeated_by_enemy_rt_index) {
|
||||
for (EnemyType type : enemy_types_for_rare_table_index(
|
||||
p->challenge_records.grave_is_ep2 ? Episode::EP2 : Episode::EP1,
|
||||
@@ -297,7 +295,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/lobbies", [this](ArgsT&&) -> RetT {
|
||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
for (const auto& [_, l] : this->state->id_to_lobby) {
|
||||
auto leader = l->clients[l->leader_id];
|
||||
Version v = leader ? leader->version() : Version::BB_V4;
|
||||
@@ -430,7 +428,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
name = ce->def.jp_short_name.decode();
|
||||
}
|
||||
cards_json.emplace_back(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
cards_json.emplace_back(deck_entry->card_ids[w].load());
|
||||
}
|
||||
}
|
||||
@@ -505,7 +503,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/accounts", [this](ArgsT&&) -> RetT {
|
||||
auto res = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
auto res = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
for (const auto& it : this->state->account_index->all()) {
|
||||
res->emplace_back(it->json());
|
||||
}
|
||||
@@ -515,14 +513,14 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/account/:account_id", [this](ArgsT&& args) -> RetT {
|
||||
uint32_t account_id = args.get_param<uint32_t>("account_id");
|
||||
try {
|
||||
co_return make_shared<phosg::JSON>(this->state->account_index->from_account_id(account_id)->json());
|
||||
co_return std::make_shared<phosg::JSON>(this->state->account_index->from_account_id(account_id)->json());
|
||||
} catch (const AccountIndex::missing_account&) {
|
||||
throw HTTPError(404, "Account does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/teams", [this](ArgsT&&) -> RetT {
|
||||
auto res = make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||
auto res = std::make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||
for (const auto& it : this->state->team_index->all()) {
|
||||
res->emplace(std::format("{}", it->team_id), it->json());
|
||||
}
|
||||
@@ -535,7 +533,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
if (!team) {
|
||||
throw HTTPError(404, "Team does not exist");
|
||||
}
|
||||
co_return make_shared<phosg::JSON>(team->json());
|
||||
co_return std::make_shared<phosg::JSON>(team->json());
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/team/:team_id/flag", [this](ArgsT&& args) -> RetT {
|
||||
@@ -576,7 +574,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
};
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/server", [generate_server_info_json](ArgsT&&) -> RetT {
|
||||
co_return make_shared<phosg::JSON>(generate_server_info_json());
|
||||
co_return std::make_shared<phosg::JSON>(generate_server_info_json());
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/config", [this](ArgsT&&) -> RetT {
|
||||
@@ -638,7 +636,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
}
|
||||
}
|
||||
|
||||
co_return make_shared<phosg::JSON>(phosg::JSON::dict({
|
||||
co_return std::make_shared<phosg::JSON>(phosg::JSON::dict({
|
||||
{"Clients", std::move(clients_json)},
|
||||
{"Games", std::move(games_json)},
|
||||
{"Server", generate_server_info_json()},
|
||||
@@ -647,8 +645,8 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/cards", [this](ArgsT&& args) -> RetT {
|
||||
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
return make_shared<phosg::JSON>(index->definitions_json());
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
return std::make_shared<phosg::JSON>(index->definitions_json());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -656,15 +654,15 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
auto& index = args.req.query_params.count("trial") ? this->state->ep3_card_index_trial : this->state->ep3_card_index;
|
||||
uint32_t card_id = args.get_param<uint32_t>("card_id");
|
||||
try {
|
||||
co_return make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json());
|
||||
co_return std::make_shared<phosg::JSON>(index->definition_for_id(card_id)->def.json());
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Card definition does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/maps", [this](ArgsT&&) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::dict());
|
||||
for (const auto& [map_number, map] : this->state->ep3_map_index->all_maps()) {
|
||||
auto languages_json = phosg::JSON::list();
|
||||
for (const auto& vm : map->all_versions()) {
|
||||
@@ -684,11 +682,11 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/ep3/map/:map_number/:language", [this](ArgsT&& args) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
try {
|
||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number", true));
|
||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||
return make_shared<phosg::JSON>(vm->map->json(vm->language));
|
||||
return std::make_shared<phosg::JSON>(vm->map->json(vm->language));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Map version does not exist");
|
||||
}
|
||||
@@ -700,7 +698,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
try {
|
||||
auto map = this->state->ep3_map_index->map_for_id(args.get_param<uint32_t>("map_number"));
|
||||
auto vm = map->version(language_for_name(args.params.at("language")));
|
||||
string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
|
||||
std::string data(reinterpret_cast<const char*>(vm->map.get()), sizeof(Episode3::MapDefinition));
|
||||
return RawResponse{.content_type = "application/octet-stream", .data = std::move(data)};
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Map version does not exist");
|
||||
@@ -709,7 +707,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/common-tables", [this](ArgsT&&) -> RetT {
|
||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
for (const auto& it : this->state->common_item_sets) {
|
||||
ret->emplace_back(it.first);
|
||||
}
|
||||
@@ -719,16 +717,16 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/common-table/:table_name", [this](ArgsT&& args) -> RetT {
|
||||
try {
|
||||
const auto& table = this->state->common_item_sets.at(args.params.at("table_name"));
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
return make_shared<phosg::JSON>(table->json());
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
return std::make_shared<phosg::JSON>(table->json());
|
||||
});
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Table does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/rare-tables", [this](ArgsT&&) -> RetT {
|
||||
auto ret = make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
auto ret = std::make_shared<phosg::JSON>(phosg::JSON::list());
|
||||
for (const auto& it : this->state->rare_item_sets) {
|
||||
ret->emplace_back(it.first);
|
||||
}
|
||||
@@ -739,7 +737,7 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
try {
|
||||
const auto& table_name = args.params.at("table_name");
|
||||
const auto& table = this->state->rare_item_sets.at(table_name);
|
||||
shared_ptr<const ItemNameIndex> name_index;
|
||||
std::shared_ptr<const ItemNameIndex> name_index;
|
||||
if (table_name.ends_with("-v1")) {
|
||||
name_index = this->state->item_name_index_opt(Version::DC_V1);
|
||||
} else if (table_name.ends_with("-v2")) {
|
||||
@@ -749,17 +747,17 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
} else if (table_name.ends_with("-v4")) {
|
||||
name_index = this->state->item_name_index_opt(Version::BB_V4);
|
||||
}
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
return make_shared<phosg::JSON>(table->json(name_index));
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
return std::make_shared<phosg::JSON>(table->json(name_index));
|
||||
});
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
throw HTTPError(404, "Table does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
this->router.add(HTTPRequest::Method::GET, "/y/data/quests", [this](ArgsT&&) -> RetT {
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> shared_ptr<phosg::JSON> {
|
||||
return make_shared<phosg::JSON>(this->state->quest_index->json());
|
||||
co_return co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::shared_ptr<phosg::JSON> {
|
||||
return std::make_shared<phosg::JSON>(this->state->quest_index->json());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -769,21 +767,21 @@ HTTPServer::HTTPServer(shared_ptr<ServerState> state)
|
||||
if (!q) {
|
||||
throw HTTPError(404, "Quest does not exist");
|
||||
}
|
||||
co_return make_shared<phosg::JSON>(q->json());
|
||||
co_return std::make_shared<phosg::JSON>(q->json());
|
||||
});
|
||||
}
|
||||
|
||||
asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const phosg::JSON> message) {
|
||||
asio::awaitable<void> HTTPServer::send_rare_drop_notification(std::shared_ptr<const phosg::JSON> message) {
|
||||
if (!this->rare_drop_subscribers.empty()) {
|
||||
string data = message->serialize();
|
||||
std::string data = message->serialize();
|
||||
|
||||
// Make a copy of the rare drop subscribers set, so we can guarantee that the client objects are all valid until
|
||||
// this coroutine returns
|
||||
unordered_set<shared_ptr<HTTPClient>> subscribers = this->rare_drop_subscribers;
|
||||
std::unordered_set<std::shared_ptr<HTTPClient>> subscribers = this->rare_drop_subscribers;
|
||||
|
||||
size_t expected_results = subscribers.size();
|
||||
AsyncPromise<void> complete_promise;
|
||||
auto fn = [this, &data, &expected_results, &complete_promise](shared_ptr<HTTPClient> c) -> asio::awaitable<void> {
|
||||
auto fn = [this, &data, &expected_results, &complete_promise](std::shared_ptr<HTTPClient> c) -> asio::awaitable<void> {
|
||||
try {
|
||||
co_await c->send_websocket_message(data);
|
||||
} catch (const std::exception& e) {
|
||||
@@ -803,14 +801,14 @@ asio::awaitable<void> HTTPServer::send_rare_drop_notification(shared_ptr<const p
|
||||
co_return;
|
||||
}
|
||||
|
||||
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared_ptr<HTTPClient> c, HTTPRequest&& req) {
|
||||
variant<RawResponse, shared_ptr<const phosg::JSON>> ret;
|
||||
asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(std::shared_ptr<HTTPClient> c, HTTPRequest&& req) {
|
||||
std::variant<RawResponse, std::shared_ptr<const phosg::JSON>> ret;
|
||||
uint32_t serialize_options = phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY;
|
||||
uint64_t start_time = phosg::now();
|
||||
|
||||
this->log.info_f("{} ...", req.path);
|
||||
|
||||
auto resp = make_unique<HTTPResponse>();
|
||||
auto resp = std::make_unique<HTTPResponse>();
|
||||
resp->http_version = req.http_version;
|
||||
resp->response_code = 200;
|
||||
resp->headers.emplace("Server", "newserv");
|
||||
@@ -830,39 +828,41 @@ asio::awaitable<std::unique_ptr<HTTPResponse>> HTTPServer::handle_request(shared
|
||||
ret = co_await this->router.call_handler(c, req);
|
||||
|
||||
} catch (const HTTPError& e) {
|
||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||
ret = std::make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||
resp->response_code = e.code;
|
||||
} catch (const exception& e) {
|
||||
ret = make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||
} catch (const std::exception& e) {
|
||||
ret = std::make_shared<phosg::JSON>(phosg::JSON::dict({{"Error", true}, {"Message", e.what()}}));
|
||||
resp->response_code = 500;
|
||||
}
|
||||
uint64_t handler_end = phosg::now();
|
||||
|
||||
if (holds_alternative<shared_ptr<const phosg::JSON>>(ret)) {
|
||||
if (holds_alternative<std::shared_ptr<const phosg::JSON>>(ret)) {
|
||||
// If the handler returns nullptr (not JSON null), assume it called enable_websockets and send no response
|
||||
auto& json = get<shared_ptr<const phosg::JSON>>(ret);
|
||||
auto& json = get<std::shared_ptr<const phosg::JSON>>(ret);
|
||||
if (!json) {
|
||||
co_return nullptr;
|
||||
}
|
||||
resp->headers.emplace("Content-Type", "application/json");
|
||||
resp->data = co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> string {
|
||||
resp->data = co_await call_on_thread_pool(*this->state->thread_pool, [&]() -> std::string {
|
||||
return json->serialize(serialize_options, 0);
|
||||
});
|
||||
uint64_t serialize_end = phosg::now();
|
||||
|
||||
string handler_time = phosg::format_duration(handler_end - start_time);
|
||||
string serialize_time = phosg::format_duration(serialize_end - handler_end);
|
||||
string size_str = phosg::format_size(resp->data.size());
|
||||
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]", req.path, handler_time, serialize_time, size_str);
|
||||
this->log.info_f("{} in [handler: {}, serialize: {}, size: {}]",
|
||||
req.path,
|
||||
phosg::format_duration(handler_end - start_time),
|
||||
phosg::format_duration(serialize_end - handler_end),
|
||||
phosg::format_size(resp->data.size()));
|
||||
|
||||
} else {
|
||||
auto& raw_resp = get<RawResponse>(ret);
|
||||
resp->headers.emplace("Content-Type", std::move(raw_resp.content_type));
|
||||
resp->data = std::move(raw_resp.data);
|
||||
|
||||
string handler_time = phosg::format_duration(handler_end - start_time);
|
||||
string size_str = phosg::format_size(resp->data.size());
|
||||
this->log.info_f("{} in [handler: {}, size: {}]", req.path, handler_time, size_str);
|
||||
this->log.info_f("{} in [handler: {}, size: {}]",
|
||||
req.path,
|
||||
phosg::format_duration(handler_end - start_time),
|
||||
phosg::format_size(resp->data.size()));
|
||||
}
|
||||
|
||||
co_return resp;
|
||||
|
||||
+14
-16
@@ -4,8 +4,6 @@
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static inline uint16_t collapse_checksum(uint32_t sum) {
|
||||
// It's impossible for this to be necessary more than twice: the first addition can carry out at most a single bit.
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
@@ -65,13 +63,13 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
break;
|
||||
|
||||
default:
|
||||
throw logic_error("invalid link type");
|
||||
throw std::logic_error("invalid link type");
|
||||
}
|
||||
|
||||
// Parse inner protocol headers
|
||||
switch (proto) {
|
||||
case Protocol::NONE:
|
||||
throw runtime_error("unknown protocol");
|
||||
throw std::runtime_error("unknown protocol");
|
||||
case Protocol::LCP:
|
||||
this->payload_size -= sizeof(LCPHeader);
|
||||
this->lcp = &r.get<LCPHeader>();
|
||||
@@ -87,7 +85,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
case Protocol::IPV4:
|
||||
this->ipv4 = &r.get<IPv4Header>();
|
||||
if (this->payload_size < this->ipv4->size) {
|
||||
throw invalid_argument("ipv4 header specifies size larger than frame");
|
||||
throw std::invalid_argument("ipv4 header specifies size larger than frame");
|
||||
}
|
||||
this->payload_size = this->ipv4->size - sizeof(IPv4Header);
|
||||
|
||||
@@ -95,7 +93,7 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
this->tcp = &r.get<TCPHeader>();
|
||||
size_t tcp_header_size = (this->tcp->flags >> 12) * 4;
|
||||
if (tcp_header_size < sizeof(TCPHeader) || tcp_header_size > this->payload_size) {
|
||||
throw invalid_argument("frame is too small for tcp4 header with options");
|
||||
throw std::invalid_argument("frame is too small for tcp4 header with options");
|
||||
}
|
||||
this->tcp_options_size = tcp_header_size - sizeof(TCPHeader);
|
||||
this->payload_size -= tcp_header_size;
|
||||
@@ -115,12 +113,12 @@ FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
this->payload = r.getv(this->payload_size);
|
||||
}
|
||||
|
||||
string FrameInfo::header_str() const {
|
||||
std::string FrameInfo::header_str() const {
|
||||
if (!this->ether && !this->hdlc) {
|
||||
return "<invalid-frame-info>";
|
||||
}
|
||||
|
||||
string ret;
|
||||
std::string ret;
|
||||
if (this->ether) {
|
||||
ret = std::format(
|
||||
"ETHER:{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}->{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||
@@ -187,10 +185,10 @@ string FrameInfo::header_str() const {
|
||||
|
||||
void FrameInfo::truncate(size_t new_total_size) {
|
||||
if (new_total_size > this->total_size) {
|
||||
throw logic_error("truncate call expands frame size");
|
||||
throw std::logic_error("truncate call expands frame size");
|
||||
}
|
||||
if (new_total_size < this->payload_size) {
|
||||
throw logic_error("truncate call destroys part of header");
|
||||
throw std::logic_error("truncate call destroys part of header");
|
||||
}
|
||||
size_t delta_bytes = this->total_size - new_total_size;
|
||||
this->total_size -= delta_bytes;
|
||||
@@ -222,7 +220,7 @@ uint16_t FrameInfo::computed_ipv4_header_checksum(const IPv4Header& ipv4) {
|
||||
|
||||
uint16_t FrameInfo::computed_ipv4_header_checksum() const {
|
||||
if (!this->ipv4) {
|
||||
throw logic_error("cannot compute ipv4 header checksum for non-ipv4 frame");
|
||||
throw std::logic_error("cannot compute ipv4 header checksum for non-ipv4 frame");
|
||||
}
|
||||
return this->computed_ipv4_header_checksum(*this->ipv4);
|
||||
}
|
||||
@@ -252,10 +250,10 @@ uint16_t FrameInfo::computed_udp4_checksum(
|
||||
|
||||
uint16_t FrameInfo::computed_udp4_checksum() const {
|
||||
if (!this->ipv4) {
|
||||
throw logic_error("cannot compute udp header checksum for non-ipv4 frame");
|
||||
throw std::logic_error("cannot compute udp header checksum for non-ipv4 frame");
|
||||
}
|
||||
if (!this->udp) {
|
||||
throw logic_error("cannot compute udp header checksum for non-udp frame");
|
||||
throw std::logic_error("cannot compute udp header checksum for non-udp frame");
|
||||
}
|
||||
return this->computed_udp4_checksum(
|
||||
*this->ipv4, *this->udp, this->payload, this->payload_size);
|
||||
@@ -293,10 +291,10 @@ uint16_t FrameInfo::computed_tcp4_checksum(
|
||||
|
||||
uint16_t FrameInfo::computed_tcp4_checksum() const {
|
||||
if (!this->ipv4) {
|
||||
throw logic_error("cannot compute tcp header checksum for non-ipv4 frame");
|
||||
throw std::logic_error("cannot compute tcp header checksum for non-ipv4 frame");
|
||||
}
|
||||
if (!this->tcp) {
|
||||
throw logic_error("cannot compute tcp header checksum for non-tcp frame");
|
||||
throw std::logic_error("cannot compute tcp header checksum for non-tcp frame");
|
||||
}
|
||||
return this->computed_tcp4_checksum(
|
||||
*this->ipv4, *this->tcp, this->tcp + 1,
|
||||
@@ -317,7 +315,7 @@ uint16_t FrameInfo::computed_hdlc_checksum(const void* vdata, size_t size) {
|
||||
|
||||
uint16_t FrameInfo::computed_hdlc_checksum() const {
|
||||
if (!this->hdlc) {
|
||||
throw logic_error("cannot compute HDLC checksum for non-HDLC frame");
|
||||
throw std::logic_error("cannot compute HDLC checksum for non-HDLC frame");
|
||||
}
|
||||
return this->computed_hdlc_checksum(&this->hdlc->address, this->total_size - 4);
|
||||
}
|
||||
|
||||
+132
-132
@@ -13,18 +13,16 @@
|
||||
#include "IPFrameInfo.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
|
||||
uint8_t* data = reinterpret_cast<uint8_t*>(vdata);
|
||||
if (size < 2) {
|
||||
throw runtime_error("escaped HDLC frame is too small");
|
||||
throw std::runtime_error("escaped HDLC frame is too small");
|
||||
}
|
||||
if (data[0] != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not begin with 7E");
|
||||
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||
}
|
||||
if (data[size - 1] != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not end with 7E");
|
||||
throw std::runtime_error("HDLC frame does not end with 7E");
|
||||
}
|
||||
|
||||
size_t read_offset = 1;
|
||||
@@ -33,33 +31,34 @@ static size_t unescape_hdlc_frame_inplace(void* vdata, size_t size) {
|
||||
uint8_t ch = data[read_offset++];
|
||||
if (ch == 0x7D) {
|
||||
if (read_offset >= size - 1) {
|
||||
throw runtime_error("abort sequence received");
|
||||
throw std::runtime_error("abort sequence received");
|
||||
}
|
||||
ch = data[read_offset++] ^ 0x20;
|
||||
}
|
||||
data[write_offset++] = ch;
|
||||
}
|
||||
if (write_offset > size - 1) {
|
||||
throw logic_error("unescaping HDLC frame resulted in longer data string");
|
||||
throw std::logic_error("unescaping HDLC frame resulted in longer data string");
|
||||
}
|
||||
data[write_offset++] = 0x7E;
|
||||
return write_offset;
|
||||
}
|
||||
|
||||
static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||
static std::string escape_hdlc_frame(
|
||||
const void* data, size_t size, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||
if (size < 2) {
|
||||
throw runtime_error("HDLC frame too small for start and end sentinels");
|
||||
throw std::runtime_error("HDLC frame too small for start and end sentinels");
|
||||
}
|
||||
|
||||
phosg::StringReader r(data, size);
|
||||
if (r.pget_u8(size - 1) != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not end with 7E");
|
||||
throw std::runtime_error("HDLC frame does not end with 7E");
|
||||
}
|
||||
r.truncate(size - 1);
|
||||
if (r.get_u8() != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not begin with 7E");
|
||||
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||
}
|
||||
string ret("\x7E", 1);
|
||||
std::string ret("\x7E", 1);
|
||||
|
||||
while (!r.eof()) {
|
||||
uint8_t ch = r.get_u8();
|
||||
@@ -74,7 +73,7 @@ static string escape_hdlc_frame(const void* data, size_t size, uint32_t escape_c
|
||||
return ret;
|
||||
}
|
||||
|
||||
static string escape_hdlc_frame(const string& data, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||
static std::string escape_hdlc_frame(const std::string& data, uint32_t escape_control_character_flags = 0xFFFFFFFF) {
|
||||
return escape_hdlc_frame(data.data(), data.size(), escape_control_character_flags);
|
||||
}
|
||||
|
||||
@@ -114,7 +113,7 @@ void IPSSClient::TCPConnection::drain_outbound_data(size_t size) {
|
||||
}
|
||||
}
|
||||
if (size > 0) {
|
||||
throw logic_error("attempted to drain more outbound data than was present");
|
||||
throw std::logic_error("attempted to drain more outbound data than was present");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +127,7 @@ void IPSSClient::TCPConnection::linearize_outbound_data(size_t size) {
|
||||
}
|
||||
|
||||
IPSSClient::IPSSClient(
|
||||
shared_ptr<IPStackSimulator> sim, uint64_t network_id, VirtualNetworkProtocol protocol, asio::ip::tcp::socket&& sock)
|
||||
std::shared_ptr<IPStackSimulator> sim, uint64_t network_id, VirtualNetworkProtocol protocol, asio::ip::tcp::socket&& sock)
|
||||
: io_context(sim->get_io_context()),
|
||||
sim(sim),
|
||||
network_id(network_id),
|
||||
@@ -143,7 +142,7 @@ IPSSClient::IPSSClient(
|
||||
void IPSSClient::reschedule_idle_timeout() {
|
||||
auto sim = this->sim.lock();
|
||||
if (!sim) {
|
||||
throw runtime_error("cannot reschedule idle timeout when simulator is missing");
|
||||
throw std::runtime_error("cannot reschedule idle timeout when simulator is missing");
|
||||
}
|
||||
this->idle_timeout_timer.cancel();
|
||||
this->idle_timeout_timer.expires_after(std::chrono::microseconds(sim->get_state()->client_idle_timeout_usecs));
|
||||
@@ -204,7 +203,7 @@ void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
||||
// If recv_buf is not null, there is a coroutine waiting to receive data, and inbound_data must be empty. Copy the
|
||||
// data directly to the waiting coroutine's buffer, and put the rest in this->inbound_data if needed.
|
||||
if (this->recv_buf) {
|
||||
size_t direct_size = min<size_t>(this->recv_buf_size, size);
|
||||
size_t direct_size = std::min<size_t>(this->recv_buf_size, size);
|
||||
memcpy(this->recv_buf, data, direct_size);
|
||||
data = reinterpret_cast<const uint8_t*>(data) + direct_size;
|
||||
size -= direct_size;
|
||||
@@ -221,7 +220,7 @@ void IPSSChannel::add_inbound_data(const void* data, size_t size) {
|
||||
this->data_available_signal.set();
|
||||
}
|
||||
|
||||
void IPSSChannel::send_raw(string&& data) {
|
||||
void IPSSChannel::send_raw(std::string&& data) {
|
||||
auto c = this->ipss_client.lock();
|
||||
if (!c) {
|
||||
return;
|
||||
@@ -249,7 +248,7 @@ void IPSSChannel::send_raw(string&& data) {
|
||||
|
||||
asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
||||
if (this->recv_buf) {
|
||||
throw logic_error("recv_raw called again when it was already pending");
|
||||
throw std::logic_error("recv_raw called again when it was already pending");
|
||||
}
|
||||
|
||||
// Receive as much data as possible from the pending inbound data buffer
|
||||
@@ -274,7 +273,7 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
||||
this->recv_buf_size = size;
|
||||
while (this->recv_buf) {
|
||||
if (!this->connected()) {
|
||||
throw runtime_error("IPSS channel closed");
|
||||
throw std::runtime_error("IPSS channel closed");
|
||||
}
|
||||
this->data_available_signal.clear();
|
||||
co_await this->data_available_signal.wait();
|
||||
@@ -282,18 +281,18 @@ asio::awaitable<void> IPSSChannel::recv_raw(void* data, size_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
IPStackSimulator::IPStackSimulator(shared_ptr<ServerState> state)
|
||||
IPStackSimulator::IPStackSimulator(std::shared_ptr<ServerState> state)
|
||||
: Server(state->io_context, "[IPStackSimulator] "), state(state) {
|
||||
this->host_mac_address_bytes.clear(0x90);
|
||||
this->broadcast_mac_address_bytes.clear(0xFF);
|
||||
}
|
||||
|
||||
void IPStackSimulator::listen(const std::string& name, const string& addr, int port, VirtualNetworkProtocol protocol) {
|
||||
void IPStackSimulator::listen(const std::string& name, const std::string& addr, int port, VirtualNetworkProtocol protocol) {
|
||||
if (port == 0) {
|
||||
throw std::runtime_error("Listening port cannot be zero");
|
||||
}
|
||||
asio::ip::address asio_addr = addr.empty() ? asio::ip::address_v4::any() : asio::ip::make_address(addr);
|
||||
auto sock = make_shared<IPSSSocket>();
|
||||
auto sock = std::make_shared<IPSSSocket>();
|
||||
sock->name = name;
|
||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||
sock->protocol = protocol;
|
||||
@@ -320,12 +319,12 @@ uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const IPv4Header& ipv4,
|
||||
|
||||
uint64_t IPStackSimulator::tcp_conn_key_for_client_frame(const FrameInfo& fi) {
|
||||
if (!fi.ipv4 || !fi.tcp) {
|
||||
throw logic_error("tcp_conn_key_for_frame called on non-TCP frame");
|
||||
throw std::logic_error("tcp_conn_key_for_frame called on non-TCP frame");
|
||||
}
|
||||
return IPStackSimulator::tcp_conn_key_for_client_frame(*fi.ipv4, *fi.tcp);
|
||||
}
|
||||
|
||||
string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
||||
std::string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
||||
be_uint32_t be_addr = addr;
|
||||
char addr_str[INET_ADDRSTRLEN];
|
||||
memset(addr_str, 0, sizeof(addr_str));
|
||||
@@ -336,16 +335,16 @@ string IPStackSimulator::str_for_ipv4_netloc(uint32_t addr, uint16_t port) {
|
||||
}
|
||||
}
|
||||
|
||||
string IPStackSimulator::str_for_tcp_connection(
|
||||
shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn) {
|
||||
std::string IPStackSimulator::str_for_tcp_connection(
|
||||
std::shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn) {
|
||||
uint64_t key = IPStackSimulator::tcp_conn_key_for_connection(conn);
|
||||
string server_netloc_str = str_for_ipv4_netloc(conn->server_addr, conn->server_port);
|
||||
string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn->client_port);
|
||||
std::string server_netloc_str = str_for_ipv4_netloc(conn->server_addr, conn->server_port);
|
||||
std::string client_netloc_str = str_for_ipv4_netloc(c->ipv4_addr, conn->client_port);
|
||||
return std::format("{:016X} ({} -> {})", key, client_netloc_str, server_netloc_str);
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||
|
||||
struct TapServerEthernetHeader {
|
||||
phosg::le_uint16_t frame_size;
|
||||
@@ -358,9 +357,9 @@ asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
||||
header.ether.src_mac = this->host_mac_address_bytes;
|
||||
switch (proto) {
|
||||
case FrameInfo::Protocol::NONE:
|
||||
throw logic_error("layer 3 protocol not specified");
|
||||
throw std::logic_error("layer 3 protocol not specified");
|
||||
case FrameInfo::Protocol::LCP:
|
||||
throw logic_error("cannot send LCP frame over Ethernet");
|
||||
throw std::logic_error("cannot send LCP frame over Ethernet");
|
||||
case FrameInfo::Protocol::IPV4:
|
||||
header.ether.protocol = 0x0800;
|
||||
break;
|
||||
@@ -368,16 +367,17 @@ asio::awaitable<void> IPStackSimulator::send_ethernet_tapserver_frame(
|
||||
header.ether.protocol = 0x0806;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown layer 3 protocol");
|
||||
throw std::logic_error("unknown layer 3 protocol");
|
||||
}
|
||||
header.frame_size = size + sizeof(EthernetHeader);
|
||||
|
||||
array<asio::const_buffer, 2> bufs{asio::buffer(static_cast<const void*>(&header), sizeof(header)), asio::buffer(data, size)};
|
||||
std::array<asio::const_buffer, 2> bufs{
|
||||
asio::buffer(static_cast<const void*>(&header), sizeof(header)), asio::buffer(data, size)};
|
||||
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size, bool is_raw) const {
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size, bool is_raw) const {
|
||||
|
||||
HDLCHeader hdlc;
|
||||
hdlc.start_sentinel1 = 0x7E;
|
||||
@@ -385,7 +385,7 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||
hdlc.control = 0x03;
|
||||
switch (proto) {
|
||||
case FrameInfo::Protocol::NONE:
|
||||
throw logic_error("layer 3 protocol not specified");
|
||||
throw std::logic_error("layer 3 protocol not specified");
|
||||
case FrameInfo::Protocol::LCP:
|
||||
hdlc.protocol = 0xC021;
|
||||
break;
|
||||
@@ -399,9 +399,9 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||
hdlc.protocol = 0x0021;
|
||||
break;
|
||||
case FrameInfo::Protocol::ARP:
|
||||
throw runtime_error("cannot send ARP packets over HDLC");
|
||||
throw std::runtime_error("cannot send ARP packets over HDLC");
|
||||
default:
|
||||
throw logic_error("unknown layer 3 protocol");
|
||||
throw std::logic_error("unknown layer 3 protocol");
|
||||
}
|
||||
|
||||
phosg::StringWriter w;
|
||||
@@ -410,14 +410,14 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||
w.put_u16l(FrameInfo::computed_hdlc_checksum(w.str().data() + 1, w.size() - 1));
|
||||
w.put_u8(0x7E);
|
||||
|
||||
string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags);
|
||||
std::string escaped = escape_hdlc_frame(w.str(), c->hdlc_escape_control_character_flags);
|
||||
if (this->log.debug_f("Sending HDLC frame to virtual network (escaped to {:X} bytes)", escaped.size())) {
|
||||
phosg::print_data(stderr, w.str());
|
||||
}
|
||||
|
||||
if (!is_raw) {
|
||||
phosg::le_uint16_t frame_size = escaped.size();
|
||||
array<asio::const_buffer, 2> bufs{
|
||||
std::array<asio::const_buffer, 2> bufs{
|
||||
asio::buffer(static_cast<const void*>(&frame_size), sizeof(frame_size)),
|
||||
asio::buffer(escaped.data(), escaped.size())};
|
||||
co_await asio::async_write(c->sock, bufs, asio::use_awaitable);
|
||||
@@ -427,7 +427,7 @@ asio::awaitable<void> IPStackSimulator::send_hdlc_frame(
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::send_layer3_frame(
|
||||
shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const {
|
||||
switch (c->protocol) {
|
||||
case VirtualNetworkProtocol::ETHERNET_TAPSERVER:
|
||||
co_await this->send_ethernet_tapserver_frame(c, proto, data, size);
|
||||
@@ -439,11 +439,11 @@ asio::awaitable<void> IPStackSimulator::send_layer3_frame(
|
||||
co_await this->send_hdlc_frame(c, proto, data, size, true);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown link type");
|
||||
throw std::logic_error("unknown link type");
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c, const void* data, size_t size) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_frame(std::shared_ptr<IPSSClient> c, const void* data, size_t size) {
|
||||
FrameInfo::LinkType link_type = (c->protocol == VirtualNetworkProtocol::ETHERNET_TAPSERVER)
|
||||
? FrameInfo::LinkType::ETHERNET
|
||||
: FrameInfo::LinkType::HDLC;
|
||||
@@ -461,17 +461,17 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
||||
if (c->mac_addr.is_filled_with(0)) {
|
||||
c->mac_addr = fi.ether->src_mac;
|
||||
} else if ((fi.ether->src_mac != c->mac_addr) && (fi.ether->src_mac != this->broadcast_mac_address_bytes)) {
|
||||
throw runtime_error("client sent IPv4 packet from different MAC address");
|
||||
throw std::runtime_error("client sent IPv4 packet from different MAC address");
|
||||
}
|
||||
} else if (fi.hdlc) {
|
||||
uint16_t expected_checksum = fi.computed_hdlc_checksum();
|
||||
uint16_t stored_checksum = fi.stored_hdlc_checksum();
|
||||
if (expected_checksum != stored_checksum) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"HDLC checksum is incorrect ({:04X} expected, {:04X} received)", expected_checksum, stored_checksum));
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("frame is not Ethernet or HDLC");
|
||||
throw std::runtime_error("frame is not Ethernet or HDLC");
|
||||
}
|
||||
|
||||
if (fi.lcp) {
|
||||
@@ -489,18 +489,18 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
||||
} else if (fi.ipv4) {
|
||||
uint16_t expected_ipv4_checksum = fi.computed_ipv4_header_checksum();
|
||||
if (fi.ipv4->checksum != expected_ipv4_checksum) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"IPv4 header checksum is incorrect ({:04X} expected, {:04X} received)", expected_ipv4_checksum, fi.ipv4->checksum));
|
||||
}
|
||||
|
||||
if ((fi.ipv4->src_addr != c->ipv4_addr) && (fi.ipv4->src_addr != 0)) {
|
||||
throw runtime_error("client sent IPv4 packet from different IPv4 address");
|
||||
throw std::runtime_error("client sent IPv4 packet from different IPv4 address");
|
||||
}
|
||||
|
||||
if (fi.udp) {
|
||||
uint16_t expected_udp_checksum = fi.computed_udp4_checksum();
|
||||
if (fi.udp->checksum != expected_udp_checksum) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"UDP checksum is incorrect ({:04X} expected, {:04X} received)", expected_udp_checksum, fi.udp->checksum));
|
||||
}
|
||||
co_await this->on_client_udp_frame(c, fi);
|
||||
@@ -508,27 +508,27 @@ asio::awaitable<void> IPStackSimulator::on_client_frame(shared_ptr<IPSSClient> c
|
||||
} else if (fi.tcp) {
|
||||
uint16_t expected_tcp_checksum = fi.computed_tcp4_checksum();
|
||||
if (fi.tcp->checksum != expected_tcp_checksum) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"TCP checksum is incorrect ({:04X} expected, {:04X} received)", expected_tcp_checksum, fi.tcp->checksum));
|
||||
}
|
||||
co_await this->on_client_tcp_frame(c, fi);
|
||||
|
||||
} else {
|
||||
throw runtime_error("frame uses unsupported IPv4 protocol");
|
||||
throw std::runtime_error("frame uses unsupported IPv4 protocol");
|
||||
}
|
||||
|
||||
} else {
|
||||
throw runtime_error("frame is not IPv4");
|
||||
throw std::runtime_error("frame is not IPv4");
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
switch (fi.lcp->command) {
|
||||
case 0x01: { // Configure-Request
|
||||
auto opts_r = fi.read_payload();
|
||||
while (!opts_r.eof()) {
|
||||
uint8_t opt = opts_r.get_u8();
|
||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
std::string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
phosg::StringReader opt_data_r(opt_data);
|
||||
switch (opt) {
|
||||
case 0x01: // Maximum receive unit
|
||||
@@ -546,9 +546,9 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
||||
case 0x04: // Quality protocol
|
||||
case 0x07: // Protocol field compression
|
||||
case 0x08: // Address and control field compression
|
||||
throw runtime_error(std::format("unimplemented LCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||
throw std::runtime_error(std::format("unimplemented LCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||
default:
|
||||
throw runtime_error("unknown LCP option");
|
||||
throw std::runtime_error("unknown LCP option");
|
||||
}
|
||||
}
|
||||
// Technically, we should implement the LCP state machine, but I'm too lazy to do this right now. In our
|
||||
@@ -591,14 +591,14 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
||||
case 0x05: { // Terminate-Request
|
||||
c->ipv4_addr = 0;
|
||||
c->tcp_connections.clear();
|
||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
response.at(0) = 0x06; // Terminate-Ack
|
||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x09: { // Echo-Request
|
||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
response.at(0) = 0x0A; // Echo-Reply
|
||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||
break;
|
||||
@@ -614,23 +614,23 @@ asio::awaitable<void> IPStackSimulator::on_client_lcp_frame(shared_ptr<IPSSClien
|
||||
case 0x07: // Code-Reject
|
||||
case 0x08: // Protocol-Reject
|
||||
case 0x0A: // Echo-Reply
|
||||
throw runtime_error("unimplemented LCP command");
|
||||
throw std::runtime_error("unimplemented LCP command");
|
||||
default:
|
||||
throw runtime_error("unknown LCP command");
|
||||
throw std::runtime_error("unknown LCP command");
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_pap_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_pap_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
if (fi.pap->command != 0x01) { // Authenticate-Request
|
||||
throw runtime_error("client sent incorrect PAP command");
|
||||
throw std::runtime_error("client sent incorrect PAP command");
|
||||
}
|
||||
|
||||
auto r = fi.read_payload();
|
||||
string username = r.read(r.get_u8());
|
||||
string password = r.read(r.get_u8());
|
||||
std::string username = r.read(r.get_u8());
|
||||
std::string password = r.read(r.get_u8());
|
||||
this->log.info_f("Client logged in with username \"{}\" and password", username);
|
||||
|
||||
static const string login_message = "newserv PPP simulator";
|
||||
static const std::string login_message = "newserv PPP simulator";
|
||||
phosg::StringWriter w;
|
||||
w.put<PAPHeader>(PAPHeader{
|
||||
.command = 0x02, // Authenticate-Ack
|
||||
@@ -642,7 +642,7 @@ asio::awaitable<void> IPStackSimulator::on_client_pap_frame(shared_ptr<IPSSClien
|
||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::PAP, w.str());
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
switch (fi.ipcp->command) {
|
||||
case 0x01: { // Configure-Request
|
||||
auto opts_r = fi.read_payload();
|
||||
@@ -653,11 +653,11 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
phosg::StringWriter rejected_opts_w;
|
||||
while (!opts_r.eof()) {
|
||||
uint8_t opt = opts_r.get_u8();
|
||||
string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
std::string opt_data = opts_r.read(opts_r.get_u8() - 2);
|
||||
phosg::StringReader opt_data_r(opt_data);
|
||||
switch (opt) {
|
||||
case 0x01: // IP addresses (deprecated as of 1992; we don't support it at all)
|
||||
throw runtime_error("IPCP client sent IP-Addresses option");
|
||||
throw std::runtime_error("IPCP client sent IP-Addresses option");
|
||||
case 0x02: // IP compression protocol
|
||||
rejected_opts_w.put_u8(0x02);
|
||||
rejected_opts_w.put_u8(opt_data_r.size() + 2);
|
||||
@@ -675,9 +675,9 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
case 0x82: // Primary NBNS server address
|
||||
case 0x84: // Secondary NBNS server address
|
||||
case 0x04: // Mobile IP address
|
||||
throw runtime_error(std::format("unimplemented IPCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||
throw std::runtime_error(std::format("unimplemented IPCP option {:02X} ({} bytes)", opt, opt_data.size()));
|
||||
default:
|
||||
throw runtime_error("unknown IPCP option");
|
||||
throw std::runtime_error("unknown IPCP option");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +755,7 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
case 0x05: { // Terminate-Request
|
||||
c->ipv4_addr = 0;
|
||||
c->tcp_connections.clear();
|
||||
string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
std::string response(reinterpret_cast<const char*>(fi.payload), fi.payload_size);
|
||||
response.at(0) = 0x06; // Terminate-Ack
|
||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::LCP, response);
|
||||
break;
|
||||
@@ -768,21 +768,21 @@ asio::awaitable<void> IPStackSimulator::on_client_ipcp_frame(shared_ptr<IPSSClie
|
||||
case 0x04: // Configure-Reject
|
||||
case 0x06: // Terminate-Ack
|
||||
case 0x07: // Code-Reject
|
||||
throw runtime_error("unimplemented IPCP command");
|
||||
throw std::runtime_error("unimplemented IPCP command");
|
||||
default:
|
||||
throw runtime_error("unknown LCP command");
|
||||
throw std::runtime_error("unknown LCP command");
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_arp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
if (fi.arp->hwaddr_len != 6 ||
|
||||
fi.arp->paddr_len != 4 ||
|
||||
fi.arp->hardware_type != 0x0001 ||
|
||||
fi.arp->protocol_type != 0x0800) {
|
||||
throw runtime_error("unsupported ARP parameters");
|
||||
throw std::runtime_error("unsupported ARP parameters");
|
||||
}
|
||||
if (fi.payload_size < 20) {
|
||||
throw runtime_error("ARP payload too small");
|
||||
throw std::runtime_error("ARP payload too small");
|
||||
}
|
||||
|
||||
if (c->ipv4_addr == 0) {
|
||||
@@ -816,7 +816,7 @@ asio::awaitable<void> IPStackSimulator::on_client_arp_frame(shared_ptr<IPSSClien
|
||||
co_await this->send_layer3_frame(c, FrameInfo::Protocol::ARP, w.str());
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_udp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
// We only implement DHCP and newserv's DNS server here.
|
||||
|
||||
// Every received UDP packet will elicit exactly one UDP response from newserv, so we prepare the headers in advance
|
||||
@@ -838,24 +838,24 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
// r_udp.size filled in later
|
||||
// r_udp.checksum filled in later
|
||||
|
||||
string r_data;
|
||||
std::string r_data;
|
||||
if (fi.udp->dest_port == 67) { // DHCP
|
||||
auto r = fi.read_payload();
|
||||
const auto& dhcp = r.get<DHCPHeader>();
|
||||
if (dhcp.hardware_type != 1) {
|
||||
throw runtime_error("unknown DHCP hardware type");
|
||||
throw std::runtime_error("unknown DHCP hardware type");
|
||||
}
|
||||
if (dhcp.hardware_address_length != 6) {
|
||||
throw runtime_error("unknown DHCP hardware address length");
|
||||
throw std::runtime_error("unknown DHCP hardware address length");
|
||||
}
|
||||
if (dhcp.magic != 0x63825363) {
|
||||
throw runtime_error("incorrect DHCP magic cookie");
|
||||
throw std::runtime_error("incorrect DHCP magic cookie");
|
||||
}
|
||||
if (dhcp.opcode != 1) { // Request
|
||||
throw runtime_error("DHCP packet is not a request");
|
||||
throw std::runtime_error("DHCP packet is not a request");
|
||||
}
|
||||
|
||||
unordered_map<uint8_t, string> option_data;
|
||||
std::unordered_map<uint8_t, std::string> option_data;
|
||||
for (;;) {
|
||||
uint8_t option = r.get_u8();
|
||||
if (option == 0xFF) {
|
||||
@@ -868,8 +868,8 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
uint8_t command = 0;
|
||||
try {
|
||||
command = option_data.at(53).at(0);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("client did not send a DHCP command option");
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("client did not send a DHCP command option");
|
||||
}
|
||||
|
||||
if (command == 7) {
|
||||
@@ -885,7 +885,7 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
r_ipv4.dest_addr = c->ipv4_addr;
|
||||
|
||||
if ((command != 1) && (command != 3)) {
|
||||
throw runtime_error("client sent unknown DHCP command option");
|
||||
throw std::runtime_error("client sent unknown DHCP command option");
|
||||
}
|
||||
|
||||
phosg::StringWriter w;
|
||||
@@ -951,19 +951,19 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
r_data = std::move(w.str());
|
||||
|
||||
} else {
|
||||
throw runtime_error("client sent unknown DHCP command");
|
||||
throw std::runtime_error("client sent unknown DHCP command");
|
||||
}
|
||||
|
||||
} else if (fi.udp->dest_port == 53) { // DNS
|
||||
if (fi.payload_size < 0x0C) {
|
||||
throw runtime_error("DNS payload too small");
|
||||
throw std::runtime_error("DNS payload too small");
|
||||
}
|
||||
|
||||
uint32_t resolved_address = this->connect_address_for_remote_address(c->ipv4_addr);
|
||||
r_data = DNSServer::response_for_query(fi.payload, fi.payload_size, resolved_address);
|
||||
|
||||
} else { // Not DHCP or DNS
|
||||
throw runtime_error("UDP packet is not DHCP or DNS");
|
||||
throw std::runtime_error("UDP packet is not DHCP or DNS");
|
||||
}
|
||||
|
||||
if (!r_data.empty()) {
|
||||
@@ -973,7 +973,7 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
r_udp.checksum = FrameInfo::computed_udp4_checksum(r_ipv4, r_udp, r_data.data(), r_data.size());
|
||||
|
||||
if (this->log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
|
||||
std::string remote_str = this->str_for_ipv4_netloc(fi.ipv4->src_addr, fi.udp->src_port);
|
||||
this->log.debug_f("Sending UDP response to {}", remote_str);
|
||||
phosg::print_data(stderr, r_data);
|
||||
}
|
||||
@@ -987,19 +987,19 @@ asio::awaitable<void> IPStackSimulator::on_client_udp_frame(shared_ptr<IPSSClien
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi) {
|
||||
this->log.debug_f("Virtual network sent TCP frame (seq={:08X}, ack={:08X})",
|
||||
fi.tcp->seq_num, fi.tcp->ack_num);
|
||||
|
||||
if (fi.tcp->flags & (TCPHeader::Flag::NS | TCPHeader::Flag::CWR | TCPHeader::Flag::ECE | TCPHeader::Flag::URG)) {
|
||||
throw runtime_error("unsupported flag in TCP packet");
|
||||
throw std::runtime_error("unsupported flag in TCP packet");
|
||||
}
|
||||
|
||||
if (fi.tcp->flags & TCPHeader::Flag::SYN) {
|
||||
// We never make connections back to the client, so we should never receive a SYN+ACK. Essentially, no other flags
|
||||
// should be set in any received SYN.
|
||||
if ((fi.tcp->flags & 0x0FFF) != TCPHeader::Flag::SYN) {
|
||||
throw runtime_error("TCP SYN contains extra flags");
|
||||
throw std::runtime_error("TCP SYN contains extra flags");
|
||||
}
|
||||
|
||||
phosg::StringReader options_r(fi.tcp + 1, fi.tcp_options_size);
|
||||
@@ -1015,19 +1015,19 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
break;
|
||||
case 2: // Max segment size
|
||||
if (option_size != 4) {
|
||||
throw runtime_error("incorrect size for TCP max frame size option");
|
||||
throw std::runtime_error("incorrect size for TCP max frame size option");
|
||||
}
|
||||
max_frame_size = options_r.get_u16b();
|
||||
break;
|
||||
case 3: // Window scale (ignored)
|
||||
if (option_size != 3) {
|
||||
throw runtime_error("incorrect size for TCP window scale option");
|
||||
throw std::runtime_error("incorrect size for TCP window scale option");
|
||||
}
|
||||
options_r.skip(option_size);
|
||||
break;
|
||||
case 4: // Selective ACK supported (ignored)
|
||||
if (option_size != 2) {
|
||||
throw runtime_error("incorrect size for TCP selective ACK supported option");
|
||||
throw std::runtime_error("incorrect size for TCP selective ACK supported option");
|
||||
}
|
||||
break;
|
||||
case 5: // Selective ACK (ignored)
|
||||
@@ -1035,21 +1035,21 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
break;
|
||||
case 8: // Timestamps (ignored)
|
||||
if (option_size != 10) {
|
||||
throw runtime_error("incorrect size for TCP timestamps option");
|
||||
throw std::runtime_error("incorrect size for TCP timestamps option");
|
||||
}
|
||||
options_r.skip(8);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid TCP option");
|
||||
throw std::runtime_error("invalid TCP option");
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<IPSSClient::TCPConnection> conn;
|
||||
string conn_str;
|
||||
std::shared_ptr<IPSSClient::TCPConnection> conn;
|
||||
std::string conn_str;
|
||||
uint64_t key = this->tcp_conn_key_for_client_frame(fi);
|
||||
auto conn_it = c->tcp_connections.find(key);
|
||||
if (conn_it == c->tcp_connections.end()) {
|
||||
conn = make_shared<IPSSClient::TCPConnection>(c);
|
||||
conn = std::make_shared<IPSSClient::TCPConnection>(c);
|
||||
c->tcp_connections.emplace(key, conn);
|
||||
conn->server_addr = fi.ipv4->dest_addr;
|
||||
conn->server_port = fi.tcp->dest_port;
|
||||
@@ -1070,7 +1070,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
|
||||
// Connection is NOT new; this is probably a resend of an earlier SYN
|
||||
if (!conn->awaiting_first_ack) {
|
||||
throw logic_error("SYN received on already-open connection after initial phase");
|
||||
throw std::logic_error("SYN received on already-open connection after initial phase");
|
||||
}
|
||||
// TODO: We should check the syn/ack numbers here instead of just assuming they're correct
|
||||
conn_str = this->str_for_tcp_connection(c, conn);
|
||||
@@ -1100,7 +1100,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
this->log.debug_f("Client sent ACK {:08X}", fi.tcp->ack_num);
|
||||
if (conn->awaiting_first_ack) {
|
||||
if (fi.tcp->ack_num != conn->acked_server_seq + 1) {
|
||||
throw runtime_error("first ack_num was not acked_server_seq + 1");
|
||||
throw std::runtime_error("first ack_num was not acked_server_seq + 1");
|
||||
}
|
||||
conn->acked_server_seq++;
|
||||
conn->awaiting_first_ack = false;
|
||||
@@ -1111,7 +1111,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
this->log.debug_f("Advancing acked_server_seq from {:08X}", conn->acked_server_seq);
|
||||
uint32_t ack_delta = fi.tcp->ack_num - conn->acked_server_seq;
|
||||
if (conn->outbound_data_bytes < ack_delta) {
|
||||
throw runtime_error("client acknowledged beyond end of sent data");
|
||||
throw std::runtime_error("client acknowledged beyond end of sent data");
|
||||
}
|
||||
|
||||
conn->drain_outbound_data(ack_delta);
|
||||
@@ -1125,7 +1125,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
ack_delta, conn->acked_server_seq);
|
||||
|
||||
} else if (seq_num_less(fi.tcp->ack_num, conn->acked_server_seq)) {
|
||||
throw runtime_error("client sent lower ack num than previous frame");
|
||||
throw std::runtime_error("client sent lower ack num than previous frame");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1137,10 +1137,10 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
if (fi.tcp->flags & (TCPHeader::Flag::RST | TCPHeader::Flag::FIN)) {
|
||||
bool is_rst = (fi.tcp->flags & TCPHeader::Flag::RST);
|
||||
if (is_rst && (fi.tcp->flags & TCPHeader::Flag::FIN)) {
|
||||
throw runtime_error("client sent TCP FIN+RST");
|
||||
throw std::runtime_error("client sent TCP FIN+RST");
|
||||
}
|
||||
|
||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
this->log.info_f("Client closed TCP connection {}", conn_str);
|
||||
if (conn->server_channel) {
|
||||
conn->server_channel->disconnect();
|
||||
@@ -1162,7 +1162,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
// newserv can handle incomplete commands, so we just ignore the PSH flag and forward any data to the server
|
||||
// immediately (hence the lack of a flag check in the above condition).
|
||||
|
||||
string conn_str = this->log.should_log(phosg::LogLevel::L_WARNING)
|
||||
std::string conn_str = this->log.should_log(phosg::LogLevel::L_WARNING)
|
||||
? this->str_for_tcp_connection(c, conn)
|
||||
: "";
|
||||
|
||||
@@ -1190,7 +1190,7 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
}
|
||||
|
||||
if (payload_skip_bytes > fi.payload_size) {
|
||||
throw logic_error("payload skip bytes too large");
|
||||
throw std::logic_error("payload skip bytes too large");
|
||||
}
|
||||
|
||||
if (payload_skip_bytes < fi.payload_size) {
|
||||
@@ -1239,9 +1239,9 @@ asio::awaitable<void> IPStackSimulator::on_client_tcp_frame(shared_ptr<IPSSClien
|
||||
}
|
||||
}
|
||||
|
||||
void IPStackSimulator::schedule_send_pending_push_frame(shared_ptr<IPSSClient::TCPConnection> conn, uint64_t delay_usecs) {
|
||||
void IPStackSimulator::schedule_send_pending_push_frame(std::shared_ptr<IPSSClient::TCPConnection> conn, uint64_t delay_usecs) {
|
||||
conn->resend_push_timer.expires_after(std::chrono::microseconds(delay_usecs));
|
||||
conn->resend_push_timer.async_wait([wconn = weak_ptr<IPSSClient::TCPConnection>(conn)](std::error_code ec) {
|
||||
conn->resend_push_timer.async_wait([wconn = std::weak_ptr<IPSSClient::TCPConnection>(conn)](std::error_code ec) {
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
@@ -1262,7 +1262,7 @@ void IPStackSimulator::schedule_send_pending_push_frame(shared_ptr<IPSSClient::T
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
if (!conn->outbound_data_bytes) {
|
||||
if (!conn->server_channel || !conn->server_channel->connected()) {
|
||||
co_await this->close_tcp_connection(c, conn);
|
||||
@@ -1270,11 +1270,11 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
co_return;
|
||||
}
|
||||
|
||||
size_t bytes_to_send = min<size_t>(conn->outbound_data_bytes, conn->next_push_max_frame_size);
|
||||
size_t bytes_to_send = std::min<size_t>(conn->outbound_data_bytes, conn->next_push_max_frame_size);
|
||||
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
||||
// There is a bug in Dolphin's modem implementation (which I wrote, so it's my fault) that causes commands to be
|
||||
// dropped when too much data is sent. To work around this, we only send up to 200 bytes in each push frame.
|
||||
bytes_to_send = min<size_t>(bytes_to_send, 200);
|
||||
bytes_to_send = std::min<size_t>(bytes_to_send, 200);
|
||||
}
|
||||
|
||||
this->log.debug_f("Sending PSH frame with seq_num {:08X}, 0x{:X}/0x{:X} data bytes",
|
||||
@@ -1284,7 +1284,7 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
if (conn->outbound_data.empty() || conn->outbound_data.front().size() < bytes_to_send) {
|
||||
// This should never happen because bytes_to_send should always be less than or equal to conn->outbound_data_bytes,
|
||||
// which itself should be equal to the number of bytes that can be linearized
|
||||
throw logic_error("failed to linearize enough bytes before sending TCP PSH");
|
||||
throw std::logic_error("failed to linearize enough bytes before sending TCP PSH");
|
||||
}
|
||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::PSH, conn->outbound_data.front().data(), bytes_to_send);
|
||||
conn->awaiting_ack = true;
|
||||
@@ -1300,17 +1300,17 @@ asio::awaitable<void> IPStackSimulator::send_pending_push_frame(
|
||||
if (conn->resend_push_usecs > 5000000) {
|
||||
conn->resend_push_usecs = 5000000;
|
||||
}
|
||||
conn->next_push_max_frame_size = max<size_t>(0x100, conn->next_push_max_frame_size - 0x100);
|
||||
conn->next_push_max_frame_size = std::max<size_t>(0x100, conn->next_push_max_frame_size - 0x100);
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::send_tcp_frame(
|
||||
shared_ptr<IPSSClient> c,
|
||||
shared_ptr<IPSSClient::TCPConnection> conn,
|
||||
std::shared_ptr<IPSSClient> c,
|
||||
std::shared_ptr<IPSSClient::TCPConnection> conn,
|
||||
uint16_t flags,
|
||||
const void* payload_data,
|
||||
size_t payload_size) {
|
||||
if (!payload_data != !(flags & TCPHeader::Flag::PSH)) {
|
||||
throw logic_error("data should be given if and only if PSH is given");
|
||||
throw std::logic_error("data should be given if and only if PSH is given");
|
||||
}
|
||||
|
||||
IPv4Header ipv4;
|
||||
@@ -1350,12 +1350,12 @@ asio::awaitable<void> IPStackSimulator::send_tcp_frame(
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::open_server_connection(
|
||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
if (conn->server_channel) {
|
||||
throw logic_error("server connection is already open");
|
||||
throw std::logic_error("server connection is already open");
|
||||
}
|
||||
|
||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
|
||||
// Figure out which logical port the connection should go to
|
||||
auto port_config_it = this->state->number_to_port_config.find(conn->server_port);
|
||||
@@ -1366,7 +1366,7 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
|
||||
}
|
||||
const auto& port_config = port_config_it->second;
|
||||
|
||||
conn->server_channel = make_shared<IPSSChannel>(
|
||||
conn->server_channel = std::make_shared<IPSSChannel>(
|
||||
this->shared_from_this(),
|
||||
c,
|
||||
conn,
|
||||
@@ -1389,13 +1389,13 @@ asio::awaitable<void> IPStackSimulator::open_server_connection(
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::close_tcp_connection(
|
||||
shared_ptr<IPSSClient> c, shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn) {
|
||||
// Send an RST to the client. This is kind of rude (we really should use FIN) but the PSO network stack always sends
|
||||
// an RST to us when disconnecting, so whatever
|
||||
co_await this->send_tcp_frame(c, conn, TCPHeader::Flag::RST);
|
||||
|
||||
// Delete the connection object
|
||||
string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
std::string conn_str = this->str_for_tcp_connection(c, conn);
|
||||
this->log.info_f("Server closed TCP connection {}", conn_str);
|
||||
c->tcp_connections.erase(this->tcp_conn_key_for_connection(conn));
|
||||
}
|
||||
@@ -1412,14 +1412,14 @@ std::shared_ptr<IPSSClient> IPStackSimulator::create_client(
|
||||
|
||||
uint64_t network_id = this->next_network_id++;
|
||||
this->log.info_f("Virtual network N-{:X} connected via {}", network_id, listen_sock->name);
|
||||
return make_shared<IPSSClient>(this->shared_from_this(), network_id, listen_sock->protocol, std::move(client_sock));
|
||||
return std::make_shared<IPSSClient>(this->shared_from_this(), network_id, listen_sock->protocol, std::move(client_sock));
|
||||
}
|
||||
|
||||
asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<IPSSClient> c) {
|
||||
for (;;) {
|
||||
le_uint16_t frame_size;
|
||||
co_await asio::async_read(c->sock, asio::buffer(&frame_size, sizeof(frame_size)), asio::use_awaitable);
|
||||
string frame(frame_size, '\0');
|
||||
std::string frame(frame_size, '\0');
|
||||
co_await asio::async_read(c->sock, asio::buffer(frame.data(), frame.size()), asio::use_awaitable);
|
||||
|
||||
if (c->protocol == VirtualNetworkProtocol::HDLC_TAPSERVER) {
|
||||
@@ -1428,7 +1428,7 @@ asio::awaitable<void> IPStackSimulator::handle_tapserver_client(std::shared_ptr<
|
||||
|
||||
try {
|
||||
co_await this->on_client_frame(c, frame.data(), frame.size());
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
||||
phosg::print_data(stderr, frame);
|
||||
}
|
||||
@@ -1454,10 +1454,10 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
||||
size_t frame_start_offset = 0;
|
||||
while (buffer.size() > frame_start_offset) {
|
||||
if (buffer[frame_start_offset] != 0x7E) {
|
||||
throw runtime_error("HDLC frame does not begin with 7E");
|
||||
throw std::runtime_error("HDLC frame does not begin with 7E");
|
||||
}
|
||||
size_t frame_end_offset = buffer.find(0x7E, frame_start_offset + 1);
|
||||
if (frame_end_offset == string::npos) {
|
||||
if (frame_end_offset == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
frame_end_offset++;
|
||||
@@ -1468,7 +1468,7 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
||||
|
||||
try {
|
||||
co_await this->on_client_frame(c, frame_data, unescaped_size);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (this->log.warning_f("Failed to process frame: {}", e.what())) {
|
||||
phosg::print_data(stderr, frame_data, unescaped_size);
|
||||
}
|
||||
@@ -1479,7 +1479,7 @@ asio::awaitable<void> IPStackSimulator::handle_hdlc_raw_client(std::shared_ptr<I
|
||||
|
||||
// Delete the processed packets from the beginning of the buffer
|
||||
if (frame_start_offset > buffer_bytes) {
|
||||
throw logic_error("frame start offset is beyond buffer bounds");
|
||||
throw std::logic_error("frame start offset is beyond buffer bounds");
|
||||
} else if (frame_start_offset == buffer_bytes) {
|
||||
buffer_bytes = 0;
|
||||
} else if (frame_start_offset > 0) {
|
||||
|
||||
+4
-6
@@ -1,7 +1,5 @@
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
||||
for (const auto& it : json.as_list()) {
|
||||
// String should be of the form a.b.c.d or a.b.c.d/e
|
||||
@@ -13,22 +11,22 @@ IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
||||
} else if (tokens.size() == 2) {
|
||||
mask_bits = stoul(tokens[1], nullptr, 10);
|
||||
if (mask_bits > 32) {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
throw std::runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
} else {
|
||||
throw runtime_error("invalid IPv4 address range");
|
||||
throw std::runtime_error("invalid IPv4 address range");
|
||||
}
|
||||
|
||||
auto addr_tokens = phosg::split(tokens[0], '.');
|
||||
if (addr_tokens.size() != 4) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
throw std::runtime_error("invalid IPv4 address");
|
||||
}
|
||||
uint32_t addr = 0;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
size_t end_pos = 0;
|
||||
size_t new_byte = stoul(addr_tokens[z], &end_pos, 10);
|
||||
if (end_pos != addr_tokens[z].size() || new_byte > 0xFF) {
|
||||
throw runtime_error("invalid IPv4 address");
|
||||
throw std::runtime_error("invalid IPv4 address");
|
||||
}
|
||||
addr = (addr << 8) | new_byte;
|
||||
}
|
||||
|
||||
+13
-11
@@ -7,8 +7,6 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct GVMFileEntry {
|
||||
be_uint16_t file_num;
|
||||
pstring<TextEncoding::ASCII, 0x1C> name;
|
||||
@@ -35,21 +33,25 @@ struct GVRHeader {
|
||||
be_uint16_t height;
|
||||
} __packed_ws__(GVRHeader, 0x10);
|
||||
|
||||
string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, const string& internal_name, uint32_t global_index) {
|
||||
std::string encode_gvm(
|
||||
const phosg::ImageRGBA8888N& img,
|
||||
GVRDataFormat data_format,
|
||||
const std::string& internal_name,
|
||||
uint32_t global_index) {
|
||||
int8_t dimensions_field = -2;
|
||||
{
|
||||
size_t h = img.get_height();
|
||||
size_t w = img.get_width();
|
||||
if ((h != w) || (w & (w - 1)) || (h & (h - 1))) {
|
||||
throw runtime_error("image must be square and dimensions must be powers of 2");
|
||||
throw std::runtime_error("image must be square and dimensions must be powers of 2");
|
||||
}
|
||||
for (w >>= 1; w; w >>= 1, dimensions_field++) {
|
||||
}
|
||||
if (dimensions_field < 1) {
|
||||
throw runtime_error("image is too small");
|
||||
throw std::runtime_error("image is too small");
|
||||
}
|
||||
if (dimensions_field > 0xF) {
|
||||
throw runtime_error("image is too large");
|
||||
throw std::runtime_error("image is too large");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +66,7 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
||||
pixel_bytes = pixel_count * 2;
|
||||
break;
|
||||
default:
|
||||
throw invalid_argument("cannot encode pixel format");
|
||||
throw std::invalid_argument("cannot encode pixel format");
|
||||
}
|
||||
|
||||
phosg::StringWriter w;
|
||||
@@ -102,7 +104,7 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
||||
w.put_u32b(phosg::argb8888_for_rgba8888(c));
|
||||
break;
|
||||
default:
|
||||
throw logic_error("cannot encode pixel format");
|
||||
throw std::logic_error("cannot encode pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,9 +114,9 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
static const array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF};
|
||||
static const std::array<uint32_t, 4> fon_colors = {0x000000FF, 0x555555FF, 0xAAAAAAFF, 0xFFFFFFFF};
|
||||
|
||||
phosg::ImageRGB888 decode_fon(const string& data, size_t width) {
|
||||
phosg::ImageRGB888 decode_fon(const std::string& data, size_t width) {
|
||||
size_t num_pixels = data.size() * 4;
|
||||
size_t height = num_pixels / width;
|
||||
phosg::ImageRGB888 ret(width, height);
|
||||
@@ -132,7 +134,7 @@ constexpr size_t uabs(size_t a, size_t b) {
|
||||
return (a > b) ? (a - b) : (b - a);
|
||||
}
|
||||
|
||||
string encode_fon(const phosg::ImageRGB888& img) {
|
||||
std::string encode_fon(const phosg::ImageRGB888& img) {
|
||||
phosg::BitWriter w;
|
||||
for (size_t y = 0; y < img.get_height(); y++) {
|
||||
for (size_t x = 0; x < img.get_width(); x++) {
|
||||
|
||||
+57
-63
@@ -19,22 +19,17 @@
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
IntegralExpression::IntegralExpression(const string& text)
|
||||
: root(this->parse_expr(text)) {}
|
||||
IntegralExpression::IntegralExpression(const std::string& text) : root(this->parse_expr(text)) {}
|
||||
|
||||
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
|
||||
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||
: type(type),
|
||||
left(std::move(left)),
|
||||
right(std::move(right)) {}
|
||||
Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right)
|
||||
: type(type), left(std::move(left)), right(std::move(right)) {}
|
||||
|
||||
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
||||
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -78,11 +73,11 @@ int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
||||
case Type::MODULUS:
|
||||
return this->left->evaluate(env) % this->right->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
throw std::logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::BinaryOperatorNode::str() const {
|
||||
std::string IntegralExpression::BinaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||
@@ -121,19 +116,18 @@ string IntegralExpression::BinaryOperatorNode::str() const {
|
||||
case Type::MODULUS:
|
||||
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid binary operator type");
|
||||
throw std::logic_error("invalid binary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
||||
: type(type),
|
||||
sub(std::move(sub)) {}
|
||||
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub)
|
||||
: type(type), sub(std::move(sub)) {}
|
||||
|
||||
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -147,11 +141,11 @@ int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
||||
case Type::NEGATIVE:
|
||||
return -this->sub->evaluate(env);
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
throw std::logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
std::string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
return "!(" + this->sub->str() + ")";
|
||||
@@ -160,7 +154,7 @@ string IntegralExpression::UnaryOperatorNode::str() const {
|
||||
case Type::NEGATIVE:
|
||||
return "-(" + this->sub->str() + ")";
|
||||
default:
|
||||
throw logic_error("invalid unary operator type");
|
||||
throw std::logic_error("invalid unary operator type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,19 +164,19 @@ bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
||||
return other_flag.flag_index == this->flag_index;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.flags) {
|
||||
throw runtime_error("quest flags not available");
|
||||
throw std::runtime_error("quest flags not available");
|
||||
}
|
||||
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::FlagLookupNode::str() const {
|
||||
std::string IntegralExpression::FlagLookupNode::str() const {
|
||||
return std::format("F_{:04X}", this->flag_index);
|
||||
}
|
||||
|
||||
@@ -193,14 +187,14 @@ bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& o
|
||||
try {
|
||||
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
|
||||
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
|
||||
if (!env.challenge_records) {
|
||||
throw runtime_error("challenge records not available");
|
||||
throw std::runtime_error("challenge records not available");
|
||||
}
|
||||
if (this->episode == Episode::EP1) {
|
||||
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
|
||||
@@ -210,17 +204,17 @@ int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& e
|
||||
return false;
|
||||
}
|
||||
|
||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
std::string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return std::format("CC_{}_{}", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name) : reward_name(reward_name) {}
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const std::string& reward_name) : reward_name(reward_name) {}
|
||||
|
||||
bool IntegralExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
||||
return other_team_reward.reward_name == this->reward_name;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -229,7 +223,7 @@ int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const
|
||||
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::TeamRewardLookupNode::str() const {
|
||||
std::string IntegralExpression::TeamRewardLookupNode::str() const {
|
||||
return "T_" + this->reward_name;
|
||||
}
|
||||
|
||||
@@ -243,7 +237,7 @@ int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const
|
||||
return env.num_players;
|
||||
}
|
||||
|
||||
string IntegralExpression::NumPlayersLookupNode::str() const {
|
||||
std::string IntegralExpression::NumPlayersLookupNode::str() const {
|
||||
return "V_NumPlayers";
|
||||
}
|
||||
|
||||
@@ -257,7 +251,7 @@ int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
|
||||
return env.event;
|
||||
}
|
||||
|
||||
string IntegralExpression::EventLookupNode::str() const {
|
||||
std::string IntegralExpression::EventLookupNode::str() const {
|
||||
return "V_Event";
|
||||
}
|
||||
|
||||
@@ -271,7 +265,7 @@ int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const
|
||||
return env.v1_present ? 1 : 0;
|
||||
}
|
||||
|
||||
string IntegralExpression::V1PresenceLookupNode::str() const {
|
||||
std::string IntegralExpression::V1PresenceLookupNode::str() const {
|
||||
return "V_V1Present";
|
||||
}
|
||||
|
||||
@@ -282,7 +276,7 @@ bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
||||
return other_const.value == this->value;
|
||||
} catch (const bad_cast&) {
|
||||
} catch (const std::bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -291,11 +285,11 @@ int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
||||
return this->value;
|
||||
}
|
||||
|
||||
string IntegralExpression::ConstantNode::str() const {
|
||||
std::string IntegralExpression::ConstantNode::str() const {
|
||||
return std::format("{}", this->value);
|
||||
}
|
||||
|
||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
||||
std::unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(std::string_view text) {
|
||||
// Strip off spaces and fully-enclosing parentheses
|
||||
for (;;) {
|
||||
size_t starting_size = text.size();
|
||||
@@ -329,22 +323,22 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
}
|
||||
}
|
||||
if (text.empty()) {
|
||||
throw runtime_error("invalid expression");
|
||||
throw std::runtime_error("invalid expression");
|
||||
}
|
||||
|
||||
// Check for binary operators at the root level
|
||||
using BinType = BinaryOperatorNode::Type;
|
||||
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||
{{make_pair("||", BinType::LOGICAL_OR)}},
|
||||
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||
static const std::vector<std::vector<std::pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||
{{std::make_pair("||", BinType::LOGICAL_OR)}},
|
||||
{{std::make_pair("&&", BinType::LOGICAL_AND)}},
|
||||
{{std::make_pair("|", BinType::BITWISE_OR)}},
|
||||
{{std::make_pair("^", BinType::BITWISE_XOR)}},
|
||||
{{std::make_pair("&", BinType::BITWISE_AND)}},
|
||||
{{std::make_pair("==", BinType::EQUAL)}, {std::make_pair("!=", BinType::NOT_EQUAL)}},
|
||||
{{std::make_pair("<=", BinType::LESS_OR_EQUAL)}, {std::make_pair(">=", BinType::GREATER_OR_EQUAL)}, {std::make_pair("<", BinType::LESS_THAN)}, {std::make_pair(">", BinType::GREATER_THAN)}},
|
||||
{{std::make_pair("<<", BinType::LEFT_SHIFT)}, {std::make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||
{{std::make_pair("+", BinType::ADD)}, {std::make_pair("-", BinType::SUBTRACT)}},
|
||||
{{std::make_pair("*", BinType::MULTIPLY)}, {std::make_pair("/", BinType::DIVIDE)}, {std::make_pair("%", BinType::MODULUS)}},
|
||||
};
|
||||
for (const auto& operators : binary_operator_levels) {
|
||||
size_t paren_level = 0;
|
||||
@@ -390,12 +384,12 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
char* endptr = nullptr;
|
||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid flag lookup token");
|
||||
throw std::runtime_error("invalid flag lookup token");
|
||||
}
|
||||
if (flag >= 0x400) {
|
||||
throw runtime_error("invalid flag index");
|
||||
throw std::runtime_error("invalid flag index");
|
||||
}
|
||||
return make_unique<FlagLookupNode>(flag);
|
||||
return std::make_unique<FlagLookupNode>(flag);
|
||||
}
|
||||
if (text.starts_with("CC_")) {
|
||||
Episode episode;
|
||||
@@ -404,45 +398,45 @@ unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string
|
||||
} else if (text.starts_with("CC_Ep2_")) {
|
||||
episode = Episode::EP2;
|
||||
} else {
|
||||
throw runtime_error("invalid challenge episode");
|
||||
throw std::runtime_error("invalid challenge episode");
|
||||
}
|
||||
char* endptr = nullptr;
|
||||
uint64_t stage_index = strtoul(text.data() + 7, &endptr, 0) - 1;
|
||||
if (endptr != text.data() + text.size()) {
|
||||
throw runtime_error("invalid challenge completion lookup token");
|
||||
throw std::runtime_error("invalid challenge completion lookup token");
|
||||
}
|
||||
if ((episode == Episode::EP1 && stage_index > 8) || (episode == Episode::EP2 && stage_index > 4)) {
|
||||
throw runtime_error("invalid challenge stage index");
|
||||
throw std::runtime_error("invalid challenge stage index");
|
||||
}
|
||||
return make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
||||
return std::make_unique<ChallengeCompletionLookupNode>(episode, stage_index);
|
||||
}
|
||||
if (text.starts_with("T_")) {
|
||||
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
||||
return std::make_unique<TeamRewardLookupNode>(std::string(text.substr(2)));
|
||||
}
|
||||
if (text == "V_NumPlayers") {
|
||||
return make_unique<NumPlayersLookupNode>();
|
||||
return std::make_unique<NumPlayersLookupNode>();
|
||||
}
|
||||
if (text == "V_Event") {
|
||||
return make_unique<EventLookupNode>();
|
||||
return std::make_unique<EventLookupNode>();
|
||||
}
|
||||
if (text == "V_V1Present") {
|
||||
return make_unique<V1PresenceLookupNode>();
|
||||
return std::make_unique<V1PresenceLookupNode>();
|
||||
}
|
||||
|
||||
// Check for constants
|
||||
if (text == "true") {
|
||||
return make_unique<ConstantNode>(1);
|
||||
return std::make_unique<ConstantNode>(1);
|
||||
}
|
||||
if (text == "false") {
|
||||
return make_unique<ConstantNode>(0);
|
||||
return std::make_unique<ConstantNode>(0);
|
||||
}
|
||||
try {
|
||||
size_t endpos;
|
||||
int64_t v = stoll(string(text), &endpos, 0);
|
||||
int64_t v = std::stoll(std::string(text), &endpos, 0);
|
||||
if (endpos == text.size()) {
|
||||
return make_unique<ConstantNode>(v);
|
||||
return std::make_unique<ConstantNode>(v);
|
||||
}
|
||||
} catch (const exception&) {
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
throw runtime_error("unparseable expression");
|
||||
throw std::runtime_error("unparseable expression");
|
||||
}
|
||||
|
||||
+65
-67
@@ -6,8 +6,6 @@
|
||||
#include "EnemyType.hh"
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
// The favored weapon type table is hardcoded in the game client. The table is:
|
||||
// Viridia shots
|
||||
// Greennill rifles
|
||||
@@ -19,23 +17,23 @@ using namespace std;
|
||||
// Oran daggers
|
||||
// Yellowboze (none)
|
||||
// Whitill slicers
|
||||
static const array<uint8_t, 10> favored_weapon_by_section_id = {
|
||||
static const std::array<uint8_t, 10> favored_weapon_by_section_id = {
|
||||
0x09, 0x07, 0x02, 0x04, 0x08, 0x0A, 0xFF, 0x03, 0xFF, 0x05};
|
||||
|
||||
ItemCreator::ItemCreator(
|
||||
shared_ptr<const CommonItemSet> common_item_set,
|
||||
shared_ptr<const RareItemSet> rare_item_set,
|
||||
shared_ptr<const ArmorRandomSet> armor_random_set,
|
||||
shared_ptr<const ToolRandomSet> tool_random_set,
|
||||
shared_ptr<const WeaponRandomSet> weapon_random_set,
|
||||
shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
|
||||
shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const CommonItemSet> common_item_set,
|
||||
std::shared_ptr<const RareItemSet> rare_item_set,
|
||||
std::shared_ptr<const ArmorRandomSet> armor_random_set,
|
||||
std::shared_ptr<const ToolRandomSet> tool_random_set,
|
||||
std::shared_ptr<const WeaponRandomSet> weapon_random_set,
|
||||
std::shared_ptr<const TekkerAdjustmentSet> tekker_adjustment_set,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const ItemData::StackLimits> stack_limits,
|
||||
GameMode mode,
|
||||
Difficulty difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||
shared_ptr<const BattleRules> restrictions)
|
||||
std::shared_ptr<const BattleRules> restrictions)
|
||||
: log(std::format("[ItemCreator:{}/{}/{}/{}] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||
logic_version(stack_limits->version),
|
||||
is_legacy_replay(false),
|
||||
@@ -175,7 +173,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
|
||||
case 6: // Nothing
|
||||
break;
|
||||
default:
|
||||
throw logic_error("this should be impossible");
|
||||
throw std::logic_error("this should be impossible");
|
||||
}
|
||||
if (item_class < 6) {
|
||||
this->generate_common_item_variances(res.item, area);
|
||||
@@ -183,7 +181,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area, bool force_r
|
||||
}
|
||||
return res;
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.error_f("Exception in item creation: {}", e.what());
|
||||
return DropResult();
|
||||
}
|
||||
@@ -233,13 +231,13 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
||||
case 2:
|
||||
try {
|
||||
item_class = pt->enemy_type_item_classes.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
this->log.info_f("Item class is not set for this enemy type");
|
||||
item_class = 0xFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid item class determinant");
|
||||
throw std::logic_error("invalid item class determinant");
|
||||
}
|
||||
|
||||
this->log.info_f(
|
||||
@@ -265,7 +263,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
||||
res.item.data1[0] = 0x04;
|
||||
try {
|
||||
res.item.data2d = this->choose_meseta_amount(pt->enemy_type_meseta_ranges.at(enemy_type)) & 0xFFFF;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
this->log.info_f("Meseta range is not set for this enemy type");
|
||||
return DropResult();
|
||||
}
|
||||
@@ -281,7 +279,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(EnemyType enemy_type,
|
||||
|
||||
return res;
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
this->log.error_f("Exception in item creation: {}", e.what());
|
||||
return DropResult();
|
||||
}
|
||||
@@ -400,7 +398,7 @@ ItemData ItemCreator::create_rare_item(const ItemData& drop_item, uint8_t area)
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid item class");
|
||||
throw std::logic_error("invalid item class");
|
||||
}
|
||||
this->set_item_kill_count_if_unsealable(item);
|
||||
}
|
||||
@@ -416,7 +414,7 @@ void ItemCreator::generate_rare_weapon_bonuses(ItemData& item, Episode episode,
|
||||
|
||||
auto pt = this->pt(episode);
|
||||
if (!pt->has_rare_bonus_value_prob_table) {
|
||||
throw logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
|
||||
throw std::logic_error("generate_rare_weapon_bonuses called for common item table without rare bonus value probability table");
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < 6; z += 2) {
|
||||
@@ -549,7 +547,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
item.clear();
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid weapon and armor mode");
|
||||
throw std::logic_error("invalid weapon and armor mode");
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
@@ -580,7 +578,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid tech disk mode");
|
||||
throw std::logic_error("invalid tech disk mode");
|
||||
}
|
||||
} else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) {
|
||||
this->log.info_f("Restricted: scape dolls not allowed");
|
||||
@@ -594,7 +592,7 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid item");
|
||||
throw std::logic_error("invalid item");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,7 +632,7 @@ void ItemCreator::generate_common_item_variances(ItemData& item, uint8_t area) {
|
||||
// Note: The original code does the following here:
|
||||
// item.clear();
|
||||
// item.data1[0] = 0x05;
|
||||
throw logic_error("invalid item class");
|
||||
throw std::logic_error("invalid item class");
|
||||
}
|
||||
|
||||
this->clear_item_if_restricted(item);
|
||||
@@ -698,7 +696,7 @@ void ItemCreator::generate_common_tool_variances(ItemData& item, uint8_t area) {
|
||||
item.data1[0] = 0x03;
|
||||
item.data1[1] = data.first;
|
||||
item.data1[2] = data.second;
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
this->log.info_f("Tool class is missing; skipping item generation");
|
||||
return;
|
||||
}
|
||||
@@ -787,7 +785,7 @@ void ItemCreator::generate_common_weapon_variances(ItemData& item, uint8_t area)
|
||||
|
||||
void ItemCreator::generate_common_weapon_grind(ItemData& item, uint8_t area, uint8_t offset_within_subtype_range) {
|
||||
if (item.data1[0] == 0) {
|
||||
uint8_t offset = clamp<uint8_t>(offset_within_subtype_range, 0, 3);
|
||||
uint8_t offset = std::clamp<uint8_t>(offset_within_subtype_range, 0, 3);
|
||||
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt(area)->grind_prob_table, offset);
|
||||
this->log.info_f("Generated grind {:02X} from offset within subtype range {:02X}", item.data1[3], offset_within_subtype_range);
|
||||
}
|
||||
@@ -852,7 +850,7 @@ void ItemCreator::generate_unit_stars_tables() {
|
||||
case Version::BB_PATCH:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
throw logic_error("ItemCreator cannot be created for Episode 3 games");
|
||||
throw std::logic_error("ItemCreator cannot be created for Episode 3 games");
|
||||
case Version::DC_NTE:
|
||||
star_base_index = 0x124;
|
||||
num_units = 0x43;
|
||||
@@ -882,7 +880,7 @@ void ItemCreator::generate_unit_stars_tables() {
|
||||
num_units = 0x64;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
throw std::logic_error("invalid game version");
|
||||
}
|
||||
|
||||
for (auto& vec : this->unit_results_by_star_count) {
|
||||
@@ -936,7 +934,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
|
||||
rand_max += tables[x * stride + offset];
|
||||
}
|
||||
if (rand_max == 0) {
|
||||
throw runtime_error("weighted table is empty");
|
||||
throw std::runtime_error("weighted table is empty");
|
||||
}
|
||||
|
||||
uint32_t x = this->rand_int(rand_max);
|
||||
@@ -947,7 +945,7 @@ IntT ItemCreator::get_rand_from_weighted_tables(const IntT* tables, size_t offse
|
||||
}
|
||||
x -= table_value;
|
||||
}
|
||||
throw logic_error("selector was not less than rand_max");
|
||||
throw std::logic_error("selector was not less than rand_max");
|
||||
}
|
||||
|
||||
template <typename IntT, size_t X>
|
||||
@@ -960,8 +958,8 @@ IntT ItemCreator::get_rand_from_weighted_tables_2d_vertical(const parray<parray<
|
||||
return ItemCreator::get_rand_from_weighted_tables<IntT>(tables[0].data(), offset, Y, X);
|
||||
}
|
||||
|
||||
vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
|
||||
vector<ItemData> shop;
|
||||
std::vector<ItemData> ItemCreator::generate_armor_shop_contents(Episode episode, size_t player_level) {
|
||||
std::vector<ItemData> shop;
|
||||
this->generate_armor_shop_armors(shop, episode, player_level);
|
||||
this->generate_armor_shop_shields(shop, player_level);
|
||||
this->generate_armor_shop_units(shop, player_level);
|
||||
@@ -984,7 +982,7 @@ size_t ItemCreator::get_table_index_for_armor_shop(
|
||||
}
|
||||
|
||||
bool ItemCreator::shop_does_not_contain_duplicate_armor(
|
||||
const vector<ItemData>& shop, const ItemData& item) {
|
||||
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||
for (const auto& shop_item : shop) {
|
||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||
(shop_item.data1[1] == item.data1[1]) &&
|
||||
@@ -997,7 +995,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_armor(
|
||||
}
|
||||
|
||||
bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
|
||||
const vector<ItemData>& shop, const ItemData& item) {
|
||||
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||
for (const auto& shop_item : shop) {
|
||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||
(shop_item.data1[1] == item.data1[1]) &&
|
||||
@@ -1010,7 +1008,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_tech_disk(
|
||||
}
|
||||
|
||||
bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
||||
const vector<ItemData>& shop, const ItemData& item) {
|
||||
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||
size_t similar_items = 0;
|
||||
for (const auto& shop_item : shop) {
|
||||
// Disallow exact matches
|
||||
@@ -1029,7 +1027,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_or_too_many_similar_weapons(
|
||||
}
|
||||
|
||||
bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
||||
const vector<ItemData>& shop, const ItemData& item) {
|
||||
const std::vector<ItemData>& shop, const ItemData& item) {
|
||||
for (const auto& shop_item : shop) {
|
||||
if ((shop_item.data1[0] == item.data1[0]) &&
|
||||
(shop_item.data1[1] == item.data1[1]) &&
|
||||
@@ -1040,7 +1038,7 @@ bool ItemCreator::shop_does_not_contain_duplicate_item_by_data1_0_1_2(
|
||||
return true;
|
||||
}
|
||||
|
||||
void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode episode, size_t player_level) {
|
||||
void ItemCreator::generate_armor_shop_armors(std::vector<ItemData>& shop, Episode episode, size_t player_level) {
|
||||
size_t num_items;
|
||||
if (player_level < 11) {
|
||||
num_items = 4;
|
||||
@@ -1084,7 +1082,7 @@ void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, Episode epi
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t player_level) {
|
||||
void ItemCreator::generate_armor_shop_shields(std::vector<ItemData>& shop, size_t player_level) {
|
||||
size_t num_items;
|
||||
if (player_level < 11) {
|
||||
num_items = 4;
|
||||
@@ -1127,7 +1125,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t player_level) {
|
||||
void ItemCreator::generate_armor_shop_units(std::vector<ItemData>& shop, size_t player_level) {
|
||||
size_t num_items;
|
||||
if (player_level < 11) {
|
||||
return; // num_items = 0
|
||||
@@ -1161,8 +1159,8 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
|
||||
}
|
||||
}
|
||||
|
||||
vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
|
||||
vector<ItemData> shop;
|
||||
std::vector<ItemData> ItemCreator::generate_tool_shop_contents(size_t player_level) {
|
||||
std::vector<ItemData> shop;
|
||||
this->generate_common_tool_shop_recovery_items(shop, player_level);
|
||||
this->generate_rare_tool_shop_recovery_items(shop, player_level);
|
||||
this->generate_tool_shop_tech_disks(shop, player_level);
|
||||
@@ -1184,11 +1182,11 @@ size_t ItemCreator::get_table_index_for_tool_shop(size_t player_level) {
|
||||
}
|
||||
}
|
||||
|
||||
static const vector<pair<uint8_t, uint8_t>> tool_item_defs{
|
||||
static const std::vector<std::pair<uint8_t, uint8_t>> tool_item_defs{
|
||||
{0x00, 0x00}, {0x00, 0x01}, {0x00, 0x02}, {0x01, 0x00}, {0x01, 0x01}, {0x01, 0x02}, {0x06, 0x00}, {0x06, 0x01},
|
||||
{0x03, 0x00}, {0x04, 0x00}, {0x05, 0x00}, {0x07, 0x00}, {0x08, 0x00}, {0x09, 0x00}, {0x0A, 0x00}, {0xFF, 0xFF}};
|
||||
|
||||
void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
|
||||
void ItemCreator::generate_common_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
|
||||
size_t table_index;
|
||||
if (player_level < 11) {
|
||||
table_index = 0;
|
||||
@@ -1218,7 +1216,7 @@ void ItemCreator::generate_common_tool_shop_recovery_items(vector<ItemData>& sho
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop, size_t player_level) {
|
||||
void ItemCreator::generate_rare_tool_shop_recovery_items(std::vector<ItemData>& shop, size_t player_level) {
|
||||
if (player_level < 11) {
|
||||
return;
|
||||
}
|
||||
@@ -1256,7 +1254,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(vector<ItemData>& shop,
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t player_level) {
|
||||
void ItemCreator::generate_tool_shop_tech_disks(std::vector<ItemData>& shop, size_t player_level) {
|
||||
size_t num_items;
|
||||
if (player_level < 11) {
|
||||
num_items = 4;
|
||||
@@ -1278,7 +1276,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t p
|
||||
}
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
static const array<uint8_t, 0x13> tech_num_map = {
|
||||
static const std::array<uint8_t, 0x13> tech_num_map = {
|
||||
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
||||
0x0E, 0x11, 0x02, 0x05, 0x08, 0x09, 0x12};
|
||||
|
||||
@@ -1301,7 +1299,7 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
|
||||
size_t table_index = this->get_table_index_for_tool_shop(player_level);
|
||||
auto table = this->tool_random_set->get_tech_disk_level_table(table_index);
|
||||
if (tech_num_index >= table.second) {
|
||||
throw runtime_error("technique number out of range");
|
||||
throw std::runtime_error("technique number out of range");
|
||||
}
|
||||
const auto& e = table.first[tech_num_index];
|
||||
|
||||
@@ -1310,23 +1308,23 @@ void ItemCreator::choose_tech_disk_level_for_tool_shop(ItemData& item, size_t pl
|
||||
item.data1[2] = 0;
|
||||
break;
|
||||
case ToolRandomSet::TechDiskLevelEntry::Mode::PLAYER_LEVEL_DIVISOR:
|
||||
item.data1[2] = clamp<ssize_t>(
|
||||
(min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
|
||||
item.data1[2] = std::clamp<ssize_t>(
|
||||
(std::min<size_t>(player_level, 99) / e.player_level_divisor_or_min_level) - 1, 0, 14);
|
||||
break;
|
||||
case ToolRandomSet::TechDiskLevelEntry::Mode::RANDOM_IN_RANGE: {
|
||||
// Note: This logic does not give a uniform distribution - if the minimumlevel is not zero (level 1), then the
|
||||
// minimum level is more likely than all the other levels. This behavior matches the client's logic, though it's
|
||||
// unclear if this nonuniformity was intentional.
|
||||
int16_t min_level = max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
|
||||
item.data1[2] = clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
|
||||
int16_t min_level = std::max<int16_t>(e.player_level_divisor_or_min_level - 1, 0);
|
||||
item.data1[2] = std::clamp<int16_t>(this->rand_int(e.max_level), min_level, 14);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid tech disk level mode");
|
||||
throw std::logic_error("invalid tech disk level mode");
|
||||
}
|
||||
}
|
||||
|
||||
vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
|
||||
std::vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level) {
|
||||
size_t num_items;
|
||||
if (player_level < 11) {
|
||||
num_items = 10;
|
||||
@@ -1377,13 +1375,13 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
||||
}
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
vector<ItemData> shop;
|
||||
std::vector<ItemData> shop;
|
||||
while (shop.size() < num_items) {
|
||||
ItemData item;
|
||||
|
||||
uint8_t which = pt.pop();
|
||||
if (which == 0x39) {
|
||||
static const vector<pair<uint8_t, uint8_t>> defs{
|
||||
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
|
||||
{0x28, 0x00}, {0x2A, 0x00}, {0x2B, 0x00}, {0x35, 0x00}, {0x52, 0x00}, {0x48, 0x00}, {0x64, 0x00},
|
||||
{0x59, 0x00}, {0x8A, 0x00}, {0x99, 0x00}};
|
||||
const auto& def = defs.at(this->section_id);
|
||||
@@ -1392,7 +1390,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
||||
item.data1[2] = def.second;
|
||||
|
||||
} else if (which == 0x3A) {
|
||||
static const vector<pair<uint8_t, uint8_t>> defs{
|
||||
static const std::vector<std::pair<uint8_t, uint8_t>> defs{
|
||||
{0x99, 0x00}, {0x64, 0x00}, {0x8A, 0x00}, {0x28, 0x00}, {0x59, 0x00}, {0x2B, 0x00}, {0x52, 0x00},
|
||||
{0x2A, 0x00}, {0x48, 0x00}, {0x35, 0x00}};
|
||||
const auto& def = defs.at(this->section_id);
|
||||
@@ -1401,7 +1399,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
||||
item.data1[2] = def.second;
|
||||
|
||||
} else {
|
||||
static const vector<pair<uint8_t, uint8_t>> defs({
|
||||
static const std::vector<std::pair<uint8_t, uint8_t>> defs({
|
||||
/* 00 */ {0x01, 0x00},
|
||||
/* 01 */ {0x01, 0x01},
|
||||
/* 02 */ {0x01, 0x02},
|
||||
@@ -1520,7 +1518,7 @@ void ItemCreator::generate_weapon_shop_item_grind(ItemData& item, size_t player_
|
||||
: this->weapon_random_set->get_standard_grind_range(table_index);
|
||||
|
||||
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
||||
item.data1[3] = clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
|
||||
item.data1[3] = std::clamp<uint8_t>(this->rand_int(range->max + 1), range->min, weapon_def.max_grind);
|
||||
}
|
||||
|
||||
void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t player_level) {
|
||||
@@ -1566,11 +1564,11 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
|
||||
item.data1[4] = this->choose_weapon_special(1);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid special mode");
|
||||
throw std::runtime_error("invalid special mode");
|
||||
}
|
||||
}
|
||||
|
||||
static const array<int8_t, 20> bonus_values = {
|
||||
static const std::array<int8_t, 20> bonus_values = {
|
||||
-50, -45, -40, -35, -30, -25, -20, -15, -10, -5, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
|
||||
|
||||
void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player_level) {
|
||||
@@ -1611,7 +1609,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1(ItemData& item, size_t player
|
||||
item.data1[7] = 0;
|
||||
} else {
|
||||
const auto* range = this->weapon_random_set->get_bonus_range(0, table_index);
|
||||
item.data1[7] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||
item.data1[7] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1655,7 +1653,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
|
||||
item.data1[9] = 0;
|
||||
} else {
|
||||
const auto* range = this->weapon_random_set->get_bonus_range(1, table_index);
|
||||
item.data1[9] = bonus_values.at(max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||
item.data1[9] = bonus_values.at(std::max<size_t>(this->rand_int(range->max + 1), range->min));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1707,7 +1705,7 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
|
||||
item.data2d = ((param5 >> 0x10) & 0xFFFF) * 10;
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid item class");
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
|
||||
return item;
|
||||
@@ -1715,10 +1713,10 @@ ItemData ItemCreator::base_item_for_specialized_box(uint32_t param4, uint32_t pa
|
||||
|
||||
ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
if (item.data1[0] != 0) {
|
||||
throw runtime_error("tekker deltas can only be applied to weapons");
|
||||
throw std::runtime_error("tekker deltas can only be applied to weapons");
|
||||
}
|
||||
|
||||
static const array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
||||
static const std::array<int8_t, 11> delta_table = {-10, -5, -3, -2, -1, 0, 1, 2, 3, 5, 10};
|
||||
|
||||
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
|
||||
ssize_t luck = 0;
|
||||
@@ -1751,7 +1749,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
this->log.info_f("(Special) Delta canceled because it would change special category");
|
||||
}
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// Invalid special number passed to get_special; just ignore it
|
||||
}
|
||||
luck += this->tekker_adjustment_set->get_luck_for_special_upgrade(delta_index);
|
||||
@@ -1766,7 +1764,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info_f("(Grind) Delta index {}, delta {}", delta_index, delta);
|
||||
int16_t new_grind = static_cast<int16_t>(item.data1[3]) + static_cast<int16_t>(delta);
|
||||
item.data1[3] = clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
|
||||
item.data1[3] = std::clamp<int16_t>(new_grind, 0, weapon_def.max_grind);
|
||||
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
|
||||
this->log.info_f("(Grind) Luck is now {}", luck);
|
||||
} else {
|
||||
@@ -1785,7 +1783,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
// to check here if each bonus is actually present.
|
||||
for (size_t z = 6; z <= 10; z += 2) {
|
||||
if (item.data1[z] >= 1 && item.data1[z] <= 5) {
|
||||
item.data1[z + 1] = min<int8_t>(item.data1[z + 1] + delta, 100);
|
||||
item.data1[z + 1] = std::min<int8_t>(item.data1[z + 1] + delta, 100);
|
||||
}
|
||||
}
|
||||
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);
|
||||
|
||||
+30
-32
@@ -6,11 +6,9 @@
|
||||
#include "ItemParameterTable.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2({10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
|
||||
const vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4(
|
||||
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_DC_NTE({10});
|
||||
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V1_V2({10, 10, 1, 10, 10, 10, 10, 10, 10, 1});
|
||||
const std::vector<uint8_t> ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4(
|
||||
{10, 10, 1, 10, 10, 10, 10, 10, 10, 1, 1, 1, 1, 1, 1, 1, 99, 1});
|
||||
|
||||
const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_DC_NTE(
|
||||
@@ -21,7 +19,7 @@ const ItemData::StackLimits ItemData::StackLimits::DEFAULT_STACK_LIMITS_V3_V4(
|
||||
Version::GC_V3, ItemData::StackLimits::DEFAULT_TOOL_LIMITS_V3_V4, 999999);
|
||||
|
||||
ItemData::StackLimits::StackLimits(
|
||||
Version version, const vector<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size)
|
||||
Version version, const std::vector<uint8_t>& max_tool_stack_sizes_by_data1_1, uint32_t max_meseta_stack_size)
|
||||
: version(version),
|
||||
max_tool_stack_sizes_by_data1_1(max_tool_stack_sizes_by_data1_1),
|
||||
max_meseta_stack_size(max_meseta_stack_size) {}
|
||||
@@ -40,7 +38,7 @@ uint8_t ItemData::StackLimits::get(uint8_t data1_0, uint8_t data1_1) const {
|
||||
}
|
||||
if (data1_0 == 3) {
|
||||
const auto& vec = this->max_tool_stack_sizes_by_data1_1;
|
||||
return vec.at(min<size_t>(data1_1, vec.size() - 1));
|
||||
return vec.at(std::min<size_t>(data1_1, vec.size() - 1));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -181,7 +179,7 @@ bool ItemData::is_wrapped(const StackLimits& limits) const {
|
||||
case 4:
|
||||
return false;
|
||||
default:
|
||||
throw runtime_error("invalid item data");
|
||||
throw std::runtime_error("invalid item data");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +204,7 @@ void ItemData::wrap(const StackLimits& limits, uint8_t present_color) {
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid item data");
|
||||
throw std::runtime_error("invalid item data");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +225,7 @@ void ItemData::unwrap(const StackLimits& limits) {
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid item data");
|
||||
throw std::runtime_error("invalid item data");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,7 +307,7 @@ uint16_t ItemData::compute_mag_strength_flags() const {
|
||||
ret |= 0x020;
|
||||
}
|
||||
|
||||
uint16_t highest = max<uint16_t>(dex, max<uint16_t>(pow, mind));
|
||||
uint16_t highest = std::max<uint16_t>(dex, std::max<uint16_t>(pow, mind));
|
||||
if ((pow == highest) + (dex == highest) + (mind == highest) > 1) {
|
||||
ret |= 0x100;
|
||||
}
|
||||
@@ -343,10 +341,10 @@ uint8_t ItemData::mag_photon_blast_for_slot(uint8_t slot) const {
|
||||
left_pb_num--;
|
||||
}
|
||||
}
|
||||
throw logic_error("failed to find unused photon blast number");
|
||||
throw std::logic_error("failed to find unused photon blast number");
|
||||
|
||||
} else {
|
||||
throw logic_error("invalid slot index");
|
||||
throw std::logic_error("invalid slot index");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +385,7 @@ void ItemData::add_mag_photon_blast(uint8_t pb_num) {
|
||||
pb_num--;
|
||||
}
|
||||
if (pb_num >= 4) {
|
||||
throw runtime_error("left photon blast number is too high");
|
||||
throw std::runtime_error("left photon blast number is too high");
|
||||
}
|
||||
pb_nums |= (pb_num << 6);
|
||||
flags |= 4;
|
||||
@@ -471,11 +469,11 @@ void ItemData::decode_for_version(Version from_version) {
|
||||
break;
|
||||
|
||||
default:
|
||||
throw runtime_error("invalid item class");
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
}
|
||||
|
||||
void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParameterTable> item_parameter_table) {
|
||||
void ItemData::encode_for_version(Version to_version, std::shared_ptr<const ItemParameterTable> item_parameter_table) {
|
||||
bool should_encode_v2_data = item_parameter_table &&
|
||||
(is_v1(to_version) || is_v2(to_version)) &&
|
||||
(to_version != Version::GC_NTE) &&
|
||||
@@ -501,7 +499,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
|
||||
break;
|
||||
|
||||
case 0x01: {
|
||||
static const array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
|
||||
static const std::array<uint8_t, 4> armor_limits = {0x00, 0x29, 0x27, 0x44};
|
||||
if (should_encode_v2_data && (this->data1[2] >= armor_limits[this->data1[1]])) {
|
||||
this->data1[3] = this->data1[2];
|
||||
this->data1[2] = 0x00;
|
||||
@@ -554,7 +552,7 @@ void ItemData::encode_for_version(Version to_version, shared_ptr<const ItemParam
|
||||
break;
|
||||
|
||||
default:
|
||||
throw runtime_error("invalid item class");
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,7 +656,7 @@ bool ItemData::has_bonuses() const {
|
||||
case 3:
|
||||
return (this->get_unit_bonus() > 0);
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
}
|
||||
case 2:
|
||||
if (this->data1[1] < 0x23) {
|
||||
@@ -670,7 +668,7 @@ bool ItemData::has_bonuses() const {
|
||||
case 4:
|
||||
return false;
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +701,7 @@ EquipSlot ItemData::default_equip_slot() const {
|
||||
case 0x02:
|
||||
return EquipSlot::MAG;
|
||||
}
|
||||
throw runtime_error("item cannot be equipped");
|
||||
throw std::runtime_error("item cannot be equipped");
|
||||
}
|
||||
|
||||
bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
||||
@@ -722,7 +720,7 @@ bool ItemData::can_be_equipped_in_slot(EquipSlot slot) const {
|
||||
case EquipSlot::WEAPON:
|
||||
return (this->data1[0] == 0x00);
|
||||
default:
|
||||
throw runtime_error("invalid equip slot");
|
||||
throw std::runtime_error("invalid equip slot");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,23 +746,23 @@ bool ItemData::compare_for_sort(const ItemData& a, const ItemData& b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemData ItemData::from_data(const string& data) {
|
||||
ItemData ItemData::from_data(const std::string& data) {
|
||||
if (data.size() < 2) {
|
||||
throw runtime_error("data is too short");
|
||||
throw std::runtime_error("data is too short");
|
||||
}
|
||||
if (data.size() > 0x10) {
|
||||
throw runtime_error("data is too long");
|
||||
throw std::runtime_error("data is too long");
|
||||
}
|
||||
|
||||
ItemData ret;
|
||||
for (size_t z = 0; z < min<size_t>(data.size(), 12); z++) {
|
||||
for (size_t z = 0; z < std::min<size_t>(data.size(), 12); z++) {
|
||||
ret.data1[z] = data[z];
|
||||
}
|
||||
for (size_t z = 12; z < min<size_t>(data.size(), 16); z++) {
|
||||
for (size_t z = 12; z < std::min<size_t>(data.size(), 16); z++) {
|
||||
ret.data2[z - 12] = data[z];
|
||||
}
|
||||
if (ret.data1[0] > 4) {
|
||||
throw runtime_error("invalid item class");
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -772,7 +770,7 @@ ItemData ItemData::from_data(const string& data) {
|
||||
ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t primary_identifier) {
|
||||
ItemData ret;
|
||||
if (primary_identifier > 0x04000000) {
|
||||
throw runtime_error("invalid item class");
|
||||
throw std::runtime_error("invalid item class");
|
||||
}
|
||||
ret.data1[0] = (primary_identifier >> 24) & 0xFF;
|
||||
ret.data1[1] = (primary_identifier >> 16) & 0xFF;
|
||||
@@ -786,16 +784,16 @@ ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t p
|
||||
return ret;
|
||||
}
|
||||
|
||||
string ItemData::hex() const {
|
||||
std::string ItemData::hex() const {
|
||||
return std::format("{:08X} {:08X} {:08X} ({:08X}) {:08X}",
|
||||
this->data1db[0], this->data1db[1], this->data1db[2], this->id, this->data2db);
|
||||
}
|
||||
|
||||
string ItemData::short_hex() const {
|
||||
std::string ItemData::short_hex() const {
|
||||
auto ret = std::format("{:08X}{:08X}{:08X}{:08X}",
|
||||
this->data1db[0], this->data1db[1], this->data1db[2], this->data2db);
|
||||
size_t offset = ret.find_last_not_of('0');
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
offset += (offset & 1) ? 1 : 2;
|
||||
offset = std::max<size_t>(offset, 6);
|
||||
if (offset < ret.size()) {
|
||||
|
||||
+59
-62
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
ItemNameIndex::ItemNameIndex(
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const ItemData::StackLimits> limits,
|
||||
@@ -19,17 +17,17 @@ ItemNameIndex::ItemNameIndex(
|
||||
continue;
|
||||
}
|
||||
|
||||
const string* name = nullptr;
|
||||
const std::string* name = nullptr;
|
||||
bool is_es_weapon = false;
|
||||
try {
|
||||
ItemData item = ItemData::from_primary_identifier(*this->limits, primary_identifier);
|
||||
is_es_weapon = item.is_s_rank_weapon();
|
||||
name = &name_coll.at(item_parameter_table->get_item_id(item));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (name) {
|
||||
auto meta = make_shared<ItemMetadata>();
|
||||
auto meta = std::make_shared<ItemMetadata>();
|
||||
meta->primary_identifier = primary_identifier;
|
||||
meta->name = *name;
|
||||
this->primary_identifier_index.emplace(meta->primary_identifier, meta);
|
||||
@@ -44,7 +42,7 @@ ItemNameIndex::ItemNameIndex(
|
||||
static std::string s_rank_name_characters("\0ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", 0x20);
|
||||
|
||||
// clang-format off
|
||||
static const array<const char*, 0x29> name_for_weapon_special = {
|
||||
static const std::array<const char*, 0x29> name_for_weapon_special = {
|
||||
nullptr,
|
||||
"Draw", // Type: 0001, amount: 0005
|
||||
"Drain", // Type: 0001, amount: 0009
|
||||
@@ -89,7 +87,7 @@ static const array<const char*, 0x29> name_for_weapon_special = {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
const array<const char*, 0x11> name_for_s_rank_special = {
|
||||
const std::array<const char*, 0x11> name_for_s_rank_special = {
|
||||
nullptr,
|
||||
"Jellen",
|
||||
"Zalure",
|
||||
@@ -117,7 +115,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
return std::format("{}{} Meseta", include_color_escapes ? "$C7" : "", item.data2d);
|
||||
}
|
||||
|
||||
vector<string> ret_tokens;
|
||||
std::vector<std::string> ret_tokens;
|
||||
|
||||
// For weapons, specials appear before the weapon name
|
||||
bool is_unidentified = false;
|
||||
@@ -135,7 +133,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
if (special_id) {
|
||||
try {
|
||||
ret_tokens.emplace_back(name_for_weapon_special.at(special_id));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
ret_tokens.emplace_back(std::format("!SP:{:02X}", special_id));
|
||||
}
|
||||
}
|
||||
@@ -143,7 +141,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
if (!name_only && (item.data1[0] == 0x00) && (item.data1[2] != 0x00) && item.is_s_rank_weapon()) {
|
||||
try {
|
||||
ret_tokens.emplace_back(name_for_s_rank_special.at(item.data1[2]));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
ret_tokens.emplace_back(std::format("!SSP:{:02X}", item.data1[2]));
|
||||
}
|
||||
}
|
||||
@@ -160,11 +158,11 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
// Add the item name
|
||||
uint32_t primary_identifier = item.primary_identifier();
|
||||
if ((primary_identifier & 0xFFFF0000) == 0x03020000) {
|
||||
string technique_name;
|
||||
std::string technique_name;
|
||||
try {
|
||||
technique_name = tech_id_to_name.at(item.data1[4]);
|
||||
technique_name[0] = toupper(technique_name[0]);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
technique_name = std::format("!TD:{:02X}", item.data1[4]);
|
||||
}
|
||||
// Hide the level for Reverser and Ryuker, unless the level isn't 1
|
||||
@@ -177,7 +175,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
try {
|
||||
auto meta = this->primary_identifier_index.at(primary_identifier);
|
||||
ret_tokens.emplace_back(meta->name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
ret_tokens.emplace_back(std::format("!ID:{:08X}", primary_identifier));
|
||||
}
|
||||
}
|
||||
@@ -208,7 +206,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
static_cast<uint8_t>(be_data1w5 & 0x1F),
|
||||
};
|
||||
|
||||
string name;
|
||||
std::string name;
|
||||
for (size_t x = 0; x < 8; x++) {
|
||||
char ch = s_rank_name_characters.at(char_indexes[x]);
|
||||
if (ch == 0) {
|
||||
@@ -306,7 +304,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
uint16_t pow = item.data1w[3];
|
||||
uint16_t dex = item.data1w[4];
|
||||
uint16_t mind = item.data1w[5];
|
||||
auto format_stat = +[](uint16_t stat) -> string {
|
||||
auto format_stat = +[](uint16_t stat) -> std::string {
|
||||
uint16_t level = stat / 100;
|
||||
uint8_t partial = stat % 100;
|
||||
if (partial == 0) {
|
||||
@@ -323,7 +321,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
|
||||
uint8_t flags = item.data2[2];
|
||||
if (flags & 7) {
|
||||
static const vector<const char*> pb_shortnames = {"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
||||
static const std::vector<const char*> pb_shortnames = {"F", "E", "G", "P", "L", "M&Y", "MG", "GR"};
|
||||
|
||||
const char* pb_names[3] = {nullptr, nullptr, nullptr};
|
||||
uint8_t left_pb = item.mag_photon_blast_for_slot(2);
|
||||
@@ -339,7 +337,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
pb_names[2] = pb_shortnames[right_pb];
|
||||
}
|
||||
|
||||
string token = "PB:";
|
||||
std::string token = "PB:";
|
||||
for (size_t x = 0; x < 3; x++) {
|
||||
if (pb_names[x] == nullptr) {
|
||||
continue;
|
||||
@@ -354,7 +352,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
|
||||
try {
|
||||
ret_tokens.emplace_back(std::format("({})", name_for_mag_color.at(item.data2[3])));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
ret_tokens.emplace_back(std::format("(!CL:{:02X})", item.data2[3]));
|
||||
}
|
||||
|
||||
@@ -365,7 +363,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, uint8_t flags) co
|
||||
}
|
||||
}
|
||||
|
||||
string ret = phosg::join(ret_tokens, " ");
|
||||
std::string ret = phosg::join(ret_tokens, " ");
|
||||
if (include_color_escapes) {
|
||||
if (is_unidentified) {
|
||||
return "$C3" + ret;
|
||||
@@ -387,18 +385,18 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
|
||||
ItemData ret;
|
||||
try {
|
||||
ret = this->parse_item_description_phase(desc, false);
|
||||
} catch (const exception& e1) {
|
||||
} catch (const std::exception& e1) {
|
||||
try {
|
||||
ret = this->parse_item_description_phase(desc, true);
|
||||
} catch (const exception& e2) {
|
||||
} catch (const std::exception& e2) {
|
||||
try {
|
||||
ret = ItemData::from_data(phosg::parse_data_string(desc));
|
||||
} catch (const exception& ed) {
|
||||
} catch (const std::exception& ed) {
|
||||
if (strcmp(e1.what(), e2.what())) {
|
||||
throw runtime_error(std::format("cannot parse item description \"{}\" (as text 1: {}) (as text 2: {}) (as data: {})",
|
||||
throw std::runtime_error(std::format("cannot parse item description \"{}\" (as text 1: {}) (as text 2: {}) (as data: {})",
|
||||
desc, e1.what(), e2.what(), ed.what()));
|
||||
} else {
|
||||
throw runtime_error(std::format("cannot parse item description \"{}\" (as text: {}) (as data: {})",
|
||||
throw std::runtime_error(std::format("cannot parse item description \"{}\" (as text: {}) (as data: {})",
|
||||
desc, e1.what(), ed.what()));
|
||||
}
|
||||
}
|
||||
@@ -414,17 +412,17 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
ret.id = 0xFFFFFFFF;
|
||||
ret.data2d = 0;
|
||||
|
||||
string desc = phosg::tolower(description);
|
||||
std::string desc = phosg::tolower(description);
|
||||
if (desc.ends_with(" meseta")) {
|
||||
ret.data1[0] = 0x04;
|
||||
ret.data2d = stol(desc, nullptr, 10);
|
||||
ret.data2d = std::stol(desc, nullptr, 10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (desc.starts_with("es ")) {
|
||||
auto parse_name = [&](const std::string& token) -> void {
|
||||
if (token.size() > 8) {
|
||||
throw runtime_error("s-rank name too long");
|
||||
throw std::runtime_error("s-rank name too long");
|
||||
}
|
||||
|
||||
uint8_t char_indexes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
@@ -432,7 +430,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
char ch = toupper(token[z]);
|
||||
size_t pos = s_rank_name_characters.find(ch);
|
||||
if (pos == std::string::npos) {
|
||||
throw runtime_error(std::format("s-rank name contains invalid character {:02X} ({})", ch, ch));
|
||||
throw std::runtime_error(std::format("s-rank name contains invalid character {:02X} ({})", ch, ch));
|
||||
}
|
||||
char_indexes[z] = pos;
|
||||
}
|
||||
@@ -538,10 +536,10 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
ret.data1[4] = tech;
|
||||
} else {
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("invalid tech disk format");
|
||||
throw std::runtime_error("invalid tech disk format");
|
||||
}
|
||||
if (!tokens[1].starts_with("lv.")) {
|
||||
throw runtime_error("invalid tech disk level");
|
||||
throw std::runtime_error("invalid tech disk level");
|
||||
}
|
||||
uint8_t tech = technique_for_name(tokens[0]);
|
||||
uint8_t level = stoul(tokens[1].substr(3), nullptr, 10) - 1;
|
||||
@@ -574,7 +572,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
if (!name_for_weapon_special[z]) {
|
||||
continue;
|
||||
}
|
||||
string prefix = phosg::tolower(name_for_weapon_special[z]);
|
||||
std::string prefix = phosg::tolower(name_for_weapon_special[z]);
|
||||
prefix += ' ';
|
||||
if (desc.starts_with(prefix)) {
|
||||
weapon_special = z;
|
||||
@@ -592,14 +590,14 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
if (name_it != this->name_index.end() && desc.starts_with(name_it->first)) {
|
||||
break;
|
||||
} else if (name_it == this->name_index.begin()) {
|
||||
throw runtime_error("no such item");
|
||||
throw std::runtime_error("no such item");
|
||||
} else {
|
||||
name_it--;
|
||||
lookback++;
|
||||
}
|
||||
}
|
||||
if (lookback >= 4) {
|
||||
throw runtime_error("item not found: " + desc);
|
||||
throw std::runtime_error("item not found: " + desc);
|
||||
}
|
||||
|
||||
desc = desc.substr(name_it->first.size());
|
||||
@@ -637,7 +635,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
} else {
|
||||
auto p_tokens = phosg::split(token, '/');
|
||||
if (p_tokens.size() > 5) {
|
||||
throw runtime_error("invalid bonuses token");
|
||||
throw std::runtime_error("invalid bonuses token");
|
||||
}
|
||||
uint8_t max_bonuses = this->item_parameter_table->is_unsealable_item(ret) ? 2 : 3;
|
||||
uint8_t bonus_index = 0;
|
||||
@@ -647,7 +645,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
continue;
|
||||
}
|
||||
if (bonus_index >= max_bonuses) {
|
||||
throw runtime_error("weapon has too many bonuses");
|
||||
throw std::runtime_error("weapon has too many bonuses");
|
||||
}
|
||||
ret.data1[6 + (2 * bonus_index)] = z + 1;
|
||||
ret.data1[7 + (2 * bonus_index)] = static_cast<uint8_t>(bonus_value);
|
||||
@@ -662,7 +660,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
|
||||
} else if (ret.data1[0] == 0x01) {
|
||||
if (ret.data1[1] == 0x03) { // Unit
|
||||
static const unordered_map<string, uint16_t> modifiers({
|
||||
static const std::unordered_map<std::string, uint16_t> modifiers({
|
||||
{"--", 0xFFFC},
|
||||
{"-", 0xFFFE},
|
||||
{"", 0x0000},
|
||||
@@ -698,7 +696,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
} else if (!token.starts_with("+")) {
|
||||
throw runtime_error("invalid armor/shield modifier");
|
||||
throw std::runtime_error("invalid armor/shield modifier");
|
||||
}
|
||||
if (token.ends_with("def")) {
|
||||
ret.data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||
@@ -721,9 +719,9 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
} else if (token.starts_with("pb:")) { // Photon blasts
|
||||
auto pb_tokens = phosg::split(token.substr(3), ',');
|
||||
if (pb_tokens.size() > 3) {
|
||||
throw runtime_error("too many photon blasts specified");
|
||||
throw std::runtime_error("too many photon blasts specified");
|
||||
}
|
||||
static const unordered_map<string, uint8_t> name_to_pb_num(
|
||||
static const std::unordered_map<std::string, uint8_t> name_to_pb_num(
|
||||
{{"f", 0}, {"e", 1}, {"g", 2}, {"p", 3}, {"l", 4}, {"m", 5}, {"my", 5}, {"m&y", 5}});
|
||||
for (const auto& pb_token : pb_tokens) {
|
||||
ret.add_mag_photon_blast(name_to_pb_num.at(pb_token));
|
||||
@@ -735,12 +733,12 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
} else if (!token.empty() && isdigit(token[0])) { // Stats
|
||||
auto s_tokens = phosg::split(token, '/');
|
||||
if (s_tokens.size() != 4) {
|
||||
throw runtime_error("incorrect stat count");
|
||||
throw std::runtime_error("incorrect stat count");
|
||||
}
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
auto n_tokens = phosg::split(s_tokens[z], '.');
|
||||
if (n_tokens.size() == 0 || n_tokens.size() > 2) {
|
||||
throw logic_error("incorrect stats argument format");
|
||||
throw std::logic_error("incorrect stats argument format");
|
||||
} else if ((n_tokens.size() == 1) || (n_tokens[1].size() == 0)) {
|
||||
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100;
|
||||
} else if (n_tokens[1].size() == 1) {
|
||||
@@ -748,7 +746,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
} else if (n_tokens[1].size() == 2) {
|
||||
ret.data1w[z + 2] = stoul(n_tokens[0], nullptr, 10) * 100 + stoul(n_tokens[1], nullptr, 10);
|
||||
} else {
|
||||
throw runtime_error("incorrect stat format");
|
||||
throw std::runtime_error("incorrect stat format");
|
||||
}
|
||||
}
|
||||
ret.data1[2] = ret.compute_mag_level();
|
||||
@@ -768,18 +766,18 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
ret.data1[5] = 1;
|
||||
}
|
||||
} else if (!desc.empty()) {
|
||||
throw runtime_error("item cannot be stacked");
|
||||
throw std::runtime_error("item cannot be stacked");
|
||||
}
|
||||
|
||||
if (is_wrapped) {
|
||||
if (ret.is_stackable(*this->limits)) {
|
||||
throw runtime_error("stackable items cannot be wrapped");
|
||||
throw std::runtime_error("stackable items cannot be wrapped");
|
||||
} else {
|
||||
ret.data1[3] |= 0x40;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw logic_error("invalid item class");
|
||||
throw std::logic_error("invalid item class");
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -793,7 +791,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes(); data1_1++) {
|
||||
uint8_t weapon_class = pmt->get_weapon_kind(data1_1);
|
||||
float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_weapons_in_class(data1_1);
|
||||
@@ -806,11 +804,11 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[0] = 0x00;
|
||||
item.data1[1] = data1_1;
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
std::string name = this->describe_item(item);
|
||||
|
||||
const auto& stat_boost = pmt->get_stat_boost(w.stat_boost_entry_index);
|
||||
|
||||
string tech_boost_str;
|
||||
std::string tech_boost_str;
|
||||
if (w.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
||||
const auto& tech_boost = pmt->get_tech_boost(w.tech_boost_entry_index);
|
||||
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
||||
@@ -874,7 +872,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB(S1:AMT1,S2:AMT2) TB(TN:FL:AMOUNT, ... ) FT A4 ST* ---DIVISOR--- NAME\n");
|
||||
for (size_t data1_1 = 1; data1_1 < 3; data1_1++) {
|
||||
float sale_divisor = pmt->get_sale_divisor(0x01, data1_1);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_armors_or_shields_in_class(data1_1);
|
||||
@@ -886,11 +884,11 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[0] = 0x01;
|
||||
item.data1[1] = data1_1;
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
std::string name = this->describe_item(item);
|
||||
|
||||
auto& stat_boost = pmt->get_stat_boost(a.stat_boost_entry_index);
|
||||
|
||||
string tech_boost_str;
|
||||
std::string tech_boost_str;
|
||||
if (a.tech_boost_entry_index < pmt->num_tech_boosts()) {
|
||||
const auto& tech_boost = pmt->get_tech_boost(a.tech_boost_entry_index);
|
||||
tech_boost_str = std::format("({:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g},{:02X}:{:02X}:{:g})",
|
||||
@@ -938,7 +936,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS STAT COUNT ST-MOD ST* ---DIVISOR--- NAME\n");
|
||||
{
|
||||
float sale_divisor = pmt->get_sale_divisor(0x01, 0x03);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_units();
|
||||
@@ -950,7 +948,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[0] = 0x01;
|
||||
item.data1[1] = 0x03;
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
std::string name = this->describe_item(item);
|
||||
|
||||
phosg::fwrite_fmt(stream, " 0103{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:6} {:2}* {} {}\n",
|
||||
data1_2,
|
||||
@@ -975,14 +973,14 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
const auto& m = pmt->get_mag(data1_1);
|
||||
|
||||
float sale_divisor = pmt->get_sale_divisor(0x02, data1_1);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
ItemData item;
|
||||
item.data1[0] = 0x02;
|
||||
item.data1[1] = data1_1;
|
||||
item.data1[2] = 0x00;
|
||||
string name = this->describe_item(item);
|
||||
std::string name = this->describe_item(item);
|
||||
|
||||
phosg::fwrite_fmt(stream, " 02{:02X}00 => {:08X} {:04X} {:04X} {:6} {:04X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:04X} {} {}\n",
|
||||
data1_1,
|
||||
@@ -1011,7 +1009,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
phosg::fwrite_fmt(stream, " CODE => ---ID--- TYPE SKIN POINTS COUNT TECH -COST- ITEMFLAG ---DIVISOR--- NAME\n");
|
||||
for (size_t data1_1 = 0; data1_1 < pmt->num_tool_classes(); data1_1++) {
|
||||
float sale_divisor = pmt->get_sale_divisor(0x03, data1_1);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
std::string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_tools_in_class(data1_1);
|
||||
@@ -1023,7 +1021,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[1] = data1_1;
|
||||
item.data1[(data1_1 == 0x02) ? 4 : 2] = data1_2;
|
||||
item.set_tool_item_amount(*this->limits, 1);
|
||||
string name = this->describe_item(item);
|
||||
std::string name = this->describe_item(item);
|
||||
|
||||
phosg::fwrite_fmt(stream, " 03{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:04X} {:6} {:08X} {} {}\n",
|
||||
data1_1,
|
||||
@@ -1078,7 +1076,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
if (index) {
|
||||
try {
|
||||
name = name_for_weapon_special.at(index);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}* {}\n", index, sp.type, sp.amount, stars, name);
|
||||
@@ -1191,7 +1189,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
static constexpr std::array<const char*, 0x10> stat_names{
|
||||
"ATP+", "ATA+", "EVP+", "DFP+", "MST+", "HP+", "LCK+", "ALL+",
|
||||
"ATP-", "ATA-", "EVP-", "DFP-", "MST-", "HP-", "LCK-", "ALL-"};
|
||||
string s;
|
||||
std::string s;
|
||||
if (sb.stat1 > 0x10) {
|
||||
s = std::format("[{:02X}:{:04X}]", sb.stat1, sb.amount1);
|
||||
} else if (sb.stat1 > 0) {
|
||||
@@ -1244,7 +1242,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
phosg::fwrite_fmt(stream, " ## => BOOSTS\n");
|
||||
for (size_t z = 0; z < pmt->num_tech_boosts(); z++) {
|
||||
const auto& tb = pmt->get_tech_boost(z);
|
||||
string s;
|
||||
std::string s;
|
||||
if (tb.amount1) {
|
||||
s += std::format("{:02X}:{:02X}:{:g}", tb.tech_num1, tb.flags1, tb.amount1);
|
||||
}
|
||||
@@ -1269,8 +1267,7 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[0] = item_code >> 16;
|
||||
item.data1[1] = item_code >> 8;
|
||||
item.data1[2] = item_code;
|
||||
string name = this->describe_item(item);
|
||||
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, name);
|
||||
phosg::fwrite_fmt(stream, " {:06X} {}\n", item_code, this->describe_item(item));
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, "RANGED SPECIALS\n");
|
||||
|
||||
+42
-44
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
/* General notes on the ItemPMT formats:
|
||||
*
|
||||
* Sega apparently serialized the fields in this order, so we do the same in BinaryItemParameterTableT::serialize.
|
||||
@@ -91,7 +89,7 @@ ServerDropMode phosg::enum_for_name<ServerDropMode>(const char* name) {
|
||||
} else if (!strcmp(name, "SERVER_DUPLICATE")) {
|
||||
return ServerDropMode::SERVER_DUPLICATE;
|
||||
} else {
|
||||
throw runtime_error("invalid drop mode");
|
||||
throw std::runtime_error("invalid drop mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +107,7 @@ const char* phosg::name_for_enum<ServerDropMode>(ServerDropMode value) {
|
||||
case ServerDropMode::SERVER_DUPLICATE:
|
||||
return "SERVER_DUPLICATE";
|
||||
default:
|
||||
throw runtime_error("invalid drop mode");
|
||||
throw std::runtime_error("invalid drop mode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1027,11 +1025,11 @@ public:
|
||||
case 3:
|
||||
return this->unit_sale_divisor;
|
||||
}
|
||||
throw runtime_error("invalid defensive item type");
|
||||
throw std::runtime_error("invalid defensive item type");
|
||||
case 2:
|
||||
return this->mag_sale_divisor;
|
||||
default:
|
||||
throw runtime_error("item type does not have a sale divisor");
|
||||
throw std::runtime_error("item type does not have a sale divisor");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2088,7 +2086,7 @@ struct HeaderV4 : HeaderV3V4Base<false> {
|
||||
// Reader implementation
|
||||
|
||||
std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() const {
|
||||
set<uint32_t> ret;
|
||||
std::set<uint32_t> ret;
|
||||
|
||||
auto find_items_1d = [&](uint64_t data1, size_t position) -> size_t {
|
||||
ItemData item(data1, 0);
|
||||
@@ -2096,7 +2094,7 @@ std::set<uint32_t> ItemParameterTable::compute_all_valid_primary_identifiers() c
|
||||
item.data1[position] = x;
|
||||
try {
|
||||
this->get_item_id(item);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return x;
|
||||
}
|
||||
ret.emplace(item.primary_identifier());
|
||||
@@ -2143,7 +2141,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
|
||||
case 3:
|
||||
return &this->get_unit(data1_2);
|
||||
default:
|
||||
throw runtime_error("invalid primary identifier");
|
||||
throw std::runtime_error("invalid primary identifier");
|
||||
}
|
||||
case 2:
|
||||
return &this->get_mag(data1_1);
|
||||
@@ -2152,7 +2150,7 @@ ItemParameterTable::definition_for_primary_identifier(uint32_t primary_identifie
|
||||
// 0302XXYY here
|
||||
return &this->get_tool(data1_1, data1_2);
|
||||
default:
|
||||
throw runtime_error("invalid primary identifier");
|
||||
throw std::runtime_error("invalid primary identifier");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2166,7 +2164,7 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
|
||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).id;
|
||||
}
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
case 2:
|
||||
return this->get_mag(item.data1[1]).id;
|
||||
case 3:
|
||||
@@ -2175,11 +2173,11 @@ uint32_t ItemParameterTable::get_item_id(const ItemData& item) const {
|
||||
} else {
|
||||
return this->get_tool(item.data1[1], item.data1[2]).id;
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
throw std::logic_error("this should be impossible");
|
||||
case 4:
|
||||
throw runtime_error("item is meseta and therefore has no definition");
|
||||
throw std::runtime_error("item is meseta and therefore has no definition");
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2193,7 +2191,7 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const {
|
||||
} else if ((item.data1[1] == 1) || (item.data1[1] == 2)) {
|
||||
return this->get_armor_or_shield(item.data1[1], item.data1[2]).team_points;
|
||||
}
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
case 2:
|
||||
return this->get_mag(item.data1[1]).team_points;
|
||||
case 3:
|
||||
@@ -2202,11 +2200,11 @@ uint32_t ItemParameterTable::get_item_team_points(const ItemData& item) const {
|
||||
} else {
|
||||
return this->get_tool(item.data1[1], item.data1[2]).team_points;
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
throw std::logic_error("this should be impossible");
|
||||
case 4:
|
||||
throw runtime_error("item is meseta and therefore has no definition");
|
||||
throw std::runtime_error("item is meseta and therefore has no definition");
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2249,7 +2247,7 @@ uint8_t ItemParameterTable::get_item_adjusted_stars(const ItemData& item, bool i
|
||||
}
|
||||
}
|
||||
}
|
||||
return min<uint8_t>(ret, 12);
|
||||
return std::min<uint8_t>(ret, 12);
|
||||
}
|
||||
|
||||
std::string ItemParameterTable::get_star_value_table() const {
|
||||
@@ -2288,7 +2286,7 @@ std::string ItemParameterTable::get_shield_stat_boost_index_table() const {
|
||||
bool ItemParameterTable::is_item_rare(const ItemData& item) const {
|
||||
try {
|
||||
return (this->get_item_base_stars(item) >= 9);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2317,8 +2315,8 @@ const std::vector<ItemParameterTable::ItemCombination>& ItemParameterTable::all_
|
||||
try {
|
||||
return this->item_combinations_index().at(item_code_to_u32(
|
||||
used_item.data1[0], used_item.data1[1], used_item.data1[2]));
|
||||
} catch (const out_of_range&) {
|
||||
static const vector<ItemCombination> ret;
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::vector<ItemCombination> ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -2332,7 +2330,7 @@ const ItemParameterTable::ItemCombination& ItemParameterTable::get_item_combinat
|
||||
return def;
|
||||
}
|
||||
}
|
||||
throw out_of_range("no item combination applies");
|
||||
throw std::out_of_range("no item combination applies");
|
||||
}
|
||||
|
||||
size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
@@ -2347,7 +2345,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
|
||||
float sale_divisor = this->get_sale_divisor(0, item.data1[1]);
|
||||
if (sale_divisor == 0.0) {
|
||||
throw runtime_error("item sale divisor is zero");
|
||||
throw std::runtime_error("item sale divisor is zero");
|
||||
}
|
||||
|
||||
const auto& def = this->get_weapon(item.data1[1], item.data1[2]);
|
||||
@@ -2380,7 +2378,7 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
|
||||
double sale_divisor = (double)this->get_sale_divisor(1, item.data1[1]);
|
||||
if (sale_divisor == 0.0) {
|
||||
throw runtime_error("item sale divisor is zero");
|
||||
throw std::runtime_error("item sale divisor is zero");
|
||||
}
|
||||
|
||||
int16_t def_bonus = item.get_armor_or_shield_defense_bonus();
|
||||
@@ -2406,9 +2404,9 @@ size_t ItemParameterTable::price_for_item(const ItemData& item) const {
|
||||
return item.data2d;
|
||||
|
||||
default:
|
||||
throw runtime_error("invalid item");
|
||||
throw std::runtime_error("invalid item");
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
throw std::logic_error("this should be impossible");
|
||||
}
|
||||
|
||||
template <
|
||||
@@ -2442,7 +2440,7 @@ public:
|
||||
const T& indirect_lookup_2d(size_t base_offset, size_t co_index, size_t item_index) const {
|
||||
const auto& co = this->r.pget<ArrayRefT<BE>>(base_offset + sizeof(ArrayRefT<BE>) * co_index);
|
||||
if (item_index >= (co.count + HasImplicitPlaceholders)) {
|
||||
throw out_of_range("2-D array index out of range");
|
||||
throw std::out_of_range("2-D array index out of range");
|
||||
}
|
||||
return this->r.pget<T>(co.offset + sizeof(T) * item_index);
|
||||
}
|
||||
@@ -2470,14 +2468,14 @@ public:
|
||||
|
||||
virtual size_t num_weapons_in_class(uint8_t data1_1) const {
|
||||
if (data1_1 >= this->num_weapon_classes()) {
|
||||
throw out_of_range("weapon ID out of range");
|
||||
throw std::out_of_range("weapon ID out of range");
|
||||
}
|
||||
return this->indirect_lookup_2d_count(this->root->weapon_table, data1_1);
|
||||
}
|
||||
|
||||
virtual const Weapon& get_weapon(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 >= this->num_weapon_classes()) {
|
||||
throw out_of_range("weapon ID out of range");
|
||||
throw std::out_of_range("weapon ID out of range");
|
||||
}
|
||||
uint16_t key = (data1_1 << 8) | data1_2;
|
||||
auto it = this->weapons.find(key);
|
||||
@@ -2490,14 +2488,14 @@ public:
|
||||
|
||||
virtual size_t num_armors_or_shields_in_class(uint8_t data1_1) const {
|
||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||
throw out_of_range("armor/shield class ID out of range");
|
||||
throw std::out_of_range("armor/shield class ID out of range");
|
||||
}
|
||||
return this->indirect_lookup_2d_count(this->root->armor_table, data1_1 - 1) + HasImplicitPlaceholders;
|
||||
}
|
||||
|
||||
virtual const ArmorOrShield& get_armor_or_shield(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if ((data1_1 < 1) || (data1_1 > 2)) {
|
||||
throw out_of_range("armor/shield class ID out of range");
|
||||
throw std::out_of_range("armor/shield class ID out of range");
|
||||
}
|
||||
auto& vec = (data1_1 == 1) ? this->armors : this->shields;
|
||||
return this->add_to_vector_cache_2d_indirect<ArmorOrShieldT>(vec, this->root->armor_table, data1_1 - 1, data1_2);
|
||||
@@ -2517,14 +2515,14 @@ public:
|
||||
|
||||
virtual size_t num_tools_in_class(uint8_t data1_1) const {
|
||||
if (data1_1 >= this->num_tool_classes()) {
|
||||
throw out_of_range("tool class ID out of range");
|
||||
throw std::out_of_range("tool class ID out of range");
|
||||
}
|
||||
return this->indirect_lookup_2d_count(this->root->tool_table, data1_1);
|
||||
}
|
||||
|
||||
virtual const Tool& get_tool(uint8_t data1_1, uint8_t data1_2) const {
|
||||
if (data1_1 >= this->num_tool_classes()) {
|
||||
throw out_of_range("tool class ID out of range");
|
||||
throw std::out_of_range("tool class ID out of range");
|
||||
}
|
||||
uint16_t key = (data1_1 << 8) | data1_2;
|
||||
auto it = this->tools.find(key);
|
||||
@@ -2543,11 +2541,11 @@ public:
|
||||
const auto* defs = &this->r.pget<ToolT>(co.offset, sizeof(ToolT) * co.count);
|
||||
for (size_t y = 0; y < co.count; y++) {
|
||||
if (defs[y].base.id == id) {
|
||||
return make_pair(z, y);
|
||||
return std::make_pair(z, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw out_of_range(std::format("invalid tool class {:08X}", id));
|
||||
throw std::out_of_range(std::format("invalid tool class {:08X}", id));
|
||||
}
|
||||
|
||||
virtual size_t num_mags() const {
|
||||
@@ -2636,10 +2634,10 @@ public:
|
||||
|
||||
virtual const MagFeedResult& get_mag_feed_result(uint8_t table_index, uint8_t item_index) const {
|
||||
if (table_index >= 8) {
|
||||
throw out_of_range("invalid mag feed table index");
|
||||
throw std::out_of_range("invalid mag feed table index");
|
||||
}
|
||||
if (item_index >= 11) {
|
||||
throw out_of_range("invalid mag feed item index");
|
||||
throw std::out_of_range("invalid mag feed item index");
|
||||
}
|
||||
const auto& table_offsets = this->r.pget<MagFeedResultsListOffsetsT<BE>>(this->root->mag_feed_table);
|
||||
return this->r.pget<MagFeedResultsList>(table_offsets[table_index])[item_index];
|
||||
@@ -2681,7 +2679,7 @@ public:
|
||||
virtual const Special& get_special(uint8_t special) const {
|
||||
special &= 0x3F;
|
||||
if (special >= this->num_specials()) {
|
||||
throw out_of_range("invalid special index");
|
||||
throw std::out_of_range("invalid special index");
|
||||
}
|
||||
while (this->specials.size() <= special) {
|
||||
this->specials.emplace_back(this->r.pget<SpecialT<BE>>(
|
||||
@@ -2772,10 +2770,10 @@ public:
|
||||
|
||||
virtual uint8_t get_max_tech_level(uint8_t char_class, uint8_t tech_num) const {
|
||||
if (char_class >= 12) {
|
||||
throw out_of_range("invalid character class");
|
||||
throw std::out_of_range("invalid character class");
|
||||
}
|
||||
if (tech_num >= 19) {
|
||||
throw out_of_range("invalid technique number");
|
||||
throw std::out_of_range("invalid technique number");
|
||||
}
|
||||
if constexpr (requires { this->root->max_tech_level_table; }) {
|
||||
return r.pget_u8(this->root->max_tech_level_table + tech_num * 12 + char_class);
|
||||
@@ -2862,13 +2860,13 @@ public:
|
||||
if constexpr (requires { this->root->unwrap_table; }) {
|
||||
const auto& co = this->r.pget<ArrayRefT<BE>>(this->root->unwrap_table);
|
||||
if (event_number >= co.count) {
|
||||
throw out_of_range("invalid event number");
|
||||
throw std::out_of_range("invalid event number");
|
||||
}
|
||||
const auto& event_co = this->r.pget<ArrayRefT<BE>>(co.offset + sizeof(ArrayRefT<BE>) * event_number);
|
||||
const auto* defs = &this->r.pget<EventItem>(event_co.offset, event_co.count * sizeof(EventItem));
|
||||
return make_pair(defs, event_co.count);
|
||||
return std::make_pair(defs, event_co.count);
|
||||
} else {
|
||||
return make_pair(nullptr, 0);
|
||||
return std::make_pair(nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-12
@@ -1,7 +1,5 @@
|
||||
#include "ItemTranslationTable.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static constexpr bool is_canonical(uint32_t id) {
|
||||
return !(id & 0x80000000);
|
||||
}
|
||||
@@ -23,12 +21,12 @@ ItemTranslationTable::ItemTranslationTable(
|
||||
if (is_canonical(id)) {
|
||||
has_any_canonical_id = true;
|
||||
if (!this->entry_index_for_version[v_s].emplace(id, z).second) {
|
||||
throw runtime_error(std::format("(row {}) duplicate canonical ID {:08X}", z, id));
|
||||
throw std::runtime_error(std::format("(row {}) duplicate canonical ID {:08X}", z, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_any_canonical_id) {
|
||||
throw runtime_error(std::format("(row {}) no canonical ID present in row", z));
|
||||
throw std::runtime_error(std::format("(row {}) no canonical ID present in row", z));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,27 +44,27 @@ ItemTranslationTable::ItemTranslationTable(
|
||||
uint32_t e_id = this->entries[z].id_for_version[v_s];
|
||||
if (is_canonical(e_id)) {
|
||||
if (!entry_index.count(e_id)) {
|
||||
throw logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id));
|
||||
throw std::logic_error(std::format("(row {} version {}) canonical ID {:08X} is missing from the index", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
try {
|
||||
item_parameter_table->definition_for_primary_identifier(e_id);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
if (!remaining_identifiers.erase(e_id)) {
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
||||
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
} else if (!entry_index.count(make_canonical(e_id))) {
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||
throw std::runtime_error(std::format("(row {} version {}) ID {:08X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!remaining_identifiers.empty()) {
|
||||
string missing_str = std::format("(version {}) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
||||
std::string missing_str = std::format("(version {}) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
||||
for (uint32_t id : remaining_identifiers) {
|
||||
missing_str += std::format(" {:08X}", id);
|
||||
}
|
||||
throw runtime_error(missing_str);
|
||||
throw std::runtime_error(missing_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +90,7 @@ uint32_t ItemTranslationTable::translate(uint32_t primary_identifier, Version fr
|
||||
ItemTranslationTable::Entry::Entry(const phosg::JSON& json) {
|
||||
const auto& l = json.as_list();
|
||||
if (l.size() != NUM_NON_PATCH_VERSIONS + 1) {
|
||||
throw runtime_error("list length is incorrect");
|
||||
throw std::runtime_error("list length is incorrect");
|
||||
}
|
||||
for (size_t z = 0; z < NUM_NON_PATCH_VERSIONS; z++) {
|
||||
this->id_for_version[z] = l[z]->as_int();
|
||||
|
||||
+27
-29
@@ -4,9 +4,7 @@
|
||||
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomGenerator> rand_crypt) {
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<RandomGenerator> rand_crypt) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
// On PC (and presumably DC), the client sends a 6x29 after this to delete the used item. On GC and later versions,
|
||||
@@ -26,13 +24,13 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
uint8_t max_level = item_parameter_table->get_max_tech_level(player->disp.visual.char_class, item.data.data1[4]);
|
||||
if (item.data.data1[2] > max_level) {
|
||||
throw runtime_error("technique level too high");
|
||||
throw std::runtime_error("technique level too high");
|
||||
}
|
||||
player->set_technique_level(item.data.data1[4], item.data.data1[2]);
|
||||
|
||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030A0000) { // Grinder
|
||||
if (item.data.data1[2] > 2) {
|
||||
throw runtime_error("incorrect grinder value");
|
||||
throw std::runtime_error("incorrect grinder value");
|
||||
}
|
||||
|
||||
auto& weapon = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::WEAPON)];
|
||||
@@ -40,9 +38,9 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
auto weapon_def = item_parameter_table->get_weapon(weapon.data.data1[1], weapon.data.data1[2]);
|
||||
if (is_v4 && (weapon.data.data1[3] >= weapon_def.max_grind)) {
|
||||
throw runtime_error("weapon already at maximum grind");
|
||||
throw std::runtime_error("weapon already at maximum grind");
|
||||
}
|
||||
weapon.data.data1[3] = min<uint8_t>(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind);
|
||||
weapon.data.data1[3] = std::min<uint8_t>(weapon.data.data1[3] + item.data.data1[2] + 1, weapon_def.max_grind);
|
||||
|
||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030B0000) { // Material
|
||||
auto p = c->character_file();
|
||||
@@ -85,11 +83,11 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
if (!is_v3_or_later && (c->version() != Version::GC_NTE)) {
|
||||
p->disp.stats.char_stats.lck += 2;
|
||||
} else {
|
||||
throw runtime_error("unknown material used");
|
||||
throw std::runtime_error("unknown material used");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unknown material used");
|
||||
throw std::runtime_error("unknown material used");
|
||||
}
|
||||
if (is_v3_or_later || (type == Type::HP) || (type == Type::TP)) {
|
||||
p->set_material_usage(type, p->get_material_usage(type) + 1);
|
||||
@@ -98,7 +96,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
} else if ((primary_identifier & 0xFFFF0000) == 0x030F0000) { // AddSlot
|
||||
auto& armor = player->inventory.items[player->inventory.find_equipped_item(EquipSlot::ARMOR)];
|
||||
if (armor.data.data1[5] >= 4) {
|
||||
throw runtime_error("armor already at maximum slot count");
|
||||
throw std::runtime_error("armor already at maximum slot count");
|
||||
}
|
||||
armor.data.data1[5]++;
|
||||
|
||||
@@ -155,7 +153,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
mag.data.data1[1] = 0x2B;
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid mag cell used");
|
||||
throw std::runtime_error("invalid mag cell used");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +166,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
sum += table.first[z].probability;
|
||||
}
|
||||
if (sum == 0) {
|
||||
throw runtime_error("no unwrap results available for event");
|
||||
throw std::runtime_error("no unwrap results available for event");
|
||||
}
|
||||
// TODO: It seems that on non-BB, clients don't synchronize this at all, so they could end up thinking the
|
||||
// unwrapped item is something completely different. (They don't even use a fixed random seed, like for rares; they
|
||||
@@ -218,30 +216,30 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
auto item_parameter_table = s->item_parameter_table(c->version());
|
||||
const auto& combo = item_parameter_table->get_item_combination(item.data, inv_item.data);
|
||||
if (combo.char_class != 0xFF && combo.char_class != player->disp.visual.char_class) {
|
||||
throw runtime_error("item combination requires specific char_class");
|
||||
throw std::runtime_error("item combination requires specific char_class");
|
||||
}
|
||||
if (combo.mag_level != 0xFF) {
|
||||
if (inv_item.data.data1[0] != 2) {
|
||||
throw runtime_error("item combination applies with mag level requirement, but equipped item is not a mag");
|
||||
throw std::runtime_error("item combination applies with mag level requirement, but equipped item is not a mag");
|
||||
}
|
||||
if (inv_item.data.compute_mag_level() < combo.mag_level) {
|
||||
throw runtime_error("item combination applies with mag level requirement, but equipped mag level is too low");
|
||||
throw std::runtime_error("item combination applies with mag level requirement, but equipped mag level is too low");
|
||||
}
|
||||
}
|
||||
if (combo.grind != 0xFF) {
|
||||
if (inv_item.data.data1[0] != 0) {
|
||||
throw runtime_error("item combination applies with grind requirement, but equipped item is not a weapon");
|
||||
throw std::runtime_error("item combination applies with grind requirement, but equipped item is not a weapon");
|
||||
}
|
||||
if (inv_item.data.data1[3] < combo.grind) {
|
||||
throw runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low");
|
||||
throw std::runtime_error("item combination applies with grind requirement, but equipped weapon grind is too low");
|
||||
}
|
||||
}
|
||||
if (combo.level != 0xFF && player->disp.stats.level + 1 < combo.level) {
|
||||
throw runtime_error("item combination applies with level requirement, but player level is too low");
|
||||
throw std::runtime_error("item combination applies with level requirement, but player level is too low");
|
||||
}
|
||||
// If we get here, then the combo applies
|
||||
if (combo_applied) {
|
||||
throw runtime_error("multiple combinations apply");
|
||||
throw std::runtime_error("multiple combinations apply");
|
||||
}
|
||||
combo_applied = true;
|
||||
|
||||
@@ -254,7 +252,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
inv_item.data.data1[4] = 0; // Flags + special
|
||||
}
|
||||
inv_item.flags &= (~8); // Unequip it
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,13 +267,13 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<RandomG
|
||||
void apply_mag_feed_result(
|
||||
ItemData& mag_item,
|
||||
const ItemData& fed_item,
|
||||
shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||
std::shared_ptr<const ItemParameterTable> item_parameter_table,
|
||||
std::shared_ptr<const MagEvolutionTable> mag_evolution_table,
|
||||
uint8_t char_class,
|
||||
uint8_t section_id,
|
||||
bool version_has_rare_mags) {
|
||||
|
||||
static const unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||
static const std::unordered_map<uint32_t, size_t> result_index_for_fed_item({
|
||||
{0x03000000, 0}, // Monomate
|
||||
{0x03000100, 1}, // Dimate
|
||||
{0x03000200, 2}, // Trimate
|
||||
@@ -298,7 +296,7 @@ void apply_mag_feed_result(
|
||||
if ((delta > 0) || ((delta < 0) && (-delta < existing_stat))) {
|
||||
uint16_t level = data.compute_mag_level();
|
||||
if (level > 200) {
|
||||
throw runtime_error("mag level is too high");
|
||||
throw std::runtime_error("mag level is too high");
|
||||
}
|
||||
if ((level == 200) && ((99 - existing_stat) < delta)) {
|
||||
delta = 99 - existing_stat;
|
||||
@@ -311,8 +309,8 @@ void apply_mag_feed_result(
|
||||
update_stat(mag_item, 3, feed_result.pow);
|
||||
update_stat(mag_item, 4, feed_result.dex);
|
||||
update_stat(mag_item, 5, feed_result.mind);
|
||||
mag_item.data2[0] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[0]) + feed_result.synchro, 0, 120);
|
||||
mag_item.data2[1] = clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
|
||||
mag_item.data2[0] = std::clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[0]) + feed_result.synchro, 0, 120);
|
||||
mag_item.data2[1] = std::clamp<ssize_t>(static_cast<ssize_t>(mag_item.data2[1]) + feed_result.iq, 0, 200);
|
||||
|
||||
uint8_t mag_level = mag_item.compute_mag_level();
|
||||
mag_item.data1[2] = mag_level;
|
||||
@@ -347,7 +345,7 @@ void apply_mag_feed_result(
|
||||
mag_item.data1[1] = 0x19; // Vritra
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid character class");
|
||||
throw std::runtime_error("invalid character class");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +399,7 @@ void apply_mag_feed_result(
|
||||
} else if (is_ranger) {
|
||||
table_index += 6;
|
||||
} else if (!is_hunter) {
|
||||
throw logic_error("char class is not any of the top-level classes");
|
||||
throw std::logic_error("char class is not any of the top-level classes");
|
||||
}
|
||||
|
||||
// Note: The original code checks the class (hunter/ranger/force) again here, and goes into 3 branches that
|
||||
@@ -433,7 +431,7 @@ void apply_mag_feed_result(
|
||||
bool is_ranger = char_class_is_ranger(char_class);
|
||||
bool is_force = char_class_is_force(char_class);
|
||||
if (is_hunter + is_ranger + is_force != 1) {
|
||||
throw logic_error("char class is not exactly one of the top-level classes");
|
||||
throw std::logic_error("char class is not exactly one of the top-level classes");
|
||||
}
|
||||
|
||||
if (is_hunter) {
|
||||
|
||||
+7
-9
@@ -7,8 +7,6 @@
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void LevelTable::reset_to_base(PlayerStats& stats, uint8_t char_class) const {
|
||||
stats.level = 0;
|
||||
stats.exp = 0;
|
||||
@@ -82,7 +80,7 @@ const LevelStatsDelta& JSONLevelTable::stats_delta_for_level(uint8_t char_class,
|
||||
return this->level_deltas.at(char_class).at(level);
|
||||
}
|
||||
|
||||
LevelTableV2::LevelTableV2(const string& data) {
|
||||
LevelTableV2::LevelTableV2(const std::string& data) {
|
||||
struct Root {
|
||||
// The overall format of this file on V2 has much more data than we actually use. This table is sorted by the
|
||||
// offset in the PlayerTable.prs file; note that the offset fields in this structure do not match that order.
|
||||
@@ -165,7 +163,7 @@ size_t LevelTableV3::num_char_classes() const {
|
||||
}
|
||||
|
||||
const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) const {
|
||||
static const array<CharacterStats, 12> data = {
|
||||
static const std::array<CharacterStats, 12> data = {
|
||||
// ATP MST EVP HP DFP ATA LCK
|
||||
CharacterStats{0x0023, 0x001D, 0x002D, 0x0014, 0x0011, 0x001E, 0x000A},
|
||||
CharacterStats{0x001E, 0x0028, 0x003C, 0x0013, 0x0016, 0x0019, 0x000A},
|
||||
@@ -183,7 +181,7 @@ const CharacterStats& LevelTableV3::base_stats_for_class(uint8_t char_class) con
|
||||
return data.at(char_class);
|
||||
}
|
||||
|
||||
static const array<PlayerStats, 12> max_stats_v3_v4 = {
|
||||
static const std::array<PlayerStats, 12> max_stats_v3_v4 = {
|
||||
// ATP MST EVP HP DFP ATA LCK ESP PRX PRY L E M
|
||||
PlayerStats{{0x056B, 0x02DC, 0x02F4, 0x0265, 0x0243, 0x054B, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||
PlayerStats{{0x04CB, 0x0499, 0x032B, 0x0254, 0x024D, 0x056C, 0x0064}, 0x0064, 0.0f, 0.0f, 0, 0, 0},
|
||||
@@ -208,7 +206,7 @@ const LevelStatsDelta& LevelTableV3::stats_delta_for_level(uint8_t char_class, u
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const string& data) {
|
||||
void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& deltas, const std::string& data) {
|
||||
// The V3 format is very simple:
|
||||
// root:
|
||||
// u32 offset:
|
||||
@@ -225,11 +223,11 @@ void parse_level_deltas_t(std::array<std::array<LevelStatsDelta, 200>, 12>& delt
|
||||
}
|
||||
}
|
||||
|
||||
LevelTableGC::LevelTableGC(const string& data) {
|
||||
LevelTableGC::LevelTableGC(const std::string& data) {
|
||||
parse_level_deltas_t<true>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
LevelTableXB::LevelTableXB(const string& data) {
|
||||
LevelTableXB::LevelTableXB(const std::string& data) {
|
||||
parse_level_deltas_t<false>(this->level_deltas, data);
|
||||
}
|
||||
|
||||
@@ -274,7 +272,7 @@ std::string LevelTable::serialize_binary_v4() const {
|
||||
return rel.finalize(root_offset);
|
||||
}
|
||||
|
||||
LevelTableV4::LevelTableV4(const string& data) {
|
||||
LevelTableV4::LevelTableV4(const std::string& data) {
|
||||
|
||||
phosg::StringReader r(data);
|
||||
const auto& footer = r.pget<RELFileFooter>(r.size() - sizeof(RELFileFooter));
|
||||
|
||||
+49
-51
@@ -10,8 +10,6 @@
|
||||
#include "ServerState.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
|
||||
return this->flags & (1 << client_id);
|
||||
}
|
||||
@@ -24,17 +22,17 @@ bool Lobby::FloorItemManager::exists(uint32_t item_id) const {
|
||||
return this->items.count(item_id);
|
||||
}
|
||||
|
||||
shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) const {
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::find(uint32_t item_id) const {
|
||||
return this->items.at(item_id);
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::add(
|
||||
const ItemData& item,
|
||||
const VectorXZF& pos,
|
||||
shared_ptr<const MapState::ObjectState> from_obj,
|
||||
shared_ptr<const MapState::EnemyState> from_ene,
|
||||
std::shared_ptr<const MapState::ObjectState> from_obj,
|
||||
std::shared_ptr<const MapState::EnemyState> from_ene,
|
||||
uint16_t flags) {
|
||||
auto fi = make_shared<FloorItem>();
|
||||
auto fi = std::make_shared<FloorItem>();
|
||||
fi->data = item;
|
||||
fi->pos = pos;
|
||||
fi->drop_number = this->next_drop_number++;
|
||||
@@ -44,14 +42,14 @@ void Lobby::FloorItemManager::add(
|
||||
this->add(fi);
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
||||
void Lobby::FloorItemManager::add(std::shared_ptr<Lobby::FloorItem> fi) {
|
||||
if (fi->flags == 0) {
|
||||
throw logic_error("floor item is not visible to any player");
|
||||
throw std::logic_error("floor item is not visible to any player");
|
||||
}
|
||||
|
||||
auto emplace_ret = this->items.emplace(fi->data.id, fi);
|
||||
if (!emplace_ret.second) {
|
||||
throw runtime_error("floor item already exists with the same ID");
|
||||
throw std::runtime_error("floor item already exists with the same ID");
|
||||
}
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (fi->visible_to_client(z)) {
|
||||
@@ -65,15 +63,15 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
||||
auto item_it = this->items.find(item_id);
|
||||
if (item_it == this->items.end()) {
|
||||
throw out_of_range("item not present");
|
||||
throw std::out_of_range("item not present");
|
||||
}
|
||||
auto fi = item_it->second;
|
||||
if ((client_id != 0xFF) && !fi->visible_to_client(client_id)) {
|
||||
throw runtime_error("client does not have access to item");
|
||||
throw std::runtime_error("client does not have access to item");
|
||||
}
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
if (fi->visible_to_client(z) && !this->queue_for_client[z].erase(fi->drop_number)) {
|
||||
throw logic_error("item queue for client is inconsistent");
|
||||
throw std::logic_error("item queue for client is inconsistent");
|
||||
}
|
||||
}
|
||||
this->items.erase(item_it);
|
||||
@@ -83,7 +81,7 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
|
||||
}
|
||||
|
||||
std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::evict() {
|
||||
unordered_set<shared_ptr<FloorItem>> ret;
|
||||
std::unordered_set<std::shared_ptr<FloorItem>> ret;
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
while (this->queue_for_client[z].size() > 48) {
|
||||
ret.emplace(this->remove(this->queue_for_client[z].begin()->second->data.id, 0xFF));
|
||||
@@ -94,7 +92,7 @@ std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::e
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask) {
|
||||
unordered_set<uint32_t> item_ids_to_delete;
|
||||
std::unordered_set<uint32_t> item_ids_to_delete;
|
||||
for (const auto& it : this->items) {
|
||||
if ((it.second->flags & remaining_clients_mask) == 0) {
|
||||
item_ids_to_delete.emplace(it.first);
|
||||
@@ -107,7 +105,7 @@ void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::clear_private() {
|
||||
unordered_set<uint32_t> item_ids_to_delete;
|
||||
std::unordered_set<uint32_t> item_ids_to_delete;
|
||||
for (const auto& it : this->items) {
|
||||
if ((it.second->flags & 0x00F) != 0x00F) {
|
||||
item_ids_to_delete.emplace(it.first);
|
||||
@@ -130,7 +128,7 @@ void Lobby::FloorItemManager::clear() {
|
||||
}
|
||||
|
||||
uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
||||
::map<uint32_t, shared_ptr<FloorItem>> old_items;
|
||||
std::map<uint32_t, std::shared_ptr<FloorItem>> old_items;
|
||||
old_items.swap(this->items);
|
||||
for (auto& queue : this->queue_for_client) {
|
||||
queue.clear();
|
||||
@@ -142,13 +140,13 @@ uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
||||
return next_item_id;
|
||||
}
|
||||
|
||||
Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
Lobby::Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
: server_state(s),
|
||||
log(std::format("[{}:{:X}] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
|
||||
creation_time(phosg::now()),
|
||||
lobby_id(id),
|
||||
random_seed(phosg::random_object<uint32_t>()),
|
||||
rand_crypt(make_shared<DisabledRandomGenerator>()),
|
||||
rand_crypt(std::make_shared<DisabledRandomGenerator>()),
|
||||
drop_mode(ServerDropMode::CLIENT),
|
||||
idle_timeout_timer(*s->io_context) {
|
||||
this->log.info_f("Created");
|
||||
@@ -178,17 +176,17 @@ uint8_t Lobby::area_for_floor(Version version, uint8_t floor) const {
|
||||
return sdt->default_floor_to_area(this->episode).at(floor);
|
||||
}
|
||||
|
||||
shared_ptr<ServerState> Lobby::require_server_state() const {
|
||||
std::shared_ptr<ServerState> Lobby::require_server_state() const {
|
||||
auto s = this->server_state.lock();
|
||||
if (!s) {
|
||||
throw logic_error("server is deleted");
|
||||
throw std::logic_error("server is deleted");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
||||
std::shared_ptr<Lobby::ChallengeParameters> Lobby::require_challenge_params() const {
|
||||
if (!this->challenge_params) {
|
||||
throw runtime_error("challenge params are missing");
|
||||
throw std::runtime_error("challenge params are missing");
|
||||
}
|
||||
return this->challenge_params;
|
||||
}
|
||||
@@ -206,17 +204,17 @@ void Lobby::create_item_creator(Version logic_version) {
|
||||
logic_version = leader_c ? leader_c->version() : Version::BB_V4;
|
||||
}
|
||||
|
||||
shared_ptr<RandomGenerator> rand_crypt;
|
||||
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||
if (s->use_psov2_rand_crypt) {
|
||||
rand_crypt = make_shared<PSOV2Encryption>(this->rand_crypt->seed());
|
||||
rand_crypt = std::make_shared<PSOV2Encryption>(this->rand_crypt->seed());
|
||||
} else {
|
||||
rand_crypt = make_shared<MT19937Generator>(this->rand_crypt->seed());
|
||||
rand_crypt = std::make_shared<MT19937Generator>(this->rand_crypt->seed());
|
||||
}
|
||||
uint8_t effective_section_id = this->effective_section_id();
|
||||
if (effective_section_id >= 10) {
|
||||
effective_section_id = 0x00;
|
||||
}
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
this->item_creator = std::make_shared<ItemCreator>(
|
||||
s->common_item_set(logic_version, this->quest),
|
||||
s->rare_item_set(logic_version, this->quest),
|
||||
s->armor_random_set,
|
||||
@@ -274,13 +272,13 @@ void Lobby::load_maps() {
|
||||
if (this->quest) {
|
||||
this->log.info_f("Loading quest supermap");
|
||||
auto supermap = this->quest->get_supermap(this->random_seed);
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->map_state = std::make_shared<MapState>(
|
||||
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermap);
|
||||
} else {
|
||||
this->log.info_f("Loading free play supermaps");
|
||||
auto s = this->require_server_state();
|
||||
auto supermaps = s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations);
|
||||
this->map_state = make_shared<MapState>(
|
||||
this->map_state = std::make_shared<MapState>(
|
||||
this->lobby_id, this->difficulty, this->event, this->random_seed, this->rare_enemy_rates, this->rand_crypt, supermaps);
|
||||
}
|
||||
|
||||
@@ -323,7 +321,7 @@ void Lobby::create_ep3_server() {
|
||||
} else {
|
||||
options.behavior_flags &= (~Episode3::BehaviorFlag::IS_TRIAL_EDITION);
|
||||
}
|
||||
this->ep3_server = make_shared<Episode3::Server>(this->shared_from_this(), std::move(options));
|
||||
this->ep3_server = std::make_shared<Episode3::Server>(this->shared_from_this(), std::move(options));
|
||||
this->ep3_server->init();
|
||||
}
|
||||
|
||||
@@ -381,9 +379,9 @@ bool Lobby::any_v1_clients_present() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
void Lobby::add_client(std::shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
if (!c->login) {
|
||||
throw runtime_error("client is not logged in");
|
||||
throw std::runtime_error("client is not logged in");
|
||||
}
|
||||
|
||||
ssize_t index;
|
||||
@@ -391,7 +389,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
|
||||
if (required_client_id >= 0) {
|
||||
if (this->clients.at(required_client_id).get()) {
|
||||
throw out_of_range("required slot is in use");
|
||||
throw std::out_of_range("required slot is in use");
|
||||
}
|
||||
this->clients[required_client_id] = c;
|
||||
index = required_client_id;
|
||||
@@ -404,7 +402,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
}
|
||||
if (index < min_client_id) {
|
||||
throw out_of_range("no space left in lobby");
|
||||
throw std::out_of_range("no space left in lobby");
|
||||
}
|
||||
} else {
|
||||
for (index = min_client_id; index < this->max_clients; index++) {
|
||||
@@ -414,7 +412,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
}
|
||||
if (index >= this->max_clients) {
|
||||
throw out_of_range("no space left in lobby");
|
||||
throw std::out_of_range("no space left in lobby");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,10 +485,10 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::remove_client(shared_ptr<Client> c) {
|
||||
void Lobby::remove_client(std::shared_ptr<Client> c) {
|
||||
if (this->clients.at(c->lobby_client_id) != c) {
|
||||
auto other_c = this->clients[c->lobby_client_id].get();
|
||||
throw logic_error(std::format(
|
||||
throw std::logic_error(std::format(
|
||||
"client\'s lobby client id ({}) does not match client list ({})",
|
||||
c->lobby_client_id,
|
||||
static_cast<uint8_t>(other_c ? other_c->lobby_client_id : 0xFF)));
|
||||
@@ -563,20 +561,20 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::move_client_to_lobby(shared_ptr<Lobby> dest_lobby, shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
void Lobby::move_client_to_lobby(std::shared_ptr<Lobby> dest_lobby, std::shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
if (dest_lobby.get() == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (required_client_id >= 0) {
|
||||
if (dest_lobby->clients.at(required_client_id)) {
|
||||
throw out_of_range("required slot is in use");
|
||||
throw std::out_of_range("required slot is in use");
|
||||
}
|
||||
} else {
|
||||
ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0;
|
||||
size_t available_slots = dest_lobby->max_clients - min_client_id;
|
||||
if (dest_lobby->count_clients() >= available_slots) {
|
||||
throw out_of_range("no space left in lobby");
|
||||
throw std::out_of_range("no space left in lobby");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,7 +582,7 @@ void Lobby::move_client_to_lobby(shared_ptr<Lobby> dest_lobby, shared_ptr<Client
|
||||
dest_lobby->add_client(c, required_client_id);
|
||||
}
|
||||
|
||||
shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t account_id) {
|
||||
std::shared_ptr<Client> Lobby::find_client(const std::string* identifier, uint64_t account_id) {
|
||||
for (size_t x = 0; x < this->max_clients; x++) {
|
||||
auto lc = this->clients[x];
|
||||
if (!lc) {
|
||||
@@ -598,7 +596,7 @@ shared_ptr<Client> Lobby::find_client(const string* identifier, uint64_t account
|
||||
}
|
||||
}
|
||||
|
||||
throw out_of_range("client not found");
|
||||
throw std::out_of_range("client not found");
|
||||
}
|
||||
|
||||
Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const std::string* password) const {
|
||||
@@ -662,7 +660,7 @@ bool Lobby::item_exists(uint8_t floor, uint32_t item_id) const {
|
||||
return this->floor_item_managers.at(floor).exists(item_id);
|
||||
}
|
||||
|
||||
shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) const {
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::find_item(uint8_t floor, uint32_t item_id) const {
|
||||
return this->floor_item_managers.at(floor).find(item_id);
|
||||
}
|
||||
|
||||
@@ -678,7 +676,7 @@ void Lobby::add_item(
|
||||
this->evict_items_from_floor(floor);
|
||||
}
|
||||
|
||||
void Lobby::add_item(uint8_t floor, shared_ptr<FloorItem> fi) {
|
||||
void Lobby::add_item(uint8_t floor, std::shared_ptr<FloorItem> fi) {
|
||||
auto& m = this->floor_item_managers.at(floor);
|
||||
m.add(fi);
|
||||
this->evict_items_from_floor(floor);
|
||||
@@ -700,7 +698,7 @@ void Lobby::evict_items_from_floor(uint8_t floor) {
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Lobby::FloorItem> Lobby::remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id) {
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::remove_item(uint8_t floor, uint32_t item_id, uint8_t requesting_client_id) {
|
||||
return this->floor_item_managers.at(floor).remove(item_id, requesting_client_id);
|
||||
}
|
||||
|
||||
@@ -721,7 +719,7 @@ void Lobby::on_item_id_generated_externally(uint32_t item_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consume_ids) {
|
||||
void Lobby::assign_inventory_and_bank_item_ids(std::shared_ptr<Client> c, bool consume_ids) {
|
||||
auto p = c->character_file();
|
||||
uint32_t orig_next_item_id = this->next_item_id_for_client.at(c->lobby_client_id);
|
||||
for (size_t z = 0; z < p->inventory.num_items; z++) {
|
||||
@@ -746,8 +744,8 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consum
|
||||
}
|
||||
}
|
||||
|
||||
unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_account_id() const {
|
||||
unordered_map<uint32_t, shared_ptr<Client>> ret;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> Lobby::clients_by_account_id() const {
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> ret;
|
||||
for (auto c : this->clients) {
|
||||
if (c && c->login) {
|
||||
ret.emplace(c->login->account->account_id, c);
|
||||
@@ -759,7 +757,7 @@ unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_account_id() const
|
||||
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||
size_t num_players = this->count_clients();
|
||||
bool v1_present = this->any_v1_clients_present();
|
||||
return [this, num_players, v1_present](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||
return [this, num_players, v1_present](std::shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||
bool is_enabled = true;
|
||||
for (const auto& lc : this->clients) {
|
||||
auto this_sh = this->shared_from_this();
|
||||
@@ -774,7 +772,7 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||
};
|
||||
}
|
||||
|
||||
bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<const Lobby>& b) {
|
||||
bool Lobby::compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b) {
|
||||
// Sort keys:
|
||||
// 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle)
|
||||
// 2. Password: public < locked
|
||||
@@ -782,7 +780,7 @@ bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<co
|
||||
// 4. Episode: 1 < 2 < 4
|
||||
// 5. Difficulty: Normal < Hard < Very Hard < Ultimate
|
||||
// 6. Game name
|
||||
static auto get_priority = +[](const shared_ptr<const Lobby>& l) -> size_t {
|
||||
static auto get_priority = +[](const std::shared_ptr<const Lobby>& l) -> size_t {
|
||||
if (l->check_flag(Lobby::Flag::QUEST_SELECTION_IN_PROGRESS) ||
|
||||
l->check_flag(Lobby::Flag::QUEST_IN_PROGRESS) ||
|
||||
l->check_flag(Lobby::Flag::BATTLE_IN_PROGRESS)) {
|
||||
@@ -864,6 +862,6 @@ const char* phosg::name_for_enum<Lobby::JoinError>(Lobby::JoinError value) {
|
||||
case Lobby::JoinError::NO_ACCESS_TO_QUEST:
|
||||
return "NO_ACCESS_TO_QUEST";
|
||||
default:
|
||||
throw runtime_error("invalid drop mode");
|
||||
throw std::runtime_error("invalid drop mode");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
struct FloorItemManager {
|
||||
phosg::PrefixedLogger log;
|
||||
uint64_t next_drop_number;
|
||||
// It's important that this is a map and not an unordered_map. See the comment in send_game_item_state for details.
|
||||
// It's important that this is a map and not an std::unordered_map. See the comment in send_game_item_state for details.
|
||||
std::map<uint32_t, std::shared_ptr<FloorItem>> items; // Keyed on item_id
|
||||
std::array<std::map<uint64_t, std::shared_ptr<FloorItem>>, 12> queue_for_client;
|
||||
|
||||
|
||||
+2
-4
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
using namespace std;
|
||||
|
||||
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_functions_log("[ClientFunctionIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
@@ -22,9 +20,9 @@ phosg::PrefixedLogger static_game_data_log("[StaticGameData] ", phosg::LogLevel:
|
||||
static void set_log_level_from_json(
|
||||
phosg::PrefixedLogger& log, const phosg::JSON& d, const char* json_key) {
|
||||
try {
|
||||
string name = phosg::toupper(d.at(json_key).as_string());
|
||||
std::string name = phosg::toupper(d.at(json_key).as_string());
|
||||
log.min_level = phosg::enum_for_name<phosg::LogLevel>(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "CommonFileFormats.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <bool BE>
|
||||
struct MotionReferenceTables {
|
||||
// It seems that there are two definition tables, but only the first is used on any version of PSO. On v3 and later,
|
||||
@@ -125,7 +123,7 @@ static uint8_t get_v1_mag_evolution_number(uint8_t data1_1) {
|
||||
/* 10 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 2, 2, 3, 4, 3, 3,
|
||||
/* 20 */ 3, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 4};
|
||||
if (data1_1 >= v1_evolution_number_table.size()) {
|
||||
throw runtime_error("invalid mag number");
|
||||
throw std::runtime_error("invalid mag number");
|
||||
}
|
||||
return v1_evolution_number_table[data1_1];
|
||||
}
|
||||
@@ -196,7 +194,7 @@ public:
|
||||
|
||||
virtual const VectorXYZTF& get_color_rgba(size_t index) const {
|
||||
if (index >= NumColors) {
|
||||
throw runtime_error("invalid mag color index");
|
||||
throw std::runtime_error("invalid mag color index");
|
||||
}
|
||||
return this->add_to_vector_cache<ColorEntry<BE>>(this->colors, this->root->color_table, index);
|
||||
}
|
||||
@@ -241,29 +239,29 @@ public:
|
||||
return 0;
|
||||
}
|
||||
virtual const MotionReference& get_motion_reference(bool, size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual std::pair<uint8_t, uint8_t> get_unknown_a2(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual size_t num_unknown_a3_entries() const {
|
||||
return 0;
|
||||
}
|
||||
virtual const UnknownA3Entry& get_unknown_a3(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual uint8_t get_unknown_a4(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual size_t num_colors() const {
|
||||
return 0;
|
||||
}
|
||||
virtual const VectorXYZTF& get_color_rgba(size_t) const {
|
||||
throw runtime_error("Mag tables not available on DC NTE");
|
||||
throw std::runtime_error("Mag tables not available on DC NTE");
|
||||
}
|
||||
|
||||
virtual uint8_t get_evolution_number(uint8_t data1_1) const {
|
||||
|
||||
+507
-541
File diff suppressed because it is too large
Load Diff
+304
-313
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -456,7 +456,7 @@ public:
|
||||
return this->has_any_random_sections;
|
||||
}
|
||||
|
||||
// If the map file has no random sections, does nothing and returns a shared_ptr to this. If it has any random
|
||||
// If the map file has no random sections, does nothing and returns a std::shared_ptr to this. If it has any random
|
||||
// sections, returns a new map with all non-random sections copied verbatim, and random sections replaced with
|
||||
// non-random sections according to the challenge mode enemy generation algorithm.
|
||||
std::shared_ptr<MapFile> materialize_random_sections(uint32_t random_seed);
|
||||
|
||||
+5
-20
@@ -1,28 +1,13 @@
|
||||
#include "Menu.hh"
|
||||
|
||||
using namespace std;
|
||||
MenuItem::MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags)
|
||||
: item_id(item_id), name(name), description(description), get_description(nullptr), flags(flags) {}
|
||||
|
||||
MenuItem::MenuItem(
|
||||
uint32_t item_id,
|
||||
const string& name,
|
||||
const string& description,
|
||||
uint32_t flags)
|
||||
: item_id(item_id),
|
||||
name(name),
|
||||
description(description),
|
||||
get_description(nullptr),
|
||||
flags(flags) {}
|
||||
|
||||
MenuItem::MenuItem(uint32_t item_id,
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
std::function<std::string()> get_description,
|
||||
uint32_t flags)
|
||||
: item_id(item_id),
|
||||
name(name),
|
||||
description(),
|
||||
get_description(std::move(get_description)),
|
||||
flags(flags) {}
|
||||
: item_id(item_id), name(name), description(), get_description(std::move(get_description)), flags(flags) {}
|
||||
|
||||
Menu::Menu(uint32_t menu_id, const std::string& name)
|
||||
: menu_id(menu_id),
|
||||
name(name) {}
|
||||
Menu::Menu(uint32_t menu_id, const std::string& name) : menu_id(menu_id), name(name) {}
|
||||
|
||||
@@ -17,18 +17,16 @@
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
map<string, uint32_t> get_local_addresses() {
|
||||
map<string, uint32_t> ret;
|
||||
std::map<std::string, uint32_t> get_local_addresses() {
|
||||
std::map<std::string, uint32_t> ret;
|
||||
|
||||
#ifndef PHOSG_WINDOWS
|
||||
struct ifaddrs* ifa_raw;
|
||||
if (getifaddrs(&ifa_raw)) {
|
||||
auto s = phosg::string_for_error(errno);
|
||||
throw runtime_error(std::format("failed to get interface addresses: {}", s));
|
||||
throw std::runtime_error(std::format("failed to get interface addresses: {}", s));
|
||||
}
|
||||
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||
std::unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||
|
||||
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) {
|
||||
@@ -56,7 +54,7 @@ map<string, uint32_t> get_local_addresses() {
|
||||
}
|
||||
|
||||
if (result != NO_ERROR) {
|
||||
throw runtime_error(std::format("GetAdaptersAddresses failed: {}", result));
|
||||
throw std::runtime_error(std::format("GetAdaptersAddresses failed: {}", result));
|
||||
}
|
||||
|
||||
for (IP_ADAPTER_ADDRESSES* adapter = adapters; adapter != nullptr; adapter = adapter->Next) {
|
||||
@@ -92,7 +90,7 @@ bool is_local_address(const sockaddr_storage& daddr) {
|
||||
return is_local_address(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
string string_for_address(uint32_t address) {
|
||||
std::string string_for_address(uint32_t address) {
|
||||
return std::format("{}.{}.{}.{}",
|
||||
static_cast<uint8_t>(address >> 24), static_cast<uint8_t>(address >> 16),
|
||||
static_cast<uint8_t>(address >> 8), static_cast<uint8_t>(address));
|
||||
|
||||
+7
-9
@@ -10,8 +10,6 @@
|
||||
#include "Compression.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct Entry {
|
||||
pstring<TextEncoding::ASCII, 0x80> filename;
|
||||
le_uint32_t unknown_a1;
|
||||
@@ -24,7 +22,7 @@ struct Entry {
|
||||
|
||||
static void decrypt_ppk_data(std::string& data, const std::string& filename, const std::string& password) {
|
||||
if (password.size() > 0xFF) {
|
||||
throw runtime_error("password is too long");
|
||||
throw std::runtime_error("password is too long");
|
||||
}
|
||||
|
||||
uint8_t key[0x100];
|
||||
@@ -47,27 +45,27 @@ std::unordered_map<std::string, std::string> decode_ppk_file(const std::string&
|
||||
|
||||
uint32_t signature = r.get_u32b();
|
||||
if (signature != 0x50503130 && signature != 0x4D5A5000) { // 'PP10' or 'MZP\0'
|
||||
throw runtime_error("file is not a ppk archive");
|
||||
throw std::runtime_error("file is not a ppk archive");
|
||||
}
|
||||
|
||||
unordered_map<string, string> ret;
|
||||
std::unordered_map<std::string, std::string> ret;
|
||||
for (size_t offset = r.size() - 4; offset >= 4;) {
|
||||
uint32_t size = r.pget_u32l(offset) ^ 0x12345678;
|
||||
uint32_t entry_offset = offset - size;
|
||||
const auto& entry = r.pget<Entry>(entry_offset);
|
||||
string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
|
||||
string filename = entry.filename.decode();
|
||||
std::string data = r.pread(entry_offset + sizeof(Entry), entry.compressed_size);
|
||||
std::string filename = entry.filename.decode();
|
||||
decrypt_ppk_data(data, phosg::tolower(filename), password);
|
||||
uint32_t checksum = phosg::crc32(data.data(), data.size());
|
||||
if (checksum != entry.checksum) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"incorrect checksum for file {} (expected {:08X}; received {:08X})", filename, entry.checksum, checksum));
|
||||
}
|
||||
if (entry.compressed_size < entry.decompressed_size) {
|
||||
data = prs_decompress(data);
|
||||
}
|
||||
if (!ret.emplace(filename, data).second) {
|
||||
throw runtime_error(std::format("archive contains multiple files with the same name ({})", filename));
|
||||
throw std::runtime_error(std::format("archive contains multiple files with the same name ({})", filename));
|
||||
}
|
||||
offset = entry_offset - 4;
|
||||
}
|
||||
|
||||
+33
-35
@@ -9,8 +9,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
RandomGenerator::RandomGenerator(uint32_t seed) : initial_seed(seed) {}
|
||||
|
||||
DisabledRandomGenerator::DisabledRandomGenerator() : RandomGenerator(0) {}
|
||||
@@ -62,7 +60,7 @@ void PSOLFGEncryption::encrypt_big_endian_minus(void* vdata, size_t size) {
|
||||
|
||||
void PSOLFGEncryption::encrypt_both_endian(void* le_vdata, void* be_vdata, size_t size) {
|
||||
if (size & 3) {
|
||||
throw invalid_argument("size must be a multiple of 4");
|
||||
throw std::invalid_argument("size must be a multiple of 4");
|
||||
}
|
||||
size >>= 2;
|
||||
|
||||
@@ -185,7 +183,7 @@ PSOBBEncryption::PSOBBEncryption(const KeyFile& key, const void* original_seed,
|
||||
void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
||||
if (this->state.subtype == Subtype::TFS1) {
|
||||
if (size & 7) {
|
||||
throw invalid_argument("size must be a multiple of 8");
|
||||
throw std::invalid_argument("size must be a multiple of 8");
|
||||
}
|
||||
|
||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||
@@ -212,7 +210,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
||||
|
||||
} else if (this->state.subtype == Subtype::JSD1) {
|
||||
if (size & 1) {
|
||||
throw invalid_argument("size must be a multiple of 2");
|
||||
throw std::invalid_argument("size must be a multiple of 2");
|
||||
}
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
@@ -230,7 +228,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
||||
|
||||
} else { // STANDARD or MOCB1
|
||||
if (size & 7) {
|
||||
throw invalid_argument("size must be a multiple of 8");
|
||||
throw std::invalid_argument("size must be a multiple of 8");
|
||||
}
|
||||
|
||||
size_t num_dwords = size >> 2;
|
||||
@@ -274,7 +272,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size) {
|
||||
void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
||||
if (this->state.subtype == Subtype::TFS1) {
|
||||
if (size & 7) {
|
||||
throw invalid_argument("size must be a multiple of 8");
|
||||
throw std::invalid_argument("size must be a multiple of 8");
|
||||
}
|
||||
|
||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||
@@ -301,7 +299,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
||||
|
||||
} else if (this->state.subtype == Subtype::JSD1) {
|
||||
if (size & 1) {
|
||||
throw invalid_argument("size must be a multiple of 2");
|
||||
throw std::invalid_argument("size must be a multiple of 2");
|
||||
}
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||
for (size_t z = 0; z < size; z += 2) {
|
||||
@@ -318,7 +316,7 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size) {
|
||||
|
||||
} else { // STANDARD or MOCB1
|
||||
if (size & 7) {
|
||||
throw invalid_argument("size must be a multiple of 8");
|
||||
throw std::invalid_argument("size must be a multiple of 8");
|
||||
}
|
||||
size_t num_dwords = size >> 2;
|
||||
le_uint32_t* dwords = reinterpret_cast<le_uint32_t*>(vdata);
|
||||
@@ -385,7 +383,7 @@ void PSOBBEncryption::tfs1_scramble(uint32_t* out1, uint32_t* out2) const {
|
||||
void PSOBBEncryption::apply_seed(const void* original_seed, size_t seed_size) {
|
||||
// Note: This part is done in the 03 command handler in the BB client, and isn't actually part of the encryption
|
||||
// library. (Why did they do this?)
|
||||
string seed;
|
||||
std::string seed;
|
||||
const uint8_t* original_seed_data = reinterpret_cast<const uint8_t*>(original_seed);
|
||||
for (size_t x = 0; x < seed_size; x += 3) {
|
||||
seed.push_back(original_seed_data[x] ^ 0x19);
|
||||
@@ -431,7 +429,7 @@ void PSOBBEncryption::apply_seed(const void* original_seed, size_t seed_size) {
|
||||
|
||||
} else { // STANDARD or MOCB1 (they share most of their logic)
|
||||
if (seed_size % 3) {
|
||||
throw invalid_argument("seed size must be divisible by 3");
|
||||
throw std::invalid_argument("seed size must be divisible by 3");
|
||||
}
|
||||
|
||||
if (this->state.subtype == Subtype::MOCB1) {
|
||||
@@ -641,27 +639,27 @@ PSOV2OrV3DetectorEncryption::PSOV2OrV3DetectorEncryption(
|
||||
void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt) {
|
||||
if (size != 4) {
|
||||
throw logic_error("initial detector decrypt size must be 4");
|
||||
throw std::logic_error("initial detector decrypt size must be 4");
|
||||
}
|
||||
|
||||
le_uint32_t encrypted = *reinterpret_cast<le_uint32_t*>(data);
|
||||
|
||||
le_uint32_t decrypted_v2 = encrypted;
|
||||
auto v2_crypt = make_unique<PSOV2Encryption>(this->key);
|
||||
auto v2_crypt = std::make_unique<PSOV2Encryption>(this->key);
|
||||
v2_crypt->decrypt(&decrypted_v2, sizeof(decrypted_v2));
|
||||
|
||||
le_uint32_t decrypted_v3 = encrypted;
|
||||
auto v3_crypt = make_unique<PSOV3Encryption>(this->key);
|
||||
auto v3_crypt = std::make_unique<PSOV3Encryption>(this->key);
|
||||
v3_crypt->decrypt(&decrypted_v3, sizeof(decrypted_v3));
|
||||
|
||||
bool v2_match = this->v2_matches.count(decrypted_v2);
|
||||
bool v3_match = this->v3_matches.count(decrypted_v3);
|
||||
if (!v2_match && !v3_match) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"unable to determine crypt version (input={:08X}, v2={:08X}, v3={:08X})",
|
||||
encrypted, decrypted_v2, decrypted_v3));
|
||||
} else if (v2_match && v3_match) {
|
||||
throw runtime_error(std::format("ambiguous crypt version (v2={:08X}, v3={:08X})", decrypted_v2, decrypted_v3));
|
||||
throw std::runtime_error(std::format("ambiguous crypt version (v2={:08X}, v3={:08X})", decrypted_v2, decrypted_v3));
|
||||
} else if (v2_match) {
|
||||
this->active_crypt = std::move(v2_crypt);
|
||||
*reinterpret_cast<le_uint32_t*>(data) = decrypted_v2;
|
||||
@@ -676,7 +674,7 @@ void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size) {
|
||||
|
||||
PSOEncryption::Type PSOV2OrV3DetectorEncryption::type() const {
|
||||
if (!this->active_crypt) {
|
||||
throw logic_error("detector encryption state is indeterminate");
|
||||
throw std::logic_error("detector encryption state is indeterminate");
|
||||
}
|
||||
return this->active_crypt->type();
|
||||
}
|
||||
@@ -689,11 +687,11 @@ void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt) {
|
||||
auto t = this->detector_crypt->type();
|
||||
if (t == Type::V2) {
|
||||
this->active_crypt = make_shared<PSOV2Encryption>(this->key);
|
||||
this->active_crypt = std::make_shared<PSOV2Encryption>(this->key);
|
||||
} else if (t == Type::V3) {
|
||||
this->active_crypt = make_shared<PSOV3Encryption>(this->key);
|
||||
this->active_crypt = std::make_shared<PSOV3Encryption>(this->key);
|
||||
} else {
|
||||
throw logic_error("detector crypt is not V2 or V3");
|
||||
throw std::logic_error("detector crypt is not V2 or V3");
|
||||
}
|
||||
}
|
||||
this->active_crypt->encrypt(data, size);
|
||||
@@ -707,8 +705,8 @@ PSOEncryption::Type PSOV2OrV3ImitatorEncryption::type() const {
|
||||
}
|
||||
|
||||
PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
||||
const vector<shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||
const unordered_set<string>& expected_first_data,
|
||||
const std::vector<std::shared_ptr<const PSOBBEncryption::KeyFile>>& possible_keys,
|
||||
const std::unordered_set<std::string>& expected_first_data,
|
||||
const void* seed,
|
||||
size_t seed_size)
|
||||
: possible_keys(possible_keys),
|
||||
@@ -717,7 +715,7 @@ PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
||||
|
||||
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt.get()) {
|
||||
throw logic_error("PSOBB multi-key encryption requires client input first");
|
||||
throw std::logic_error("PSOBB multi-key encryption requires client input first");
|
||||
}
|
||||
this->active_crypt->encrypt(data, size);
|
||||
}
|
||||
@@ -729,13 +727,13 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
|
||||
}
|
||||
|
||||
if (size != 8) {
|
||||
throw logic_error("initial decryption size does not match expected first data size");
|
||||
throw std::logic_error("initial decryption size does not match expected first data size");
|
||||
}
|
||||
|
||||
for (const auto& key : this->possible_keys) {
|
||||
this->active_key = key;
|
||||
this->active_crypt = make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
|
||||
string test_data(reinterpret_cast<const char*>(data), size);
|
||||
this->active_crypt = std::make_shared<PSOBBEncryption>(*this->active_key, this->seed.data(), this->seed.size());
|
||||
std::string test_data(reinterpret_cast<const char*>(data), size);
|
||||
this->active_crypt->decrypt(test_data.data(), test_data.size());
|
||||
if (this->expected_first_data.count(test_data)) {
|
||||
memcpy(data, test_data.data(), size);
|
||||
@@ -744,7 +742,7 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
|
||||
this->active_key.reset();
|
||||
this->active_crypt.reset();
|
||||
}
|
||||
throw runtime_error("none of the registered private keys are valid for this client");
|
||||
throw std::runtime_error("none of the registered private keys are valid for this client");
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
||||
@@ -752,7 +750,7 @@ PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
||||
}
|
||||
|
||||
PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
|
||||
shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
std::shared_ptr<const PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
const void* seed,
|
||||
size_t seed_size,
|
||||
bool jsd1_use_detector_seed)
|
||||
@@ -772,19 +770,19 @@ PSOEncryption::Type PSOBBMultiKeyImitatorEncryption::type() const {
|
||||
return Type::BB;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
|
||||
std::shared_ptr<PSOBBEncryption> PSOBBMultiKeyImitatorEncryption::ensure_crypt() {
|
||||
if (!this->active_crypt.get()) {
|
||||
auto key = this->detector_crypt->get_active_key();
|
||||
if (!key.get()) {
|
||||
throw logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||
throw std::logic_error("server crypt cannot be initialized because client crypt is not ready");
|
||||
}
|
||||
// Hack: JSD1 uses the client seed for both ends of the connection and ignores the server seed (though each end has
|
||||
// its own state after that). To handle this, we use the other crypt's seed if the type is JSD1.
|
||||
if ((key->subtype == PSOBBEncryption::Subtype::JSD1) && this->jsd1_use_detector_seed) {
|
||||
const auto& detector_seed = this->detector_crypt->get_seed();
|
||||
this->active_crypt = make_shared<PSOBBEncryption>(*key, detector_seed.data(), detector_seed.size());
|
||||
this->active_crypt = std::make_shared<PSOBBEncryption>(*key, detector_seed.data(), detector_seed.size());
|
||||
} else {
|
||||
this->active_crypt = make_shared<PSOBBEncryption>(*key, this->seed.data(), this->seed.size());
|
||||
this->active_crypt = std::make_shared<PSOBBEncryption>(*key, this->seed.data(), this->seed.size());
|
||||
}
|
||||
}
|
||||
return this->active_crypt;
|
||||
@@ -836,7 +834,7 @@ static uint8_t count_one_bits(uint16_t v) {
|
||||
}
|
||||
|
||||
uint32_t encrypt_challenge_time(uint16_t value) {
|
||||
vector<uint8_t> available_bits({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
|
||||
std::vector<uint8_t> available_bits({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
|
||||
|
||||
uint16_t mask = 0;
|
||||
uint8_t num_one_bits = (phosg::random_object<uint8_t>() % 9) + 4; // Range [4, 12]
|
||||
@@ -856,8 +854,8 @@ uint16_t decrypt_challenge_time(uint32_t value) {
|
||||
return ((mask_one_bits < 4) || (mask_one_bits > 12)) ? 0xFFFF : ((mask ^ value) & 0xFFFF);
|
||||
}
|
||||
|
||||
string decrypt_v2_registry_value(const void* data, size_t size) {
|
||||
string ret(reinterpret_cast<const char*>(data), size);
|
||||
std::string decrypt_v2_registry_value(const void* data, size_t size) {
|
||||
std::string ret(reinterpret_cast<const char*>(data), size);
|
||||
PSOV2Encryption crypt(0x66);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
ret[z] ^= (crypt.next() & 0x7F);
|
||||
|
||||
@@ -421,12 +421,11 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
|
||||
w.put<U32T<BE>>(seed);
|
||||
w.write(data);
|
||||
|
||||
std::string ret = std::move(w.str());
|
||||
PSOV2Encryption crypt(seed);
|
||||
if (BE) {
|
||||
crypt.encrypt_big_endian(ret.data() + 8, ret.size() - 8);
|
||||
crypt.encrypt_big_endian(w.str().data() + 8, w.str().size() - 8);
|
||||
} else {
|
||||
crypt.decrypt(ret.data() + 8, ret.size() - 8);
|
||||
crypt.decrypt(w.str().data() + 8, w.str().size() - 8);
|
||||
}
|
||||
return ret;
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
+8
-10
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct TObjectVTable {
|
||||
phosg::be_uint32_t unused_a1;
|
||||
phosg::be_uint32_t unused_a2;
|
||||
@@ -24,19 +22,19 @@ struct TObject {
|
||||
phosg::be_uint32_t vtable_addr;
|
||||
} __packed_ws__(TObject, 0x1C);
|
||||
|
||||
PSOGCObjectGraph::PSOGCObjectGraph(const string& memory_data, uint32_t root_address) {
|
||||
PSOGCObjectGraph::PSOGCObjectGraph(const std::string& memory_data, uint32_t root_address) {
|
||||
phosg::StringReader r(memory_data);
|
||||
this->root = this->parse_object_memo(r, root_address);
|
||||
}
|
||||
|
||||
shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::StringReader& r, uint32_t addr) {
|
||||
std::shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::StringReader& r, uint32_t addr) {
|
||||
try {
|
||||
return this->all_vtables.at(addr);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
const auto& vt = r.pget<TObjectVTable>(addr & 0x01FFFFFF);
|
||||
auto ret = this->all_vtables.emplace(addr, make_shared<VTable>()).first->second;
|
||||
auto ret = this->all_vtables.emplace(addr, std::make_shared<VTable>()).first->second;
|
||||
ret->address = addr;
|
||||
ret->destroy_addr = vt.destroy;
|
||||
ret->update_addr = vt.update;
|
||||
@@ -45,16 +43,16 @@ shared_ptr<PSOGCObjectGraph::VTable> PSOGCObjectGraph::parse_vtable_memo(phosg::
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOGCObjectGraph::Object> PSOGCObjectGraph::parse_object_memo(phosg::StringReader& r, uint32_t addr) {
|
||||
std::shared_ptr<PSOGCObjectGraph::Object> PSOGCObjectGraph::parse_object_memo(phosg::StringReader& r, uint32_t addr) {
|
||||
try {
|
||||
return this->all_objects.at(addr);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
const auto& obj = r.pget<TObject>(addr & 0x01FFFFFF);
|
||||
string type_name = r.pget_cstr(obj.type_name_addr & 0x01FFFFFF);
|
||||
std::string type_name = r.pget_cstr(obj.type_name_addr & 0x01FFFFFF);
|
||||
|
||||
auto ret = this->all_objects.emplace(addr, make_shared<Object>()).first->second;
|
||||
auto ret = this->all_objects.emplace(addr, std::make_shared<Object>()).first->second;
|
||||
ret->address = addr;
|
||||
ret->flags = obj.flags;
|
||||
ret->type_name = std::move(type_name);
|
||||
|
||||
+7
-9
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
PSOCommandHeader::PSOCommandHeader() {
|
||||
@@ -37,7 +35,7 @@ uint16_t PSOCommandHeader::command(Version version) const {
|
||||
case Version::BB_V4:
|
||||
return this->bb.command;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ void PSOCommandHeader::set_command(Version version, uint16_t command) {
|
||||
this->bb.command = command;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +92,7 @@ uint16_t PSOCommandHeader::size(Version version) const {
|
||||
case Version::BB_V4:
|
||||
return this->bb.size;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +123,7 @@ void PSOCommandHeader::set_size(Version version, uint32_t size) {
|
||||
this->bb.size = size;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +149,7 @@ uint32_t PSOCommandHeader::flag(Version version) const {
|
||||
case Version::BB_V4:
|
||||
return this->bb.flag;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +180,7 @@ void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
|
||||
this->bb.flag = flag;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unknown game version");
|
||||
throw std::logic_error("unknown game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +253,7 @@ std::string prepend_command_header(
|
||||
}
|
||||
|
||||
default:
|
||||
throw logic_error("unimplemented game version in prepend_command_header");
|
||||
throw std::logic_error("unimplemented game version in prepend_command_header");
|
||||
}
|
||||
ret.write(data);
|
||||
return std::move(ret.str());
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
PatchDownloadSession::PatchDownloadSession(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
const std::string& remote_host,
|
||||
@@ -54,9 +52,9 @@ PatchDownloadSession::PatchDownloadSession(
|
||||
}
|
||||
|
||||
asio::awaitable<void> PatchDownloadSession::run() {
|
||||
string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||
std::string netloc_str = std::format("{}:{}", this->remote_host, this->remote_port);
|
||||
this->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(this->remote_host, this->remote_port));
|
||||
this->channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
std::move(sock),
|
||||
@@ -79,14 +77,14 @@ void PatchDownloadSession::check_path_token(const std::string& token) {
|
||||
if (token == "..") {
|
||||
throw std::runtime_error("parent directory token is not allowed");
|
||||
}
|
||||
if ((token.find('/') != string::npos) || (token.find('\\') != string::npos)) {
|
||||
if ((token.find('/') != std::string::npos) || (token.find('\\') != std::string::npos)) {
|
||||
throw std::runtime_error("directory token contains path separator");
|
||||
}
|
||||
}
|
||||
|
||||
std::string PatchDownloadSession::resolve_filename(const std::string& filename) const {
|
||||
check_path_token(filename);
|
||||
string path = this->output_dir;
|
||||
std::string path = this->output_dir;
|
||||
for (const auto& dir_name : this->dir_path) {
|
||||
path.push_back('/');
|
||||
path += dir_name;
|
||||
@@ -105,8 +103,8 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
||||
if (cmd.copyright.decode() != "Patch Server. Copyright SonicTeam, LTD. 2001") {
|
||||
throw std::runtime_error("incorrect copyright message");
|
||||
}
|
||||
this->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->channel->send(0x02);
|
||||
this->log.info_f("Enabled encryption");
|
||||
break;
|
||||
@@ -181,7 +179,7 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
||||
}
|
||||
|
||||
const auto& cmd = msg.check_size_t<S_EnterDirectory_Patch_09>();
|
||||
string dirname = cmd.name.decode();
|
||||
std::string dirname = cmd.name.decode();
|
||||
check_path_token(dirname);
|
||||
this->dir_path.emplace_back(std::move(dirname));
|
||||
std::filesystem::create_directories(this->resolve_filename(""));
|
||||
@@ -257,9 +255,9 @@ asio::awaitable<void> PatchDownloadSession::on_message(Channel::Message& msg) {
|
||||
const auto& cmd = msg.check_size_t<S_Reconnect_Patch_14>();
|
||||
|
||||
auto new_ep = make_endpoint_ipv4(cmd.address, cmd.port);
|
||||
string netloc_str = str_for_endpoint(new_ep);
|
||||
std::string netloc_str = str_for_endpoint(new_ep);
|
||||
this->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
|
||||
auto old_channel = this->channel;
|
||||
auto new_channel = SocketChannel::create(
|
||||
|
||||
+24
-31
@@ -12,8 +12,6 @@
|
||||
|
||||
#include "Loggers.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
int64_t file_mtime_int(const std::string& path) {
|
||||
auto mtime = std::filesystem::last_write_time(path);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::nanoseconds>(
|
||||
@@ -21,32 +19,27 @@ int64_t file_mtime_int(const std::string& path) {
|
||||
return sctp.time_since_epoch().count();
|
||||
}
|
||||
|
||||
PatchFileIndex::File::File(PatchFileIndex* index)
|
||||
: index(index),
|
||||
crc32(0),
|
||||
size(0) {}
|
||||
PatchFileIndex::File::File(PatchFileIndex* index) : index(index), crc32(0), size(0) {}
|
||||
|
||||
std::shared_ptr<const std::string> PatchFileIndex::File::load_data() {
|
||||
if (!this->loaded_data) {
|
||||
string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
|
||||
string full_path = this->index->root_dir + "/" + relative_path;
|
||||
std::string relative_path = phosg::join(this->path_directories, "/") + "/" + this->name;
|
||||
std::string full_path = this->index->root_dir + "/" + relative_path;
|
||||
patch_index_log.debug_f("Loading data for {}", relative_path);
|
||||
this->loaded_data = make_shared<string>(phosg::load_file(full_path));
|
||||
this->loaded_data = std::make_shared<std::string>(phosg::load_file(full_path));
|
||||
this->size = this->loaded_data->size();
|
||||
}
|
||||
return this->loaded_data;
|
||||
}
|
||||
|
||||
PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
: root_dir(root_dir) {
|
||||
|
||||
string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||
PatchFileIndex::PatchFileIndex(const std::string& root_dir) : root_dir(root_dir) {
|
||||
std::string metadata_cache_filename = root_dir + "/.metadata-cache.json";
|
||||
phosg::JSON metadata_cache_json;
|
||||
try {
|
||||
string metadata_text = phosg::load_file(metadata_cache_filename);
|
||||
std::string metadata_text = phosg::load_file(metadata_cache_filename);
|
||||
metadata_cache_json = phosg::JSON::parse(metadata_text);
|
||||
patch_index_log.debug_f("Loaded patch metadata cache from {}", metadata_cache_filename);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
metadata_cache_json = phosg::JSON::dict();
|
||||
patch_index_log.warning_f("Cannot load patch metadata cache from {}: {}", metadata_cache_filename, e.what());
|
||||
}
|
||||
@@ -56,45 +49,45 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
bool should_write_metadata_cache = false;
|
||||
phosg::JSON new_metadata_cache_json = phosg::JSON::dict();
|
||||
|
||||
vector<string> path_directories;
|
||||
function<void(const string&)> collect_dir = [&](const string& dir) -> void {
|
||||
std::vector<std::string> path_directories;
|
||||
std::function<void(const std::string&)> collect_dir = [&](const std::string& dir) -> void {
|
||||
path_directories.emplace_back(dir);
|
||||
|
||||
string relative_dirs = phosg::join(path_directories, "/");
|
||||
string full_dir_path = root_dir + '/' + relative_dirs;
|
||||
std::string relative_dirs = phosg::join(path_directories, "/");
|
||||
std::string full_dir_path = root_dir + '/' + relative_dirs;
|
||||
patch_index_log.debug_f("Listing directory {}", full_dir_path);
|
||||
|
||||
for (const auto& dir_item : std::filesystem::directory_iterator(full_dir_path)) {
|
||||
string item = dir_item.path().filename().string();
|
||||
std::string item = dir_item.path().filename().string();
|
||||
|
||||
// Skip invisible files (e.g. .DS_Store on macOS)
|
||||
if (item.starts_with(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string relative_item_path = relative_dirs + '/' + item;
|
||||
string full_item_path = root_dir + '/' + relative_item_path;
|
||||
std::string relative_item_path = relative_dirs + '/' + item;
|
||||
std::string full_item_path = root_dir + '/' + relative_item_path;
|
||||
if (std::filesystem::is_directory(full_item_path)) {
|
||||
collect_dir(item);
|
||||
} else if (std::filesystem::is_regular_file(full_item_path)) {
|
||||
|
||||
auto f = make_shared<File>(this);
|
||||
auto f = std::make_shared<File>(this);
|
||||
f->path_directories = path_directories;
|
||||
f->name = item;
|
||||
|
||||
int64_t file_mtime = file_mtime_int(full_item_path);
|
||||
|
||||
string compute_crc32s_message; // If not empty, should compute crc32s
|
||||
std::string compute_crc32s_message; // If not empty, should compute crc32s
|
||||
phosg::JSON cache_item_json;
|
||||
try {
|
||||
cache_item_json = metadata_cache_json.at(relative_item_path);
|
||||
uint64_t cached_size = cache_item_json.get_int(0);
|
||||
int64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (file_mtime != cached_mtime) {
|
||||
throw runtime_error("file has been modified");
|
||||
throw std::runtime_error("file has been modified");
|
||||
}
|
||||
if (std::filesystem::file_size(full_item_path) != cached_size) {
|
||||
throw runtime_error("file size has changed");
|
||||
throw std::runtime_error("file size has changed");
|
||||
}
|
||||
f->size = cached_size;
|
||||
f->crc32 = cache_item_json.get_int(2);
|
||||
@@ -102,7 +95,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
f->chunk_crcs.emplace_back(chunk_crc32_json->as_int());
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
compute_crc32s_message = e.what();
|
||||
}
|
||||
|
||||
@@ -110,7 +103,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
auto data = f->load_data(); // Sets f->size
|
||||
f->crc32 = phosg::crc32(data->data(), f->size);
|
||||
for (size_t x = 0; x < data->size(); x += this->CHUNK_SIZE) {
|
||||
size_t chunk_bytes = min<size_t>(f->size - x, this->CHUNK_SIZE);
|
||||
size_t chunk_bytes = std::min<size_t>(f->size - x, this->CHUNK_SIZE);
|
||||
f->chunk_crcs.emplace_back(phosg::crc32(data->data() + x, chunk_bytes));
|
||||
}
|
||||
|
||||
@@ -149,7 +142,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
try {
|
||||
phosg::save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||
patch_index_log.debug_f("Saved patch metadata cache to {}", metadata_cache_filename);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
patch_index_log.warning_f("Cannot save patch metadata cache to {}: {}", metadata_cache_filename, e.what());
|
||||
}
|
||||
} else {
|
||||
@@ -157,10 +150,10 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
}
|
||||
}
|
||||
|
||||
const vector<shared_ptr<PatchFileIndex::File>>& PatchFileIndex::all_files() const {
|
||||
const std::vector<std::shared_ptr<PatchFileIndex::File>>& PatchFileIndex::all_files() const {
|
||||
return this->files_by_patch_order;
|
||||
}
|
||||
|
||||
shared_ptr<PatchFileIndex::File> PatchFileIndex::get(const string& filename) const {
|
||||
std::shared_ptr<PatchFileIndex::File> PatchFileIndex::get(const std::string& filename) const {
|
||||
return this->files_by_name.at(filename);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ChoiceSearch.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
+21
-23
@@ -20,8 +20,6 @@
|
||||
#include "Text.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void PlayerDispDataBB::apply_dressing_room(const PlayerDispDataBBPreview& pre) {
|
||||
this->visual.name_color = pre.visual.name_color;
|
||||
this->visual.extra_model = pre.visual.extra_model;
|
||||
@@ -414,7 +412,7 @@ const char* phosg::name_for_enum<BattleRules::TechDiskMode>(BattleRules::TechDis
|
||||
case BattleRules::TechDiskMode::LIMIT_LEVEL:
|
||||
return "LIMIT_LEVEL";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::TechDiskMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::TechDiskMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -426,7 +424,7 @@ BattleRules::TechDiskMode phosg::enum_for_name<BattleRules::TechDiskMode>(const
|
||||
} else if (!strcmp(name, "LIMIT_LEVEL")) {
|
||||
return BattleRules::TechDiskMode::LIMIT_LEVEL;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::TechDiskMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::TechDiskMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +440,7 @@ const char* phosg::name_for_enum<BattleRules::WeaponAndArmorMode>(BattleRules::W
|
||||
case BattleRules::WeaponAndArmorMode::FORBID_RARES:
|
||||
return "FORBID_RARES";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::WeaponAndArmorMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -456,7 +454,7 @@ BattleRules::WeaponAndArmorMode phosg::enum_for_name<BattleRules::WeaponAndArmor
|
||||
} else if (!strcmp(name, "FORBID_RARES")) {
|
||||
return BattleRules::WeaponAndArmorMode::FORBID_RARES;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::WeaponAndArmorMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::WeaponAndArmorMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,7 +466,7 @@ const char* phosg::name_for_enum<BattleRules::MagMode>(BattleRules::MagMode v) {
|
||||
case BattleRules::MagMode::FORBID_ALL:
|
||||
return "FORBID_ALL";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::MagMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::MagMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -478,7 +476,7 @@ BattleRules::MagMode phosg::enum_for_name<BattleRules::MagMode>(const char* name
|
||||
} else if (!strcmp(name, "FORBID_ALL")) {
|
||||
return BattleRules::MagMode::FORBID_ALL;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::MagMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::MagMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,7 +490,7 @@ const char* phosg::name_for_enum<BattleRules::ToolMode>(BattleRules::ToolMode v)
|
||||
case BattleRules::ToolMode::FORBID_ALL:
|
||||
return "FORBID_ALL";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::ToolMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::ToolMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -504,7 +502,7 @@ BattleRules::ToolMode phosg::enum_for_name<BattleRules::ToolMode>(const char* na
|
||||
} else if (!strcmp(name, "FORBID_ALL")) {
|
||||
return BattleRules::ToolMode::FORBID_ALL;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::ToolMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::ToolMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,7 +514,7 @@ const char* phosg::name_for_enum<BattleRules::TrapMode>(BattleRules::TrapMode v)
|
||||
case BattleRules::TrapMode::ALL_PLAYERS:
|
||||
return "ALL_PLAYERS";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::TrapMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::TrapMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -526,7 +524,7 @@ BattleRules::TrapMode phosg::enum_for_name<BattleRules::TrapMode>(const char* na
|
||||
} else if (!strcmp(name, "ALL_PLAYERS")) {
|
||||
return BattleRules::TrapMode::ALL_PLAYERS;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::TrapMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::TrapMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +538,7 @@ const char* phosg::name_for_enum<BattleRules::MesetaMode>(BattleRules::MesetaMod
|
||||
case BattleRules::MesetaMode::CLEAR_AND_ALLOW:
|
||||
return "CLEAR_AND_ALLOW";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -552,7 +550,7 @@ BattleRules::MesetaMode phosg::enum_for_name<BattleRules::MesetaMode>(const char
|
||||
} else if (!strcmp(name, "CLEAR_AND_ALLOW")) {
|
||||
return BattleRules::MesetaMode::CLEAR_AND_ALLOW;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +564,7 @@ const char* phosg::name_for_enum<BattleRules::RespawnMode>(BattleRules::RespawnM
|
||||
case BattleRules::RespawnMode::LIMIT_LIVES:
|
||||
return "LIMIT_LIVES";
|
||||
default:
|
||||
throw invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||
throw std::invalid_argument("invalid BattleRules::MesetaDropMode value");
|
||||
}
|
||||
}
|
||||
template <>
|
||||
@@ -578,7 +576,7 @@ BattleRules::RespawnMode phosg::enum_for_name<BattleRules::RespawnMode>(const ch
|
||||
} else if (!strcmp(name, "LIMIT_LIVES")) {
|
||||
return BattleRules::RespawnMode::LIMIT_LIVES;
|
||||
} else {
|
||||
throw invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||
throw std::invalid_argument("invalid BattleRules::MesetaDropMode name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +596,7 @@ static PlayerInventoryItem v3_item(bool equipped, uint64_t first_data, uint64_t
|
||||
|
||||
const ChallengeTemplateDefinition& get_challenge_template_definition(Version version, uint32_t class_flags, size_t index) {
|
||||
// clang-format off
|
||||
static const vector<ChallengeTemplateDefinition> v2_hunter_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v2_hunter_templates({
|
||||
{0, {v2_item(true, 0x0001000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{4, {v2_item(true, 0x0001000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02010D002003F501, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{6, {v2_item(true, 0x0002000000000000, 0x0000000000000000), v2_item(true, 0x0101020000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x0201100020032103, 0x0100010000002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
@@ -616,7 +614,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
{50, {v2_item(true, 0x02058200A00F8913, 0xD107D10700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{99, {v2_item(true, 0x0205BE007017591B, 0xB90BB90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
});
|
||||
static const vector<ChallengeTemplateDefinition> v2_ranger_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v2_ranger_templates({
|
||||
{0, {v2_item(true, 0x0006000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{4, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x020D0C00F401C900, 0xF501010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{5, {v2_item(true, 0x0006000500000000, 0x0000000000000000), v2_item(true, 0x0101010000000000, 0x0000000000000000), v2_item(true, 0x0102010000000000, 0x0000000000000000), v2_item(true, 0x020D0E00F401C900, 0xBD02010000002800), v2_item(false, 0x0300000000050000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0308000000050000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
@@ -634,7 +632,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
{50, {v2_item(true, 0x020C8C00B80BC509, 0x7117C50900002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{99, {v2_item(true, 0x0206B400B80BB90B, 0x2923B90B00002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
});
|
||||
static const vector<ChallengeTemplateDefinition> v2_force_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v2_force_templates({
|
||||
{0, {v2_item(true, 0x000A000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x02000500F4010100, 0x0100010000002800), v2_item(false, 0x0300000000040000, 0x0000000000000000), v2_item(false, 0x0301000000040000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
||||
{4, {v2_item(true, 0x000A000500000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190D0020036500, 0x0100910100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||
{6, {v2_item(true, 0x000B000000000000, 0x0000000000000000), v2_item(true, 0x0101000000000000, 0x0000000000000000), v2_item(true, 0x0102000000000000, 0x0000000000000000), v2_item(true, 0x02190F002003C900, 0x0100F50100002800), v2_item(false, 0x0300000000060000, 0x0000000000000000), v2_item(false, 0x0301000000060000, 0x0000000000000000), v2_item(false, 0x0306010000030000, 0x0000000000000000), v2_item(false, 0x0306000000030000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||
@@ -653,7 +651,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
{99, {v2_item(true, 0x021CB400AC0DD107, 0xC509112700002800), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000), v2_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}} },
|
||||
});
|
||||
|
||||
static const vector<ChallengeTemplateDefinition> v3_hunter_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v3_hunter_templates({
|
||||
{0, {v3_item(true, 0x0001000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{4, {v3_item(true, 0x0001000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02010D002003F401, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{6, {v3_item(true, 0x0002000000000000, 0x0000000000000000), v3_item(true, 0x0101020000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x0201100020032003, 0x0000000028000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
@@ -671,7 +669,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
{50, {v3_item(true, 0x02058200A00F8813, 0xD007D00728000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{99, {v3_item(true, 0x0205BE007017581B, 0xB80BB80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
});
|
||||
static const vector<ChallengeTemplateDefinition> v3_ranger_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v3_ranger_templates({
|
||||
{0, {v3_item(true, 0x0006000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{4, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x020D0C00F401C800, 0xF401000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{5, {v3_item(true, 0x0006000500000000, 0x0000000000000000), v3_item(true, 0x0101010000000000, 0x0000000000000000), v3_item(true, 0x0102010000000000, 0x0000000000000000), v3_item(true, 0x020D0E00F401C800, 0xBC02000028000012), v3_item(false, 0x0300000000050000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0308000000050000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
@@ -689,7 +687,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
{50, {v3_item(true, 0x020C8C00B80BC409, 0x7017C40928000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
{99, {v3_item(true, 0x0206B400B80BB80B, 0x2823B80B28000012), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {}},
|
||||
});
|
||||
static const vector<ChallengeTemplateDefinition> v3_force_templates({
|
||||
static const std::vector<ChallengeTemplateDefinition> v3_force_templates({
|
||||
{0, {v3_item(true, 0x000A000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x02000500F4010000, 0x0000000028000012), v3_item(false, 0x0300000000040000, 0x0000000000000000), v3_item(false, 0x0301000000040000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 0}}},
|
||||
{4, {v3_item(true, 0x000A000500000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190D0020036400, 0x0000900128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||
{6, {v3_item(true, 0x000B000000000000, 0x0000000000000000), v3_item(true, 0x0101000000000000, 0x0000000000000000), v3_item(true, 0x0102000000000000, 0x0000000000000000), v3_item(true, 0x02190F002003C800, 0x0000F40128000012), v3_item(false, 0x0300000000060000, 0x0000000000000000), v3_item(false, 0x0301000000060000, 0x0000000000000000), v3_item(false, 0x0306010000030000, 0x0000000000000000), v3_item(false, 0x0306000000030000, 0x0000000000000000), v3_item(false, 0x0309000000000000, 0x0000000000000000)}, {{0x00, 2}, {0x03, 2}, {0x0D, 2}, {0x0A, 2}}},
|
||||
@@ -716,7 +714,7 @@ const ChallengeTemplateDefinition& get_challenge_template_definition(Version ver
|
||||
} else if ((class_flags & 0xE0) == 0x80) {
|
||||
return is_v1_or_v2(version) ? v2_force_templates.at(index) : v3_force_templates.at(index);
|
||||
} else {
|
||||
throw runtime_error("invalid class flags on original player");
|
||||
throw std::runtime_error("invalid class flags on original player");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include "ChoiceSearch.hh"
|
||||
#include "CommonFileFormats.hh"
|
||||
#include "FileContentsCache.hh"
|
||||
#include "ItemData.hh"
|
||||
#include "LevelTable.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
|
||||
+136
-137
@@ -29,17 +29,15 @@
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum class HandlerResult {
|
||||
FORWARD = 0,
|
||||
SUPPRESS,
|
||||
MODIFIED,
|
||||
};
|
||||
|
||||
typedef asio::awaitable<HandlerResult> (*MessageHandler)(shared_ptr<Client> c, Channel::Message& msg);
|
||||
typedef asio::awaitable<HandlerResult> (*MessageHandler)(std::shared_ptr<Client> c, Channel::Message& msg);
|
||||
|
||||
static void forward_command(shared_ptr<Client> c, bool to_server, const Channel::Message& msg, bool print_contents = true) {
|
||||
static void forward_command(std::shared_ptr<Client> c, bool to_server, const Channel::Message& msg, bool print_contents = true) {
|
||||
auto ch = to_server ? (c->proxy_session ? c->proxy_session->server_channel : nullptr) : c->channel;
|
||||
if (!ch || !ch->connected()) {
|
||||
proxy_server_log.warning_f("No endpoint is present; dropping command");
|
||||
@@ -55,20 +53,20 @@ static void forward_command(shared_ptr<Client> c, bool to_server, const Channel:
|
||||
// versions), and COMMAND-NUMBERS are the hexadecimal value in the command header field that this handler is called
|
||||
// for. If VERSIONS is omitted, the command handler is for all versions (for example, the 97 handler is like this).
|
||||
|
||||
static asio::awaitable<HandlerResult> default_handler(shared_ptr<Client>, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> default_handler(std::shared_ptr<Client>, Channel::Message&) {
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_invalid(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_invalid(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
c->log.error_f("Server sent invalid command");
|
||||
string error_str = is_v4(c->version())
|
||||
std::string error_str = is_v4(c->version())
|
||||
? std::format("Server sent invalid\ncommand: {:04X} {:08X}", msg.command, msg.flag)
|
||||
: std::format("Server sent invalid\ncommand: {:02X} {:02X}", msg.command, msg.flag);
|
||||
c->proxy_session->server_channel->disconnect();
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_1D(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> C_1D(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
if (c->ping_start_time) {
|
||||
uint64_t ping_usecs = phosg::now() - c->ping_start_time;
|
||||
c->ping_start_time = 0;
|
||||
@@ -80,11 +78,11 @@ static asio::awaitable<HandlerResult> C_1D(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_1D(shared_ptr<Client>, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_1D(std::shared_ptr<Client>, Channel::Message&) {
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_97(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_97(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
// We always assume a 97 has already been received by the client - we should have sent 97 01 before sending the
|
||||
// client to the proxy server.
|
||||
c->proxy_session->server_channel->send(0xB1, 0x00);
|
||||
@@ -204,7 +202,7 @@ static void send_9E_XB_to_server(std::shared_ptr<Client> c) {
|
||||
c->proxy_session->server_channel->send(0x9E, 0x01, &cmd, sizeof(C_LoginExtended_XB_9E));
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_9A(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_G_9A(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
// TODO: Either delete this handler or finish implementing it (flag=00/02 should do the below, 01 should send 9C,
|
||||
// anything else should end the session)
|
||||
C_LoginExtended_GC_9E cmd;
|
||||
@@ -236,9 +234,9 @@ static asio::awaitable<HandlerResult> S_G_9A(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_V123U_02_17(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (is_patch(c->version()) && msg.command == 0x17) {
|
||||
throw invalid_argument("patch server sent 17 server init");
|
||||
throw std::invalid_argument("patch server sent 17 server init");
|
||||
}
|
||||
|
||||
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
||||
@@ -246,11 +244,11 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
||||
|
||||
// This isn't forwarded to the client, so don't recreate the client's crypts
|
||||
if (uses_v3_encryption(c->version())) {
|
||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
} else {
|
||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
}
|
||||
|
||||
// Respond with an appropriate login command. We don't let the client do this because it believes it already did
|
||||
@@ -264,7 +262,7 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
||||
|
||||
case Version::DC_NTE:
|
||||
// TODO
|
||||
throw runtime_error("DC NTE proxy is not implemented");
|
||||
throw std::runtime_error("DC NTE proxy is not implemented");
|
||||
|
||||
case Version::DC_11_2000:
|
||||
case Version::DC_V1:
|
||||
@@ -301,7 +299,7 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
||||
// For command 02, send the same as if we had received 9A from the server
|
||||
co_return co_await S_G_9A(c, msg);
|
||||
}
|
||||
throw logic_error("GC init command not handled");
|
||||
throw std::logic_error("GC init command not handled");
|
||||
|
||||
case Version::XB_V3: {
|
||||
send_9E_XB_to_server(c);
|
||||
@@ -309,13 +307,13 @@ static asio::awaitable<HandlerResult> S_V123U_02_17(shared_ptr<Client> c, Channe
|
||||
}
|
||||
|
||||
case Version::BB_V4:
|
||||
throw logic_error("v1/v2/v3 server init handler should not be called on BB");
|
||||
throw std::logic_error("v1/v2/v3 server init handler should not be called on BB");
|
||||
default:
|
||||
throw logic_error("invalid game version in server init handler");
|
||||
throw std::logic_error("invalid game version in server init handler");
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_U_04(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_U_04(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
C_Login_Patch_04 ret;
|
||||
ret.username.encode(c->username);
|
||||
ret.password.encode(c->password);
|
||||
@@ -324,7 +322,7 @@ static asio::awaitable<HandlerResult> S_U_04(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_03(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// Most servers don't include after_message or have a shorter after_message than newserv does, so don't require it
|
||||
const auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||
|
||||
@@ -332,11 +330,11 @@ static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Mess
|
||||
// the server has the luxury of being able to try all the crypts it knows to detect what type the client uses, but
|
||||
// the client can't do this since it sends the first encrypted data on the connection.
|
||||
if (!c->bb_detector_crypt) {
|
||||
throw logic_error("Client proxy session started with missing detector crypt");
|
||||
throw std::logic_error("Client proxy session started with missing detector crypt");
|
||||
}
|
||||
c->proxy_session->server_channel->crypt_in = make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||
c->proxy_session->server_channel->crypt_in = std::make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||
c->bb_detector_crypt, cmd.server_key.data(), sizeof(cmd.server_key), false);
|
||||
c->proxy_session->server_channel->crypt_out = make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||
c->proxy_session->server_channel->crypt_out = std::make_shared<PSOBBMultiKeyImitatorEncryption>(
|
||||
c->bb_detector_crypt, cmd.client_key.data(), sizeof(cmd.client_key), false);
|
||||
|
||||
C_LoginWithHardwareInfo_BB_93 resp;
|
||||
@@ -360,7 +358,7 @@ static asio::awaitable<HandlerResult> S_B_03(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_E6(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_E6(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
const auto& cmd = msg.check_size_t<S_ClientInit_BB_00E6>(0xFFFF);
|
||||
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
||||
c->bb_security_token = cmd.security_token;
|
||||
@@ -376,7 +374,7 @@ static asio::awaitable<HandlerResult> S_B_E6(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_V123_04(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// Suppress extremely short commands from the server instead of disconnecting
|
||||
if (msg.data.size() < offsetof(S_UpdateClientConfig_V3_04, client_config)) {
|
||||
le_uint64_t checksum = phosg::random_object<uint64_t>() & 0x0000FFFFFFFFFFFF;
|
||||
@@ -396,7 +394,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
||||
if (c->proxy_session->remote_guild_card_number != cmd.guild_card_number) {
|
||||
c->proxy_session->remote_guild_card_number = cmd.guild_card_number;
|
||||
c->log.info_f("Remote guild card number set to {}", c->proxy_session->remote_guild_card_number);
|
||||
string message = std::format(
|
||||
std::string message = std::format(
|
||||
"The remote server\nhas assigned your\nGuild Card number:\n$C6{}", c->proxy_session->remote_guild_card_number);
|
||||
send_ship_info(c->channel, message);
|
||||
}
|
||||
@@ -411,7 +409,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
||||
memcpy(c->proxy_session->remote_client_config_data.data(),
|
||||
had_guild_card_number ? "t Lobby Server. Copyright SEGA E" : "t Port Map. Copyright SEGA Enter", 0x20);
|
||||
memcpy(c->proxy_session->remote_client_config_data.data(), &cmd.client_config,
|
||||
min<size_t>(msg.data.size() - offsetof(S_UpdateClientConfig_V3_04, client_config),
|
||||
std::min<size_t>(msg.data.size() - offsetof(S_UpdateClientConfig_V3_04, client_config),
|
||||
c->proxy_session->remote_client_config_data.bytes()));
|
||||
|
||||
// If the guild card number was not set, pretend (to the server) that this is the first 04 command the client has
|
||||
@@ -424,7 +422,7 @@ static asio::awaitable<HandlerResult> S_V123_04(shared_ptr<Client> c, Channel::M
|
||||
co_return c->login ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_V123_06(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_V123_06(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
bool modified = false;
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
||||
@@ -449,7 +447,7 @@ static asio::awaitable<HandlerResult> S_V123_06(shared_ptr<Client> c, Channel::M
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> S_41(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_41(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->login) {
|
||||
auto& cmd = msg.check_size_t<CmdT>();
|
||||
if ((cmd.searcher_guild_card_number == c->proxy_session->remote_guild_card_number) &&
|
||||
@@ -485,7 +483,7 @@ constexpr MessageHandler S_P_41 = &S_41<S_GuildCardSearchResult_PC_41>;
|
||||
constexpr MessageHandler S_B_41 = &S_41<S_GuildCardSearchResult_BB_41>;
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> S_81(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_81(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
bool modified = false;
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
auto& cmd = msg.check_size_t<CmdT>();
|
||||
@@ -505,7 +503,7 @@ constexpr MessageHandler S_DGX_81 = &S_81<SC_SimpleMail_DC_V3_81>;
|
||||
constexpr MessageHandler S_P_81 = &S_81<SC_SimpleMail_PC_81>;
|
||||
constexpr MessageHandler S_B_81 = &S_81<SC_SimpleMail_BB_81>;
|
||||
|
||||
static asio::awaitable<HandlerResult> S_88(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_88(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// If the client isn't in the lobby, suppress the command (Ep3 can crash if it receives this while loading; other
|
||||
// versions probably also will crash)
|
||||
if (!c->proxy_session->is_in_lobby) {
|
||||
@@ -526,19 +524,19 @@ static asio::awaitable<HandlerResult> S_88(shared_ptr<Client> c, Channel::Messag
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B1(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_B1(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
// Block all time updates from the remote server, so client's time remains consistent
|
||||
c->proxy_session->server_channel->send(0x99, 0x00);
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
const auto& cmd = msg.check_size_t<S_ExecuteCode_B2>(0xFFFF);
|
||||
|
||||
if (cmd.code_size && c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
uint64_t filename_timestamp = phosg::now();
|
||||
string code = msg.data.substr(sizeof(S_ExecuteCode_B2));
|
||||
std::string code = msg.data.substr(sizeof(S_ExecuteCode_B2));
|
||||
|
||||
if (c->check_flag(Client::Flag::ENCRYPTED_SEND_FUNCTION_CALL)) {
|
||||
phosg::StringReader r(code);
|
||||
@@ -547,7 +545,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
uint32_t key = is_big_endian ? r.get_u32b() : r.get_u32l();
|
||||
|
||||
PSOV2Encryption crypt(key);
|
||||
string decrypted_data;
|
||||
std::string decrypted_data;
|
||||
if (is_big_endian) {
|
||||
phosg::StringWriter w;
|
||||
while (!r.eof()) {
|
||||
@@ -563,7 +561,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
if (decompressed_size < code.size()) {
|
||||
code.resize(decompressed_size);
|
||||
} else if (decompressed_size > code.size()) {
|
||||
throw runtime_error("decompressed code smaller than expected");
|
||||
throw std::runtime_error("decompressed code smaller than expected");
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -573,7 +571,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
string output_filename = std::format("code.{}.bin", filename_timestamp);
|
||||
std::string output_filename = std::format("code.{}.bin", filename_timestamp);
|
||||
phosg::save_file(output_filename, msg.data);
|
||||
c->log.info_f("Wrote code from server to file {}", output_filename);
|
||||
|
||||
@@ -585,7 +583,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
if (is_ppc || is_x86 || is_sh4) {
|
||||
try {
|
||||
if (code.size() < sizeof(FooterT)) {
|
||||
throw runtime_error("code section is too small");
|
||||
throw std::runtime_error("code section is too small");
|
||||
}
|
||||
|
||||
size_t footer_offset = code.size() - sizeof(FooterT);
|
||||
@@ -593,7 +591,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
phosg::StringReader r(code.data(), code.size());
|
||||
const auto& footer = r.pget<FooterT>(footer_offset);
|
||||
|
||||
multimap<uint32_t, string> labels;
|
||||
std::multimap<uint32_t, std::string> labels;
|
||||
r.go(footer.relocations_offset);
|
||||
uint32_t reloc_offset = 0;
|
||||
for (size_t x = 0; x < footer.num_relocations; x++) {
|
||||
@@ -604,7 +602,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
labels.emplace(footer_offset, "footer");
|
||||
labels.emplace(r.pget<U32T<BE>>(footer.root_offset), "start");
|
||||
|
||||
string disassembly;
|
||||
std::string disassembly;
|
||||
if (is_ppc) {
|
||||
disassembly = ResourceDASM::PPC32Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
||||
} else if (is_x86) {
|
||||
@@ -613,7 +611,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
disassembly = ResourceDASM::SH4Emulator::disassemble(&r.pget<uint8_t>(0, code.size()), code.size(), 0, &labels);
|
||||
} else {
|
||||
// We shouldn't have entered the outer if statement if this happens
|
||||
throw logic_error("unsupported architecture");
|
||||
throw std::logic_error("unsupported architecture");
|
||||
}
|
||||
|
||||
output_filename = std::format("code.{}.txt", filename_timestamp);
|
||||
@@ -626,7 +624,7 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
c->log.info_f("Wrote disassembly to file {}", output_filename);
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.info_f("Failed to disassemble code from server: {}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -645,10 +643,10 @@ static asio::awaitable<HandlerResult> S_B2(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_B3(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_B3(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto cmd = msg.check_size_t<C_ExecuteCodeResult_B3>();
|
||||
|
||||
shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
|
||||
std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>> promise;
|
||||
if (!c->function_call_response_queue.empty()) {
|
||||
promise = std::move(c->function_call_response_queue.front());
|
||||
c->function_call_response_queue.pop_front();
|
||||
@@ -662,18 +660,18 @@ static asio::awaitable<HandlerResult> C_B3(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_B_E0(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> C_B_E0(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
auto ret = c->proxy_session->bb_client_sent_E0 ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||
c->proxy_session->bb_client_sent_E0 = true;
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_E2(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_E2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
const auto& cmd = msg.check_size_t<S_SyncSystemFile_BB_E2>();
|
||||
uint64_t ts = phosg::now();
|
||||
string system_filename = std::format("system.{}.psosys", ts);
|
||||
string team_membership_filename = std::format("system.{}.psosysteam", ts);
|
||||
std::string system_filename = std::format("system.{}.psosys", ts);
|
||||
std::string team_membership_filename = std::format("system.{}.psosysteam", ts);
|
||||
phosg::save_object_file(system_filename, cmd.system_file);
|
||||
phosg::save_object_file(team_membership_filename, cmd.team_membership);
|
||||
c->log.info_f("Wrote system file to {}", system_filename);
|
||||
@@ -682,9 +680,9 @@ static asio::awaitable<HandlerResult> S_B_E2(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_E7(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_E7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
string output_filename = std::format("player.{}.psochar", phosg::now());
|
||||
std::string output_filename = std::format("player.{}.psochar", phosg::now());
|
||||
auto f = phosg::fopen_unique(output_filename, "wb");
|
||||
PSOCommandHeaderBB header = {msg.data.size() + sizeof(PSOCommandHeaderBB), msg.command, msg.flag};
|
||||
phosg::fwritex(f.get(), &header, sizeof(header));
|
||||
@@ -694,7 +692,7 @@ static asio::awaitable<HandlerResult> S_B_E7(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_DC(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_DC(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x02DC)) {
|
||||
const auto& cmd = msg.check_size_t<S_GuildCardFileChunk_02DC>(8, sizeof(S_GuildCardFileChunk_02DC));
|
||||
size_t chunk_size = msg.data.size() - 8;
|
||||
@@ -716,11 +714,11 @@ static asio::awaitable<HandlerResult> S_B_DC(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_B_DC(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_B_DC(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES) && (msg.command == 0x03DC)) {
|
||||
const auto& cmd = msg.check_size_t<C_GuildCardDataRequest_BB_03DC>();
|
||||
if ((cmd.cont == 0) && c->proxy_session->bb_guild_card_data) {
|
||||
string output_filename = std::format("guildcard.{}.psocard", phosg::now());
|
||||
std::string output_filename = std::format("guildcard.{}.psocard", phosg::now());
|
||||
phosg::save_object_file(output_filename, *c->proxy_session->bb_guild_card_data);
|
||||
c->log.info_f("Wrote Guild Card data to {}", output_filename);
|
||||
}
|
||||
@@ -728,7 +726,7 @@ static asio::awaitable<HandlerResult> C_B_DC(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_EB(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
if (msg.command == 0x01EB) {
|
||||
const auto* entries = &msg.check_size_t<S_StreamFileIndexEntry_BB_01EB>(
|
||||
@@ -758,7 +756,7 @@ static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Mess
|
||||
memcpy(c->proxy_session->bb_stream_file_data.data() + chunk_offset, cmd.data.data(), chunk_size);
|
||||
c->proxy_session->bb_stream_file_data_received += chunk_size;
|
||||
if (c->proxy_session->bb_stream_file_data_received == c->proxy_session->bb_stream_file_data.size()) {
|
||||
string output_prefix = std::format("streamfile.{}.", phosg::now());
|
||||
std::string output_prefix = std::format("streamfile.{}.", phosg::now());
|
||||
for (const auto& entry : c->proxy_session->bb_stream_file_entries) {
|
||||
std::string filename = entry.filename.decode();
|
||||
std::string sanitized_filename = filename;
|
||||
@@ -785,7 +783,7 @@ static asio::awaitable<HandlerResult> S_B_EB(shared_ptr<Client> c, Channel::Mess
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> S_C4(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_C4(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
bool modified = false;
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
size_t expected_size = sizeof(CmdT) * msg.flag;
|
||||
@@ -805,7 +803,7 @@ constexpr MessageHandler S_DGX_C4 = &S_C4<S_ChoiceSearchResultEntry_DC_V3_C4>;
|
||||
constexpr MessageHandler S_P_C4 = &S_C4<S_ChoiceSearchResultEntry_PC_C4>;
|
||||
constexpr MessageHandler S_B_C4 = &S_C4<S_ChoiceSearchResultEntry_BB_C4>;
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_E4(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_E4(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto& cmd = msg.check_size_t<S_CardBattleTableState_Ep3_E4>();
|
||||
bool modified = false;
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
@@ -819,7 +817,7 @@ static asio::awaitable<HandlerResult> S_G_E4(shared_ptr<Client> c, Channel::Mess
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_22(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_B_22(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// We use this command (which is sent before the init encryption command) to detect a particular server behavior that
|
||||
// we'll have to work around later. It looks like this command's existence is an anti-proxy measure, since this
|
||||
// command is 0x34 bytes in total, and the logic that adds padding bytes when the command size isn't a multiple of 8
|
||||
@@ -833,7 +831,7 @@ static asio::awaitable<HandlerResult> S_B_22(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_19_U_14(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// If the command is shorter than 6 bytes, use the previous server command to fill it in. This simulates a behavior
|
||||
// used by some private servers where a longer previous command is used to fill part of the client's receive buffer
|
||||
// with meaningful data, then an intentionally undersize 19 command is sent which results in the client using the
|
||||
@@ -869,9 +867,9 @@ static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::M
|
||||
}
|
||||
|
||||
// Replace the server channel with a new channel to the new endpoint
|
||||
string netloc_str = str_for_endpoint(new_ep);
|
||||
std::string netloc_str = str_for_endpoint(new_ep);
|
||||
c->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
auto sock = std::make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
|
||||
// Close the old channel only after replacing it with the new one
|
||||
auto s = c->require_server_state();
|
||||
@@ -895,7 +893,7 @@ static asio::awaitable<HandlerResult> S_19_U_14(shared_ptr<Client> c, Channel::M
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_V3_1A_D5(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_V3_1A_D5(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
// If the client is a version that sends close confirmations and the client has the no-close-confirmation flag set in
|
||||
// its newserv client config, send a fake confirmation to the remote server immediately.
|
||||
if (is_v3(c->version()) && c->check_flag(Client::Flag::NO_D6)) {
|
||||
@@ -904,7 +902,7 @@ static asio::awaitable<HandlerResult> S_V3_1A_D5(shared_ptr<Client> c, Channel::
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_V3_BB_DA(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_V3_BB_DA(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// This command is supported on all V3 and V4 versions except Ep1&2 Trial
|
||||
if (c->version() == Version::GC_NTE) {
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
@@ -916,7 +914,7 @@ static asio::awaitable<HandlerResult> S_V3_BB_DA(shared_ptr<Client> c, Channel::
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> SC_6x60_6xA2(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (!c->proxy_session->is_in_game) {
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
@@ -944,7 +942,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
||||
case ProxyDropMode::INTERCEPT:
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid drop mode");
|
||||
throw std::logic_error("invalid drop mode");
|
||||
}
|
||||
|
||||
if (!c->proxy_session->item_creator) {
|
||||
@@ -982,7 +980,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
||||
c->log.info_f("No item was created");
|
||||
} else {
|
||||
auto s = c->require_server_state();
|
||||
string name = s->describe_item(c->version(), res.item);
|
||||
std::string name = s->describe_item(c->version(), res.item);
|
||||
c->log.info_f("Entity {:04X} (area {:02X}) created item {}", cmd.entity_index, cmd.effective_area, name);
|
||||
res.item.id = c->proxy_session->next_item_id++;
|
||||
c->log.info_f("Creating item {:08X} at {:02X}:{:g},{:g} for all clients",
|
||||
@@ -994,7 +992,7 @@ static asio::awaitable<HandlerResult> SC_6x60_6xA2(shared_ptr<Client> c, Channel
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_6x(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto s = c->require_server_state();
|
||||
|
||||
if (msg.data.size() < 4) {
|
||||
@@ -1044,7 +1042,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
|
||||
case 0x46: {
|
||||
const auto& header = msg.check_size_t<G_AttackFinished_Header_6x46>(0xFFFF);
|
||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) {
|
||||
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_AttackFinished_Header_6x46) / 4, 10)) {
|
||||
c->log.warning_f("Blocking subcommand 6x46 with invalid count");
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
@@ -1053,7 +1051,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
|
||||
case 0x47: {
|
||||
const auto& header = msg.check_size_t<G_CastTechnique_Header_6x47>(0xFFFF);
|
||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_CastTechnique_Header_6x47) / 4, 10)) {
|
||||
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_CastTechnique_Header_6x47) / 4, 10)) {
|
||||
c->log.warning_f("Blocking subcommand 6x47 with invalid count");
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
@@ -1062,7 +1060,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
|
||||
case 0x49: {
|
||||
const auto& header = msg.check_size_t<G_ExecutePhotonBlast_Header_6x49>(0xFFFF);
|
||||
if (header.target_count > min<size_t>(header.header.size - sizeof(G_ExecutePhotonBlast_Header_6x49) / 4, 10)) {
|
||||
if (header.target_count > std::min<size_t>(header.header.size - sizeof(G_ExecutePhotonBlast_Header_6x49) / 4, 10)) {
|
||||
c->log.warning_f("Blocking subcommand 6x49 with invalid count");
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
@@ -1084,10 +1082,10 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
case 0x6A: {
|
||||
auto& cmd = msg.check_size_t<G_SetBossWarpFlags_6x6A>();
|
||||
if (c->proxy_session->map_state) {
|
||||
shared_ptr<MapState::ObjectState> obj_st;
|
||||
std::shared_ptr<MapState::ObjectState> obj_st;
|
||||
try {
|
||||
obj_st = c->proxy_session->map_state->object_state_for_index(c->version(), cmd.header.entity_id - 0x4000);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.warning_f("Invalid object reference ({})", e.what());
|
||||
}
|
||||
|
||||
@@ -1203,8 +1201,8 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
const auto& header = msg.check_size_t<G_MapSubsubcommand_Ep3_6xB6>(0xFFFF);
|
||||
if (header.subsubcommand == 0x00000041) {
|
||||
const auto& cmd = msg.check_size_t<G_MapData_Ep3_6xB6x41>(0xFFFF);
|
||||
string filename = std::format("map{:08X}.{}.mnmd", cmd.map_number, phosg::now());
|
||||
string map_data = prs_decompress(msg.data.data() + sizeof(cmd), msg.data.size() - sizeof(cmd));
|
||||
std::string filename = std::format("map{:08X}.{}.mnmd", cmd.map_number, phosg::now());
|
||||
std::string map_data = prs_decompress(msg.data.data() + sizeof(cmd), msg.data.size() - sizeof(cmd));
|
||||
phosg::save_file(filename, map_data);
|
||||
if ((map_data.size() != sizeof(Episode3::MapDefinition)) &&
|
||||
(map_data.size() != sizeof(Episode3::MapDefinitionTrial))) {
|
||||
@@ -1245,7 +1243,7 @@ static asio::awaitable<HandlerResult> S_6x(shared_ptr<Client> c, Channel::Messag
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_GXB_61(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_GXB_61(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
bool modified = false;
|
||||
// TODO: We should check if the info board text was actually modified and return MODIFIED if so.
|
||||
|
||||
@@ -1284,7 +1282,7 @@ static asio::awaitable<HandlerResult> C_GXB_61(shared_ptr<Client> c, Channel::Me
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_GX_D9(shared_ptr<Client>, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_GX_D9(std::shared_ptr<Client>, Channel::Message& msg) {
|
||||
phosg::strip_trailing_zeroes(msg.data);
|
||||
msg.data = add_color(msg.data);
|
||||
msg.data.push_back(0);
|
||||
@@ -1295,19 +1293,19 @@ static asio::awaitable<HandlerResult> C_GX_D9(shared_ptr<Client>, Channel::Messa
|
||||
co_return HandlerResult::MODIFIED;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_B_D9(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_B_D9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
try {
|
||||
phosg::strip_trailing_zeroes(msg.data);
|
||||
if (msg.data.size() & 1) {
|
||||
msg.data.push_back(0);
|
||||
}
|
||||
string decoded = tt_utf16_to_utf8(msg.data.data(), msg.data.size());
|
||||
std::string decoded = tt_utf16_to_utf8(msg.data.data(), msg.data.size());
|
||||
add_color_inplace(decoded);
|
||||
msg.data = tt_utf8_to_utf16(decoded.data(), decoded.size());
|
||||
while (msg.data.size() & 3) {
|
||||
msg.data.push_back(0);
|
||||
}
|
||||
} catch (const runtime_error& e) {
|
||||
} catch (const std::runtime_error& e) {
|
||||
c->log.warning_f("Failed to decode and unescape D9 command: {}", e.what());
|
||||
}
|
||||
// TODO: We should check if the info board text was actually modified and return HandlerResult::FORWARD if not.
|
||||
@@ -1315,16 +1313,16 @@ static asio::awaitable<HandlerResult> C_B_D9(shared_ptr<Client> c, Channel::Mess
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static asio::awaitable<HandlerResult> S_44_A6(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_44_A6(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
const auto& cmd = msg.check_size_t<T>();
|
||||
|
||||
string filename = cmd.filename.decode();
|
||||
string output_filename;
|
||||
std::string filename = cmd.filename.decode();
|
||||
std::string output_filename;
|
||||
bool is_download = (msg.command == 0xA6);
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
size_t extension_offset = filename.rfind('.');
|
||||
string basename, extension;
|
||||
if (extension_offset != string::npos) {
|
||||
std::string basename, extension;
|
||||
if (extension_offset != std::string::npos) {
|
||||
basename = filename.substr(0, extension_offset);
|
||||
extension = filename.substr(extension_offset);
|
||||
if (extension == ".bin" && is_ep3(c->version())) {
|
||||
@@ -1368,15 +1366,15 @@ constexpr MessageHandler S_PG_44_A6 = &S_44_A6<S_OpenFile_PC_GC_44_A6>;
|
||||
constexpr MessageHandler S_X_44_A6 = &S_44_A6<S_OpenFile_XB_44_A6>;
|
||||
constexpr MessageHandler S_B_44_A6 = &S_44_A6<S_OpenFile_BB_44_A6>;
|
||||
|
||||
static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_13_A7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
||||
bool modified = false;
|
||||
|
||||
ProxySession::SavingFile* sf = nullptr;
|
||||
try {
|
||||
sf = &c->proxy_session->saving_files.at(cmd.filename.decode());
|
||||
} catch (const out_of_range&) {
|
||||
string filename = cmd.filename.decode();
|
||||
} catch (const std::out_of_range&) {
|
||||
std::string filename = cmd.filename.decode();
|
||||
c->log.warning_f("Received data for non-open file {}", filename);
|
||||
}
|
||||
if (!sf) {
|
||||
@@ -1386,7 +1384,7 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
||||
bool is_last_block = (cmd.data_size != 0x400);
|
||||
size_t block_offset = msg.flag * 0x400;
|
||||
size_t allowed_block_size = (block_offset < sf->total_size)
|
||||
? min<size_t>(sf->total_size - block_offset, 0x400)
|
||||
? std::min<size_t>(sf->total_size - block_offset, 0x400)
|
||||
: 0;
|
||||
|
||||
if (cmd.data_size > allowed_block_size) {
|
||||
@@ -1421,9 +1419,9 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
||||
|
||||
if (!sf->is_download) {
|
||||
if (sf->basename.ends_with(".bin")) {
|
||||
c->proxy_session->last_bin_contents = make_shared<std::string>(prs_decompress(sf->data));
|
||||
c->proxy_session->last_bin_contents = std::make_shared<std::string>(prs_decompress(sf->data));
|
||||
} else if (sf->basename.ends_with(".dat")) {
|
||||
c->proxy_session->last_dat_contents = make_shared<std::string>(prs_decompress(sf->data));
|
||||
c->proxy_session->last_dat_contents = std::make_shared<std::string>(prs_decompress(sf->data));
|
||||
}
|
||||
|
||||
if (c->proxy_session->last_bin_contents && c->proxy_session->last_dat_contents) {
|
||||
@@ -1436,23 +1434,23 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
||||
c->version(),
|
||||
c->language());
|
||||
|
||||
auto map_file = make_shared<MapFile>(c->proxy_session->last_dat_contents);
|
||||
auto map_file = std::make_shared<MapFile>(c->proxy_session->last_dat_contents);
|
||||
auto materialized_map_file = map_file->materialize_random_sections(c->proxy_session->lobby_random_seed);
|
||||
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
map_files.at(static_cast<size_t>(c->version())) = materialized_map_file;
|
||||
auto supermap = make_shared<SuperMap>(map_files, meta.get_floor_to_area());
|
||||
auto supermap = std::make_shared<SuperMap>(map_files, meta.get_floor_to_area());
|
||||
|
||||
c->proxy_session->map_state = make_shared<MapState>(
|
||||
c->proxy_session->map_state = std::make_shared<MapState>(
|
||||
c->id,
|
||||
c->proxy_session->lobby_difficulty,
|
||||
c->proxy_session->lobby_event,
|
||||
c->proxy_session->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||
supermap);
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.warning_f("Failed to load quest map: {}", e.what());
|
||||
c->proxy_session->map_state.reset();
|
||||
}
|
||||
@@ -1468,7 +1466,7 @@ static asio::awaitable<HandlerResult> S_13_A7(shared_ptr<Client> c, Channel::Mes
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_B7(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_B7(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (is_ep3(c->version())) {
|
||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = msg.check_size_t<S_RankUpdate_Ep3_B7>();
|
||||
@@ -1484,7 +1482,7 @@ static asio::awaitable<HandlerResult> S_G_B7(shared_ptr<Client> c, Channel::Mess
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_B8(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
if (msg.data.size() < 4) {
|
||||
c->log.warning_f("Card list data size is too small; not saving file");
|
||||
@@ -1498,7 +1496,7 @@ static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
string output_filename = std::format("card-definitions.{}.mnr", phosg::now());
|
||||
std::string output_filename = std::format("card-definitions.{}.mnr", phosg::now());
|
||||
phosg::save_file(output_filename, r.read(size));
|
||||
c->log.info_f("Wrote {} bytes to {}", size, output_filename);
|
||||
}
|
||||
@@ -1510,19 +1508,19 @@ static asio::awaitable<HandlerResult> S_G_B8(shared_ptr<Client> c, Channel::Mess
|
||||
co_return is_ep3(c->version()) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_B9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_SAVE_FILES)) {
|
||||
try {
|
||||
const auto& header = msg.check_size_t<S_UpdateMediaHeader_Ep3_B9>(0xFFFF);
|
||||
|
||||
if (msg.data.size() - sizeof(header) < header.size) {
|
||||
throw runtime_error("Media data size extends beyond end of command; not saving file");
|
||||
throw std::runtime_error("Media data size extends beyond end of command; not saving file");
|
||||
}
|
||||
|
||||
string decompressed_data = prs_decompress(
|
||||
std::string decompressed_data = prs_decompress(
|
||||
msg.data.data() + sizeof(header), msg.data.size() - sizeof(header));
|
||||
|
||||
string output_filename = std::format("media-update.{}", phosg::now());
|
||||
std::string output_filename = std::format("media-update.{}", phosg::now());
|
||||
if (header.type == 1) {
|
||||
output_filename += ".gvm";
|
||||
} else if (header.type == 2 || header.type == 3) {
|
||||
@@ -1532,7 +1530,7 @@ static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Mess
|
||||
}
|
||||
phosg::save_file(output_filename, decompressed_data);
|
||||
c->log.info_f("Wrote {} bytes to {}", decompressed_data.size(), output_filename);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.warning_f("Failed to save file: {}", e.what());
|
||||
}
|
||||
}
|
||||
@@ -1541,7 +1539,7 @@ static asio::awaitable<HandlerResult> S_G_B9(shared_ptr<Client> c, Channel::Mess
|
||||
co_return (c->version() == Version::GC_EP3) ? HandlerResult::FORWARD : HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_G_B9(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> C_G_B9(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
if (c->proxy_session->suppress_next_ep3_media_update_confirmation) {
|
||||
c->proxy_session->suppress_next_ep3_media_update_confirmation = false;
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
@@ -1549,7 +1547,7 @@ static asio::awaitable<HandlerResult> C_G_B9(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_EF(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_EF(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (is_ep3(c->version())) {
|
||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = msg.check_size_t<S_StartCardAuction_Ep3_EF>(offsetof(S_StartCardAuction_Ep3_EF, unused), 0xFFFF);
|
||||
@@ -1564,12 +1562,12 @@ static asio::awaitable<HandlerResult> S_G_EF(shared_ptr<Client> c, Channel::Mess
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_B_EF(shared_ptr<Client>, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_B_EF(std::shared_ptr<Client>, Channel::Message&) {
|
||||
// See the comments on EF in CommandFormats.hh for why we unconditionally suppress these.
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_G_BA(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_G_BA(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (c->check_flag(Client::Flag::PROXY_EP3_INFINITE_MESETA_ENABLED)) {
|
||||
auto& cmd = msg.check_size_t<S_MesetaTransaction_Ep3_BA>();
|
||||
if (cmd.current_meseta != 1000000) {
|
||||
@@ -1580,7 +1578,7 @@ static asio::awaitable<HandlerResult> S_G_BA(shared_ptr<Client> c, Channel::Mess
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static void update_leader_id(shared_ptr<Client> c, uint8_t leader_id) {
|
||||
static void update_leader_id(std::shared_ptr<Client> c, uint8_t leader_id) {
|
||||
if (c->proxy_session->leader_client_id != leader_id) {
|
||||
c->proxy_session->leader_client_id = leader_id;
|
||||
c->log.info_f("Changed room leader to {:X}", c->proxy_session->leader_client_id);
|
||||
@@ -1592,7 +1590,7 @@ static void update_leader_id(shared_ptr<Client> c, uint8_t leader_id) {
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> S_65_67_68_EB(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_65_67_68_EB(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (msg.command == 0x67) {
|
||||
c->proxy_session->clear_lobby_players(12);
|
||||
c->proxy_session->is_in_lobby = true;
|
||||
@@ -1625,7 +1623,7 @@ static asio::awaitable<HandlerResult> S_65_67_68_EB(shared_ptr<Client> c, Channe
|
||||
if (index >= c->proxy_session->lobby_players.size()) {
|
||||
c->log.warning_f("Ignoring invalid player index {} at position {}", index, x);
|
||||
} else {
|
||||
string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language));
|
||||
std::string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language));
|
||||
if (c->login && (entry.lobby_data.guild_card_number == c->proxy_session->remote_guild_card_number)) {
|
||||
num_replacements++;
|
||||
if (c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
@@ -1714,7 +1712,7 @@ Episode get_episode<S_JoinGame_Ep3_64>(const S_JoinGame_Ep3_64&) {
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_64(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
CmdT* cmd;
|
||||
S_JoinGame_Ep3_64* cmd_ep3 = nullptr;
|
||||
if ((c->sub_version >= 0x40) && is_v3(c->version())) {
|
||||
@@ -1789,13 +1787,13 @@ static asio::awaitable<HandlerResult> S_64(shared_ptr<Client> c, Channel::Messag
|
||||
c->proxy_session->lobby_mode,
|
||||
c->proxy_session->lobby_difficulty,
|
||||
cmd->variations);
|
||||
c->proxy_session->map_state = make_shared<MapState>(
|
||||
c->proxy_session->map_state = std::make_shared<MapState>(
|
||||
c->id,
|
||||
c->proxy_session->lobby_difficulty,
|
||||
c->proxy_session->lobby_event,
|
||||
c->proxy_session->lobby_random_seed,
|
||||
MapState::DEFAULT_RARE_ENEMIES,
|
||||
make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||
std::make_shared<MT19937Generator>(c->proxy_session->lobby_random_seed),
|
||||
supermaps);
|
||||
} else {
|
||||
c->proxy_session->map_state.reset();
|
||||
@@ -1834,7 +1832,7 @@ constexpr MessageHandler S_G_64 = &S_64<S_JoinGame_GC_64>;
|
||||
constexpr MessageHandler S_X_64 = &S_64<S_JoinGame_XB_64>;
|
||||
constexpr MessageHandler S_B_64 = &S_64<S_JoinGame_BB_64>;
|
||||
|
||||
static asio::awaitable<HandlerResult> S_E8(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_E8(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto& cmd = msg.check_size_t<S_JoinSpectatorTeam_Ep3_E8>();
|
||||
|
||||
c->floor = 0;
|
||||
@@ -1896,7 +1894,7 @@ static asio::awaitable<HandlerResult> S_E8(shared_ptr<Client> c, Channel::Messag
|
||||
co_return modified ? HandlerResult::MODIFIED : HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_AC(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> S_AC(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
if (!c->proxy_session->is_in_game) {
|
||||
co_return HandlerResult::SUPPRESS;
|
||||
} else {
|
||||
@@ -1905,7 +1903,7 @@ static asio::awaitable<HandlerResult> S_AC(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> S_66_69_E9(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
// Schtserv sends a large command here for unknown reasons. The client ignores the extra data, so we allow the large
|
||||
// command here.
|
||||
const auto& cmd = msg.check_size_t<S_LeaveLobby_66_69_Ep3_E9>(0xFFFF);
|
||||
@@ -1914,7 +1912,7 @@ static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::
|
||||
c->log.warning_f("Lobby leave command references missing position");
|
||||
} else {
|
||||
auto& p = c->proxy_session->lobby_players[index];
|
||||
string name = escape_player_name(p.name);
|
||||
std::string name = escape_player_name(p.name);
|
||||
if (c->check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED)) {
|
||||
send_text_message_fmt(c->channel, "$C4Leave: {}/{}\n{}", index, p.guild_card_number, name);
|
||||
}
|
||||
@@ -1926,7 +1924,7 @@ static asio::awaitable<HandlerResult> S_66_69_E9(shared_ptr<Client> c, Channel::
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_98(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_98(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
c->floor = 0x0F;
|
||||
c->proxy_session->is_in_lobby = false;
|
||||
c->proxy_session->is_in_game = false;
|
||||
@@ -1948,11 +1946,11 @@ static asio::awaitable<HandlerResult> C_98(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_06(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (msg.data.size() >= 0x0C) {
|
||||
const auto& cmd = msg.check_size_t<SC_TextHeader_01_06_11_B0_EE>(0xFFFF);
|
||||
|
||||
string text = msg.data.substr(sizeof(cmd));
|
||||
std::string text = msg.data.substr(sizeof(cmd));
|
||||
phosg::strip_trailing_zeroes(text);
|
||||
|
||||
uint8_t private_flags = 0;
|
||||
@@ -1968,7 +1966,7 @@ static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Messag
|
||||
} else {
|
||||
text = tt_decode_marked(text, c->language(), false);
|
||||
}
|
||||
} catch (const runtime_error& e) {
|
||||
} catch (const std::runtime_error& e) {
|
||||
c->log.warning_f("Failed to decode and unescape chat text: {}", e.what());
|
||||
text.clear();
|
||||
}
|
||||
@@ -2002,7 +2000,7 @@ static asio::awaitable<HandlerResult> C_06(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
}
|
||||
|
||||
static asio::awaitable<HandlerResult> C_40(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_40(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
bool modified = false;
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
auto& cmd = msg.check_size_t<C_GuildCardSearch_40>();
|
||||
@@ -2019,7 +2017,7 @@ static asio::awaitable<HandlerResult> C_40(shared_ptr<Client> c, Channel::Messag
|
||||
}
|
||||
|
||||
template <typename CmdT>
|
||||
static asio::awaitable<HandlerResult> C_81(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
static asio::awaitable<HandlerResult> C_81(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
auto& cmd = msg.check_size_t<CmdT>();
|
||||
if (c->login && c->login->account->account_id != c->proxy_session->remote_guild_card_number) {
|
||||
if (cmd.from_guild_card_number == c->login->account->account_id) {
|
||||
@@ -2039,7 +2037,7 @@ constexpr MessageHandler C_P_81 = &C_81<SC_SimpleMail_PC_81>;
|
||||
constexpr MessageHandler C_B_81 = &C_81<SC_SimpleMail_BB_81>;
|
||||
|
||||
template <typename SendGuildCardCmdT>
|
||||
asio::awaitable<HandlerResult> C_6x(shared_ptr<Client> c, Channel::Message& msg) {
|
||||
asio::awaitable<HandlerResult> C_6x(std::shared_ptr<Client> c, Channel::Message& msg) {
|
||||
if (msg.data.size() < 4) {
|
||||
co_return HandlerResult::FORWARD;
|
||||
}
|
||||
@@ -2214,7 +2212,7 @@ constexpr MessageHandler C_G_6x = &C_6x<G_SendGuildCard_GC_6x06>;
|
||||
constexpr MessageHandler C_X_6x = &C_6x<G_SendGuildCard_XB_6x06>;
|
||||
constexpr MessageHandler C_B_6x = &C_6x<G_SendGuildCard_BB_6x06>;
|
||||
|
||||
static asio::awaitable<HandlerResult> C_V123_A0(shared_ptr<Client> c, Channel::Message&) {
|
||||
static asio::awaitable<HandlerResult> C_V123_A0(std::shared_ptr<Client> c, Channel::Message&) {
|
||||
// A0 is sent after downloading a quest (either successfully, or by backing out of the menu), and when choosing
|
||||
// Change Ship from the lobby counter menu. We override the Change Ship action to end the proxy session, but we only
|
||||
// do so if the player is in a lobby in order to properly handle the download quest case.
|
||||
@@ -2785,13 +2783,14 @@ static MessageHandler get_handler(Version version, bool from_server, uint8_t com
|
||||
const auto& handlers = from_server ? server_handlers : client_handlers;
|
||||
size_t version_index = static_cast<size_t>(version);
|
||||
if (version_index >= handlers[command].size()) {
|
||||
throw logic_error("invalid game version on proxy server");
|
||||
throw std::logic_error("invalid game version on proxy server");
|
||||
}
|
||||
auto ret = handlers[command][version_index];
|
||||
return ret ? ret : default_handler;
|
||||
}
|
||||
|
||||
asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, unique_ptr<Channel::Message> msg) {
|
||||
asio::awaitable<void> on_proxy_command(
|
||||
std::shared_ptr<Client> c, bool from_server, std::unique_ptr<Channel::Message> msg) {
|
||||
auto fn = get_handler(c->version(), from_server, msg->command & 0xFF);
|
||||
try {
|
||||
auto res = co_await fn(c, *msg);
|
||||
@@ -2803,9 +2802,9 @@ asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, u
|
||||
} else if (res == HandlerResult::SUPPRESS) {
|
||||
c->log.info_f("The preceding command from the {} was not forwarded", from_server ? "server" : "client");
|
||||
} else {
|
||||
throw logic_error("invalid handler result");
|
||||
throw std::logic_error("invalid handler result");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.error_f("Error in proxy command handler: {}", e.what());
|
||||
if (c->proxy_session && c->proxy_session->server_channel) {
|
||||
c->proxy_session->server_channel->disconnect();
|
||||
@@ -2814,13 +2813,13 @@ asio::awaitable<void> on_proxy_command(shared_ptr<Client> c, bool from_server, u
|
||||
}
|
||||
|
||||
asio::awaitable<void> handle_proxy_server_commands(
|
||||
shared_ptr<Client> c, shared_ptr<ProxySession> ses, shared_ptr<Channel> channel) {
|
||||
std::shared_ptr<Client> c, std::shared_ptr<ProxySession> ses, std::shared_ptr<Channel> channel) {
|
||||
std::string error_str;
|
||||
// server_channel can be changed by receiving a 19 command, hence the exception handler is inside the loop here
|
||||
while ((c->proxy_session == ses) && (ses->server_channel == channel) && channel->connected()) {
|
||||
unique_ptr<Channel::Message> msg;
|
||||
std::unique_ptr<Channel::Message> msg;
|
||||
try {
|
||||
msg = make_unique<Channel::Message>(co_await channel->recv());
|
||||
msg = std::make_unique<Channel::Message>(co_await channel->recv());
|
||||
if (c->proxy_session == ses) {
|
||||
for (size_t z = 0; z < std::min<size_t>(c->proxy_session->prev_server_command_bytes.size(), msg->data.size()); z++) {
|
||||
c->proxy_session->prev_server_command_bytes[z] = msg->data[z];
|
||||
@@ -2838,7 +2837,7 @@ asio::awaitable<void> handle_proxy_server_commands(
|
||||
error_str = e.what();
|
||||
}
|
||||
channel->disconnect();
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
c->log.info_f("Error in proxy server channel handler (command {:04X}): {}", msg ? msg->command : 0, e.what());
|
||||
error_str = e.what();
|
||||
channel->disconnect();
|
||||
|
||||
+4
-6
@@ -2,11 +2,9 @@
|
||||
|
||||
#include "ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
size_t ProxySession::num_proxy_sessions = 0;
|
||||
|
||||
ProxySession::ProxySession(shared_ptr<Channel> server_channel, const PersistentConfig* pc)
|
||||
ProxySession::ProxySession(std::shared_ptr<Channel> server_channel, const PersistentConfig* pc)
|
||||
: server_channel(server_channel) {
|
||||
if (pc) {
|
||||
this->remote_guild_card_number = pc->remote_guild_card_number;
|
||||
@@ -22,11 +20,11 @@ ProxySession::~ProxySession() {
|
||||
}
|
||||
|
||||
void ProxySession::set_drop_mode(
|
||||
shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode) {
|
||||
std::shared_ptr<ServerState> s, Version version, int64_t override_random_seed, ProxyDropMode new_mode) {
|
||||
this->drop_mode = new_mode;
|
||||
if (this->drop_mode == ProxyDropMode::INTERCEPT) {
|
||||
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
auto rand_crypt = std::make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
||||
this->item_creator = std::make_shared<ItemCreator>(
|
||||
s->common_item_set(version, nullptr),
|
||||
s->rare_item_set(version, nullptr),
|
||||
s->armor_random_set,
|
||||
|
||||
+185
-184
@@ -20,8 +20,6 @@
|
||||
#include "SaveFileFormats.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
QuestCategoryIndex::Category::Category(uint32_t category_id, const phosg::JSON& json) : category_id(category_id) {
|
||||
this->enabled_flags = json.get_int(0);
|
||||
this->directory_name = json.get_string(1);
|
||||
@@ -32,11 +30,11 @@ QuestCategoryIndex::Category::Category(uint32_t category_id, const phosg::JSON&
|
||||
QuestCategoryIndex::QuestCategoryIndex(const phosg::JSON& json) {
|
||||
uint32_t next_category_id = 1;
|
||||
for (const auto& it : json.as_list()) {
|
||||
this->categories.emplace_back(make_shared<Category>(next_category_id++, *it));
|
||||
this->categories.emplace_back(std::make_shared<Category>(next_category_id++, *it));
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const QuestCategoryIndex::Category> QuestCategoryIndex::at(uint32_t category_id) const {
|
||||
std::shared_ptr<const QuestCategoryIndex::Category> QuestCategoryIndex::at(uint32_t category_id) const {
|
||||
return this->categories.at(category_id - 1);
|
||||
}
|
||||
|
||||
@@ -56,9 +54,9 @@ using PSOVMSDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT<false>;
|
||||
using PSOGCIDLQFileEncryptedHeader = PSOMemCardDLQFileEncryptedHeaderT<true>;
|
||||
|
||||
template <bool BE>
|
||||
string decrypt_download_quest_data_section(
|
||||
std::string decrypt_download_quest_data_section(
|
||||
const void* data_section, size_t size, uint32_t seed, bool skip_checksum = false, bool is_ep3_trial = false) {
|
||||
string decrypted = decrypt_data_section<BE>(data_section, size, seed);
|
||||
std::string decrypted = decrypt_data_section<BE>(data_section, size, seed);
|
||||
|
||||
size_t orig_size = decrypted.size();
|
||||
decrypted.resize((decrypted.size() + 3) & (~3));
|
||||
@@ -74,7 +72,7 @@ string decrypt_download_quest_data_section(
|
||||
phosg::StringReader r(decrypted);
|
||||
r.skip(16);
|
||||
if (r.readx(15) != "SONICTEAM,SEGA.") {
|
||||
throw runtime_error("Episode 3 GCI file is not a quest");
|
||||
throw std::runtime_error("Episode 3 GCI file is not a quest");
|
||||
}
|
||||
r.skip(9);
|
||||
|
||||
@@ -84,7 +82,7 @@ string decrypt_download_quest_data_section(
|
||||
size_t decompressed_size = prs_decompress_size(
|
||||
r.getv(r.remaining(), false), r.remaining(), sizeof(Episode3::MapDefinitionTrial), true);
|
||||
if (decompressed_size < sizeof(Episode3::MapDefinitionTrial)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"decompressed size ({}) does not match expected size ({})",
|
||||
decompressed_size, sizeof(Episode3::MapDefinitionTrial)));
|
||||
}
|
||||
@@ -92,7 +90,7 @@ string decrypt_download_quest_data_section(
|
||||
|
||||
} else {
|
||||
if (header->decompressed_size & 0xFFF00000) {
|
||||
throw runtime_error(std::format("decompressed_size too large ({:08X})", header->decompressed_size));
|
||||
throw std::runtime_error(std::format("decompressed_size too large ({:08X})", header->decompressed_size));
|
||||
}
|
||||
|
||||
if (!skip_checksum) {
|
||||
@@ -101,7 +99,7 @@ string decrypt_download_quest_data_section(
|
||||
uint32_t actual_crc = phosg::crc32(decrypted.data(), orig_size);
|
||||
header->checksum = expected_crc;
|
||||
if (expected_crc != actual_crc && expected_crc != phosg::bswap32(actual_crc)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"incorrect decrypted data section checksum: expected {:08X}; received {:08X}", expected_crc, actual_crc));
|
||||
}
|
||||
}
|
||||
@@ -117,7 +115,7 @@ string decrypt_download_quest_data_section(
|
||||
decrypted.data() + sizeof(HeaderT), decrypted.size() - sizeof(HeaderT));
|
||||
size_t expected_decompressed_size = header->decompressed_size;
|
||||
if ((decompressed_size != expected_decompressed_size) && (decompressed_size != expected_decompressed_size - 8)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"decompressed size ({}) does not match expected size ({})", decompressed_size, expected_decompressed_size));
|
||||
}
|
||||
|
||||
@@ -125,12 +123,12 @@ string decrypt_download_quest_data_section(
|
||||
}
|
||||
}
|
||||
|
||||
string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
|
||||
std::string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
|
||||
phosg::StringReader r(data_section, size);
|
||||
uint32_t expected_decompressed_size = r.get_u32l();
|
||||
uint32_t seed = r.get_u32l();
|
||||
|
||||
string data = r.read(r.remaining());
|
||||
std::string data = r.read(r.remaining());
|
||||
|
||||
size_t orig_size = data.size();
|
||||
data.resize((orig_size + 3) & (~3));
|
||||
@@ -139,7 +137,7 @@ string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
|
||||
|
||||
size_t actual_decompressed_size = prs_decompress_size(data);
|
||||
if (actual_decompressed_size != expected_decompressed_size) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"decompressed size ({}) does not match size in header ({})",
|
||||
actual_decompressed_size, expected_decompressed_size));
|
||||
}
|
||||
@@ -148,17 +146,17 @@ string decrypt_vms_v1_data_section(const void* data_section, size_t size) {
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
string find_seed_and_decrypt_download_quest_data_section(
|
||||
std::string find_seed_and_decrypt_download_quest_data_section(
|
||||
const void* data_section, size_t size, bool skip_checksum, bool is_ep3_trial, size_t num_threads) {
|
||||
mutex result_lock;
|
||||
string result;
|
||||
std::mutex result_lock;
|
||||
std::string result;
|
||||
uint64_t result_seed = phosg::parallel_blocks<uint64_t>([&](uint64_t seed, size_t) {
|
||||
try {
|
||||
string ret = decrypt_download_quest_data_section<BE>(data_section, size, seed, skip_checksum, is_ep3_trial);
|
||||
lock_guard<mutex> g(result_lock);
|
||||
std::string ret = decrypt_download_quest_data_section<BE>(data_section, size, seed, skip_checksum, is_ep3_trial);
|
||||
std::lock_guard g(result_lock);
|
||||
result = std::move(ret);
|
||||
return true;
|
||||
} catch (const runtime_error& e) {
|
||||
} catch (const std::runtime_error& e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@@ -168,7 +166,7 @@ string find_seed_and_decrypt_download_quest_data_section(
|
||||
static_game_data_log.info_f("Found seed {:08X}", result_seed);
|
||||
return result;
|
||||
} else {
|
||||
throw runtime_error("no seed found");
|
||||
throw std::runtime_error("no seed found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,16 +177,16 @@ struct PSODownloadQuestHeader {
|
||||
|
||||
void VersionedQuest::assert_valid() const {
|
||||
if (this->meta.category_id == 0xFFFFFFFF) {
|
||||
throw runtime_error("category ID is not set");
|
||||
throw std::runtime_error("category ID is not set");
|
||||
}
|
||||
if (this->meta.quest_number == 0xFFFFFFFF) {
|
||||
throw runtime_error("quest number is not set");
|
||||
throw std::runtime_error("quest number is not set");
|
||||
}
|
||||
if (this->meta.version == Version::UNKNOWN) {
|
||||
throw runtime_error("version is not set");
|
||||
throw std::runtime_error("version is not set");
|
||||
}
|
||||
if (this->meta.language == Language::UNKNOWN) {
|
||||
throw runtime_error("language is not set");
|
||||
throw std::runtime_error("language is not set");
|
||||
}
|
||||
|
||||
uint8_t num_floors = is_v1(this->meta.version) ? 0x10 : 0x12;
|
||||
@@ -209,10 +207,10 @@ void VersionedQuest::assert_valid() const {
|
||||
for (size_t floor = 0; floor < num_floors; floor++) {
|
||||
const auto& fa = this->meta.floor_assignments[floor];
|
||||
if (fa.floor != floor) {
|
||||
throw logic_error("floor assignment is inconsistent");
|
||||
throw std::logic_error("floor assignment is inconsistent");
|
||||
}
|
||||
if ((fa.area != 0xFF) && (fa.area > num_areas)) {
|
||||
throw runtime_error(std::format("floor assignment 0x{:02X} specifies invalid area 0x{:02X}", floor, fa.area));
|
||||
throw std::runtime_error(std::format("floor assignment 0x{:02X} specifies invalid area 0x{:02X}", floor, fa.area));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,70 +219,70 @@ void VersionedQuest::assert_valid() const {
|
||||
break;
|
||||
case Episode::EP2:
|
||||
if (is_v1_or_v2(this->meta.version)) {
|
||||
throw runtime_error("v1 or v2 quest specifies Episode 2");
|
||||
throw std::runtime_error("v1 or v2 quest specifies Episode 2");
|
||||
}
|
||||
break;
|
||||
case Episode::EP3:
|
||||
if (!is_ep3(this->meta.version)) {
|
||||
throw runtime_error("non-Ep3 quest specifies Episode 3");
|
||||
throw std::runtime_error("non-Ep3 quest specifies Episode 3");
|
||||
}
|
||||
break;
|
||||
case Episode::EP4:
|
||||
if (!is_v4(this->meta.version)) {
|
||||
throw runtime_error("non-v4 quest specifies Episode 4");
|
||||
throw std::runtime_error("non-v4 quest specifies Episode 4");
|
||||
}
|
||||
break;
|
||||
case Episode::NONE:
|
||||
throw runtime_error("episode is not set");
|
||||
throw std::runtime_error("episode is not set");
|
||||
default:
|
||||
throw runtime_error("episode is not valid");
|
||||
throw std::runtime_error("episode is not valid");
|
||||
}
|
||||
|
||||
if (!this->bin_contents) {
|
||||
throw runtime_error("bin file is missing");
|
||||
throw std::runtime_error("bin file is missing");
|
||||
}
|
||||
if (!this->dat_contents) {
|
||||
throw runtime_error("dat file is missing");
|
||||
throw std::runtime_error("dat file is missing");
|
||||
}
|
||||
if (!this->map_file) {
|
||||
throw runtime_error("parsed map file is missing");
|
||||
throw std::runtime_error("parsed map file is missing");
|
||||
}
|
||||
if (this->meta.allowed_drop_modes &&
|
||||
!(this->meta.allowed_drop_modes & (1 << static_cast<size_t>(this->meta.default_drop_mode)))) {
|
||||
throw runtime_error("default drop mode is not allowed");
|
||||
throw std::runtime_error("default drop mode is not allowed");
|
||||
}
|
||||
}
|
||||
|
||||
string VersionedQuest::bin_filename() const {
|
||||
std::string VersionedQuest::bin_filename() const {
|
||||
return std::format("quest{}.bin", this->meta.quest_number);
|
||||
}
|
||||
|
||||
string VersionedQuest::dat_filename() const {
|
||||
std::string VersionedQuest::dat_filename() const {
|
||||
return std::format("quest{}.dat", this->meta.quest_number);
|
||||
}
|
||||
|
||||
string VersionedQuest::pvr_filename() const {
|
||||
std::string VersionedQuest::pvr_filename() const {
|
||||
return std::format("quest{}.pvr", this->meta.quest_number);
|
||||
}
|
||||
|
||||
string VersionedQuest::xb_filename() const {
|
||||
std::string VersionedQuest::xb_filename() const {
|
||||
return std::format("quest{}_{}.dat",
|
||||
this->meta.quest_number, static_cast<char>(tolower(char_for_language(this->meta.language))));
|
||||
}
|
||||
|
||||
string VersionedQuest::encode_qst() const {
|
||||
unordered_map<string, shared_ptr<const string>> files;
|
||||
std::string VersionedQuest::encode_qst() const {
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> files;
|
||||
files.emplace(std::format("quest{}.bin", this->meta.quest_number), this->bin_contents);
|
||||
files.emplace(std::format("quest{}.dat", this->meta.quest_number), this->dat_contents);
|
||||
if (this->pvr_contents) {
|
||||
files.emplace(std::format("quest{}.pvr", this->meta.quest_number), this->pvr_contents);
|
||||
}
|
||||
string xb_filename = std::format("quest{}_{}.dat",
|
||||
std::string xb_filename = std::format("quest{}_{}.dat",
|
||||
this->meta.quest_number, static_cast<char>(tolower(char_for_language(this->meta.language))));
|
||||
return encode_qst_file(files, this->meta.name, this->meta.quest_number, xb_filename, this->meta.version, this->is_dlq_encoded);
|
||||
}
|
||||
|
||||
Quest::Quest(shared_ptr<const VersionedQuest> initial_version) : meta(initial_version->meta), supermap(nullptr) {
|
||||
Quest::Quest(std::shared_ptr<const VersionedQuest> initial_version) : meta(initial_version->meta), supermap(nullptr) {
|
||||
this->add_version(initial_version);
|
||||
}
|
||||
|
||||
@@ -310,7 +308,7 @@ uint32_t Quest::versions_key(Version v, Language language) {
|
||||
return (static_cast<uint32_t>(v) << 8) | static_cast<uint8_t>(language);
|
||||
}
|
||||
|
||||
const string& Quest::name_for_language(Language language) const {
|
||||
const std::string& Quest::name_for_language(Language language) const {
|
||||
size_t lang_index = static_cast<size_t>(language);
|
||||
if (!this->names_by_language.at(lang_index).empty()) {
|
||||
return this->names_by_language[lang_index];
|
||||
@@ -319,7 +317,7 @@ const string& Quest::name_for_language(Language language) const {
|
||||
if (!this->names_by_language[english_lang_index].empty()) {
|
||||
return this->names_by_language[english_lang_index];
|
||||
}
|
||||
for (const string& name : this->names_by_language) {
|
||||
for (const std::string& name : this->names_by_language) {
|
||||
if (!name.empty()) {
|
||||
return name;
|
||||
}
|
||||
@@ -327,7 +325,7 @@ const string& Quest::name_for_language(Language language) const {
|
||||
return this->meta.name;
|
||||
}
|
||||
|
||||
void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
||||
void Quest::add_version(std::shared_ptr<const VersionedQuest> vq) {
|
||||
this->meta.assert_compatible(vq->meta);
|
||||
if (this->meta.create_item_mask_entries.empty()) {
|
||||
this->meta.create_item_mask_entries = vq->meta.create_item_mask_entries;
|
||||
@@ -348,7 +346,7 @@ std::shared_ptr<const SuperMap> Quest::get_supermap(int64_t random_seed) const {
|
||||
|
||||
bool save_to_cache = true;
|
||||
bool any_map_file_present = false;
|
||||
array<shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
std::array<std::shared_ptr<const MapFile>, NUM_VERSIONS> map_files;
|
||||
for (Version v : ALL_NON_PATCH_VERSIONS) {
|
||||
auto vq = this->version(v, Language::ENGLISH);
|
||||
if (vq && vq->map_file) {
|
||||
@@ -369,7 +367,7 @@ std::shared_ptr<const SuperMap> Quest::get_supermap(int64_t random_seed) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto supermap = make_shared<SuperMap>(map_files, this->meta.get_floor_to_area());
|
||||
auto supermap = std::make_shared<SuperMap>(map_files, this->meta.get_floor_to_area());
|
||||
if (save_to_cache) {
|
||||
this->supermap = supermap;
|
||||
}
|
||||
@@ -389,17 +387,17 @@ bool Quest::has_version_any_language(Version v) const {
|
||||
return ((it != this->versions.end()) && ((it->first & 0xFF00) == k));
|
||||
}
|
||||
|
||||
shared_ptr<const VersionedQuest> Quest::version(Version v, Language language) const {
|
||||
std::shared_ptr<const VersionedQuest> Quest::version(Version v, Language language) const {
|
||||
// Return the requested version, if it exists
|
||||
try {
|
||||
return this->versions.at(this->versions_key(v, language));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
// Return the English version, if it exists
|
||||
try {
|
||||
return this->versions.at(this->versions_key(v, Language::ENGLISH));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
// Return the first language, if it exists
|
||||
@@ -411,36 +409,36 @@ shared_ptr<const VersionedQuest> Quest::version(Version v, Language language) co
|
||||
}
|
||||
|
||||
QuestIndex::QuestIndex(
|
||||
const string& directory, shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure)
|
||||
const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool raise_on_any_failure)
|
||||
: directory(directory), category_index(category_index) {
|
||||
|
||||
struct FileData {
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
std::string filename;
|
||||
std::shared_ptr<const std::string> data;
|
||||
};
|
||||
struct BINFileData {
|
||||
string filename;
|
||||
shared_ptr<const AssembledQuestScript> assembled;
|
||||
shared_ptr<const string> data;
|
||||
std::string filename;
|
||||
std::shared_ptr<const AssembledQuestScript> assembled;
|
||||
std::shared_ptr<const std::string> data;
|
||||
};
|
||||
struct DATFileData {
|
||||
string filename;
|
||||
shared_ptr<const string> data;
|
||||
shared_ptr<const MapFile> map_file;
|
||||
std::string filename;
|
||||
std::shared_ptr<const std::string> data;
|
||||
std::shared_ptr<const MapFile> map_file;
|
||||
};
|
||||
map<string, BINFileData> bin_files;
|
||||
map<string, DATFileData> dat_files;
|
||||
map<string, FileData> pvr_files;
|
||||
map<string, FileData> json_files;
|
||||
map<string, uint32_t> categories;
|
||||
std::map<std::string, BINFileData> bin_files;
|
||||
std::map<std::string, DATFileData> dat_files;
|
||||
std::map<std::string, FileData> pvr_files;
|
||||
std::map<std::string, FileData> json_files;
|
||||
std::map<std::string, uint32_t> categories;
|
||||
for (const auto& cat : this->category_index->categories) {
|
||||
auto add_file = [&](map<string, FileData>& files, const string& basename, const string& filename, string&& value, bool check_chunk_size) {
|
||||
auto add_file = [&](std::map<std::string, FileData>& files, const std::string& basename, const std::string& filename, std::string&& value, bool check_chunk_size) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("file " + basename + " exists in multiple categories");
|
||||
throw std::runtime_error("file " + basename + " exists in multiple categories");
|
||||
}
|
||||
auto data_ptr = make_shared<string>(std::move(value));
|
||||
auto data_ptr = std::make_shared<std::string>(std::move(value));
|
||||
if (!files.emplace(basename, FileData{filename, data_ptr}).second) {
|
||||
throw runtime_error("file " + basename + " already exists");
|
||||
throw std::runtime_error("file " + basename + " already exists");
|
||||
}
|
||||
// There is a bug in the client that prevents quests from loading properly if any file's size is a multiple of
|
||||
// 0x400. See the comments on the 13 command in CommandFormats.hh for more details.
|
||||
@@ -449,14 +447,14 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
};
|
||||
|
||||
auto add_bin_file = [&](const string& basename, const string& filename, string&& data, shared_ptr<AssembledQuestScript> assembled) {
|
||||
auto add_bin_file = [&](const std::string& basename, const std::string& filename, std::string&& data, std::shared_ptr<AssembledQuestScript> assembled) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("bin file " + basename + " exists in multiple categories");
|
||||
throw std::runtime_error("bin file " + basename + " exists in multiple categories");
|
||||
}
|
||||
auto data_ptr = make_shared<string>(std::move(data));
|
||||
auto data_ptr = std::make_shared<std::string>(std::move(data));
|
||||
auto emplace_ret = bin_files.emplace(basename, BINFileData{});
|
||||
if (!emplace_ret.second) {
|
||||
throw runtime_error("bin file " + basename + " already exists");
|
||||
throw std::runtime_error("bin file " + basename + " already exists");
|
||||
}
|
||||
auto& entry = emplace_ret.first->second;
|
||||
entry.filename = filename;
|
||||
@@ -466,36 +464,36 @@ QuestIndex::QuestIndex(
|
||||
data_ptr->push_back(0x00);
|
||||
}
|
||||
};
|
||||
auto add_dat_file = [&](const string& basename, const string& filename, string&& data) {
|
||||
auto add_dat_file = [&](const std::string& basename, const std::string& filename, std::string&& data) {
|
||||
if (categories.emplace(basename, cat->category_id).first->second != cat->category_id) {
|
||||
throw runtime_error("dat file " + basename + " exists in multiple categories");
|
||||
throw std::runtime_error("dat file " + basename + " exists in multiple categories");
|
||||
}
|
||||
auto data_ptr = make_shared<string>(std::move(data));
|
||||
auto map_file = make_shared<MapFile>(make_shared<string>(prs_decompress(*data_ptr)));
|
||||
auto data_ptr = std::make_shared<std::string>(std::move(data));
|
||||
auto map_file = std::make_shared<MapFile>(std::make_shared<std::string>(prs_decompress(*data_ptr)));
|
||||
if (!dat_files.emplace(basename, DATFileData{filename, data_ptr, map_file}).second) {
|
||||
throw runtime_error("dat file " + basename + " already exists");
|
||||
throw std::runtime_error("dat file " + basename + " already exists");
|
||||
}
|
||||
if (!(data_ptr->size() & 0x3FF)) {
|
||||
data_ptr->push_back(0x00);
|
||||
}
|
||||
};
|
||||
|
||||
string cat_path = directory + "/" + cat->directory_name;
|
||||
std::string cat_path = directory + "/" + cat->directory_name;
|
||||
if (!std::filesystem::is_directory(cat_path)) {
|
||||
static_game_data_log.warning_f("Quest category directory {} is missing; skipping it", cat_path);
|
||||
continue;
|
||||
}
|
||||
for (const auto& item : std::filesystem::directory_iterator(cat_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
std::string filename = item.path().filename().string();
|
||||
if (filename == ".DS_Store") {
|
||||
continue;
|
||||
}
|
||||
|
||||
string file_path = cat_path + "/" + filename;
|
||||
shared_ptr<AssembledQuestScript> assembled;
|
||||
std::string file_path = cat_path + "/" + filename;
|
||||
std::shared_ptr<AssembledQuestScript> assembled;
|
||||
try {
|
||||
string orig_filename = filename;
|
||||
string file_data;
|
||||
std::string orig_filename = filename;
|
||||
std::string file_data;
|
||||
if (filename.ends_with(".gci")) {
|
||||
file_data = decode_gci_data(phosg::load_file(file_path));
|
||||
filename.resize(filename.size() - 4);
|
||||
@@ -506,8 +504,8 @@ QuestIndex::QuestIndex(
|
||||
file_data = decode_dlq_data(phosg::load_file(file_path));
|
||||
filename.resize(filename.size() - 4);
|
||||
} else if (filename.ends_with(".bin.txt")) {
|
||||
string include_dir = phosg::dirname(file_path);
|
||||
assembled = make_shared<AssembledQuestScript>(assemble_quest_script(
|
||||
std::string include_dir = phosg::dirname(file_path);
|
||||
assembled = std::make_shared<AssembledQuestScript>(assemble_quest_script(
|
||||
phosg::load_file(file_path),
|
||||
{include_dir, "system/quests/includes"},
|
||||
{include_dir, "system/quests/includes", "system/client-functions/System"}));
|
||||
@@ -521,9 +519,9 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
|
||||
size_t dot_pos = filename.rfind('.');
|
||||
string file_basename;
|
||||
string extension;
|
||||
if (dot_pos != string::npos) {
|
||||
std::string file_basename;
|
||||
std::string extension;
|
||||
if (dot_pos != std::string::npos) {
|
||||
file_basename = phosg::tolower(filename.substr(0, dot_pos));
|
||||
extension = phosg::tolower(filename.substr(dot_pos + 1));
|
||||
} else {
|
||||
@@ -552,14 +550,14 @@ QuestIndex::QuestIndex(
|
||||
} else if (it.first.ends_with(".pvr")) {
|
||||
add_file(pvr_files, file_basename, orig_filename, std::move(it.second), true);
|
||||
} else {
|
||||
throw runtime_error("qst file contains unsupported file type: " + it.first);
|
||||
throw std::runtime_error("qst file contains unsupported file type: " + it.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (raise_on_any_failure) {
|
||||
throw runtime_error(format("({}) {}", filename, e.what()));
|
||||
throw std::runtime_error(std::format("({}) {}", filename, e.what()));
|
||||
}
|
||||
static_game_data_log.warning_f("({}) Failed to load quest file: ({})", filename, e.what());
|
||||
}
|
||||
@@ -568,10 +566,10 @@ QuestIndex::QuestIndex(
|
||||
|
||||
// All quests have a bin file (even in Episode 3, though its format is different), so we use bin_files as the primary
|
||||
// list of all quests that should be indexed
|
||||
unordered_map<const FileData*, shared_ptr<const phosg::JSON>> parsed_json_files;
|
||||
std::unordered_map<const FileData*, std::shared_ptr<const phosg::JSON>> parsed_json_files;
|
||||
for (auto& [basename, entry] : bin_files) {
|
||||
try {
|
||||
auto vq = make_shared<VersionedQuest>();
|
||||
auto vq = std::make_shared<VersionedQuest>();
|
||||
|
||||
// Quest .bin filenames are like K###-VERS-LANG.EXT, where:
|
||||
// K can be any character (usually it's q)
|
||||
@@ -580,11 +578,11 @@ QuestIndex::QuestIndex(
|
||||
// LANG = client language (j, e, g, f, s)
|
||||
// EXT = file type (bin, bind, bin.dlq, qst, etc.)
|
||||
// EXT has already been stripped off by the time we get here, so we just parse the remaining fields.
|
||||
string quest_number_token, version_token, language_token;
|
||||
std::string quest_number_token, version_token, language_token;
|
||||
{
|
||||
vector<string> filename_tokens = phosg::split(basename, '-');
|
||||
std::vector<std::string> filename_tokens = phosg::split(basename, '-');
|
||||
if (filename_tokens.size() != 3) {
|
||||
throw invalid_argument("incorrect filename format");
|
||||
throw std::invalid_argument("incorrect filename format");
|
||||
}
|
||||
quest_number_token = std::move(filename_tokens[0]);
|
||||
version_token = std::move(filename_tokens[1]);
|
||||
@@ -599,12 +597,12 @@ QuestIndex::QuestIndex(
|
||||
} else {
|
||||
// Get the number from the first token
|
||||
if (quest_number_token.empty()) {
|
||||
throw runtime_error("quest number token is missing");
|
||||
throw std::runtime_error("quest number token is missing");
|
||||
}
|
||||
vq->meta.quest_number = strtoull(quest_number_token.c_str() + 1, nullptr, 10);
|
||||
|
||||
// Get the version from the second token
|
||||
static const unordered_map<string, Version> name_to_version({
|
||||
static const std::unordered_map<std::string, Version> name_to_version({
|
||||
{"dn", Version::DC_NTE},
|
||||
{"dp", Version::DC_11_2000},
|
||||
{"d1", Version::DC_V1},
|
||||
@@ -622,7 +620,7 @@ QuestIndex::QuestIndex(
|
||||
|
||||
// Get the language from the last token
|
||||
if (language_token.size() != 1) {
|
||||
throw runtime_error("language token is not a single character");
|
||||
throw std::runtime_error("language token is not a single character");
|
||||
}
|
||||
vq->meta.language = language_for_char(language_token[0]);
|
||||
}
|
||||
@@ -636,19 +634,19 @@ QuestIndex::QuestIndex(
|
||||
const FileData* pvr_filedata = nullptr;
|
||||
try {
|
||||
dat_filedata = &dat_files.at(basename);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
try {
|
||||
dat_filedata = &dat_files.at(quest_number_token + "-" + version_token);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error("no dat file found for bin file " + basename);
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error("no dat file found for bin file " + basename);
|
||||
}
|
||||
}
|
||||
try {
|
||||
pvr_filedata = &pvr_files.at(basename);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
try {
|
||||
pvr_filedata = &pvr_files.at(quest_number_token + "-" + version_token);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// pvr files aren't required (and most quests do not have them), so don't fail if it's missing
|
||||
}
|
||||
}
|
||||
@@ -665,21 +663,21 @@ QuestIndex::QuestIndex(
|
||||
const FileData* json_filedata = nullptr;
|
||||
try {
|
||||
json_filedata = &json_files.at(basename);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
try {
|
||||
json_filedata = &json_files.at(quest_number_token + "-" + version_token);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
try {
|
||||
json_filedata = &json_files.at(quest_number_token);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (json_filedata) {
|
||||
try {
|
||||
vq->json_contents = parsed_json_files.at(json_filedata);
|
||||
} catch (const out_of_range&) {
|
||||
vq->json_contents = make_shared<phosg::JSON>(phosg::JSON::parse(*json_filedata->data));
|
||||
} catch (const std::out_of_range&) {
|
||||
vq->json_contents = std::make_shared<phosg::JSON>(phosg::JSON::parse(*json_filedata->data));
|
||||
parsed_json_files.emplace(json_filedata, vq->json_contents);
|
||||
}
|
||||
vq->meta.apply_json_overrides(*vq->json_contents);
|
||||
@@ -688,7 +686,7 @@ QuestIndex::QuestIndex(
|
||||
vq->assert_valid();
|
||||
|
||||
auto category_name = this->category_index->at(vq->meta.category_id)->name;
|
||||
string filenames_str = entry.filename;
|
||||
std::string filenames_str = entry.filename;
|
||||
if (dat_filedata) {
|
||||
filenames_str += std::format("/{}", dat_filedata->filename);
|
||||
}
|
||||
@@ -708,7 +706,7 @@ QuestIndex::QuestIndex(
|
||||
vq->meta.quest_number,
|
||||
vq->meta.name);
|
||||
} else {
|
||||
auto q = make_shared<Quest>(vq);
|
||||
auto q = std::make_shared<Quest>(vq);
|
||||
this->quests_by_number.emplace(vq->meta.quest_number, q);
|
||||
this->quests_by_name.emplace(vq->meta.name, q);
|
||||
this->quests_by_category_id_and_number[q->meta.category_id].emplace(vq->meta.quest_number, q);
|
||||
@@ -723,9 +721,9 @@ QuestIndex::QuestIndex(
|
||||
vq->meta.category_id,
|
||||
vq->meta.joinable ? "joinable" : "not joinable");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
if (raise_on_any_failure) {
|
||||
throw runtime_error(format("({}) {}", basename, e.what()));
|
||||
throw std::runtime_error(std::format("({}) {}", basename, e.what()));
|
||||
}
|
||||
static_game_data_log.warning_f("({}) Failed to index quest file: {}", basename, e.what());
|
||||
}
|
||||
@@ -757,25 +755,25 @@ phosg::JSON QuestIndex::json() const {
|
||||
});
|
||||
}
|
||||
|
||||
shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
std::shared_ptr<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
try {
|
||||
return this->quests_by_number.at(quest_number);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const Quest> QuestIndex::get(const std::string& name) const {
|
||||
std::shared_ptr<const Quest> QuestIndex::get(const std::string& name) const {
|
||||
try {
|
||||
return this->quests_by_name.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
QuestMenuType menu_type, Episode episode, uint16_t version_flags, IncludeCondition include_condition) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> ret;
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> ret;
|
||||
for (const auto& cat : this->category_index->categories) {
|
||||
if (cat->check_flag(menu_type) && !this->filter(episode, version_flags, cat->category_id, include_condition, 1).empty()) {
|
||||
ret.emplace_back(cat);
|
||||
@@ -784,7 +782,7 @@ vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
return ret;
|
||||
}
|
||||
|
||||
vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filter(
|
||||
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> QuestIndex::filter(
|
||||
Episode episode,
|
||||
uint16_t version_flags,
|
||||
uint32_t category_id,
|
||||
@@ -793,7 +791,7 @@ vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filt
|
||||
auto cat = this->category_index->at(category_id);
|
||||
Episode effective_episode = cat->enable_episode_filter() ? episode : Episode::NONE;
|
||||
|
||||
vector<pair<IncludeState, shared_ptr<const Quest>>> ret;
|
||||
std::vector<std::pair<IncludeState, std::shared_ptr<const Quest>>> ret;
|
||||
auto category_it = this->quests_by_category_id_and_number.find(category_id);
|
||||
if (category_it == this->quests_by_category_id_and_number.end()) {
|
||||
return ret;
|
||||
@@ -824,7 +822,8 @@ vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filt
|
||||
return ret;
|
||||
}
|
||||
|
||||
string encode_download_quest_data(const string& compressed_data, size_t decompressed_size, uint32_t encryption_seed) {
|
||||
std::string encode_download_quest_data(
|
||||
const std::string& compressed_data, size_t decompressed_size, uint32_t encryption_seed) {
|
||||
// Download quest files are like normal (PRS-compressed) quest files, but they are encrypted with PSO V2 encryption
|
||||
// (even on V3 / PSO GC), and a small header (PSODownloadQuestHeader) is prepended to the encrypted data.
|
||||
|
||||
@@ -835,7 +834,7 @@ string encode_download_quest_data(const string& compressed_data, size_t decompre
|
||||
decompressed_size = prs_decompress_size(compressed_data);
|
||||
}
|
||||
|
||||
string data(8, '\0');
|
||||
std::string data(8, '\0');
|
||||
auto* header = reinterpret_cast<PSODownloadQuestHeader*>(data.data());
|
||||
header->size = decompressed_size;
|
||||
header->encryption_seed = encryption_seed;
|
||||
@@ -852,18 +851,18 @@ string encode_download_quest_data(const string& compressed_data, size_t decompre
|
||||
return data;
|
||||
}
|
||||
|
||||
shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language override_language) const {
|
||||
std::shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language override_language) const {
|
||||
// The download flag needs to be set in the bin header, or else the client will ignore it when scanning for download
|
||||
// quests in an offline game. To set this flag, we need to decompress the quest's .bin file, set the flag, then
|
||||
// recompress it again.
|
||||
|
||||
string decompressed_bin = prs_decompress(*this->bin_contents);
|
||||
std::string decompressed_bin = prs_decompress(*this->bin_contents);
|
||||
|
||||
void* data_ptr = decompressed_bin.data();
|
||||
switch (this->meta.version) {
|
||||
case Version::DC_NTE:
|
||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDCNTE)) {
|
||||
throw runtime_error("bin file is too small for header");
|
||||
throw std::runtime_error("bin file is too small for header");
|
||||
}
|
||||
// There's no known language field in this version, so we don't write anything here
|
||||
break;
|
||||
@@ -871,7 +870,7 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) {
|
||||
throw runtime_error("bin file is too small for header");
|
||||
throw std::runtime_error("bin file is too small for header");
|
||||
}
|
||||
if (override_language != Language::UNKNOWN) {
|
||||
reinterpret_cast<PSOQuestHeaderDC*>(data_ptr)->language = override_language;
|
||||
@@ -880,7 +879,7 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) {
|
||||
throw runtime_error("bin file is too small for header");
|
||||
throw std::runtime_error("bin file is too small for header");
|
||||
}
|
||||
if (override_language != Language::UNKNOWN) {
|
||||
reinterpret_cast<PSOQuestHeaderPC*>(data_ptr)->language = override_language;
|
||||
@@ -890,30 +889,31 @@ shared_ptr<VersionedQuest> VersionedQuest::create_download_quest(Language overri
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
if (decompressed_bin.size() < sizeof(PSOQuestHeaderV3)) {
|
||||
throw runtime_error("bin file is too small for header");
|
||||
throw std::runtime_error("bin file is too small for header");
|
||||
}
|
||||
if (override_language != Language::UNKNOWN) {
|
||||
reinterpret_cast<PSOQuestHeaderV3*>(data_ptr)->language = override_language;
|
||||
}
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
throw invalid_argument("PSOBB does not support download quests");
|
||||
throw std::invalid_argument("PSOBB does not support download quests");
|
||||
default:
|
||||
throw invalid_argument("unknown game version");
|
||||
throw std::invalid_argument("unknown game version");
|
||||
}
|
||||
|
||||
string compressed_bin = prs_compress(decompressed_bin);
|
||||
std::string compressed_bin = prs_compress(decompressed_bin);
|
||||
|
||||
// Return a new VersionedQuest object with appropriately-processed .bin and .dat file contents
|
||||
auto dlq = make_shared<VersionedQuest>(*this);
|
||||
dlq->bin_contents = make_shared<string>(encode_download_quest_data(compressed_bin, decompressed_bin.size()));
|
||||
dlq->dat_contents = make_shared<string>(encode_download_quest_data(*this->dat_contents));
|
||||
auto dlq = std::make_shared<VersionedQuest>(*this);
|
||||
dlq->bin_contents = std::make_shared<std::string>(encode_download_quest_data(compressed_bin, decompressed_bin.size()));
|
||||
dlq->dat_contents = std::make_shared<std::string>(encode_download_quest_data(*this->dat_contents));
|
||||
dlq->pvr_contents = this->pvr_contents;
|
||||
dlq->is_dlq_encoded = true;
|
||||
return dlq;
|
||||
}
|
||||
|
||||
string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
|
||||
std::string decode_gci_data(
|
||||
const std::string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
|
||||
phosg::StringReader r(data);
|
||||
const auto& header = r.get<PSOGCIFileHeader>();
|
||||
header.check();
|
||||
@@ -933,10 +933,10 @@ string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
|
||||
} else {
|
||||
if (find_seed_num_threads < 0) {
|
||||
throw runtime_error("file is encrypted");
|
||||
throw std::runtime_error("file is encrypted");
|
||||
}
|
||||
if (find_seed_num_threads == 0) {
|
||||
find_seed_num_threads = thread::hardware_concurrency();
|
||||
find_seed_num_threads = std::thread::hardware_concurrency();
|
||||
}
|
||||
return find_seed_and_decrypt_download_quest_data_section<true>(
|
||||
r.getv(header.data_size), header.data_size, skip_checksum, false, find_seed_num_threads);
|
||||
@@ -944,12 +944,12 @@ string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
|
||||
} else { // Unencrypted GCI format
|
||||
r.skip(sizeof(PSOGCIDLQFileEncryptedHeader));
|
||||
string compressed_data = r.readx(header.data_size - sizeof(PSOGCIDLQFileEncryptedHeader));
|
||||
std::string compressed_data = r.readx(header.data_size - sizeof(PSOGCIDLQFileEncryptedHeader));
|
||||
size_t decompressed_bytes = prs_decompress_size(compressed_data);
|
||||
|
||||
size_t expected_decompressed_bytes = dlq_header.decompressed_size - 8;
|
||||
if (decompressed_bytes < expected_decompressed_bytes) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"GCI decompressed data is smaller than expected size (have 0x{:X} bytes, expected 0x{:X} bytes)",
|
||||
decompressed_bytes, expected_decompressed_bytes));
|
||||
}
|
||||
@@ -964,10 +964,10 @@ string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
r.getv(header.data_size), header.data_size, known_seed, true, true);
|
||||
} else {
|
||||
if (find_seed_num_threads < 0) {
|
||||
throw runtime_error("file is encrypted");
|
||||
throw std::runtime_error("file is encrypted");
|
||||
}
|
||||
if (find_seed_num_threads == 0) {
|
||||
find_seed_num_threads = thread::hardware_concurrency();
|
||||
find_seed_num_threads = std::thread::hardware_concurrency();
|
||||
}
|
||||
return find_seed_and_decrypt_download_quest_data_section<true>(
|
||||
r.getv(header.data_size), header.data_size, true, true, find_seed_num_threads);
|
||||
@@ -982,21 +982,21 @@ string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
// The game treats this field as a 16-byte string (including the \0). The 8 bytes after it appear to be
|
||||
// completely unused.
|
||||
if (r.readx(15) != "SONICTEAM,SEGA.") {
|
||||
throw runtime_error("Episode 3 GCI file is not a quest");
|
||||
throw std::runtime_error("Episode 3 GCI file is not a quest");
|
||||
}
|
||||
r.skip(9);
|
||||
|
||||
string decrypted = r.readx(header.data_size - 40);
|
||||
std::string decrypted = r.readx(header.data_size - 40);
|
||||
|
||||
// For some reason, Sega decided not to encrypt Episode 3 quest files in the same way as Episodes 1&2 quest files
|
||||
// (see above). Instead, they just wrote a fairly trivial XOR loop over the first 0x100 bytes, leaving the
|
||||
// remaining bytes completely unencrypted (but still compressed).
|
||||
size_t unscramble_size = min<size_t>(0x100, data.size());
|
||||
size_t unscramble_size = std::min<size_t>(0x100, data.size());
|
||||
decrypt_trivial_gci_data(decrypted.data(), unscramble_size, 0);
|
||||
|
||||
size_t decompressed_size = prs_decompress_size(decrypted);
|
||||
if (decompressed_size != sizeof(Episode3::MapDefinition)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"decompressed quest is 0x{:X} bytes; expected 0x{:X} bytes",
|
||||
decompressed_size, sizeof(Episode3::MapDefinition)));
|
||||
}
|
||||
@@ -1004,22 +1004,23 @@ string decode_gci_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
}
|
||||
|
||||
} else {
|
||||
throw runtime_error("unknown game name in GCI header");
|
||||
throw std::runtime_error("unknown game name in GCI header");
|
||||
}
|
||||
}
|
||||
|
||||
string decode_vms_data(const string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
|
||||
std::string decode_vms_data(
|
||||
const std::string& data, ssize_t find_seed_num_threads, int64_t known_seed, bool skip_checksum) {
|
||||
phosg::StringReader r(data);
|
||||
const auto& header = r.get<PSOVMSFileHeader>();
|
||||
if (!header.checksum_correct()) {
|
||||
throw runtime_error("VMS file unencrypted header checksum is incorrect");
|
||||
throw std::runtime_error("VMS file unencrypted header checksum is incorrect");
|
||||
}
|
||||
r.skip(header.num_icons * 0x200);
|
||||
|
||||
const void* data_section = r.getv(header.data_size);
|
||||
try {
|
||||
return decrypt_vms_v1_data_section(data_section, header.data_size);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
}
|
||||
|
||||
if (known_seed >= 0) {
|
||||
@@ -1027,17 +1028,17 @@ string decode_vms_data(const string& data, ssize_t find_seed_num_threads, int64_
|
||||
|
||||
} else {
|
||||
if (find_seed_num_threads < 0) {
|
||||
throw runtime_error("file is encrypted");
|
||||
throw std::runtime_error("file is encrypted");
|
||||
}
|
||||
if (find_seed_num_threads == 0) {
|
||||
find_seed_num_threads = thread::hardware_concurrency();
|
||||
find_seed_num_threads = std::thread::hardware_concurrency();
|
||||
}
|
||||
return find_seed_and_decrypt_download_quest_data_section<false>(
|
||||
data_section, header.data_size, skip_checksum, 0, find_seed_num_threads);
|
||||
}
|
||||
}
|
||||
|
||||
string decode_dlq_data(const string& data) {
|
||||
std::string decode_dlq_data(const std::string& data) {
|
||||
phosg::StringReader r(data);
|
||||
uint32_t decompressed_size = r.get_u32l();
|
||||
uint32_t key = r.get_u32l();
|
||||
@@ -1045,7 +1046,7 @@ string decode_dlq_data(const string& data) {
|
||||
// The compressed data size does not need to be a multiple of 4, but the V2 encryption (which is used for all
|
||||
// download quests, even in V3) requires the data size to be a multiple of 4. We'll just temporarily stick a few
|
||||
// bytes on the end, then throw them away later if needed.
|
||||
string decrypted = r.read(r.remaining());
|
||||
std::string decrypted = r.read(r.remaining());
|
||||
PSOV2Encryption encr(key);
|
||||
size_t original_size = data.size();
|
||||
decrypted.resize((decrypted.size() + 3) & (~3));
|
||||
@@ -1053,18 +1054,18 @@ string decode_dlq_data(const string& data) {
|
||||
decrypted.resize(original_size);
|
||||
|
||||
if (prs_decompress_size(decrypted) != decompressed_size) {
|
||||
throw runtime_error("decompressed size does not match size in header");
|
||||
throw std::runtime_error("decompressed size does not match size in header");
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
template <typename HeaderT, typename OpenFileT>
|
||||
static unordered_map<string, string> decode_qst_data_t(const string& data) {
|
||||
static std::unordered_map<std::string, std::string> decode_qst_data_t(const std::string& data) {
|
||||
phosg::StringReader r(data);
|
||||
|
||||
unordered_map<string, string> files;
|
||||
unordered_map<string, size_t> file_remaining_bytes;
|
||||
std::unordered_map<std::string, std::string> files;
|
||||
std::unordered_map<std::string, size_t> file_remaining_bytes;
|
||||
QuestFileFormat subformat = QuestFileFormat::QST; // Stand-in for unknown
|
||||
while (!r.eof()) {
|
||||
// Handle BB's implicit 8-byte command alignment
|
||||
@@ -1081,62 +1082,62 @@ static unordered_map<string, string> decode_qst_data_t(const string& data) {
|
||||
if (subformat == QuestFileFormat::QST) {
|
||||
subformat = QuestFileFormat::BIN_DAT;
|
||||
} else if (subformat != QuestFileFormat::BIN_DAT) {
|
||||
throw runtime_error("QST file contains mixed download and non-download commands");
|
||||
throw std::runtime_error("QST file contains mixed download and non-download commands");
|
||||
}
|
||||
} else if (header.command == 0xA6 || header.command == 0xA7) {
|
||||
if (subformat == QuestFileFormat::QST) {
|
||||
subformat = QuestFileFormat::BIN_DAT_DLQ;
|
||||
} else if (subformat != QuestFileFormat::BIN_DAT_DLQ) {
|
||||
throw runtime_error("QST file contains mixed download and non-download commands");
|
||||
throw std::runtime_error("QST file contains mixed download and non-download commands");
|
||||
}
|
||||
}
|
||||
|
||||
if (header.command == 0x44 || header.command == 0xA6) {
|
||||
if (header.size != sizeof(HeaderT) + sizeof(OpenFileT)) {
|
||||
throw runtime_error("qst open file command has incorrect size");
|
||||
throw std::runtime_error("qst open file command has incorrect size");
|
||||
}
|
||||
const auto& cmd = r.get<OpenFileT>();
|
||||
string internal_filename = cmd.filename.decode();
|
||||
std::string internal_filename = cmd.filename.decode();
|
||||
|
||||
if (!files.emplace(internal_filename, "").second) {
|
||||
throw runtime_error("qst opens the same file multiple times: " + internal_filename);
|
||||
throw std::runtime_error("qst opens the same file multiple times: " + internal_filename);
|
||||
}
|
||||
if (!file_remaining_bytes.emplace(internal_filename, cmd.file_size).second) {
|
||||
throw runtime_error("qst opens the same file multiple times: " + internal_filename);
|
||||
throw std::runtime_error("qst opens the same file multiple times: " + internal_filename);
|
||||
}
|
||||
|
||||
} else if (header.command == 0x13 || header.command == 0xA7) {
|
||||
// We have to allow larger commands here, because it seems some tools encoded QST files with BB's extra 4 padding
|
||||
// bytes included in the command size.
|
||||
if (header.size < sizeof(HeaderT) + sizeof(S_WriteFile_13_A7)) {
|
||||
throw runtime_error("qst write file command has incorrect size");
|
||||
throw std::runtime_error("qst write file command has incorrect size");
|
||||
}
|
||||
const auto& cmd = r.get<S_WriteFile_13_A7>();
|
||||
if (cmd.data_size > 0x400) {
|
||||
throw runtime_error("qst contains invalid write command");
|
||||
throw std::runtime_error("qst contains invalid write command");
|
||||
}
|
||||
string filename = cmd.filename.decode();
|
||||
std::string filename = cmd.filename.decode();
|
||||
|
||||
string& file_data = files.at(filename);
|
||||
std::string& file_data = files.at(filename);
|
||||
size_t& remaining_bytes = file_remaining_bytes.at(filename);
|
||||
|
||||
if (file_data.size() & 0x3FF) {
|
||||
throw runtime_error("qst contains uneven chunks out of order");
|
||||
throw std::runtime_error("qst contains uneven chunks out of order");
|
||||
}
|
||||
if (header.flag != file_data.size() / 0x400) {
|
||||
throw runtime_error("qst contains chunks out of order");
|
||||
throw std::runtime_error("qst contains chunks out of order");
|
||||
}
|
||||
file_data.append(reinterpret_cast<const char*>(cmd.data.data()), cmd.data_size);
|
||||
remaining_bytes -= cmd.data_size;
|
||||
|
||||
} else {
|
||||
throw runtime_error("invalid command in qst file");
|
||||
throw std::runtime_error("invalid command in qst file");
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& it : file_remaining_bytes) {
|
||||
if (it.second) {
|
||||
throw runtime_error(std::format("expected {} (0x{:X}) more bytes for file {}", it.second, it.second, it.first));
|
||||
throw std::runtime_error(std::format("expected {} (0x{:X}) more bytes for file {}", it.second, it.second, it.first));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1149,7 +1150,7 @@ static unordered_map<string, string> decode_qst_data_t(const string& data) {
|
||||
return files;
|
||||
}
|
||||
|
||||
unordered_map<string, string> decode_qst_data(const string& data) {
|
||||
std::unordered_map<std::string, std::string> decode_qst_data(const std::string& data) {
|
||||
// QST files start with an open file command, but the format differs depending on the PSO version that the qst file
|
||||
// is for. We can detect the format from the first 4 bytes in the file:
|
||||
// - BB: 58 00 44 00 or 58 00 A6 00
|
||||
@@ -1174,7 +1175,7 @@ unordered_map<string, string> decode_qst_data(const string& data) {
|
||||
} else if (((signature & 0xFF00FFFF) == 0x44005400) || ((signature & 0xFF00FFFF) == 0xA6005400)) {
|
||||
return decode_qst_data_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(data);
|
||||
} else {
|
||||
throw runtime_error("invalid qst file format");
|
||||
throw std::runtime_error("invalid qst file format");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1229,12 +1230,12 @@ void add_open_file_command_t<PSOCommandHeaderDCV3, S_OpenFile_XB_44_A6>(
|
||||
template <typename HeaderT>
|
||||
void add_write_file_commands_t(
|
||||
phosg::StringWriter& w,
|
||||
const string& filename,
|
||||
const string& data,
|
||||
const std::string& filename,
|
||||
const std::string& data,
|
||||
bool is_download,
|
||||
bool bb_alignment) {
|
||||
for (size_t z = 0; z < data.size(); z += 0x400) {
|
||||
size_t chunk_size = min<size_t>(data.size() - z, 0x400);
|
||||
size_t chunk_size = std::min<size_t>(data.size() - z, 0x400);
|
||||
add_command_header<HeaderT>(w, is_download ? 0xA7 : 0x13, z >> 10, sizeof(S_WriteFile_13_A7));
|
||||
S_WriteFile_13_A7 cmd;
|
||||
cmd.filename.encode(filename);
|
||||
@@ -1249,11 +1250,11 @@ void add_write_file_commands_t(
|
||||
}
|
||||
}
|
||||
|
||||
string encode_qst_file(
|
||||
const unordered_map<string, shared_ptr<const string>>& files,
|
||||
const string& name,
|
||||
std::string encode_qst_file(
|
||||
const std::unordered_map<std::string, std::shared_ptr<const std::string>>& files,
|
||||
const std::string& name,
|
||||
uint32_t quest_number,
|
||||
const string& xb_filename,
|
||||
const std::string& xb_filename,
|
||||
Version version,
|
||||
bool is_dlq_encoded) {
|
||||
phosg::StringWriter w;
|
||||
@@ -1313,7 +1314,7 @@ string encode_qst_file(
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
throw std::logic_error("invalid game version");
|
||||
}
|
||||
|
||||
return std::move(w.str());
|
||||
|
||||
+51
-52
@@ -1,7 +1,5 @@
|
||||
#include "QuestMetadata.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
phosg::JSON QuestMetadata::FloorAssignment::json() const {
|
||||
return phosg::JSON::dict({
|
||||
{"Floor", this->floor},
|
||||
@@ -21,51 +19,51 @@ std::string QuestMetadata::FloorAssignment::str() const {
|
||||
void QuestMetadata::apply_json_overrides(const phosg::JSON& json) {
|
||||
try {
|
||||
this->description_flag = json.at("DescriptionFlag").as_int();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->available_expression = make_shared<IntegralExpression>(json.get_string("AvailableIf"));
|
||||
} catch (const out_of_range&) {
|
||||
this->available_expression = std::make_shared<IntegralExpression>(json.get_string("AvailableIf"));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->enabled_expression = make_shared<IntegralExpression>(json.get_string("EnabledIf"));
|
||||
} catch (const out_of_range&) {
|
||||
this->enabled_expression = std::make_shared<IntegralExpression>(json.get_string("EnabledIf"));
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->allow_start_from_chat_command = json.get_bool("AllowStartFromChatCommand");
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->joinable = json.get_bool("Joinable");
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->lock_status_register = json.get_int("LockStatusRegister");
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->enemy_exp_overrides = QuestMetadata::parse_enemy_exp_overrides(json.at("EnemyEXPOverrides"));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->common_item_set_name = json.at("CommonItemSetName").as_string();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->rare_item_set_name = json.at("RareItemSetName").as_string();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->allowed_drop_modes = json.at("AllowedDropModes").as_int();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->default_drop_mode = phosg::enum_for_name<ServerDropMode>(json.at("DefaultDropMode").as_string());
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->enable_schtserv_commands = json.at("EnableSchtservCommands").as_bool();
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,51 +80,51 @@ void QuestMetadata::assign_default_floors() {
|
||||
|
||||
void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
||||
if (this->quest_number != other.quest_number) {
|
||||
throw logic_error(std::format(
|
||||
throw std::logic_error(std::format(
|
||||
"incorrect versioned quest number (existing: {:08X}, new: {:08X})", this->quest_number, other.quest_number));
|
||||
}
|
||||
if (this->category_id != other.category_id) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version is in a different category (existing: {:08X}, new: {:08X})",
|
||||
this->category_id, other.category_id));
|
||||
}
|
||||
if (this->episode != other.episode) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version is in a different episode (existing: {}, new: {})",
|
||||
name_for_episode(this->episode), name_for_episode(other.episode)));
|
||||
}
|
||||
if (this->allow_start_from_chat_command != other.allow_start_from_chat_command) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different allow_start_from_chat_command state (existing: {}, new: {})",
|
||||
this->allow_start_from_chat_command ? "true" : "false",
|
||||
other.allow_start_from_chat_command ? "true" : "false"));
|
||||
}
|
||||
if (this->joinable != other.joinable) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different joinability state (existing: {}, new: {})",
|
||||
this->joinable ? "true" : "false", other.joinable ? "true" : "false"));
|
||||
}
|
||||
bool this_has_player_limit = (this->max_players != 0) && (this->max_players != 4);
|
||||
bool other_has_player_limit = (other.max_players != 0) && (other.max_players != 4);
|
||||
if ((this_has_player_limit || other_has_player_limit) && (this->max_players != other.max_players)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different maximum player count (existing: {}, new: {})",
|
||||
this->max_players, other.max_players));
|
||||
}
|
||||
if (this->lock_status_register != other.lock_status_register) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different lock status register (existing: {:04X}, new: {:04X})",
|
||||
this->lock_status_register, other.lock_status_register));
|
||||
}
|
||||
if (this->enemy_exp_overrides != other.enemy_exp_overrides) {
|
||||
throw runtime_error("quest version has different enemy EXP overrides");
|
||||
throw std::runtime_error("quest version has different enemy EXP overrides");
|
||||
}
|
||||
if (this->solo_unlock_flags != other.solo_unlock_flags) {
|
||||
throw runtime_error(std::format("quest version has a different set of solo unlock flags"));
|
||||
throw std::runtime_error(std::format("quest version has a different set of solo unlock flags"));
|
||||
}
|
||||
if (!this->create_item_mask_entries.empty() && !other.create_item_mask_entries.empty() &&
|
||||
this->create_item_mask_entries != other.create_item_mask_entries) {
|
||||
string this_str, other_str;
|
||||
std::string this_str, other_str;
|
||||
for (const auto& item : this->create_item_mask_entries) {
|
||||
if (!this_str.empty()) {
|
||||
this_str += ", ";
|
||||
@@ -139,32 +137,32 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
||||
}
|
||||
other_str += item.str();
|
||||
}
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different set of create item masks (existing: {}, new: {})", this_str, other_str));
|
||||
}
|
||||
if (!this->battle_rules != !other.battle_rules) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different battle rules presence state (existing: {}, new: {})",
|
||||
this->battle_rules ? "present" : "absent", other.battle_rules ? "present" : "absent"));
|
||||
}
|
||||
if (this->battle_rules && (*this->battle_rules != *other.battle_rules)) {
|
||||
string existing_str = this->battle_rules->json().serialize();
|
||||
string new_str = other.battle_rules->json().serialize();
|
||||
throw runtime_error(std::format(
|
||||
std::string existing_str = this->battle_rules->json().serialize();
|
||||
std::string new_str = other.battle_rules->json().serialize();
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different battle rules (existing: {}, new: {})", existing_str, new_str));
|
||||
}
|
||||
if (this->challenge_template_index != other.challenge_template_index) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different challenge template index (existing: {}, new: {})",
|
||||
this->challenge_template_index, other.challenge_template_index));
|
||||
}
|
||||
if (this->challenge_exp_multiplier != other.challenge_exp_multiplier) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different challenge EXP multiplier (existing: {}, new: {})",
|
||||
this->challenge_exp_multiplier, other.challenge_exp_multiplier));
|
||||
}
|
||||
if (this->challenge_difficulty != other.challenge_difficulty) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different challenge difficulty (existing: {}, new: {})",
|
||||
name_for_difficulty(this->challenge_difficulty), name_for_difficulty(other.challenge_difficulty)));
|
||||
}
|
||||
@@ -172,58 +170,59 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const {
|
||||
const auto& this_fa = this->floor_assignments[z];
|
||||
const auto& other_fa = other.floor_assignments[z];
|
||||
if (this_fa.area != other_fa.area) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different area on floor 0x{:02X} (existing: {}, new: {})",
|
||||
z, this_fa.str(), other_fa.str()));
|
||||
}
|
||||
}
|
||||
if (this->description_flag != other.description_flag) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different description flag (existing: {:02X}, new: {:02X})",
|
||||
this->description_flag, other.description_flag));
|
||||
}
|
||||
if (!this->available_expression != !other.available_expression) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has available expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||
this->available_expression ? "present" : "absent", other.available_expression ? "present" : "absent"));
|
||||
}
|
||||
if (this->available_expression && *this->available_expression != *other.available_expression) {
|
||||
string existing_str = this->available_expression->str();
|
||||
string new_str = other.available_expression->str();
|
||||
throw runtime_error(std::format(
|
||||
std::string existing_str = this->available_expression->str();
|
||||
std::string new_str = other.available_expression->str();
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different available expression (existing: {}, new: {})", existing_str, new_str));
|
||||
}
|
||||
if (!this->enabled_expression != !other.enabled_expression) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has enabled expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||
this->enabled_expression ? "present" : "absent", other.enabled_expression ? "present" : "absent"));
|
||||
}
|
||||
if (this->enabled_expression && *this->enabled_expression != *other.enabled_expression) {
|
||||
string existing_str = this->enabled_expression->str();
|
||||
string new_str = other.enabled_expression->str();
|
||||
throw runtime_error(std::format(
|
||||
std::string existing_str = this->enabled_expression->str();
|
||||
std::string new_str = other.enabled_expression->str();
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has a different enabled expression (existing: {}, new: {})", existing_str, new_str));
|
||||
}
|
||||
if (this->common_item_set_name != other.common_item_set_name) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different common table name (existing: {}, new: {})",
|
||||
this->common_item_set_name, other.common_item_set_name));
|
||||
}
|
||||
if (this->rare_item_set_name != other.rare_item_set_name) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different rare table name (existing: {}, new: {})",
|
||||
this->rare_item_set_name, other.rare_item_set_name));
|
||||
}
|
||||
if (this->allowed_drop_modes != other.allowed_drop_modes) {
|
||||
throw runtime_error(format("quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different allowed drop modes (existing: {:02X}, new: {:02X})",
|
||||
this->allowed_drop_modes, other.allowed_drop_modes));
|
||||
}
|
||||
if (this->default_drop_mode != other.default_drop_mode) {
|
||||
throw runtime_error(format("quest version has different default drop mode (existing: {}, new: {})",
|
||||
throw std::runtime_error(std::format("quest version has different default drop mode (existing: {}, new: {})",
|
||||
phosg::name_for_enum(this->default_drop_mode), phosg::name_for_enum(other.default_drop_mode)));
|
||||
}
|
||||
if (this->enable_schtserv_commands != other.enable_schtserv_commands) {
|
||||
throw runtime_error(format(
|
||||
throw std::runtime_error(std::format(
|
||||
"quest version has different value for enable_schtserv_commands (existing: {}, new: {})",
|
||||
this->enable_schtserv_commands ? "true" : "false", other.enable_schtserv_commands ? "true" : "false"));
|
||||
}
|
||||
@@ -354,7 +353,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
||||
// Key is like "Difficulty:Floor:EnemyType" or "Difficulty:EnemyType"
|
||||
auto key_tokens = phosg::split(key, ':');
|
||||
|
||||
static const unordered_map<string, Difficulty> difficulty_keys(
|
||||
static const std::unordered_map<std::string, Difficulty> difficulty_keys(
|
||||
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
||||
|
||||
Difficulty difficulty = Difficulty::NORMAL;
|
||||
@@ -366,7 +365,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
||||
floor = stoul(key_tokens[1], nullptr, 0);
|
||||
enemy_type = phosg::enum_for_name<EnemyType>(key_tokens[2]);
|
||||
} else {
|
||||
throw runtime_error("malformatted key: " + key);
|
||||
throw std::runtime_error("malformatted key: " + key);
|
||||
}
|
||||
difficulty = difficulty_keys.at(key_tokens[0]);
|
||||
if (floor == 0xFF) {
|
||||
@@ -379,7 +378,7 @@ std::unordered_map<uint32_t, uint32_t> QuestMetadata::parse_enemy_exp_overrides(
|
||||
}
|
||||
return ret;
|
||||
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("invalid enemy EXP overrides: ", e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
+199
-200
File diff suppressed because it is too large
Load Diff
+66
-71
@@ -9,18 +9,14 @@
|
||||
#include "ItemData.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
string RareItemSet::ExpandedDrop::str() const {
|
||||
std::string RareItemSet::ExpandedDrop::str() const {
|
||||
auto frac = phosg::reduce_fraction<uint64_t>(this->probability, 0x100000000);
|
||||
auto hex = this->data.hex();
|
||||
return std::format(
|
||||
"({:08X} => {}/{}) {}",
|
||||
this->probability, frac.first, frac.second, hex);
|
||||
return std::format("({:08X} => {}/{}) {}", this->probability, frac.first, frac.second, hex);
|
||||
}
|
||||
|
||||
string RareItemSet::ExpandedDrop::str(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
string ret = this->str();
|
||||
std::string RareItemSet::ExpandedDrop::str(std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||
std::string ret = this->str();
|
||||
ret += " (";
|
||||
ret += name_index->describe_item(this->data);
|
||||
ret += ")";
|
||||
@@ -74,7 +70,7 @@ uint8_t RareItemSet::compress_rate(uint32_t probability) {
|
||||
RareItemSet::ParsedRELData::PackedDrop::PackedDrop(const ExpandedDrop& exp)
|
||||
: probability(RareItemSet::compress_rate(exp.probability)) {
|
||||
if (!exp.data.can_be_encoded_in_rel_rare_table()) {
|
||||
throw runtime_error("item " + exp.data.short_hex() + " has extended attributes and cannot be encoded in a REL file");
|
||||
throw std::runtime_error("item " + exp.data.short_hex() + " has extended attributes and cannot be encoded in a REL file");
|
||||
}
|
||||
this->item_code[0] = exp.data.data1[0];
|
||||
this->item_code[1] = exp.data.data1[1];
|
||||
@@ -182,7 +178,7 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
|
||||
for (const auto& [enemy_type, specs] : collection.enemy_specs) {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
if (def.rt_index == 0xFF) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"monster spec for {} has no rt_index and cannot be converted to ItemRT format", def.enum_name));
|
||||
}
|
||||
|
||||
@@ -191,14 +187,14 @@ RareItemSet::ParsedRELData::ParsedRELData(const SpecCollection& collection) {
|
||||
if (dest_spec.data.empty()) {
|
||||
dest_spec = spec;
|
||||
} else if ((dest_spec.probability != spec.probability) || (dest_spec.data != spec.data)) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"monster spec for {} contains multiple drops and cannot be converted to ItemRT format", def.enum_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collection.box_specs.size() > 0xFF) {
|
||||
throw runtime_error("area_norm value too high");
|
||||
throw std::runtime_error("area_norm value too high");
|
||||
}
|
||||
for (uint8_t area_norm = 0; area_norm < collection.box_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection.box_specs[area_norm]) {
|
||||
@@ -249,14 +245,14 @@ RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) {
|
||||
ParsedRELData rel(afs.get_reader(index), false, is_v1);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(mode, Episode::EP1, difficulty, section_id), rel.as_collection(Episode::EP1));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string RareItemSet::gsl_entry_name_for_table(
|
||||
std::string RareItemSet::gsl_entry_name_for_table(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) {
|
||||
return std::format("ItemRT{}{}{}{}.rel",
|
||||
((mode == GameMode::CHALLENGE) ? "c" : ""),
|
||||
@@ -271,11 +267,11 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (size_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id);
|
||||
std::string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id);
|
||||
ParsedRELData rel(gsl.get_reader(filename), is_big_endian, false);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(mode, episode, difficulty, section_id), rel.as_collection(episode));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,7 +279,7 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) {
|
||||
}
|
||||
}
|
||||
|
||||
RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
||||
RareItemSet::RareItemSet(const std::string& rel_data, bool is_big_endian) {
|
||||
// Tables are 0x280 bytes in size in this format, laid out sequentially
|
||||
phosg::StringReader r(rel_data);
|
||||
for (Episode episode : ALL_EPISODES_V4) {
|
||||
@@ -295,26 +291,26 @@ RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) {
|
||||
ParsedRELData rel(r.sub(0x280 * index, 0x280), is_big_endian, false);
|
||||
this->collections.emplace(
|
||||
this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection(episode));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex> name_index) {
|
||||
RareItemSet::RareItemSet(const phosg::JSON& json, std::shared_ptr<const ItemNameIndex> name_index) {
|
||||
for (const auto& mode_it : json.as_dict()) {
|
||||
static const unordered_map<string, GameMode> mode_keys(
|
||||
static const std::unordered_map<std::string, GameMode> mode_keys(
|
||||
{{"Normal", GameMode::NORMAL}, {"Battle", GameMode::BATTLE}, {"Challenge", GameMode::CHALLENGE}, {"Solo", GameMode::SOLO}});
|
||||
GameMode mode = mode_keys.at(mode_it.first);
|
||||
|
||||
for (const auto& episode_it : mode_it.second->as_dict()) {
|
||||
static const unordered_map<string, Episode> episode_keys(
|
||||
static const std::unordered_map<std::string, Episode> episode_keys(
|
||||
{{"Episode1", Episode::EP1}, {"Episode2", Episode::EP2}, {"Episode4", Episode::EP4}});
|
||||
Episode episode = episode_keys.at(episode_it.first);
|
||||
|
||||
for (const auto& difficulty_it : episode_it.second->as_dict()) {
|
||||
static const unordered_map<string, Difficulty> difficulty_keys(
|
||||
static const std::unordered_map<std::string, Difficulty> difficulty_keys(
|
||||
{{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}});
|
||||
Difficulty difficulty = difficulty_keys.at(difficulty_it.first);
|
||||
|
||||
@@ -323,7 +319,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
||||
|
||||
auto& collection = this->collections[this->key_for_params(mode, episode, difficulty, section_id)];
|
||||
for (const auto& [enemy_type_name, specs_json] : section_id_it.second->as_dict()) {
|
||||
vector<ExpandedDrop>* target;
|
||||
std::vector<ExpandedDrop>* target;
|
||||
if (enemy_type_name.starts_with("Box-")) {
|
||||
uint8_t area_norm = FloorDefinition::get(episode, enemy_type_name.substr(4)).drop_area_norm;
|
||||
if (collection.box_specs.size() <= area_norm) {
|
||||
@@ -343,7 +339,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
||||
} else if (prob_desc.is_string()) {
|
||||
auto tokens = phosg::split(prob_desc.as_string(), '/');
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("invalid probability specification");
|
||||
throw std::runtime_error("invalid probability specification");
|
||||
}
|
||||
uint64_t numerator = stoull(tokens[0], nullptr, 0);
|
||||
uint64_t denominator = stoull(tokens[1], nullptr, 0);
|
||||
@@ -362,11 +358,11 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
||||
d.data.data1[2] = item_code & 0xFF;
|
||||
} else if (item_desc.is_string()) {
|
||||
if (!name_index) {
|
||||
throw runtime_error("item name index is not available");
|
||||
throw std::runtime_error("item name index is not available");
|
||||
}
|
||||
d.data = name_index->parse_item_description(item_desc.as_string());
|
||||
} else {
|
||||
throw runtime_error("invalid item description type");
|
||||
throw std::runtime_error("invalid item description type");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +373,7 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr<const ItemNameIndex
|
||||
}
|
||||
|
||||
std::string RareItemSet::serialize_afs(bool is_v1) const {
|
||||
vector<string> files;
|
||||
std::vector<std::string> files;
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
if (is_v1 && (difficulty == Difficulty::ULTIMATE)) {
|
||||
continue;
|
||||
@@ -391,16 +387,16 @@ std::string RareItemSet::serialize_afs(bool is_v1) const {
|
||||
}
|
||||
|
||||
std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
||||
unordered_map<string, string> files;
|
||||
std::unordered_map<std::string, std::string> files;
|
||||
|
||||
for (Episode episode : ALL_EPISODES_V3) {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
string filename = this->gsl_entry_name_for_table(GameMode::NORMAL, episode, difficulty, section_id);
|
||||
std::string filename = this->gsl_entry_name_for_table(GameMode::NORMAL, episode, difficulty, section_id);
|
||||
ParsedRELData rel(this->get_collection(GameMode::NORMAL, episode, difficulty, section_id));
|
||||
files.emplace(filename, rel.serialize(big_endian, false));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// Collection does not exist; skip it
|
||||
}
|
||||
}
|
||||
@@ -410,10 +406,10 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
||||
for (Difficulty difficulty : ALL_DIFFICULTIES_V234) {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
string filename = this->gsl_entry_name_for_table(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id);
|
||||
std::string filename = this->gsl_entry_name_for_table(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id);
|
||||
ParsedRELData rel(this->get_collection(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id));
|
||||
files.emplace(filename, rel.serialize(big_endian, false));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// Collection does not exist; skip it
|
||||
}
|
||||
}
|
||||
@@ -421,17 +417,17 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const {
|
||||
return GSLArchive::generate(files, big_endian);
|
||||
}
|
||||
|
||||
string RareItemSet::serialize_html(
|
||||
std::string RareItemSet::serialize_html(
|
||||
GameMode mode,
|
||||
Episode episode,
|
||||
Difficulty difficulty,
|
||||
shared_ptr<const ItemNameIndex> name_index,
|
||||
shared_ptr<const CommonItemSet> common_item_set) const {
|
||||
std::shared_ptr<const ItemNameIndex> name_index,
|
||||
std::shared_ptr<const CommonItemSet> common_item_set) const {
|
||||
|
||||
struct ZoneTypes {
|
||||
const char* name;
|
||||
vector<uint8_t> floors;
|
||||
vector<EnemyType> types;
|
||||
std::vector<uint8_t> floors;
|
||||
std::vector<EnemyType> types;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
@@ -561,7 +557,7 @@ string RareItemSet::serialize_html(
|
||||
return (((r / 8) & 0xFF) << 16) | (((g / 8) & 0xFF) << 8) | ((b / 8) & 0xFF);
|
||||
};
|
||||
|
||||
deque<string> blocks;
|
||||
std::deque<std::string> blocks;
|
||||
blocks.emplace_back(std::format("\
|
||||
<html>\n\
|
||||
<head>\n\
|
||||
@@ -643,7 +639,7 @@ string RareItemSet::serialize_html(
|
||||
</style>\n\
|
||||
</head><body>\n");
|
||||
|
||||
string mode_token;
|
||||
std::string mode_token;
|
||||
switch (mode) {
|
||||
case GameMode::NORMAL:
|
||||
mode_token = "";
|
||||
@@ -658,7 +654,7 @@ string RareItemSet::serialize_html(
|
||||
mode_token = " (solo mode)";
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid game mode");
|
||||
throw std::logic_error("invalid game mode");
|
||||
}
|
||||
|
||||
blocks.emplace_back(std::format(
|
||||
@@ -679,7 +675,7 @@ string RareItemSet::serialize_html(
|
||||
blocks.emplace_back("</tr>");
|
||||
};
|
||||
|
||||
auto add_specs_row = [&](const EnemyTypeDefinition* type_def, const char* loc_name, bool is_box, const array<vector<ExpandedDrop>, 10>& specs_lists) -> void {
|
||||
auto add_specs_row = [&](const EnemyTypeDefinition* type_def, const char* loc_name, bool is_box, const std::array<std::vector<ExpandedDrop>, 10>& specs_lists) -> void {
|
||||
bool any_list_nonempty = false;
|
||||
for (const auto& specs_list : specs_lists) {
|
||||
any_list_nonempty |= !specs_list.empty();
|
||||
@@ -691,7 +687,7 @@ string RareItemSet::serialize_html(
|
||||
blocks.emplace_back(std::format("<tr><td class=\"loc-{}\">{}</td>", is_box ? "box" : "enemy", loc_name));
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
blocks.emplace_back(std::format("<td class=\"sec{}-{}\">", section_id, is_box ? "box" : "enemy"));
|
||||
vector<string> tokens;
|
||||
std::vector<std::string> tokens;
|
||||
for (const auto& spec : specs_lists[section_id]) {
|
||||
if (!tokens.empty()) {
|
||||
tokens.emplace_back("");
|
||||
@@ -705,7 +701,7 @@ string RareItemSet::serialize_html(
|
||||
uint8_t dar = 0;
|
||||
try {
|
||||
dar = table->enemy_type_drop_probs.at(type_def->type);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
exact_token += std::format(" (DAR: {}%)", dar);
|
||||
frac.first *= dar;
|
||||
@@ -729,12 +725,12 @@ string RareItemSet::serialize_html(
|
||||
}
|
||||
}
|
||||
|
||||
string hex = example_item.short_hex();
|
||||
string desc = name_index->describe_item(example_item, ItemNameIndex::Flag::NAME_ONLY);
|
||||
std::string hex = example_item.short_hex();
|
||||
std::string desc = name_index->describe_item(example_item, ItemNameIndex::Flag::NAME_ONLY);
|
||||
tokens.emplace_back(std::format("<span class=\"item\" title=\"Hex: {}\">{}</span>", hex, desc));
|
||||
|
||||
float denom = static_cast<float>(frac.second) / static_cast<double>(frac.first);
|
||||
string denom_token = (floor(denom) == denom)
|
||||
std::string denom_token = (floor(denom) == denom)
|
||||
? std::format("1 / {:.0f}", denom)
|
||||
: std::format("1 / {:.02f}", denom);
|
||||
tokens.emplace_back(std::format("<span class=\"rate\" title=\"{}\">{}</span>", exact_token, denom_token));
|
||||
@@ -751,7 +747,7 @@ string RareItemSet::serialize_html(
|
||||
for (const auto& zone_type : zone_types) {
|
||||
add_location_header(zone_type.name);
|
||||
for (EnemyType type : zone_type.types) {
|
||||
array<vector<ExpandedDrop>, 10> specs_lists;
|
||||
std::array<std::vector<ExpandedDrop>, 10> specs_lists;
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, type);
|
||||
}
|
||||
@@ -762,9 +758,9 @@ string RareItemSet::serialize_html(
|
||||
for (uint8_t floor : zone_type.floors) {
|
||||
const auto& floor_def = FloorDefinition::get(episode, floor);
|
||||
if (floor_def.drop_area_norm == 0xFF) {
|
||||
throw runtime_error("zone includes floors with no drop area");
|
||||
throw std::runtime_error("zone includes floors with no drop area");
|
||||
}
|
||||
array<vector<ExpandedDrop>, 10> specs_lists;
|
||||
std::array<std::vector<ExpandedDrop>, 10> specs_lists;
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
specs_lists[section_id] = this->get_box_specs(mode, episode, difficulty, section_id, floor_def.drop_area_norm);
|
||||
}
|
||||
@@ -777,7 +773,7 @@ string RareItemSet::serialize_html(
|
||||
return phosg::join(blocks, "");
|
||||
}
|
||||
|
||||
phosg::JSON RareItemSet::json(shared_ptr<const ItemNameIndex> name_index) const {
|
||||
phosg::JSON RareItemSet::json(std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||
auto modes_dict = phosg::JSON::dict();
|
||||
for (const auto& mode : ALL_GAME_MODES_V4) {
|
||||
auto episodes_dict = phosg::JSON::dict();
|
||||
@@ -853,12 +849,12 @@ void RareItemSet::multiply_all_rates(double factor) {
|
||||
for (auto& [_, collection] : this->collections) {
|
||||
for (auto& [_, specs] : collection.enemy_specs) {
|
||||
for (auto& spec : specs) {
|
||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
spec.probability = std::min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
for (auto& specs : collection.box_specs) {
|
||||
for (auto& spec : specs) {
|
||||
spec.probability = min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
spec.probability = std::min<uint64_t>(spec.probability * factor, 0xFFFFFFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -870,11 +866,11 @@ void RareItemSet::print_collection(
|
||||
Episode episode,
|
||||
Difficulty difficulty,
|
||||
uint8_t section_id,
|
||||
shared_ptr<const ItemNameIndex> name_index) const {
|
||||
std::shared_ptr<const ItemNameIndex> name_index) const {
|
||||
const SpecCollection* collection;
|
||||
try {
|
||||
collection = &this->get_collection(mode, episode, difficulty, section_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -889,18 +885,17 @@ void RareItemSet::print_collection(
|
||||
try {
|
||||
const auto& def = type_definition_for_enemy(enemy_type);
|
||||
for (const auto& spec : collection->enemy_specs.at(enemy_type)) {
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, s);
|
||||
phosg::fwrite_fmt(stream, " {:<23} {}\n", def.enum_name, (name_index ? spec.str(name_index) : spec.str()));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
phosg::fwrite_fmt(stream, " Box rares:\n");
|
||||
for (size_t area_norm = 0; area_norm < collection->box_specs.size(); area_norm++) {
|
||||
for (const auto& spec : collection->box_specs[area_norm]) {
|
||||
string s = name_index ? spec.str(name_index) : spec.str();
|
||||
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n", area_norm, s);
|
||||
phosg::fwrite_fmt(stream, " (area-norm {:02X}) {}\n",
|
||||
area_norm, (name_index ? spec.str(name_index) : spec.str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -912,7 +907,7 @@ void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr<const Item
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
this->print_collection(stream, mode, episode, difficulty, section_id, name_index);
|
||||
} catch (const out_of_range& e) {
|
||||
} catch (const std::out_of_range& e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -938,11 +933,11 @@ void RareItemSet::SpecCollection::print_diff(FILE* stream, const SpecCollection&
|
||||
const std::vector<ExpandedDrop>* other_specs = &empty_specs;
|
||||
try {
|
||||
this_specs = &this->enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
other_specs = &other.enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (*this_specs != *other_specs) {
|
||||
phosg::fwrite_fmt(stream, " {}: {} -> {}\n",
|
||||
@@ -969,11 +964,11 @@ void RareItemSet::print_diff(FILE* stream, const RareItemSet& other) const {
|
||||
const SpecCollection* other_coll = nullptr;
|
||||
try {
|
||||
this_coll = &this->get_collection(mode, episode, difficulty, section_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
other_coll = &other.get_collection(mode, episode, difficulty, section_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
|
||||
if (!this_coll && !other_coll) {
|
||||
@@ -1014,7 +1009,7 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_enemy_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, EnemyType enemy_type) const {
|
||||
try {
|
||||
return this->get_collection(mode, episode, difficulty, secid).enemy_specs.at(enemy_type);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
}
|
||||
@@ -1024,7 +1019,7 @@ std::vector<RareItemSet::ExpandedDrop> RareItemSet::get_box_specs(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const {
|
||||
try {
|
||||
return this->get_collection(mode, episode, difficulty, secid).box_specs.at(area_norm);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::vector<ExpandedDrop> empty_vector;
|
||||
return empty_vector;
|
||||
}
|
||||
@@ -1043,7 +1038,7 @@ const RareItemSet::SpecCollection& RareItemSet::get_collection(
|
||||
GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const {
|
||||
try {
|
||||
return this->collections.at(this->key_for_params(mode, episode, difficulty, secid));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
if (mode == GameMode::BATTLE || mode == GameMode::SOLO) {
|
||||
return this->collections.at(this->key_for_params(GameMode::NORMAL, episode, difficulty, secid));
|
||||
}
|
||||
@@ -1053,10 +1048,10 @@ const RareItemSet::SpecCollection& RareItemSet::get_collection(
|
||||
|
||||
uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) {
|
||||
if (static_cast<size_t>(difficulty) > 3) {
|
||||
throw logic_error("incorrect difficulty");
|
||||
throw std::logic_error("incorrect difficulty");
|
||||
}
|
||||
if (secid > 10) {
|
||||
throw logic_error("incorrect section id");
|
||||
throw std::logic_error("incorrect section id");
|
||||
}
|
||||
|
||||
uint16_t key = ((static_cast<size_t>(difficulty) & 3) << 4) | (secid & 0x0F);
|
||||
@@ -1073,7 +1068,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
|
||||
key |= 0x00C0;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid episode in RareItemSet");
|
||||
throw std::logic_error("invalid episode in RareItemSet");
|
||||
}
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
@@ -1085,7 +1080,7 @@ uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty
|
||||
key |= 0x0200;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid episode in RareItemSet");
|
||||
throw std::logic_error("invalid episode in RareItemSet");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
+424
-436
File diff suppressed because it is too large
Load Diff
+393
-395
File diff suppressed because it is too large
Load Diff
+69
-77
@@ -8,10 +8,8 @@
|
||||
#include "Loggers.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static string encode_chat_message(Version version, const string& message) {
|
||||
string encoded_message;
|
||||
static std::string encode_chat_message(Version version, const std::string& message) {
|
||||
std::string encoded_message;
|
||||
encoded_message.resize(8, 0);
|
||||
encoded_message += uses_utf16(version) ? tt_utf8_to_utf16("\tE" + message) : tt_utf8_to_ascii("\tE" + message);
|
||||
encoded_message.resize((encoded_message.size() + 3) & (~3));
|
||||
@@ -19,14 +17,10 @@ static string encode_chat_message(Version version, const string& message) {
|
||||
}
|
||||
|
||||
ReplaySession::Event::Event(Type type, uint64_t client_id, size_t line_num)
|
||||
: type(type),
|
||||
client_id(client_id),
|
||||
allow_size_disparity(false),
|
||||
complete(false),
|
||||
line_num(line_num) {}
|
||||
: type(type), client_id(client_id), allow_size_disparity(false), complete(false), line_num(line_num) {}
|
||||
|
||||
string ReplaySession::Event::str() const {
|
||||
string ret;
|
||||
std::string ReplaySession::Event::str() const {
|
||||
std::string ret;
|
||||
if (this->type == Type::CONNECT) {
|
||||
ret = std::format("Event[{}, CONNECT", this->client_id);
|
||||
} else if (this->type == Type::DISCONNECT) {
|
||||
@@ -46,11 +40,11 @@ string ReplaySession::Event::str() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
||||
ReplaySession::Client::Client(std::shared_ptr<asio::io_context> io_context, uint64_t id, uint16_t port, Version version)
|
||||
: id(id),
|
||||
port(port),
|
||||
version(version),
|
||||
channel(make_shared<PeerChannel>(
|
||||
channel(std::make_shared<PeerChannel>(
|
||||
io_context,
|
||||
this->version,
|
||||
Language::ENGLISH,
|
||||
@@ -60,12 +54,12 @@ ReplaySession::Client::Client(shared_ptr<asio::io_context> io_context, uint64_t
|
||||
false,
|
||||
false)) {}
|
||||
|
||||
string ReplaySession::Client::str() const {
|
||||
std::string ReplaySession::Client::str() const {
|
||||
return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version));
|
||||
}
|
||||
|
||||
shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, shared_ptr<Client> c, size_t line_num) {
|
||||
auto event = make_shared<Event>(type, c->id, line_num);
|
||||
std::shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, std::shared_ptr<Client> c, size_t line_num) {
|
||||
auto event = std::make_shared<Event>(type, c->id, line_num);
|
||||
if (!this->last_event.get()) {
|
||||
this->first_event = event;
|
||||
} else {
|
||||
@@ -78,7 +72,7 @@ shared_ptr<ReplaySession::Event> ReplaySession::create_event(Event::Type type, s
|
||||
return event;
|
||||
}
|
||||
|
||||
void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
void ReplaySession::apply_default_mask(std::shared_ptr<Event> ev) {
|
||||
auto version = this->clients.at(ev->client_id)->version;
|
||||
|
||||
void* cmd_data = ev->data.data() + ((version == Version::BB_V4) ? 8 : 4);
|
||||
@@ -325,11 +319,11 @@ void ReplaySession::apply_default_mask(shared_ptr<Event> ev) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("invalid game version");
|
||||
throw std::logic_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
ReplaySession::ReplaySession(std::shared_ptr<ServerState> state, FILE* input_log)
|
||||
: state(state),
|
||||
prev_psov2_crypt_enabled(this->state->use_psov2_rand_crypt),
|
||||
commands_sent(0),
|
||||
@@ -338,13 +332,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
bytes_received(0),
|
||||
idle_timeout_timer(*this->state->io_context),
|
||||
run_failed(false) {
|
||||
shared_ptr<Event> parsing_command = nullptr;
|
||||
std::shared_ptr<Event> parsing_command = nullptr;
|
||||
|
||||
size_t line_num = 0;
|
||||
size_t num_events = 0;
|
||||
while (!feof(input_log)) {
|
||||
line_num++;
|
||||
string line = phosg::fgets(input_log);
|
||||
std::string line = phosg::fgets(input_log);
|
||||
if (line.ends_with("\n")) {
|
||||
line.resize(line.size() - 1);
|
||||
}
|
||||
@@ -353,11 +347,11 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
}
|
||||
|
||||
if (parsing_command.get()) {
|
||||
string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
||||
std::string expected_start = std::format("{:04X} |", parsing_command->data.size());
|
||||
if (line.starts_with(expected_start)) {
|
||||
// Parse out the hex part of the hex/ASCII dump
|
||||
string mask_bytes;
|
||||
string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
||||
std::string mask_bytes;
|
||||
std::string data_bytes = phosg::parse_data_string(line.substr(expected_start.size(), 16 * 3 + 1), &mask_bytes);
|
||||
parsing_command->data += data_bytes;
|
||||
parsing_command->mask += mask_bytes;
|
||||
continue;
|
||||
@@ -378,35 +372,35 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
if (line.starts_with("### cc ")) {
|
||||
// ### cc $<chat command>
|
||||
if (this->clients.size() != 1) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) cc shortcut cannot be used with multiple clients connected; use on C-X cc instead",
|
||||
line_num));
|
||||
}
|
||||
shared_ptr<Event> event;
|
||||
std::shared_ptr<Event> event;
|
||||
try {
|
||||
auto c = this->clients.begin()->second;
|
||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||
event->data = encode_chat_message(c->version, line.substr(7));
|
||||
num_events++;
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (line.starts_with("### on C-")) {
|
||||
// ### on C-{} cc <chat command>
|
||||
shared_ptr<Event> event;
|
||||
std::shared_ptr<Event> event;
|
||||
try {
|
||||
size_t end_offset;
|
||||
auto c = this->clients.at(stoull(line.substr(9), &end_offset, 16));
|
||||
if (line.compare(end_offset + 9, 4, " cc ") != 0) {
|
||||
throw runtime_error("malformed `on C-X cc $...` shortcut command");
|
||||
throw std::runtime_error("malformed `on C-X cc $...` shortcut command");
|
||||
}
|
||||
event = this->create_event(Event::Type::SEND, c, line_num);
|
||||
event->data = encode_chat_message(c->version, line.substr(end_offset + 13));
|
||||
num_events++;
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) failed to generate chat message ({})", line_num, e.what()));
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -414,25 +408,25 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
// I <pid/ts> - [GameServer] Client connected: C-1 via TG-9000-GC_V3-gc-jp10-game_server
|
||||
// I <pid/ts> - [GameServer] Client connected: C-3 via TSI-9000-GC_V3-game_server
|
||||
size_t offset = line.find(" - [GameServer] Client connected: C-");
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
|
||||
if (!tokens[8].starts_with("C-")) {
|
||||
throw runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client connection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
|
||||
auto listen_tokens = phosg::split(tokens[10], '-');
|
||||
if (listen_tokens.size() < 4) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connection message listening socket token format is incorrect", line_num));
|
||||
}
|
||||
uint16_t port = stoul(listen_tokens[1], nullptr, 10);
|
||||
Version version = phosg::enum_for_name<Version>(listen_tokens[2]);
|
||||
|
||||
auto c = make_shared<Client>(state->io_context, client_id, port, version);
|
||||
auto c = std::make_shared<Client>(state->io_context, client_id, port, version);
|
||||
if (!this->clients.emplace(c->id, c).second) {
|
||||
throw runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) duplicate client ID in input log", line_num));
|
||||
}
|
||||
this->create_event(Event::Type::CONNECT, c, line_num);
|
||||
num_events++;
|
||||
@@ -441,24 +435,24 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
|
||||
// I <pid/ts> - [GameServer] Running cleanup tasks for C-{}
|
||||
offset = line.find(" - [GameServer] Running cleanup tasks for C-");
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 11) {
|
||||
throw runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message has incorrect token count", line_num));
|
||||
}
|
||||
if (!tokens[10].starts_with("C-")) {
|
||||
throw runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client disconnection message missing client ID token", line_num));
|
||||
}
|
||||
uint64_t client_id = stoul(tokens[10].substr(2), nullptr, 16);
|
||||
try {
|
||||
auto& c = this->clients.at(client_id);
|
||||
if (c->disconnect_event.get()) {
|
||||
throw runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) client has multiple disconnect events", line_num));
|
||||
}
|
||||
c->disconnect_event = this->create_event(Event::Type::DISCONNECT, c, line_num);
|
||||
num_events++;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) unknown disconnecting client ID in input log", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -466,13 +460,13 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
// I <pid/ts> - [Commands] Sending to C-{:X} (...)
|
||||
// I <pid/ts> - [Commands] Received from C-{:X} (...)
|
||||
offset = line.find(" - [Commands] Sending to C-");
|
||||
if (offset == string::npos) {
|
||||
if (offset == std::string::npos) {
|
||||
offset = line.find(" - [Commands] Received from C-");
|
||||
}
|
||||
if (offset != string::npos) {
|
||||
if (offset != std::string::npos) {
|
||||
auto tokens = phosg::split(line, ' ');
|
||||
if (tokens.size() < 10) {
|
||||
throw runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) command header line too short", line_num));
|
||||
}
|
||||
bool from_client = (tokens[6] == "Received");
|
||||
uint64_t client_id = stoull(tokens[8].substr(2), nullptr, 16);
|
||||
@@ -480,8 +474,8 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
parsing_command = this->create_event(
|
||||
from_client ? Event::Type::SEND : Event::Type::RECEIVE, this->clients.at(client_id), line_num);
|
||||
num_events++;
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format("(ev-line {}) input log contains command for missing client", line_num));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -490,14 +484,12 @@ ReplaySession::ReplaySession(shared_ptr<ServerState> state, FILE* input_log)
|
||||
|
||||
replay_log.debug_f("{} clients in log", this->clients.size());
|
||||
for (const auto& it : this->clients) {
|
||||
string client_str = it.second->str();
|
||||
replay_log.debug_f(" {} => {}", it.first, client_str);
|
||||
replay_log.debug_f(" {} => {}", it.first, it.second->str());
|
||||
}
|
||||
|
||||
replay_log.debug_f("{} events in replay log", num_events);
|
||||
for (auto ev = this->first_event; ev != nullptr; ev = ev->next_event) {
|
||||
string ev_str = ev->str();
|
||||
replay_log.debug_f(" {}", ev_str);
|
||||
replay_log.debug_f(" {}", ev->str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,25 +504,25 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
switch (this->first_event->type) {
|
||||
case Event::Type::CONNECT: {
|
||||
if (c->channel->connected()) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) connect event on already-connected client", this->first_event->line_num));
|
||||
}
|
||||
|
||||
shared_ptr<const PortConfiguration> port_config;
|
||||
std::shared_ptr<const PortConfiguration> port_config;
|
||||
try {
|
||||
port_config = this->state->number_to_port_config.at(c->port);
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(std::format(
|
||||
} catch (const std::out_of_range&) {
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) client connected to port missing from configuration", this->first_event->line_num));
|
||||
}
|
||||
|
||||
auto server_channel = make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||
auto server_channel = std::make_shared<PeerChannel>(this->state->io_context, port_config->version, c->channel->language, "", phosg::TerminalFormat::END, phosg::TerminalFormat::END, false, false);
|
||||
PeerChannel::link_peers(c->channel, server_channel);
|
||||
|
||||
if (this->state->game_server.get()) {
|
||||
this->state->game_server->connect_channel(server_channel, c->port, port_config->behavior);
|
||||
} else {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) no server available for connection", this->first_event->line_num));
|
||||
}
|
||||
break;
|
||||
@@ -542,7 +534,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
|
||||
case Event::Type::SEND:
|
||||
if (!c->channel->connected()) {
|
||||
throw runtime_error(std::format(
|
||||
throw std::runtime_error(std::format(
|
||||
"(ev-line {}) send event attempted on unconnected client", this->first_event->line_num));
|
||||
}
|
||||
c->channel->send(this->first_event->data);
|
||||
@@ -552,25 +544,25 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
|
||||
case Event::Type::RECEIVE: {
|
||||
if (!c->channel->connected()) {
|
||||
throw runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||
throw std::runtime_error(std::format("(ev-line {}) receive event on non-connected client",
|
||||
this->first_event->line_num));
|
||||
}
|
||||
if (c->receive_events.front() != this->first_event) {
|
||||
throw logic_error("Client receive events are out of order");
|
||||
throw std::logic_error("Client receive events are out of order");
|
||||
}
|
||||
|
||||
this->reschedule_idle_timeout();
|
||||
auto msg = co_await c->channel->recv();
|
||||
|
||||
// TODO: Use the iovec form of phosg::print_data here instead of prepend_command_header (which copies data)
|
||||
string full_command = prepend_command_header(
|
||||
std::string full_command = prepend_command_header(
|
||||
c->version, (c->channel->crypt_in.get() != nullptr), msg.command, msg.flag, msg.data);
|
||||
this->commands_received++;
|
||||
this->bytes_received += full_command.size();
|
||||
|
||||
if (c->receive_events.empty()) {
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error("received unexpected command for client");
|
||||
throw std::runtime_error("received unexpected command for client");
|
||||
}
|
||||
|
||||
auto& ev = c->receive_events.front();
|
||||
@@ -579,15 +571,15 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command sizes do not match", ev->line_num));
|
||||
}
|
||||
for (size_t x = 0; x < min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||
for (size_t x = 0; x < std::min<size_t>(full_command.size(), ev->data.size()); x++) {
|
||||
if ((full_command[x] & ev->mask[x]) != (ev->data[x] & ev->mask[x])) {
|
||||
replay_log.error_f("Expected command:");
|
||||
phosg::print_data(stderr, ev->data, 0, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
replay_log.error_f("Received command:");
|
||||
phosg::print_data(stderr, full_command, 0, ev->data, phosg::FormatDataFlags::PRINT_ASCII | phosg::FormatDataFlags::OFFSET_16_BITS);
|
||||
throw runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||
throw std::runtime_error(std::format("(ev-line {}) received command data does not match expected data", ev->line_num));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,8 +592,8 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
case Version::BB_PATCH:
|
||||
if (msg.command == 0x02) {
|
||||
auto& cmd = msg.check_size_t<S_ServerInit_Patch_02>();
|
||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
}
|
||||
break;
|
||||
case Version::DC_NTE:
|
||||
@@ -618,11 +610,11 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
if (msg.command == 0x02 || msg.command == 0x17 || msg.command == 0x91 || msg.command == 0x9B) {
|
||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(0xFFFF);
|
||||
if (is_v1_or_v2(c->version)) {
|
||||
c->channel->crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
} else { // V3
|
||||
c->channel->crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
c->channel->crypt_in = std::make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
c->channel->crypt_out = std::make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -631,19 +623,19 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
auto& cmd = msg.check_size_t<S_ServerInitDefault_BB_03_9B>(0xFFFF);
|
||||
// TODO: At some point it may matter which BB private key file we use. Don't just blindly use the
|
||||
// first one here.
|
||||
c->channel->crypt_in = make_shared<PSOBBEncryption>(
|
||||
c->channel->crypt_in = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.server_key.data(), cmd.server_key.size());
|
||||
c->channel->crypt_out = make_shared<PSOBBEncryption>(
|
||||
c->channel->crypt_out = std::make_shared<PSOBBEncryption>(
|
||||
*this->state->bb_private_keys[0], cmd.client_key.data(), cmd.client_key.size());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw logic_error("unsupported encryption version");
|
||||
throw std::logic_error("unsupported encryption version");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw logic_error("unhandled event type");
|
||||
throw std::logic_error("unhandled event type");
|
||||
}
|
||||
this->first_event->complete = true;
|
||||
}
|
||||
@@ -653,7 +645,7 @@ asio::awaitable<void> ReplaySession::run() {
|
||||
this->last_event = nullptr;
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
replay_log.error_f("Replay failed: {}", e.what());
|
||||
if (this->first_event) {
|
||||
replay_log.error_f("Next pending event: {}", this->first_event->str());
|
||||
|
||||
+59
-60
@@ -7,13 +7,11 @@
|
||||
#include "LevelTable.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
struct DefaultSymbolChatEntry {
|
||||
array<const char*, 8> language_to_name;
|
||||
std::array<const char*, 8> language_to_name;
|
||||
uint32_t spec;
|
||||
array<uint16_t, 4> corner_objects;
|
||||
array<SymbolChatFacePart, 12> face_parts;
|
||||
std::array<uint16_t, 4> corner_objects;
|
||||
std::array<SymbolChatFacePart, 12> face_parts;
|
||||
|
||||
SaveFileSymbolChatEntryBB to_entry(Language language) const {
|
||||
SaveFileSymbolChatEntryBB ret;
|
||||
@@ -30,7 +28,7 @@ struct DefaultSymbolChatEntry {
|
||||
}
|
||||
};
|
||||
|
||||
static const array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
||||
static const std::array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1\xE3\x81\xAF", "\tEHello", "\tEHallo", "\tESalut", "\tEHola", "\tB\xE4\xBD\xA0\xE5\xA5\xBD", "\tT\xE4\xBD\xA0\xE5\xA5\xBD", "\tK\xEC\x95\x88\xEB\x85\x95"}, 0x28, {0xFFFF, 0x000D, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x05, 0x18, 0x1D, 0x00}, {0x05, 0x28, 0x1D, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x95\xE3\x82\x88\xE3\x81\x86\xE3\x81\xAA\xE3\x82\x89", "\tEGood-bye", "\tETschus", "\tEAu revoir", "\tEAdios", "\tB\xE5\x86\x8D\xE8\xA7\x81", "\tT\xE5\x86\x8D\xE8\xA6\x8B", "\tK\xEC\x9E\x98\xEA\xB0\x80"}, 0x74, {0x0476, 0x000C, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x06, 0x15, 0x14, 0x00}, {0x06, 0x2B, 0x14, 0x01}, {0x05, 0x18, 0x1F, 0x00}, {0x05, 0x28, 0x1F, 0x01}, {0x36, 0x20, 0x2A, 0x00}, {0x3C, 0x00, 0x32, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\xB0\xE3\x82\x93\xE3\x81\x96\xE3\x83\xBC\xE3\x81\x84", "\tEHurrah!", "\tEHurra!", "\tEHourra !", "\tEHurra", "\tB\xE4\xB8\x87\xE5\xB2\x81", "\tT\xE8\x90\xAC\xE6\xAD\xB2", "\tK\xEB\xA7\x8C\xEC\x84\xB8"}, 0x28, {0x0362, 0x0362, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x09, 0x16, 0x1B, 0x00}, {0x09, 0x2B, 0x1B, 0x01}, {0x37, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
@@ -39,11 +37,11 @@ static const array<DefaultSymbolChatEntry, 6> DEFAULT_SYMBOL_CHATS = {
|
||||
DefaultSymbolChatEntry{{"\tJ\xE3\x81\x9F\xE3\x81\x99\xE3\x81\x91\xE3\x81\xA6\xEF\xBC\x81", "\tEHelp me!", "\tEHilf mir!", "\tEAide-moi !", "\tEAyuda", "\tB\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tT\xE6\x95\x91\xE5\x91\xBD\xE5\x95\x8A\xEF\xBC\x81", "\tK\xEB\x8F\x84\xEC\x99\x80\xEC\xA4\x98\xEF\xBC\x81"}, 0xEC, {0x065E, 0x0138, 0xFFFF, 0xFFFF}, {SymbolChatFacePart{0x02, 0x17, 0x1B, 0x01}, {0x02, 0x2A, 0x1B, 0x00}, {0x31, 0x20, 0x2C, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x00}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}, {0xFF, 0x00, 0x00, 0x02}}},
|
||||
};
|
||||
|
||||
static const array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG = {
|
||||
static const std::array<uint16_t, 20> DEFAULT_TECH_MENU_CONFIG = {
|
||||
0x0000, 0x0006, 0x0003, 0x0001, 0x0007, 0x0004, 0x0002, 0x0008, 0x0005, 0x0009,
|
||||
0x0012, 0x000F, 0x0010, 0x0011, 0x000D, 0x000A, 0x000B, 0x000C, 0x000E, 0x0000};
|
||||
|
||||
static const array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
||||
static const std::array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -68,7 +66,7 @@ static const array<uint8_t, 0x016C> DEFAULT_KEY_CONFIG = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
|
||||
|
||||
static const array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG = {
|
||||
static const std::array<uint8_t, 0x0038> DEFAULT_JOYSTICK_CONFIG = {
|
||||
0x00, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
||||
@@ -88,7 +86,7 @@ ShuffleTables::ShuffleTables(PSOV2Encryption& crypt) {
|
||||
while (r28 >= 0) {
|
||||
uint32_t r3 = this->pseudorand(crypt, r28 + 1);
|
||||
if (r3 >= 0x100) {
|
||||
throw logic_error("bad r3");
|
||||
throw std::logic_error("bad r3");
|
||||
}
|
||||
uint8_t t = this->forward_table[r3];
|
||||
this->forward_table[r3] = *r31;
|
||||
@@ -144,7 +142,7 @@ bool PSOVMSFileHeader::checksum_correct() const {
|
||||
|
||||
void PSOVMSFileHeader::check() const {
|
||||
if (!this->checksum_correct()) {
|
||||
throw runtime_error("VMS file unencrypted header checksum is incorrect");
|
||||
throw std::runtime_error("VMS file unencrypted header checksum is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,19 +163,19 @@ bool PSOGCIFileHeader::checksum_correct() const {
|
||||
|
||||
void PSOGCIFileHeader::check() const {
|
||||
if (!this->checksum_correct()) {
|
||||
throw runtime_error("GCI file unencrypted header checksum is incorrect");
|
||||
throw std::runtime_error("GCI file unencrypted header checksum is incorrect");
|
||||
}
|
||||
if (this->developer_id[0] != '8' || this->developer_id[1] != 'P') {
|
||||
throw runtime_error("GCI file is not for a Sega game");
|
||||
throw std::runtime_error("GCI file is not for a Sega game");
|
||||
}
|
||||
if ((this->game_id[0] != 'G') && (this->game_id[0] != 'D')) {
|
||||
throw runtime_error("GCI file is not for a GameCube game");
|
||||
throw std::runtime_error("GCI file is not for a GameCube game");
|
||||
}
|
||||
if (this->game_id[1] != 'P') {
|
||||
throw runtime_error("GCI file is not for Phantasy Star Online");
|
||||
throw std::runtime_error("GCI file is not for Phantasy Star Online");
|
||||
}
|
||||
if ((this->game_id[2] != 'S') && (this->game_id[2] != 'O')) {
|
||||
throw runtime_error("GCI file is not for Phantasy Star Online");
|
||||
throw std::runtime_error("GCI file is not for Phantasy Star Online");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,10 +213,10 @@ phosg::ImageRGB888 PSOGCSnapshotFile::decode_image() const {
|
||||
size_t width = this->width ? this->width.load() : 256;
|
||||
size_t height = this->height ? this->height.load() : 192;
|
||||
if (width != 256) {
|
||||
throw runtime_error("width is incorrect");
|
||||
throw std::runtime_error("width is incorrect");
|
||||
}
|
||||
if (height != 192) {
|
||||
throw runtime_error("height is incorrect");
|
||||
throw std::runtime_error("height is incorrect");
|
||||
}
|
||||
|
||||
// 4x4 blocks of pixels
|
||||
@@ -307,7 +305,7 @@ bool PSOXBFileHeader::checksum_correct() const {
|
||||
|
||||
void PSOXBFileHeader::check() const {
|
||||
if (!this->checksum_correct()) {
|
||||
throw runtime_error("Xbox file intermediate header checksum is incorrect");
|
||||
throw std::runtime_error("Xbox file intermediate header checksum is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +320,7 @@ uint32_t PSOBBGuildCardFile::checksum() const {
|
||||
|
||||
void PSOBBGuildCardFile::delete_duplicates() {
|
||||
{
|
||||
unordered_set<uint32_t> seen;
|
||||
std::unordered_set<uint32_t> seen;
|
||||
size_t read_index = 0, write_index = 0;
|
||||
for (read_index = 0; read_index < this->blocked_senders.size(); read_index++) {
|
||||
const auto& read_blocked_senders = this->blocked_senders[read_index];
|
||||
@@ -339,7 +337,7 @@ void PSOBBGuildCardFile::delete_duplicates() {
|
||||
}
|
||||
|
||||
{
|
||||
unordered_set<uint32_t> seen;
|
||||
std::unordered_set<uint32_t> seen;
|
||||
size_t read_index = 0, write_index = 0;
|
||||
for (read_index = 0; read_index < this->entries.size(); read_index++) {
|
||||
const auto& read_entry = this->entries[read_index];
|
||||
@@ -377,13 +375,13 @@ PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const {
|
||||
return pre;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
uint32_t guild_card_number,
|
||||
Language language,
|
||||
const PlayerVisualConfig& visual,
|
||||
const std::string& name,
|
||||
shared_ptr<const LevelTable> level_table) {
|
||||
static const array<array<PlayerInventoryItem, 5>, 12> initial_inventory{{
|
||||
std::shared_ptr<const LevelTable> level_table) {
|
||||
static const std::array<std::array<PlayerInventoryItem, 5>, 12> initial_inventory{{
|
||||
{
|
||||
PlayerInventoryItem(ItemData(0x0001000000000000, 0x0000000000000000), true),
|
||||
PlayerInventoryItem(ItemData(0x0101000000000000, 0x0000000000000000), true),
|
||||
@@ -470,7 +468,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
},
|
||||
}};
|
||||
|
||||
static const array<uint8_t, 0xE8> config_hunter_ranger{
|
||||
static const std::array<uint8_t, 0xE8> config_hunter_ranger{
|
||||
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -486,7 +484,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
static const array<uint8_t, 0xE8> config_force{
|
||||
static const std::array<uint8_t, 0xE8> config_force{
|
||||
{0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00,
|
||||
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@@ -503,7 +501,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
|
||||
|
||||
auto ret = make_shared<PSOBBCharacterFile>();
|
||||
auto ret = std::make_shared<PSOBBCharacterFile>();
|
||||
ret->disp.visual = visual;
|
||||
ret->disp.name.encode(name, language);
|
||||
|
||||
@@ -514,7 +512,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
}
|
||||
|
||||
// Set mag color based on initial costume
|
||||
static const array<array<uint8_t, 25>, 12> mag_colors = {{
|
||||
static const std::array<std::array<uint8_t, 25>, 12> mag_colors = {{
|
||||
{0x09, 0x01, 0x02, 0x11, 0x0A, 0x05, 0x06, 0x0B, 0x05, 0x00, 0x07, 0x0B, 0x0C, 0x04, 0x05, 0x06, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x05, 0x06, 0x08, 0x11, 0x0D, 0x01, 0x02, 0x0C, 0x04, 0x05, 0x06, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x01, 0x02, 0x11, 0x04, 0x0E, 0x06, 0x01, 0x0E, 0x09, 0x07, 0x02, 0x11, 0x04, 0x05, 0x06, 0x04, 0x11, 0x0D, 0x01, 0x0B, 0x11, 0x0D, 0x05, 0x06},
|
||||
@@ -567,16 +565,16 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_config(
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_preview(
|
||||
uint32_t guild_card_number,
|
||||
Language language,
|
||||
const PlayerDispDataBBPreview& preview,
|
||||
shared_ptr<const LevelTable> level_table) {
|
||||
std::shared_ptr<const LevelTable> level_table) {
|
||||
return PSOBBCharacterFile::create_from_config(
|
||||
guild_card_number, language, preview.visual, preview.name.decode(language), level_table);
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
Language::JAPANESE,
|
||||
@@ -609,7 +607,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCN
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC112000CharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -652,7 +650,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODC1
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV1CharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -685,7 +683,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV2CharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -725,7 +723,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSODCV
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCNTECharacterFileCharacter& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -765,7 +763,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCN
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCCharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -815,7 +813,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCC
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCEp3CharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -863,7 +861,7 @@ shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOGCE
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
|
||||
std::shared_ptr<PSOBBCharacterFile> PSOBBCharacterFile::create_from_file(const PSOXBCharacterFile::Character& src) {
|
||||
auto ret = PSOBBCharacterFile::create_from_config(
|
||||
src.guild_card.guild_card_number,
|
||||
src.inventory.language,
|
||||
@@ -1178,24 +1176,24 @@ PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
PSOCHARFile::LoadSharedResult PSOCHARFile::load_shared(const string& filename, bool load_system) {
|
||||
PSOCHARFile::LoadSharedResult PSOCHARFile::load_shared(const std::string& filename, bool load_system) {
|
||||
auto f = phosg::fopen_unique(filename, "rb");
|
||||
auto header = phosg::freadx<PSOCommandHeaderBB>(f.get());
|
||||
if (header.size != 0x399C) {
|
||||
throw runtime_error("incorrect size in character file header");
|
||||
throw std::runtime_error("incorrect size in character file header");
|
||||
}
|
||||
if (header.command != 0x00E7) {
|
||||
throw runtime_error("incorrect command in character file header");
|
||||
throw std::runtime_error("incorrect command in character file header");
|
||||
}
|
||||
if (header.flag != 0x00000000) {
|
||||
throw runtime_error("incorrect flag in character file header");
|
||||
throw std::runtime_error("incorrect flag in character file header");
|
||||
}
|
||||
static_assert(sizeof(PSOBBCharacterFile) + sizeof(PSOBBBaseSystemFile) + sizeof(PSOBBFullTeamMembership) == 0x3994, ".psochar size is incorrect");
|
||||
|
||||
LoadSharedResult ret;
|
||||
ret.character_file = make_shared<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
||||
ret.character_file = std::make_shared<PSOBBCharacterFile>(phosg::freadx<PSOBBCharacterFile>(f.get()));
|
||||
if (load_system) {
|
||||
ret.system_file = make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(f.get()));
|
||||
ret.system_file = std::make_shared<PSOBBBaseSystemFile>(phosg::freadx<PSOBBBaseSystemFile>(f.get()));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1245,7 +1243,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim
|
||||
if (y < this->inventory.num_items) {
|
||||
size_t new_stack_size = this->inventory.items[y].data.data1[5] + item.data1[5];
|
||||
if (new_stack_size > combine_max) {
|
||||
throw out_of_range("stack is too large");
|
||||
throw std::out_of_range("stack is too large");
|
||||
}
|
||||
this->inventory.items[y].data.data1[5] = new_stack_size;
|
||||
return;
|
||||
@@ -1254,7 +1252,7 @@ void PSOBBCharacterFile::add_item(const ItemData& item, const ItemData::StackLim
|
||||
|
||||
// If we get here, then it's not meseta and not a combine item, so it needs to go into an empty inventory slot
|
||||
if (this->inventory.num_items >= 30) {
|
||||
throw out_of_range("inventory is full");
|
||||
throw std::out_of_range("inventory is full");
|
||||
}
|
||||
auto& inv_item = this->inventory.items[this->inventory.num_items];
|
||||
inv_item.state = 1;
|
||||
@@ -1285,7 +1283,7 @@ ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, cons
|
||||
// if amount is nonzero.
|
||||
if (amount && (inventory_item.data.stack_size(limits) > 1) && (amount < inventory_item.data.data1[5])) {
|
||||
if (is_equipped) {
|
||||
throw runtime_error("character has a combine item equipped");
|
||||
throw std::runtime_error("character has a combine item equipped");
|
||||
}
|
||||
ret = inventory_item.data;
|
||||
ret.data1[5] = amount;
|
||||
@@ -1318,7 +1316,7 @@ ItemData PSOBBCharacterFile::remove_item(uint32_t item_id, uint32_t amount, cons
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::add_meseta(uint32_t amount) {
|
||||
this->disp.stats.meseta = min<size_t>(static_cast<size_t>(this->disp.stats.meseta) + amount, 999999);
|
||||
this->disp.stats.meseta = std::min<size_t>(static_cast<size_t>(this->disp.stats.meseta) + amount, 999999);
|
||||
}
|
||||
|
||||
void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
||||
@@ -1327,7 +1325,7 @@ void PSOBBCharacterFile::remove_meseta(uint32_t amount, bool allow_overdraft) {
|
||||
} else if (allow_overdraft) {
|
||||
this->disp.stats.meseta = 0;
|
||||
} else {
|
||||
throw out_of_range("player does not have enough meseta");
|
||||
throw std::out_of_range("player does not have enough meseta");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1363,7 +1361,7 @@ uint8_t PSOBBCharacterFile::get_material_usage(MaterialType which) const {
|
||||
case MaterialType::LUCK:
|
||||
return this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2;
|
||||
default:
|
||||
throw logic_error("invalid material type");
|
||||
throw std::logic_error("invalid material type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,7 +1381,7 @@ void PSOBBCharacterFile::set_material_usage(MaterialType which, uint8_t usage) {
|
||||
this->inventory.items[8 + static_cast<uint8_t>(which)].extension_data2 = usage;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid material type");
|
||||
throw std::logic_error("invalid material type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1490,29 +1488,30 @@ static uint16_t crc16(const void* data, size_t size) {
|
||||
return ret ^ 0xFFFF;
|
||||
}
|
||||
|
||||
string encode_psobb_hangame_credentials(const string& user_id, const string& token, const string& unused) {
|
||||
std::string encode_psobb_hangame_credentials(
|
||||
const std::string& user_id, const std::string& token, const std::string& unused) {
|
||||
if (user_id.size() < 4) {
|
||||
throw runtime_error("user_id must be at least 4 characters");
|
||||
throw std::runtime_error("user_id must be at least 4 characters");
|
||||
}
|
||||
if (user_id.size() > 12) {
|
||||
throw runtime_error("user_id must be at most 12 characters");
|
||||
throw std::runtime_error("user_id must be at most 12 characters");
|
||||
}
|
||||
if (!user_id.ends_with("@HG")) {
|
||||
throw runtime_error("user_id must end with \"@HG\"");
|
||||
throw std::runtime_error("user_id must end with \"@HG\"");
|
||||
}
|
||||
if (token.empty()) {
|
||||
throw runtime_error("token must not be empty");
|
||||
throw std::runtime_error("token must not be empty");
|
||||
}
|
||||
if (token.size() > 8) {
|
||||
throw runtime_error("token must be at most 8 characters");
|
||||
throw std::runtime_error("token must be at most 8 characters");
|
||||
}
|
||||
for (char ch : token) {
|
||||
if (!isdigit(ch)) {
|
||||
throw runtime_error("token must contain only decimal digits");
|
||||
throw std::runtime_error("token must contain only decimal digits");
|
||||
}
|
||||
}
|
||||
if (unused.size() > 0xFF) {
|
||||
throw runtime_error("unused must be at most 255 characters");
|
||||
throw std::runtime_error("unused must be at most 255 characters");
|
||||
}
|
||||
|
||||
// The encoded format is:
|
||||
@@ -1526,7 +1525,7 @@ string encode_psobb_hangame_credentials(const string& user_id, const string& tok
|
||||
// uint8_t unused_size;
|
||||
// char unused[unused_size]; // Ignored (possibly email address?)
|
||||
// We'll fill in mask_key and checksum after all the other fields.
|
||||
string data(7, '\0'); // mask_key, checksum, unused
|
||||
std::string data(7, '\0'); // mask_key, checksum, unused
|
||||
data.push_back(user_id.size());
|
||||
data += user_id;
|
||||
data.push_back(token.size());
|
||||
|
||||
+387
-385
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -24,7 +24,7 @@ extern const std::unordered_set<std::string> bb_crypt_initial_client_commands;
|
||||
|
||||
constexpr size_t V3_V4_QUEST_LOAD_MAX_CHUNKS_IN_FLIGHT = 4;
|
||||
|
||||
// TODO: Many of these functions should take a shared_ptr<Channel> instead of a shared_ptr<Client>. Refactor functions
|
||||
// TODO: Many of these functions should take a std::shared_ptr<Channel> instead of a std::shared_ptr<Client>. Refactor functions
|
||||
// appropriately.
|
||||
|
||||
// Note: There are so many versions of this function for a few reasons:
|
||||
|
||||
+4
-6
@@ -13,9 +13,7 @@
|
||||
#include "ShellCommands.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
ServerShell::ServerShell(shared_ptr<ServerState> state)
|
||||
ServerShell::ServerShell(std::shared_ptr<ServerState> state)
|
||||
: state(state), th(&ServerShell::thread_fn, this) {}
|
||||
|
||||
ServerShell::~ServerShell() {
|
||||
@@ -28,7 +26,7 @@ void ServerShell::thread_fn() {
|
||||
for (;;) {
|
||||
phosg::fwrite_fmt(stdout, "newserv> ");
|
||||
fflush(stdout);
|
||||
string command;
|
||||
std::string command;
|
||||
uint64_t read_start_usecs = phosg::now();
|
||||
try {
|
||||
command = phosg::fgets(stdin);
|
||||
@@ -53,7 +51,7 @@ void ServerShell::thread_fn() {
|
||||
phosg::strip_leading_whitespace(command);
|
||||
|
||||
try {
|
||||
std::promise<deque<string>> promise;
|
||||
std::promise<std::deque<std::string>> promise;
|
||||
auto future = promise.get_future();
|
||||
|
||||
asio::co_spawn(
|
||||
@@ -73,7 +71,7 @@ void ServerShell::thread_fn() {
|
||||
} catch (const exit_shell&) {
|
||||
this->state->io_context->stop();
|
||||
return;
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
+277
-285
File diff suppressed because it is too large
Load Diff
+162
-160
@@ -14,48 +14,48 @@
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
vector<const ShellCommand*> ShellCommand::commands_by_order;
|
||||
unordered_map<string, const ShellCommand*> ShellCommand::commands_by_name;
|
||||
std::vector<const ShellCommand*> ShellCommand::commands_by_order;
|
||||
std::unordered_map<std::string, const ShellCommand*> ShellCommand::commands_by_name;
|
||||
|
||||
exit_shell::exit_shell() : runtime_error("shell exited") {}
|
||||
|
||||
shared_ptr<Client> ShellCommand::Args::get_client() const {
|
||||
std::shared_ptr<Client> ShellCommand::Args::get_client() const {
|
||||
if (!s->game_server) {
|
||||
throw logic_error("game server is missing");
|
||||
throw std::logic_error("game server is missing");
|
||||
}
|
||||
|
||||
shared_ptr<Client> c;
|
||||
std::shared_ptr<Client> c;
|
||||
if (this->session_name.empty()) {
|
||||
return this->s->game_server->get_client();
|
||||
} else {
|
||||
auto clients = this->s->game_server->get_clients_by_identifier(this->session_name);
|
||||
if (clients.empty()) {
|
||||
throw runtime_error("no such client");
|
||||
throw std::runtime_error("no such client");
|
||||
}
|
||||
if (clients.size() > 1) {
|
||||
throw runtime_error("multiple clients found");
|
||||
throw std::runtime_error("multiple clients found");
|
||||
}
|
||||
return clients[0];
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Client> ShellCommand::Args::get_proxy_client() const {
|
||||
std::shared_ptr<Client> ShellCommand::Args::get_proxy_client() const {
|
||||
auto c = this->get_client();
|
||||
if (!c->proxy_session) {
|
||||
throw runtime_error("client is not in a proxy session");
|
||||
throw std::runtime_error("client is not in a proxy session");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
ShellCommand::ShellCommand(const char* name, const char* help_text, asio::awaitable<deque<string>> (*run)(Args&))
|
||||
ShellCommand::ShellCommand(
|
||||
const char* name, const char* help_text, asio::awaitable<std::deque<std::string>> (*run)(Args&))
|
||||
: name(name), help_text(help_text), run(run) {
|
||||
ShellCommand::commands_by_order.emplace_back(this);
|
||||
ShellCommand::commands_by_name.emplace(this->name, this);
|
||||
}
|
||||
|
||||
asio::awaitable<deque<string>> ShellCommand::dispatch_str(shared_ptr<ServerState> s, const string& command) {
|
||||
asio::awaitable<std::deque<std::string>> ShellCommand::dispatch_str(
|
||||
std::shared_ptr<ServerState> s, const std::string& command) {
|
||||
size_t command_end = phosg::skip_non_whitespace(command, 0);
|
||||
size_t args_begin = phosg::skip_whitespace(command, command_end);
|
||||
Args args;
|
||||
@@ -65,21 +65,21 @@ asio::awaitable<deque<string>> ShellCommand::dispatch_str(shared_ptr<ServerState
|
||||
co_return co_await ShellCommand::dispatch(args);
|
||||
}
|
||||
|
||||
asio::awaitable<deque<string>> ShellCommand::dispatch(Args& args) {
|
||||
asio::awaitable<std::deque<std::string>> ShellCommand::dispatch(Args& args) {
|
||||
const ShellCommand* def = nullptr;
|
||||
try {
|
||||
def = commands_by_name.at(args.command);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (!def) {
|
||||
throw runtime_error("no such command; try 'help'");
|
||||
throw std::runtime_error("no such command; try 'help'");
|
||||
} else {
|
||||
return def->run(args);
|
||||
}
|
||||
}
|
||||
|
||||
static string get_quoted_string(string& s) {
|
||||
string ret;
|
||||
static std::string get_quoted_string(std::string& s) {
|
||||
std::string ret;
|
||||
char end_char = (s.at(0) == '\"') ? '\"' : ' ';
|
||||
size_t z = (s.at(0) == '\"') ? 1 : 0;
|
||||
for (; (z < s.size()) && (s[z] != end_char); z++) {
|
||||
@@ -87,7 +87,7 @@ static string get_quoted_string(string& s) {
|
||||
if (z + 1 < s.size()) {
|
||||
ret.push_back(s[z + 1]);
|
||||
} else {
|
||||
throw runtime_error("incomplete escape sequence");
|
||||
throw std::runtime_error("incomplete escape sequence");
|
||||
}
|
||||
} else {
|
||||
ret.push_back(s[z]);
|
||||
@@ -95,7 +95,7 @@ static string get_quoted_string(string& s) {
|
||||
}
|
||||
if (end_char != ' ') {
|
||||
if (z >= s.size()) {
|
||||
throw runtime_error("unterminated quoted string");
|
||||
throw std::runtime_error("unterminated quoted string");
|
||||
}
|
||||
s = s.substr(phosg::skip_whitespace(s, z + 1));
|
||||
} else {
|
||||
@@ -104,8 +104,8 @@ static string get_quoted_string(string& s) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static asio::awaitable<deque<string>> empty_handler(ShellCommand::Args&) {
|
||||
co_return deque<string>();
|
||||
static asio::awaitable<std::deque<std::string>> empty_handler(ShellCommand::Args&) {
|
||||
co_return std::deque<std::string>();
|
||||
}
|
||||
|
||||
ShellCommand c_nop1("", nullptr, empty_handler);
|
||||
@@ -115,8 +115,8 @@ ShellCommand c_nop3("#", nullptr, empty_handler);
|
||||
ShellCommand c_help(
|
||||
"help", "help\n\
|
||||
You\'re reading it now.",
|
||||
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
|
||||
deque<string> ret({"Commands:"});
|
||||
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::deque<std::string> ret({"Commands:"});
|
||||
for (const auto& def : ShellCommand::commands_by_order) {
|
||||
if (def->help_text) {
|
||||
// TODO: It's not great that we copy the text here.
|
||||
@@ -129,7 +129,7 @@ ShellCommand c_help(
|
||||
ShellCommand c_exit(
|
||||
"exit", "exit (or ctrl+d)\n\
|
||||
Shut down the server.",
|
||||
+[](ShellCommand::Args&) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args&) -> asio::awaitable<std::deque<std::string>> {
|
||||
throw exit_shell();
|
||||
});
|
||||
ShellCommand c_on(
|
||||
@@ -141,7 +141,7 @@ ShellCommand c_on(
|
||||
gamertag, or a BB account username. For proxy commands, SESSION should be\n\
|
||||
the session ID, which generally is the same as the player\'s account ID\n\
|
||||
and appears after \"LinkedSession:\" in the log output.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
size_t session_name_end = phosg::skip_non_whitespace(args.args, 0);
|
||||
size_t command_begin = phosg::skip_whitespace(args.args, session_name_end);
|
||||
size_t command_end = phosg::skip_non_whitespace(args.args, command_begin);
|
||||
@@ -181,7 +181,7 @@ ShellCommand c_reload(
|
||||
disconnect or reload the battle parameters, so if these are changed without\n\
|
||||
restarting, clients may see (for example) EXP messages inconsistent with\n\
|
||||
the amounts of EXP actually received.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto types = phosg::split(args.args, ' ');
|
||||
for (const auto& type : types) {
|
||||
if (type == "all") {
|
||||
@@ -231,18 +231,18 @@ ShellCommand c_reload(
|
||||
} else if (type == "quests") {
|
||||
args.s->load_quest_index();
|
||||
} else {
|
||||
throw runtime_error("invalid data type: " + type);
|
||||
throw std::runtime_error("invalid data type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
|
||||
ShellCommand c_list_accounts(
|
||||
"list-accounts", "list-accounts\n\
|
||||
List all accounts registered on the server.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
deque<string> ret;
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::deque<std::string> ret;
|
||||
auto accounts = args.s->account_index->all();
|
||||
if (accounts.empty()) {
|
||||
ret.emplace_back("No accounts registered");
|
||||
@@ -254,20 +254,20 @@ ShellCommand c_list_accounts(
|
||||
co_return ret;
|
||||
});
|
||||
|
||||
uint32_t parse_account_flags(const string& flags_str) {
|
||||
uint32_t parse_account_flags(const std::string& flags_str) {
|
||||
try {
|
||||
size_t end_pos = 0;
|
||||
uint32_t ret = stoul(flags_str, &end_pos, 16);
|
||||
uint32_t ret = std::stoul(flags_str, &end_pos, 16);
|
||||
if (end_pos == flags_str.size()) {
|
||||
return ret;
|
||||
}
|
||||
} catch (const exception&) {
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
|
||||
uint32_t ret = 0;
|
||||
auto tokens = phosg::split(flags_str, ',');
|
||||
for (const auto& token : tokens) {
|
||||
string token_upper = phosg::toupper(token);
|
||||
std::string token_upper = phosg::toupper(token);
|
||||
if (token_upper == "NONE") {
|
||||
// Nothing to do
|
||||
} else if (token_upper == "KICK_USER") {
|
||||
@@ -299,32 +299,32 @@ uint32_t parse_account_flags(const string& flags_str) {
|
||||
} else if (token_upper == "IS_SHARED_ACCOUNT") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::IS_SHARED_ACCOUNT);
|
||||
} else {
|
||||
throw runtime_error("invalid flag name: " + token_upper);
|
||||
throw std::runtime_error("invalid flag name: " + token_upper);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t parse_account_user_flags(const string& user_flags_str) {
|
||||
uint32_t parse_account_user_flags(const std::string& user_flags_str) {
|
||||
try {
|
||||
size_t end_pos = 0;
|
||||
uint32_t ret = stoul(user_flags_str, &end_pos, 16);
|
||||
uint32_t ret = std::stoul(user_flags_str, &end_pos, 16);
|
||||
if (end_pos == user_flags_str.size()) {
|
||||
return ret;
|
||||
}
|
||||
} catch (const exception&) {
|
||||
} catch (const std::exception&) {
|
||||
}
|
||||
|
||||
uint32_t ret = 0;
|
||||
auto tokens = phosg::split(user_flags_str, ',');
|
||||
for (const auto& token : tokens) {
|
||||
string token_upper = phosg::toupper(token);
|
||||
std::string token_upper = phosg::toupper(token);
|
||||
if (token_upper == "NONE") {
|
||||
// Nothing to do
|
||||
} else if (token_upper == "DISABLE_DROP_NOTIFICATION_BROADCAST") {
|
||||
ret |= static_cast<uint32_t>(Account::UserFlag::DISABLE_DROP_NOTIFICATION_BROADCAST);
|
||||
} else {
|
||||
throw runtime_error("invalid user flag name: " + token_upper);
|
||||
throw std::runtime_error("invalid user flag name: " + token_upper);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -360,15 +360,15 @@ ShellCommand c_add_account(
|
||||
IS_SHARED_ACCOUNT: Account is a shared serial (disables Access Key and\n\
|
||||
password checks; players will get Guild Cards based on their player\n\
|
||||
names)",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
auto account = make_shared<Account>();
|
||||
for (const string& token : phosg::split(args.args, ' ')) {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto account = std::make_shared<Account>();
|
||||
for (const std::string& token : phosg::split(args.args, ' ')) {
|
||||
if (token.starts_with("id=")) {
|
||||
account->account_id = stoul(token.substr(3), nullptr, 16);
|
||||
account->account_id = std::stoul(token.substr(3), nullptr, 16);
|
||||
} else if (token.starts_with("ep3-current-meseta=")) {
|
||||
account->ep3_current_meseta = stoul(token.substr(19), nullptr, 0);
|
||||
account->ep3_current_meseta = std::stoul(token.substr(19), nullptr, 0);
|
||||
} else if (token.starts_with("ep3-total-meseta=")) {
|
||||
account->ep3_total_meseta_earned = stoul(token.substr(17), nullptr, 0);
|
||||
account->ep3_total_meseta_earned = std::stoul(token.substr(17), nullptr, 0);
|
||||
} else if (token == "temporary") {
|
||||
account->is_temporary = true;
|
||||
} else if (token.starts_with("flags=")) {
|
||||
@@ -376,12 +376,12 @@ ShellCommand c_add_account(
|
||||
} else if (token.starts_with("user-flags=")) {
|
||||
account->user_flags = parse_account_user_flags(token.substr(11));
|
||||
} else {
|
||||
throw invalid_argument("invalid account field: " + token);
|
||||
throw std::invalid_argument("invalid account field: " + token);
|
||||
}
|
||||
}
|
||||
args.s->account_index->add(account);
|
||||
account->save();
|
||||
co_return deque<string>{format("Account {:08X} added", account->account_id)};
|
||||
co_return std::deque<std::string>{std::format("Account {:08X} added", account->account_id)};
|
||||
});
|
||||
ShellCommand c_update_account(
|
||||
"update-account", "update-account ACCOUNT-ID PARAMETERS...\n\
|
||||
@@ -400,10 +400,10 @@ ShellCommand c_update_account(
|
||||
temporary: Marks the account as temporary; it is not saved to disk and\n\
|
||||
therefore will be deleted when the server shuts down.\n\
|
||||
permanent: If the account was temporary, makes it non-temporary.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto tokens = phosg::split(args.args, ' ');
|
||||
if (tokens.size() < 2) {
|
||||
throw runtime_error("not enough arguments");
|
||||
throw std::runtime_error("not enough arguments");
|
||||
}
|
||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||
tokens.erase(tokens.begin());
|
||||
@@ -416,11 +416,11 @@ ShellCommand c_update_account(
|
||||
int64_t new_user_flags = -1;
|
||||
uint8_t new_is_temporary = 0xFF;
|
||||
int64_t new_ban_duration = -1;
|
||||
for (const string& token : tokens) {
|
||||
for (const std::string& token : tokens) {
|
||||
if (token.starts_with("ep3-current-meseta=")) {
|
||||
new_ep3_current_meseta = stoul(token.substr(19), nullptr, 0);
|
||||
new_ep3_current_meseta = std::stoul(token.substr(19), nullptr, 0);
|
||||
} else if (token.starts_with("ep3-total-meseta=")) {
|
||||
new_ep3_total_meseta = stoul(token.substr(17), nullptr, 0);
|
||||
new_ep3_total_meseta = std::stoul(token.substr(17), nullptr, 0);
|
||||
} else if (token == "temporary") {
|
||||
new_is_temporary = 1;
|
||||
} else if (token == "permanent") {
|
||||
@@ -448,10 +448,10 @@ ShellCommand c_update_account(
|
||||
} else if (duration_str.ends_with("y")) {
|
||||
new_ban_duration = stoull(duration_str.substr(0, duration_str.size() - 1)) * 31536000000000LL;
|
||||
} else {
|
||||
throw runtime_error("invalid time unit");
|
||||
throw std::runtime_error("invalid time unit");
|
||||
}
|
||||
} else {
|
||||
throw invalid_argument("invalid account field: " + token);
|
||||
throw std::invalid_argument("invalid account field: " + token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,18 +479,18 @@ ShellCommand c_update_account(
|
||||
args.s->disconnect_all_banned_clients();
|
||||
}
|
||||
|
||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
||||
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||
});
|
||||
ShellCommand c_delete_account(
|
||||
"delete-account", "delete-account ACCOUNT-ID\n\
|
||||
Delete an account from the server. If a player is online with the deleted\n\
|
||||
account, they will not be automatically disconnected.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto account = args.s->account_index->from_account_id(stoul(args.args, nullptr, 16));
|
||||
args.s->account_index->remove(account->account_id);
|
||||
account->is_temporary = true;
|
||||
account->delete_file();
|
||||
co_return deque<string>{"Account deleted"};
|
||||
co_return std::deque<std::string>{"Account deleted"};
|
||||
});
|
||||
|
||||
ShellCommand c_add_license(
|
||||
@@ -509,57 +509,57 @@ ShellCommand c_add_license(
|
||||
add-license 385A92C4 DC 107862F9 d38XTu2p\n\
|
||||
add-license 385A92C4 GC 0418572923 282949185033 hunter2\n\
|
||||
add-license 385A92C4 BB user1 trustno1",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto tokens = phosg::split(args.args, ' ');
|
||||
if (tokens.size() < 3) {
|
||||
throw runtime_error("not enough arguments");
|
||||
throw std::runtime_error("not enough arguments");
|
||||
}
|
||||
|
||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||
|
||||
string type_str = phosg::toupper(tokens[1]);
|
||||
std::string type_str = phosg::toupper(tokens[1]);
|
||||
if (type_str == "DC-NTE") {
|
||||
if (tokens.size() != 4) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<DCNTELicense>();
|
||||
auto license = std::make_shared<DCNTELicense>();
|
||||
license->serial_number = std::move(tokens[2]);
|
||||
license->access_key = std::move(tokens[3]);
|
||||
args.s->account_index->add_dc_nte_license(account, license);
|
||||
|
||||
} else if (type_str == "DC") {
|
||||
if (tokens.size() != 4) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<V1V2License>();
|
||||
license->serial_number = stoul(tokens[2], nullptr, 16);
|
||||
auto license = std::make_shared<V1V2License>();
|
||||
license->serial_number = std::stoul(tokens[2], nullptr, 16);
|
||||
license->access_key = std::move(tokens[3]);
|
||||
args.s->account_index->add_dc_license(account, license);
|
||||
|
||||
} else if (type_str == "PC") {
|
||||
if (tokens.size() != 4) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<V1V2License>();
|
||||
license->serial_number = stoul(tokens[2], nullptr, 16);
|
||||
auto license = std::make_shared<V1V2License>();
|
||||
license->serial_number = std::stoul(tokens[2], nullptr, 16);
|
||||
license->access_key = std::move(tokens[3]);
|
||||
args.s->account_index->add_pc_license(account, license);
|
||||
|
||||
} else if (type_str == "GC") {
|
||||
if (tokens.size() != 5) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<GCLicense>();
|
||||
license->serial_number = stoul(tokens[2], nullptr, 10);
|
||||
auto license = std::make_shared<GCLicense>();
|
||||
license->serial_number = std::stoul(tokens[2], nullptr, 10);
|
||||
license->access_key = std::move(tokens[3]);
|
||||
license->password = std::move(tokens[4]);
|
||||
args.s->account_index->add_gc_license(account, license);
|
||||
|
||||
} else if (type_str == "XB") {
|
||||
if (tokens.size() != 5) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<XBLicense>();
|
||||
auto license = std::make_shared<XBLicense>();
|
||||
license->gamertag = std::move(tokens[2]);
|
||||
license->user_id = stoull(tokens[3], nullptr, 16);
|
||||
license->account_id = stoull(tokens[4], nullptr, 16);
|
||||
@@ -567,19 +567,19 @@ ShellCommand c_add_license(
|
||||
|
||||
} else if (type_str == "BB") {
|
||||
if (tokens.size() != 4) {
|
||||
throw runtime_error("incorrect number of parameters");
|
||||
throw std::runtime_error("incorrect number of parameters");
|
||||
}
|
||||
auto license = make_shared<BBLicense>();
|
||||
auto license = std::make_shared<BBLicense>();
|
||||
license->username = std::move(tokens[2]);
|
||||
license->password = std::move(tokens[3]);
|
||||
args.s->account_index->add_bb_license(account, license);
|
||||
|
||||
} else {
|
||||
throw runtime_error("invalid license type");
|
||||
throw std::runtime_error("invalid license type");
|
||||
}
|
||||
|
||||
account->save();
|
||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
||||
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||
});
|
||||
ShellCommand c_delete_license(
|
||||
"delete-license", "delete-license ACCOUNT-ID TYPE PRIMARY-CREDENTIAL\n\
|
||||
@@ -598,46 +598,46 @@ ShellCommand c_delete_license(
|
||||
delete-license 385A92C4 GC 0418572923\n\
|
||||
delete-license 385A92C4 XB 7E29A2950019EB20\n\
|
||||
delete-license 385A92C4 BB user1",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto tokens = phosg::split(args.args, ' ');
|
||||
if (tokens.size() != 3) {
|
||||
throw runtime_error("incorrect argument count");
|
||||
throw std::runtime_error("incorrect argument count");
|
||||
}
|
||||
|
||||
auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16));
|
||||
|
||||
string type_str = phosg::toupper(tokens[1]);
|
||||
std::string type_str = phosg::toupper(tokens[1]);
|
||||
if (type_str == "DC-NTE") {
|
||||
args.s->account_index->remove_dc_nte_license(account, tokens[2]);
|
||||
} else if (type_str == "DC") {
|
||||
args.s->account_index->remove_dc_license(account, stoul(tokens[2], nullptr, 16));
|
||||
args.s->account_index->remove_dc_license(account, std::stoul(tokens[2], nullptr, 16));
|
||||
} else if (type_str == "PC") {
|
||||
args.s->account_index->remove_pc_license(account, stoul(tokens[2], nullptr, 16));
|
||||
args.s->account_index->remove_pc_license(account, std::stoul(tokens[2], nullptr, 16));
|
||||
} else if (type_str == "GC") {
|
||||
args.s->account_index->remove_gc_license(account, stoul(tokens[2], nullptr, 0));
|
||||
args.s->account_index->remove_gc_license(account, std::stoul(tokens[2], nullptr, 0));
|
||||
} else if (type_str == "XB") {
|
||||
args.s->account_index->remove_xb_license(account, stoull(tokens[2], nullptr, 16));
|
||||
} else if (type_str == "BB") {
|
||||
args.s->account_index->remove_bb_license(account, tokens[2]);
|
||||
} else {
|
||||
throw runtime_error("invalid license type");
|
||||
throw std::runtime_error("invalid license type");
|
||||
}
|
||||
|
||||
account->save();
|
||||
co_return deque<string>{format("Account {:08X} updated", account->account_id)};
|
||||
co_return std::deque<std::string>{std::format("Account {:08X} updated", account->account_id)};
|
||||
});
|
||||
|
||||
ShellCommand c_lookup(
|
||||
"lookup", "lookup USER\n\
|
||||
Find the account for a logged-in user.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto target = args.s->find_client(&args.args);
|
||||
if (target->login) {
|
||||
co_return deque<string>{format("Found client {} with account ID {:08X}",
|
||||
co_return std::deque<std::string>{format("Found client {} with account ID {:08X}",
|
||||
target->channel->name, target->login->account->account_id)};
|
||||
} else {
|
||||
// This should be impossible
|
||||
throw logic_error("find_client found user who is not logged in");
|
||||
throw std::logic_error("find_client found user who is not logged in");
|
||||
}
|
||||
});
|
||||
ShellCommand c_kick(
|
||||
@@ -645,26 +645,26 @@ ShellCommand c_kick(
|
||||
Disconnect a user from the server. USER may be an account ID, player name,\n\
|
||||
or client ID (beginning with \"C-\"). This does not ban the user; they are\n\
|
||||
free to reconnect after doing this.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto target = args.s->find_client(&args.args);
|
||||
send_message_box(target, "$C6You have been kicked off the server.");
|
||||
target->channel->disconnect();
|
||||
co_return deque<string>{format("Client C-{:X} disconnected from server", target->id)};
|
||||
co_return std::deque<std::string>{std::format("Client C-{:X} disconnected from server", target->id)};
|
||||
});
|
||||
|
||||
ShellCommand c_announce(
|
||||
"announce", "announce MESSAGE\n\
|
||||
Send an announcement message to all players.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
send_text_or_scrolling_message(args.s, args.args, args.args);
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
ShellCommand c_announce_mail(
|
||||
"announce-mail", "announce-mail MESSAGE\n\
|
||||
Send an announcement message via Simple Mail to all players.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
send_simple_mail(args.s, 0, args.s->name, args.args);
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
|
||||
ShellCommand c_create_tournament(
|
||||
@@ -695,11 +695,11 @@ ShellCommand c_create_tournament(
|
||||
dialogue=ON/OFF: Enable/disable dialogue\n\
|
||||
dice-exchange=ATK/DEF/NONE: Set dice exchange mode\n\
|
||||
dice-boost=ON/OFF: Enable/disable dice boost",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
string name = get_quoted_string(args.args);
|
||||
string map_name = get_quoted_string(args.args);
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::string name = get_quoted_string(args.args);
|
||||
std::string map_name = get_quoted_string(args.args);
|
||||
auto map = args.s->ep3_map_index->map_for_name(map_name);
|
||||
uint32_t num_teams = stoul(get_quoted_string(args.args), nullptr, 0);
|
||||
uint32_t num_teams = std::stoul(get_quoted_string(args.args), nullptr, 0);
|
||||
Episode3::Rules rules;
|
||||
rules.set_defaults();
|
||||
uint8_t flags = Episode3::Tournament::Flag::HAS_COM_TEAMS;
|
||||
@@ -716,24 +716,24 @@ ShellCommand c_create_tournament(
|
||||
} else if (token == "resize") {
|
||||
flags |= Episode3::Tournament::Flag::RESIZE_ON_START;
|
||||
} else if (token.starts_with("dice=")) {
|
||||
auto parse_range_c = +[](const string& s) -> uint8_t {
|
||||
auto parse_range_c = +[](const std::string& s) -> uint8_t {
|
||||
auto tokens = phosg::split(s, '-');
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("dice spec must be of the form MIN-MAX");
|
||||
throw std::runtime_error("dice spec must be of the form MIN-MAX");
|
||||
}
|
||||
return (stoul(tokens[0]) << 4) | (stoul(tokens[1]) & 0x0F);
|
||||
};
|
||||
auto parse_range_p = +[](const string& s) -> pair<uint8_t, uint8_t> {
|
||||
auto parse_range_p = +[](const std::string& s) -> std::pair<uint8_t, uint8_t> {
|
||||
auto tokens = phosg::split(s, '-');
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("dice spec must be of the form MIN-MAX");
|
||||
throw std::runtime_error("dice spec must be of the form MIN-MAX");
|
||||
}
|
||||
return make_pair(stoul(tokens[0]), stoul(tokens[1]));
|
||||
return std::make_pair(stoul(tokens[0]), std::stoul(tokens[1]));
|
||||
};
|
||||
|
||||
auto subtokens = phosg::split(token.substr(5), ':');
|
||||
if (subtokens.size() < 1) {
|
||||
throw runtime_error("no dice ranges specified in dice= option");
|
||||
throw std::runtime_error("no dice ranges specified in dice= option");
|
||||
}
|
||||
auto atk_range = parse_range_p(subtokens[0]);
|
||||
rules.min_dice_value = atk_range.first;
|
||||
@@ -747,7 +747,7 @@ ShellCommand c_create_tournament(
|
||||
} else if (subtokens.size() == 4) {
|
||||
rules.def_dice_value_range_2v1 = parse_range_c(subtokens[3]);
|
||||
} else {
|
||||
throw runtime_error("too many range specs given");
|
||||
throw std::runtime_error("too many range specs given");
|
||||
}
|
||||
} else {
|
||||
rules.atk_dice_value_range_2v1 = 0;
|
||||
@@ -759,18 +759,18 @@ ShellCommand c_create_tournament(
|
||||
rules.def_dice_value_range_2v1 = 0;
|
||||
}
|
||||
} else if (token.starts_with("overall-time-limit=")) {
|
||||
uint32_t limit = stoul(token.substr(19));
|
||||
uint32_t limit = std::stoul(token.substr(19));
|
||||
if (limit > 600) {
|
||||
throw runtime_error("overall-time-limit must be 600 or fewer minutes");
|
||||
throw std::runtime_error("overall-time-limit must be 600 or fewer minutes");
|
||||
}
|
||||
if (limit % 5) {
|
||||
throw runtime_error("overall-time-limit must be a multiple of 5 minutes");
|
||||
throw std::runtime_error("overall-time-limit must be a multiple of 5 minutes");
|
||||
}
|
||||
rules.overall_time_limit = limit;
|
||||
} else if (token.starts_with("phase-time-limit=")) {
|
||||
rules.phase_time_limit = stoul(token.substr(17));
|
||||
rules.phase_time_limit = std::stoul(token.substr(17));
|
||||
} else if (token.starts_with("hp=")) {
|
||||
rules.char_hp = stoul(token.substr(3));
|
||||
rules.char_hp = std::stoul(token.substr(3));
|
||||
} else if (token == "allowed-cards=all") {
|
||||
rules.allowed_cards = Episode3::AllowedCards::ALL;
|
||||
} else if (token == "allowed-cards=n") {
|
||||
@@ -812,11 +812,11 @@ ShellCommand c_create_tournament(
|
||||
} else if (token == "dice-exchange=none") {
|
||||
rules.dice_exchange_mode = Episode3::DiceExchangeMode::NONE;
|
||||
} else {
|
||||
throw runtime_error("invalid rules option: " + token);
|
||||
throw std::runtime_error("invalid rules option: " + token);
|
||||
}
|
||||
}
|
||||
}
|
||||
deque<string> ret;
|
||||
std::deque<std::string> ret;
|
||||
if (rules.check_and_reset_invalid_fields()) {
|
||||
ret.emplace_back("Warning: Some rules were invalid and reset to defaults");
|
||||
}
|
||||
@@ -829,19 +829,19 @@ ShellCommand c_delete_tournament(
|
||||
"delete-tournament", "delete-tournament TOURNAMENT-NAME\n\
|
||||
Delete a tournament. Quotes are required around the tournament name unless\n\
|
||||
the name contains no spaces.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
string name = get_quoted_string(args.args);
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::string name = get_quoted_string(args.args);
|
||||
if (args.s->ep3_tournament_index->delete_tournament(name)) {
|
||||
co_return deque<string>{"Deleted tournament"};
|
||||
co_return std::deque<std::string>{"Deleted tournament"};
|
||||
} else {
|
||||
throw runtime_error("tournament does not exist");
|
||||
throw std::runtime_error("tournament does not exist");
|
||||
}
|
||||
});
|
||||
ShellCommand c_list_tournaments(
|
||||
"list-tournaments", "list-tournaments\n\
|
||||
List the names and numbers of all existing tournaments.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
deque<string> ret;
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::deque<std::string> ret;
|
||||
for (const auto& it : args.s->ep3_tournament_index->all_tournaments()) {
|
||||
ret.emplace_back(" " + it.second->get_name());
|
||||
}
|
||||
@@ -851,30 +851,30 @@ ShellCommand c_start_tournament(
|
||||
"start-tournament", "start-tournament TOURNAMENT-NAME\n\
|
||||
End registration for a tournament and allow matches to begin. Quotes are\n\
|
||||
required around the tournament name unless the name contains no spaces.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
string name = get_quoted_string(args.args);
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::string name = get_quoted_string(args.args);
|
||||
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
||||
if (tourn) {
|
||||
tourn->start();
|
||||
args.s->ep3_tournament_index->save();
|
||||
tourn->send_all_state_updates();
|
||||
send_ep3_text_message_fmt(args.s, "$C7The tournament\n$C6{}$C7\nhas begun", tourn->get_name());
|
||||
co_return deque<string>{"Tournament started"};
|
||||
co_return std::deque<std::string>{"Tournament started"};
|
||||
} else {
|
||||
throw runtime_error("tournament does not exist");
|
||||
throw std::runtime_error("tournament does not exist");
|
||||
}
|
||||
});
|
||||
ShellCommand c_describe_tournament(
|
||||
"describe-tournament", "describe-tournament TOURNAMENT-NAME\n\
|
||||
Show the current state of a tournament. Quotes are required around the\n\
|
||||
tournament name unless the name contains no spaces.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
string name = get_quoted_string(args.args);
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
std::string name = get_quoted_string(args.args);
|
||||
auto tourn = args.s->ep3_tournament_index->get_tournament(name);
|
||||
if (tourn) {
|
||||
co_return deque<string>{tourn->bracket_str()};
|
||||
co_return std::deque<std::string>{tourn->bracket_str()};
|
||||
} else {
|
||||
throw runtime_error("tournament does not exist");
|
||||
throw std::runtime_error("tournament does not exist");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -888,16 +888,16 @@ ShellCommand c_cc(
|
||||
via this command are exempt from permission checks, so commands that\n\
|
||||
require cheat mode or debug mode are always available via cc even if the\n\
|
||||
player cannot normamlly use them.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto c = args.get_client();
|
||||
co_await on_chat_command(c, args.args, false);
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
|
||||
asio::awaitable<deque<string>> f_sc_ss(ShellCommand::Args& args) {
|
||||
string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||
asio::awaitable<std::deque<std::string>> f_sc_ss(ShellCommand::Args& args) {
|
||||
std::string data = phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||
if (data.size() == 0) {
|
||||
throw invalid_argument("no data given");
|
||||
throw std::invalid_argument("no data given");
|
||||
}
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
|
||||
@@ -912,7 +912,7 @@ asio::awaitable<deque<string>> f_sc_ss(ShellCommand::Args& args) {
|
||||
send_command_with_header(c->channel, data.data(), data.size());
|
||||
}
|
||||
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
}
|
||||
|
||||
ShellCommand c_sc("sc", "sc DATA\n\
|
||||
@@ -926,10 +926,10 @@ ShellCommand c_show_slots(
|
||||
"show-slots", "show-slots\n\
|
||||
Show the player names, Guild Card numbers, and client IDs of all players in\n\
|
||||
the current lobby or game.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto c = args.get_proxy_client();
|
||||
|
||||
deque<string> ret;
|
||||
std::deque<std::string> ret;
|
||||
for (size_t z = 0; z < c->proxy_session->lobby_players.size(); z++) {
|
||||
const auto& player = c->proxy_session->lobby_players[z];
|
||||
if (player.guild_card_number) {
|
||||
@@ -939,13 +939,13 @@ ShellCommand c_show_slots(
|
||||
name_for_char_class(player.char_class),
|
||||
name_for_section_id(player.section_id)));
|
||||
} else {
|
||||
ret.emplace_back(format(" {}: (no player)", z));
|
||||
ret.emplace_back(std::format(" {}: (no player)", z));
|
||||
}
|
||||
}
|
||||
co_return ret;
|
||||
});
|
||||
|
||||
asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
||||
asio::awaitable<std::deque<std::string>> fn_chat(ShellCommand::Args& args) {
|
||||
auto c = args.get_client();
|
||||
bool is_dchat = (args.command == "dchat");
|
||||
|
||||
@@ -953,7 +953,7 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
||||
if (!is_dchat && uses_utf16(c->version())) {
|
||||
send_chat_message_from_client(c->proxy_session->server_channel, args.args, 0);
|
||||
} else {
|
||||
string data(8, '\0');
|
||||
std::string data(8, '\0');
|
||||
data.push_back('\x09');
|
||||
data.push_back('E');
|
||||
if (is_dchat) {
|
||||
@@ -966,7 +966,9 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
||||
c->proxy_session->server_channel->send(0x06, 0x00, data);
|
||||
}
|
||||
} else if (c->login) {
|
||||
string text = is_dchat ? phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES) : args.args;
|
||||
std::string text = is_dchat
|
||||
? phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES)
|
||||
: args.args;
|
||||
auto l = c->require_lobby();
|
||||
for (auto& lc : l->clients) {
|
||||
if (lc) {
|
||||
@@ -975,7 +977,7 @@ asio::awaitable<deque<string>> fn_chat(ShellCommand::Args& args) {
|
||||
}
|
||||
}
|
||||
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
}
|
||||
ShellCommand c_c("c", "c TEXT", fn_chat);
|
||||
ShellCommand c_chat("chat", "chat TEXT\n\
|
||||
@@ -985,13 +987,13 @@ ShellCommand c_dchat("dchat", "dchat DATA\n\
|
||||
Send a chat message to the server with arbitrary data in it.",
|
||||
fn_chat);
|
||||
|
||||
asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
|
||||
asio::awaitable<std::deque<std::string>> fn_wchat(ShellCommand::Args& args) {
|
||||
auto c = args.get_client();
|
||||
if (!is_ep3(c->version())) {
|
||||
throw runtime_error("wchat can only be used on Episode 3");
|
||||
throw std::runtime_error("wchat can only be used on Episode 3");
|
||||
}
|
||||
if (c->proxy_session) {
|
||||
string data(8, '\0');
|
||||
std::string data(8, '\0');
|
||||
data.push_back('\x40'); // private_flags: visible to all
|
||||
data.push_back('\x09');
|
||||
data.push_back('E');
|
||||
@@ -1008,7 +1010,7 @@ asio::awaitable<deque<string>> fn_wchat(ShellCommand::Args& args) {
|
||||
}
|
||||
}
|
||||
}
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
}
|
||||
ShellCommand c_wc("wc", "wc TEXT", fn_wchat);
|
||||
ShellCommand c_wchat("wchat", "wchat TEXT\n\
|
||||
@@ -1018,20 +1020,20 @@ ShellCommand c_wchat("wchat", "wchat TEXT\n\
|
||||
ShellCommand c_marker(
|
||||
"marker", "marker COLOR-ID\n\
|
||||
Change your lobby marker color.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto c = args.get_proxy_client();
|
||||
c->proxy_session->server_channel->send(0x89, stoul(args.args));
|
||||
co_return deque<string>{};
|
||||
c->proxy_session->server_channel->send(0x89, std::stoul(args.args));
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
|
||||
asio::awaitable<deque<string>> fn_warp(ShellCommand::Args& args) {
|
||||
asio::awaitable<std::deque<std::string>> fn_warp(ShellCommand::Args& args) {
|
||||
auto c = args.get_proxy_client();
|
||||
uint8_t floor = stoul(args.args);
|
||||
uint8_t floor = std::stoul(args.args);
|
||||
send_warp(c->channel, c->lobby_client_id, floor, true);
|
||||
if (args.command == "warpall") {
|
||||
send_warp(c->proxy_session->server_channel, c->lobby_client_id, floor, false);
|
||||
}
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
}
|
||||
ShellCommand c_warp("warp", "warp FLOOR-ID", fn_warp);
|
||||
ShellCommand c_warpme("warpme", "warpme FLOOR-ID\n\
|
||||
@@ -1041,10 +1043,10 @@ ShellCommand c_warpall("warpall", "warpall FLOOR-ID\n\
|
||||
Send everyone to a specific floor.",
|
||||
fn_warp);
|
||||
|
||||
asio::awaitable<deque<string>> fn_info_board(ShellCommand::Args& args) {
|
||||
asio::awaitable<std::deque<std::string>> fn_info_board(ShellCommand::Args& args) {
|
||||
auto c = args.get_proxy_client();
|
||||
|
||||
string data;
|
||||
std::string data;
|
||||
if (args.command == "info-board-data") {
|
||||
data += phosg::parse_data_string(args.args, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||
} else {
|
||||
@@ -1054,7 +1056,7 @@ asio::awaitable<deque<string>> fn_info_board(ShellCommand::Args& args) {
|
||||
data.resize((data.size() + 3) & (~3));
|
||||
|
||||
c->proxy_session->server_channel->send(0xD9, 0x00, data);
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
}
|
||||
ShellCommand c_info_board("info-board", "info-board TEXT\n\
|
||||
Set your info board contents. This will affect the current session only,\n\
|
||||
@@ -1069,17 +1071,17 @@ ShellCommand c_info_board_data("info-board-data", "info-board-data DATA\n\
|
||||
ShellCommand c_create_item(
|
||||
"create-item", "create-item DATA\n\
|
||||
Create an item as if the client had run the $item command.",
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<deque<string>> {
|
||||
+[](ShellCommand::Args& args) -> asio::awaitable<std::deque<std::string>> {
|
||||
auto c = args.get_proxy_client();
|
||||
|
||||
if (c->version() == Version::BB_V4) {
|
||||
throw runtime_error("proxy session is BB");
|
||||
throw std::runtime_error("proxy session is BB");
|
||||
}
|
||||
if (!c->proxy_session->is_in_game) {
|
||||
throw runtime_error("proxy session is not in a game");
|
||||
throw std::runtime_error("proxy session is not in a game");
|
||||
}
|
||||
if (c->lobby_client_id != c->proxy_session->leader_client_id) {
|
||||
throw runtime_error("proxy session is not game leader");
|
||||
throw std::runtime_error("proxy session is not game leader");
|
||||
}
|
||||
|
||||
ItemData item = args.s->parse_item_description(c->version(), args.args);
|
||||
@@ -1088,7 +1090,7 @@ ShellCommand c_create_item(
|
||||
send_drop_stacked_item_to_channel(args.s, c->channel, item, c->floor, c->pos);
|
||||
send_drop_stacked_item_to_channel(args.s, c->proxy_session->server_channel, item, c->floor, c->pos);
|
||||
|
||||
string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||
std::string name = args.s->describe_item(c->version(), item, ItemNameIndex::Flag::INCLUDE_PSO_COLOR_ESCAPES);
|
||||
send_text_message(c->channel, "$C7Item created:\n" + name);
|
||||
co_return deque<string>{};
|
||||
co_return std::deque<std::string>{};
|
||||
});
|
||||
|
||||
@@ -12,12 +12,8 @@
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
SignalWatcher::SignalWatcher(shared_ptr<ServerState> state)
|
||||
: log("[SignalWatcher] "),
|
||||
state(state),
|
||||
signals(*this->state->io_context) {
|
||||
SignalWatcher::SignalWatcher(std::shared_ptr<ServerState> state)
|
||||
: log("[SignalWatcher] "), state(state), signals(*this->state->io_context) {
|
||||
asio::co_spawn(*this->state->io_context, this->signal_handler_task(), asio::detached);
|
||||
}
|
||||
|
||||
@@ -34,7 +30,7 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
|
||||
this->state->load_config_early();
|
||||
this->state->load_config_late();
|
||||
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
|
||||
phosg::fwrite_fmt(stderr, "Some configuration may have been reloaded. Fix the underlying issue and try again.\n");
|
||||
}
|
||||
@@ -44,7 +40,7 @@ asio::awaitable<void> SignalWatcher::signal_handler_task() {
|
||||
try {
|
||||
this->state->load_all(true);
|
||||
phosg::fwrite_fmt(stderr, "Configuration update complete\n");
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
phosg::fwrite_fmt(stderr, "FAILED: {}\n", e.what());
|
||||
phosg::fwrite_fmt(stderr, "Some configuration may have been reloaded. Fix the underlying issue and try again.\n");
|
||||
}
|
||||
|
||||
+89
-91
@@ -4,8 +4,6 @@
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool episode_has_arpg_semantics(Episode ep) {
|
||||
return (ep == Episode::EP1) || (ep == Episode::EP2) || (ep == Episode::EP4);
|
||||
}
|
||||
@@ -38,11 +36,11 @@ const char* token_name_for_episode(Episode ep) {
|
||||
case Episode::EP4:
|
||||
return "Episode4";
|
||||
default:
|
||||
throw logic_error("invalid episode");
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
}
|
||||
|
||||
Episode episode_for_token_name(const string& name) {
|
||||
Episode episode_for_token_name(const std::string& name) {
|
||||
if (name == "Episode1") {
|
||||
return Episode::EP1;
|
||||
}
|
||||
@@ -55,7 +53,7 @@ Episode episode_for_token_name(const string& name) {
|
||||
if (name == "Episode4") {
|
||||
return Episode::EP4;
|
||||
}
|
||||
throw runtime_error("unknown episode");
|
||||
throw std::runtime_error("unknown episode");
|
||||
}
|
||||
|
||||
Episode episode_for_area(uint8_t area) {
|
||||
@@ -117,13 +115,13 @@ const char* abbreviation_for_mode(GameMode mode) {
|
||||
}
|
||||
}
|
||||
|
||||
static const array<const char*, 10> section_id_to_name = {
|
||||
static const std::array<const char*, 10> section_id_to_name = {
|
||||
"Viridia", "Greennill", "Skyly", "Bluefull", "Purplenum", "Pinkal", "Redria", "Oran", "Yellowboze", "Whitill"};
|
||||
|
||||
static const array<const char*, 10> section_id_to_abbreviation = {
|
||||
static const std::array<const char*, 10> section_id_to_abbreviation = {
|
||||
"Vir", "Grn", "Sky", "Blu", "Prp", "Pnk", "Red", "Orn", "Ylw", "Wht"};
|
||||
|
||||
const unordered_map<string, uint8_t> name_to_section_id({{"viridia", 0},
|
||||
const std::unordered_map<std::string, uint8_t> name_to_section_id({{"viridia", 0},
|
||||
// Greennill is spelled Greenill in some places, so we accept both spellings
|
||||
{"greennill", 1}, {"greenill", 1}, {"skyly", 2}, {"bluefull", 3}, {"purplenum", 4}, {"pinkal", 5}, {"redria", 6},
|
||||
{"oran", 7}, {"yellowboze", 8}, {"whitill", 9},
|
||||
@@ -131,32 +129,32 @@ const unordered_map<string, uint8_t> name_to_section_id({{"viridia", 0},
|
||||
// Shortcuts for chat commands
|
||||
{"b", 3}, {"g", 1}, {"o", 7}, {"pi", 5}, {"pu", 4}, {"r", 6}, {"s", 2}, {"v", 0}, {"w", 9}, {"y", 8}});
|
||||
|
||||
const vector<string> lobby_event_to_name = {
|
||||
const std::vector<std::string> lobby_event_to_name = {
|
||||
"none", "xmas", "none", "val", "easter", "hallo", "sonic", "newyear",
|
||||
"summer", "white", "wedding", "fall", "s-spring", "s-summer", "spring"};
|
||||
|
||||
const unordered_map<string, uint8_t> name_to_lobby_event = {
|
||||
const std::unordered_map<std::string, uint8_t> name_to_lobby_event = {
|
||||
{"none", 0}, {"xmas", 1}, {"val", 3}, {"easter", 4}, {"hallo", 5}, {"sonic", 6}, {"newyear", 7}, {"summer", 8},
|
||||
{"white", 9}, {"wedding", 10}, {"fall", 11}, {"s-spring", 12}, {"s-summer", 13}, {"spring", 14}};
|
||||
|
||||
const unordered_map<uint8_t, string> lobby_type_to_name = {
|
||||
const std::unordered_map<uint8_t, std::string> lobby_type_to_name = {
|
||||
{0x00, "normal"}, {0x0F, "inormal"}, {0x10, "ipc"}, {0x11, "iball"}, {0x67, "cave2u"}, {0xD4, "cave1"},
|
||||
{0xE9, "planet"}, {0xEA, "clouds"}, {0xED, "cave"}, {0xEE, "jungle"}, {0xEF, "forest2-2"}, {0xF0, "forest2-1"},
|
||||
{0xF1, "windpower"}, {0xF2, "overview"}, {0xF3, "seaside"}, {0xF4, "fons"}, {0xF5, "dmorgue"}, {0xF6, "caelum"},
|
||||
{0xF8, "cyber"}, {0xF9, "boss1"}, {0xFA, "boss2"}, {0xFB, "dolor"}, {0xFC, "dragon"}, {0xFD, "derolle"},
|
||||
{0xFE, "volopt"}, {0xFF, "darkfalz"}};
|
||||
|
||||
const unordered_map<string, uint8_t> name_to_lobby_type = {
|
||||
const std::unordered_map<std::string, uint8_t> name_to_lobby_type = {
|
||||
{"normal", 0x00}, {"inormal", 0x0F}, {"ipc", 0x10}, {"iball", 0x11}, {"cave1", 0xD4}, {"cave2u", 0x67},
|
||||
{"dragon", 0xFC}, {"derolle", 0xFD}, {"volopt", 0xFE}, {"darkfalz", 0xFF}, {"planet", 0xE9}, {"clouds", 0xEA},
|
||||
{"cave", 0xED}, {"jungle", 0xEE}, {"forest2-2", 0xEF}, {"forest2-1", 0xF0}, {"windpower", 0xF1},
|
||||
{"overview", 0xF2}, {"seaside", 0xF3}, {"fons", 0xF4}, {"dmorgue", 0xF5}, {"caelum", 0xF6}, {"cyber", 0xF8},
|
||||
{"boss1", 0xF9}, {"boss2", 0xFA}, {"dolor", 0xFB}, {"ravum", 0xFC}, {"sky", 0xFE}, {"morgue", 0xFF}};
|
||||
|
||||
const vector<string> npc_id_to_name = {
|
||||
const std::vector<std::string> npc_id_to_name = {
|
||||
"ninja", "rico", "sonic", "knuckles", "tails", "flowen", "elly", "momoka", "irene", "guild", "nurse"};
|
||||
|
||||
const unordered_map<string, uint8_t> name_to_npc_id = {
|
||||
const std::unordered_map<std::string, uint8_t> name_to_npc_id = {
|
||||
{"ninja", 0}, {"rico", 1}, {"sonic", 2}, {"knuckles", 3}, {"tails", 4}, {"flowen", 5}, {"elly", 6}, {"momoka", 7},
|
||||
{"irene", 8}, {"guild", 9}, {"nurse", 10}};
|
||||
|
||||
@@ -199,100 +197,100 @@ const char* name_for_section_id(uint8_t section_id) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t section_id_for_name(const string& name) {
|
||||
string lower_name = phosg::tolower(name);
|
||||
uint8_t section_id_for_name(const std::string& name) {
|
||||
std::string lower_name = phosg::tolower(name);
|
||||
try {
|
||||
return name_to_section_id.at(lower_name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
uint64_t x = stoul(name);
|
||||
uint64_t x = std::stoul(name);
|
||||
if (x < section_id_to_name.size()) {
|
||||
return x;
|
||||
}
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
const string& name_for_event(uint8_t event) {
|
||||
const std::string& name_for_event(uint8_t event) {
|
||||
if (event < lobby_event_to_name.size()) {
|
||||
return lobby_event_to_name[event];
|
||||
} else {
|
||||
static const string ret = "Unknown lobby event";
|
||||
static const std::string ret = "Unknown lobby event";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t event_for_name(const string& name) {
|
||||
uint8_t event_for_name(const std::string& name) {
|
||||
try {
|
||||
return name_to_lobby_event.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
uint64_t x = stoul(name);
|
||||
if (x < lobby_event_to_name.size()) {
|
||||
return x;
|
||||
}
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
const string& name_for_lobby_type(uint8_t type) {
|
||||
const std::string& name_for_lobby_type(uint8_t type) {
|
||||
try {
|
||||
return lobby_type_to_name.at(type);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "Unknown lobby type";
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::string ret = "Unknown lobby type";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t lobby_type_for_name(const string& name) {
|
||||
uint8_t lobby_type_for_name(const std::string& name) {
|
||||
try {
|
||||
return name_to_lobby_type.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
uint64_t x = stoul(name, nullptr, 0);
|
||||
uint64_t x = std::stoul(name, nullptr, 0);
|
||||
if (lobby_type_to_name.count(x)) {
|
||||
return x;
|
||||
}
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
return 0x80;
|
||||
}
|
||||
|
||||
const string& name_for_npc(uint8_t npc) {
|
||||
const std::string& name_for_npc(uint8_t npc) {
|
||||
try {
|
||||
return npc_id_to_name.at(npc);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "Unknown NPC";
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::string ret = "Unknown NPC";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t npc_for_name(const string& name, Version version) {
|
||||
uint8_t npc_for_name(const std::string& name, Version version) {
|
||||
uint8_t npc_id = 0xFF;
|
||||
try {
|
||||
npc_id = name_to_npc_id.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
if (npc_id == 0xFF) {
|
||||
try {
|
||||
npc_id = stoul(name);
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const out_of_range&) {
|
||||
npc_id = std::stoul(name);
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
return npc_valid_for_version(npc_id, version) ? npc_id : 0xFF;
|
||||
}
|
||||
|
||||
const char* name_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
static const std::array<const char*, 12> names = {
|
||||
/* 00 */ "HUmar",
|
||||
/* 01 */ "HUnewearl",
|
||||
/* 02 */ "HUcast",
|
||||
@@ -307,17 +305,17 @@ const char* name_for_char_class(uint8_t cls) {
|
||||
/* 0B */ "RAmarl"};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* abbreviation_for_char_class(uint8_t cls) {
|
||||
static const array<const char*, 12> names = {
|
||||
static const std::array<const char*, 12> names = {
|
||||
"HUmr", "HUnl", "HUct", "RAmr", "RAct", "RAcl", "FOml", "FOnm", "FOnl", "HUcl", "FOmr", "RAml"};
|
||||
try {
|
||||
return names.at(cls);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
@@ -332,7 +330,7 @@ enum ClassFlag {
|
||||
FORCE = 0x40,
|
||||
};
|
||||
|
||||
static array<uint8_t, 12> class_flags = {
|
||||
static std::array<uint8_t, 12> class_flags = {
|
||||
ClassFlag::HUNTER | ClassFlag::HUMAN | ClassFlag::MALE, // HUmar
|
||||
ClassFlag::HUNTER | ClassFlag::NEWMAN, // HUnewearl
|
||||
ClassFlag::HUNTER | ClassFlag::ANDROID | ClassFlag::MALE, // HUcast
|
||||
@@ -376,34 +374,34 @@ bool char_class_is_force(uint8_t cls) {
|
||||
}
|
||||
|
||||
const char* name_for_difficulty(Difficulty difficulty) {
|
||||
static const array<const char*, 4> names = {"Normal", "Hard", "Very Hard", "Ultimate"};
|
||||
static const std::array<const char*, 4> names = {"Normal", "Hard", "Very Hard", "Ultimate"};
|
||||
try {
|
||||
return names.at(static_cast<size_t>(difficulty));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char* token_name_for_difficulty(Difficulty difficulty) {
|
||||
static const array<const char*, 4> names = {"Normal", "Hard", "VeryHard", "Ultimate"};
|
||||
static const std::array<const char*, 4> names = {"Normal", "Hard", "VeryHard", "Ultimate"};
|
||||
try {
|
||||
return names.at(static_cast<size_t>(difficulty));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
char abbreviation_for_difficulty(Difficulty difficulty) {
|
||||
static const array<char, 4> names = {'N', 'H', 'V', 'U'};
|
||||
static const std::array<char, 4> names = {'N', 'H', 'V', 'U'};
|
||||
try {
|
||||
return names.at(static_cast<size_t>(difficulty));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
const char* name_for_language(Language language) {
|
||||
array<const char*, 8> names = {
|
||||
std::array<const char*, 8> names = {
|
||||
"Japanese", "English", "German", "French", "Spanish", "Simplified Chinese", "Traditional Chinese", "Korean"};
|
||||
size_t lang_index = static_cast<size_t>(language);
|
||||
return (lang_index < 8) ? names[lang_index] : "Unknown";
|
||||
@@ -441,15 +439,15 @@ Language language_for_char(char language_char) {
|
||||
case 'k':
|
||||
return Language::KOREAN;
|
||||
default:
|
||||
throw runtime_error("unknown language");
|
||||
throw std::runtime_error("unknown language");
|
||||
}
|
||||
}
|
||||
|
||||
Language language_for_name(const string& name) {
|
||||
Language language_for_name(const std::string& name) {
|
||||
if (name.size() == 1) {
|
||||
return language_for_char(name[0]);
|
||||
}
|
||||
string lower_name = phosg::tolower(name);
|
||||
std::string lower_name = phosg::tolower(name);
|
||||
if (lower_name == "japanese") {
|
||||
return Language::JAPANESE;
|
||||
}
|
||||
@@ -474,48 +472,48 @@ Language language_for_name(const string& name) {
|
||||
if (lower_name == "korean") {
|
||||
return Language::KOREAN;
|
||||
}
|
||||
throw runtime_error("unknown language");
|
||||
throw std::runtime_error("unknown language");
|
||||
}
|
||||
|
||||
const vector<string> tech_id_to_name = {
|
||||
const std::vector<std::string> tech_id_to_name = {
|
||||
"Foie", "Gifoie", "Rafoie", "Barta", "Gibarta", "Rabarta", "Zonde", "Gizonde", "Razonde", "Grants", "Deband",
|
||||
"Jellen", "Zalure", "Shifta", "Ryuker", "Resta", "Anti", "Reverser", "Megid"};
|
||||
|
||||
const unordered_map<string, uint8_t> name_to_tech_id = {
|
||||
const std::unordered_map<std::string, uint8_t> name_to_tech_id = {
|
||||
{"foie", 0}, {"gifoie", 1}, {"rafoie", 2}, {"barta", 3}, {"gibarta", 4}, {"rabarta", 5}, {"zonde", 6},
|
||||
{"gizonde", 7}, {"razonde", 8}, {"grants", 9}, {"deband", 10}, {"jellen", 11}, {"zalure", 12}, {"shifta", 13},
|
||||
{"ryuker", 14}, {"resta", 15}, {"anti", 16}, {"reverser", 17}, {"megid", 18}};
|
||||
|
||||
const string& name_for_technique(uint8_t tech) {
|
||||
const std::string& name_for_technique(uint8_t tech) {
|
||||
try {
|
||||
return tech_id_to_name.at(tech);
|
||||
} catch (const out_of_range&) {
|
||||
static const string ret = "Unknown technique";
|
||||
} catch (const std::out_of_range&) {
|
||||
static const std::string ret = "Unknown technique";
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t technique_for_name(const string& name) {
|
||||
uint8_t technique_for_name(const std::string& name) {
|
||||
try {
|
||||
return name_to_tech_id.at(phosg::tolower(name));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
uint64_t x = stoul(name);
|
||||
uint64_t x = std::stoul(name);
|
||||
if (x < tech_id_to_name.size()) {
|
||||
return x;
|
||||
}
|
||||
} catch (const invalid_argument&) {
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::invalid_argument&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
const vector<const char*> name_for_mag_color = {
|
||||
const std::vector<const char*> name_for_mag_color = {
|
||||
"red", "blue", "yellow", "green", "purple", "black", "white", "cyan", "brown", "orange", "light-blue", "olive",
|
||||
"turquoise", "fuchsia", "grey", "cream", "pink", "dark-green", "costume"};
|
||||
|
||||
const unordered_map<string, uint8_t> mag_color_for_name = {
|
||||
const std::unordered_map<std::string, uint8_t> mag_color_for_name = {
|
||||
{"red", 0x00}, {"blue", 0x01}, {"yellow", 0x02}, {"green", 0x03}, {"purple", 0x04}, {"black", 0x05},
|
||||
{"white", 0x06}, {"cyan", 0x07}, {"brown", 0x08}, {"orange", 0x09}, {"light-blue", 0x0A}, {"olive", 0x0B},
|
||||
{"turquoise", 0x0C}, {"fuchsia", 0x0D}, {"grey", 0x0E}, {"cream", 0x0F}, {"pink", 0x10}, {"dark-green", 0x11},
|
||||
@@ -586,7 +584,7 @@ static const std::vector<FloorDefinition> floor_defs{
|
||||
|
||||
const FloorDefinition& FloorDefinition::get(Episode episode, uint8_t floor) {
|
||||
if (floor >= FloorDefinition::limit_for_episode(episode)) {
|
||||
throw runtime_error("invalid floor number");
|
||||
throw std::runtime_error("invalid floor number");
|
||||
}
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
@@ -596,7 +594,7 @@ const FloorDefinition& FloorDefinition::get(Episode episode, uint8_t floor) {
|
||||
case Episode::EP4:
|
||||
return floor_defs.at(floor + 0x24);
|
||||
default:
|
||||
throw logic_error("invalid episode");
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,16 +607,16 @@ const FloorDefinition& FloorDefinition::get_by_drop_area_norm(Episode episode, u
|
||||
case Episode::EP4:
|
||||
return floor_defs[area_norm + 0x24];
|
||||
default:
|
||||
throw logic_error("invalid episode number");
|
||||
throw std::logic_error("invalid episode number");
|
||||
}
|
||||
}
|
||||
|
||||
const FloorDefinition& FloorDefinition::get(Episode episode, const std::string& name) {
|
||||
static unordered_map<std::string, size_t> index_ep1;
|
||||
static unordered_map<std::string, size_t> index_ep2;
|
||||
static unordered_map<std::string, size_t> index_ep4;
|
||||
static std::unordered_map<std::string, size_t> index_ep1;
|
||||
static std::unordered_map<std::string, size_t> index_ep2;
|
||||
static std::unordered_map<std::string, size_t> index_ep4;
|
||||
|
||||
unordered_map<std::string, size_t>* index;
|
||||
std::unordered_map<std::string, size_t>* index;
|
||||
switch (episode) {
|
||||
case Episode::EP1:
|
||||
index = &index_ep1;
|
||||
@@ -630,7 +628,7 @@ const FloorDefinition& FloorDefinition::get(Episode episode, const std::string&
|
||||
index = &index_ep4;
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid episode");
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
|
||||
if (index->empty()) {
|
||||
@@ -663,7 +661,7 @@ size_t FloorDefinition::limit_for_episode(Episode ep) {
|
||||
uint32_t class_flags_for_class(uint8_t char_class) {
|
||||
static constexpr uint8_t flags[12] = {0x25, 0x2A, 0x31, 0x45, 0x51, 0x52, 0x86, 0x89, 0x8A, 0x32, 0x85, 0x46};
|
||||
if (char_class >= 12) {
|
||||
throw runtime_error("invalid character class");
|
||||
throw std::runtime_error("invalid character class");
|
||||
}
|
||||
return flags[char_class];
|
||||
}
|
||||
@@ -675,16 +673,16 @@ char char_for_challenge_rank(uint8_t rank) {
|
||||
return "BAS"[rank];
|
||||
}
|
||||
|
||||
const array<size_t, 4> DEFAULT_MIN_LEVELS_V123({0, 19, 39, 79});
|
||||
const array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP1({0, 19, 39, 79});
|
||||
const array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP2({0, 29, 49, 89});
|
||||
const array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP4({0, 39, 79, 109});
|
||||
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V123({0, 19, 39, 79});
|
||||
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP1({0, 19, 39, 79});
|
||||
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP2({0, 29, 49, 89});
|
||||
const std::array<size_t, 4> DEFAULT_MIN_LEVELS_V4_EP4({0, 39, 79, 109});
|
||||
|
||||
const array<GameMode, 2> ALL_GAME_MODES_V1 = {GameMode::NORMAL, GameMode::BATTLE};
|
||||
const array<GameMode, 3> ALL_GAME_MODES_V23 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE};
|
||||
const array<GameMode, 4> ALL_GAME_MODES_V4 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
const array<Episode, 1> ALL_EPISODES_V12 = {Episode::EP1};
|
||||
const array<Episode, 2> ALL_EPISODES_V3 = {Episode::EP1, Episode::EP2};
|
||||
const array<Episode, 3> ALL_EPISODES_V4 = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
const array<Difficulty, 3> ALL_DIFFICULTIES_V1 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD};
|
||||
const array<Difficulty, 4> ALL_DIFFICULTIES_V234 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD, Difficulty::ULTIMATE};
|
||||
const std::array<GameMode, 2> ALL_GAME_MODES_V1 = {GameMode::NORMAL, GameMode::BATTLE};
|
||||
const std::array<GameMode, 3> ALL_GAME_MODES_V23 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE};
|
||||
const std::array<GameMode, 4> ALL_GAME_MODES_V4 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO};
|
||||
const std::array<Episode, 1> ALL_EPISODES_V12 = {Episode::EP1};
|
||||
const std::array<Episode, 2> ALL_EPISODES_V3 = {Episode::EP1, Episode::EP2};
|
||||
const std::array<Episode, 3> ALL_EPISODES_V4 = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
const std::array<Difficulty, 3> ALL_DIFFICULTIES_V1 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD};
|
||||
const std::array<Difficulty, 4> ALL_DIFFICULTIES_V234 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD, Difficulty::ULTIMATE};
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "FileContentsCache.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
enum class Episode {
|
||||
|
||||
+42
-46
@@ -11,13 +11,11 @@
|
||||
#include "Loggers.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TeamIndex::Team::Member::Member(const phosg::JSON& json)
|
||||
: flags(json.get_int("Flags", 0)), points(json.get_int("Points", 0)), name(json.get_string("Name", "")) {
|
||||
try {
|
||||
this->account_id = json.get_int("AccountID");
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
// Old format
|
||||
this->account_id = json.get_int("SerialNumber");
|
||||
}
|
||||
@@ -42,11 +40,11 @@ TeamIndex::Team::Team(uint32_t team_id) : Team() {
|
||||
this->team_id = team_id;
|
||||
}
|
||||
|
||||
string TeamIndex::Team::json_filename() const {
|
||||
std::string TeamIndex::Team::json_filename() const {
|
||||
return std::format("system/teams/{:08X}.json", this->team_id);
|
||||
}
|
||||
|
||||
string TeamIndex::Team::flag_filename() const {
|
||||
std::string TeamIndex::Team::flag_filename() const {
|
||||
return std::format("system/teams/{:08X}.bmp", this->team_id);
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ void TeamIndex::Team::load_config() {
|
||||
for (const auto& it : json.get_list("RewardKeys")) {
|
||||
this->reward_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
this->reward_flags = json.get_int("RewardFlags");
|
||||
}
|
||||
@@ -98,7 +96,7 @@ void TeamIndex::Team::save_config() const {
|
||||
void TeamIndex::Team::load_flag() {
|
||||
auto img = phosg::ImageRGBA8888N::from_file_data(phosg::load_file(this->flag_filename()));
|
||||
if (img.get_width() != 32 || img.get_height() != 32) {
|
||||
throw runtime_error("incorrect flag image dimensions");
|
||||
throw std::runtime_error("incorrect flag image dimensions");
|
||||
}
|
||||
this->flag_data.reset(new parray<le_uint16_t, 0x20 * 0x20>());
|
||||
for (size_t y = 0; y < 32; y++) {
|
||||
@@ -127,10 +125,8 @@ void TeamIndex::Team::save_flag() const {
|
||||
}
|
||||
|
||||
void TeamIndex::Team::delete_files() const {
|
||||
string json_filename = this->json_filename();
|
||||
string flag_filename = this->flag_filename();
|
||||
remove(json_filename.c_str());
|
||||
remove(flag_filename.c_str());
|
||||
std::filesystem::remove(this->json_filename());
|
||||
std::filesystem::remove(this->flag_filename());
|
||||
}
|
||||
|
||||
PSOBBBaseTeamMembership TeamIndex::Team::base_membership_for_member(uint32_t account_id) const {
|
||||
@@ -161,7 +157,7 @@ PSOBBFullTeamMembership TeamIndex::Team::full_membership_for_member(uint32_t acc
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TeamIndex::Team::has_reward(const string& key) const {
|
||||
bool TeamIndex::Team::has_reward(const std::string& key) const {
|
||||
return this->reward_keys.count(key);
|
||||
}
|
||||
|
||||
@@ -226,21 +222,20 @@ TeamIndex::Reward::Reward(uint32_t menu_item_id, const phosg::JSON& def_json)
|
||||
for (const auto& it : def_json.get_list("PrerequisiteKeys")) {
|
||||
this->prerequisite_keys.emplace(it->as_string());
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_flag = static_cast<Team::RewardFlag>(def_json.get_int("RewardFlag"));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
try {
|
||||
this->reward_item = ItemData::from_data(phosg::parse_data_string(def_json.get_string("RewardItem")));
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
TeamIndex::TeamIndex(const string& directory, const phosg::JSON& reward_defs_json)
|
||||
: directory(directory),
|
||||
next_team_id(1) {
|
||||
TeamIndex::TeamIndex(const std::string& directory, const phosg::JSON& reward_defs_json)
|
||||
: directory(directory), next_team_id(1) {
|
||||
uint32_t reward_menu_item_id = 0;
|
||||
for (const auto& it : reward_defs_json.as_list()) {
|
||||
this->reward_defs.emplace_back(reward_menu_item_id++, *it);
|
||||
@@ -251,24 +246,24 @@ TeamIndex::TeamIndex(const string& directory, const phosg::JSON& reward_defs_jso
|
||||
return;
|
||||
}
|
||||
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
||||
string filename = item.path().filename().string();
|
||||
string file_path = this->directory + "/" + filename;
|
||||
std::string filename = item.path().filename().string();
|
||||
std::string file_path = this->directory + "/" + filename;
|
||||
if (filename == "base.json") {
|
||||
auto json = phosg::JSON::parse(phosg::load_file(file_path));
|
||||
this->next_team_id = json.get_int("NextTeamID");
|
||||
} else if (filename.ends_with(".json")) {
|
||||
try {
|
||||
uint32_t team_id = stoul(filename.substr(0, filename.size() - 5), nullptr, 16);
|
||||
auto team = make_shared<Team>(team_id);
|
||||
auto team = std::make_shared<Team>(team_id);
|
||||
team->load_config();
|
||||
try {
|
||||
team->load_flag();
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
static_game_data_log.warning_f("Failed to load flag for team {:08X}: {}", team_id, e.what());
|
||||
}
|
||||
this->add_to_indexes(team);
|
||||
static_game_data_log.info_f("Indexed team {:08X} ({}) ({} members)", team_id, team->name, team->num_members());
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
static_game_data_log.warning_f("Failed to index team from {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
@@ -279,40 +274,41 @@ size_t TeamIndex::count() const {
|
||||
return this->id_to_team.size();
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
||||
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_id(uint32_t team_id) const {
|
||||
try {
|
||||
return this->id_to_team.at(team_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) const {
|
||||
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_name(const std::string& name) const {
|
||||
try {
|
||||
return this->name_to_team.at(name);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
|
||||
std::shared_ptr<const TeamIndex::Team> TeamIndex::get_by_account_id(uint32_t account_id) const {
|
||||
try {
|
||||
return this->account_id_to_team.at(account_id);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
vector<shared_ptr<const Team>> ret;
|
||||
std::vector<std::shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
std::vector<std::shared_ptr<const Team>> ret;
|
||||
for (const auto& it : this->id_to_team) {
|
||||
ret.emplace_back(it.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
|
||||
auto team = make_shared<Team>(this->next_team_id++);
|
||||
std::shared_ptr<const TeamIndex::Team> TeamIndex::create(
|
||||
const std::string& name, uint32_t master_account_id, const std::string& master_name) {
|
||||
auto team = std::make_shared<Team>(this->next_team_id++);
|
||||
phosg::save_file(this->directory + "/base.json", phosg::JSON::dict({{"NextTeamID", this->next_team_id}}).serialize());
|
||||
|
||||
Team::Member m;
|
||||
@@ -338,17 +334,17 @@ void TeamIndex::disband(uint32_t team_id) {
|
||||
void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->name_to_team.emplace(new_team_name, team).second) {
|
||||
throw runtime_error("team name is already in use");
|
||||
throw std::runtime_error("team name is already in use");
|
||||
}
|
||||
this->name_to_team.erase(team->name);
|
||||
team->name = new_team_name;
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) {
|
||||
void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const std::string& name) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (!this->account_id_to_team.emplace(account_id, team).second) {
|
||||
throw runtime_error("user is already in a different team");
|
||||
throw std::runtime_error("user is already in a different team");
|
||||
}
|
||||
|
||||
Team::Member m;
|
||||
@@ -364,7 +360,7 @@ void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string&
|
||||
void TeamIndex::remove_member(uint32_t account_id) {
|
||||
auto team_it = this->account_id_to_team.find(account_id);
|
||||
if (team_it == this->account_id_to_team.end()) {
|
||||
throw runtime_error("client is not in any team");
|
||||
throw std::runtime_error("client is not in any team");
|
||||
}
|
||||
auto team = std::move(team_it->second);
|
||||
this->account_id_to_team.erase(team_it);
|
||||
@@ -401,7 +397,7 @@ bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_accou
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
throw std::runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
@@ -417,7 +413,7 @@ bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_accoun
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
throw std::runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& other_m = team->members.at(leader_account_id);
|
||||
|
||||
@@ -433,7 +429,7 @@ void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_ac
|
||||
auto team = this->account_id_to_team.at(master_account_id);
|
||||
auto& master_m = team->members.at(master_account_id);
|
||||
if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) {
|
||||
throw runtime_error("incorrect master account ID");
|
||||
throw std::runtime_error("incorrect master account ID");
|
||||
}
|
||||
auto& new_master_m = team->members.at(new_master_account_id);
|
||||
|
||||
@@ -445,10 +441,10 @@ void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_ac
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
||||
void TeamIndex::buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag) {
|
||||
auto team = this->id_to_team.at(team_id);
|
||||
if (team->spent_points + points > team->points) {
|
||||
throw runtime_error("not enough points available");
|
||||
throw std::runtime_error("not enough points available");
|
||||
}
|
||||
team->reward_keys.emplace(key);
|
||||
team->spent_points += points;
|
||||
@@ -458,13 +454,13 @@ void TeamIndex::buy_reward(uint32_t team_id, const string& key, uint32_t points,
|
||||
team->save_config();
|
||||
}
|
||||
|
||||
void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
|
||||
void TeamIndex::add_to_indexes(std::shared_ptr<Team> team) {
|
||||
if (!this->id_to_team.emplace(team->team_id, team).second) {
|
||||
throw runtime_error("team ID is already in use");
|
||||
throw std::runtime_error("team ID is already in use");
|
||||
}
|
||||
if (!this->name_to_team.emplace(team->name, team).second) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
throw runtime_error("team name is already in use");
|
||||
throw std::runtime_error("team name is already in use");
|
||||
}
|
||||
for (const auto& [_, member] : team->members) {
|
||||
if (!this->account_id_to_team.emplace(member.account_id, team).second) {
|
||||
@@ -474,7 +470,7 @@ void TeamIndex::add_to_indexes(shared_ptr<Team> team) {
|
||||
}
|
||||
}
|
||||
|
||||
void TeamIndex::remove_from_indexes(shared_ptr<Team> team) {
|
||||
void TeamIndex::remove_from_indexes(std::shared_ptr<Team> team) {
|
||||
this->id_to_team.erase(team->team_id);
|
||||
this->name_to_team.erase(team->name);
|
||||
for (const auto& it : team->members) {
|
||||
|
||||
+47
-49
@@ -9,15 +9,13 @@
|
||||
#include <phosg/Strings.hh>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
const iconv_t TextTranscoder::INVALID_IC = (iconv_t)(-1);
|
||||
const size_t TextTranscoder::FAILURE_RESULT = static_cast<size_t>(-1);
|
||||
|
||||
TextTranscoder::TextTranscoder(const char* to, const char* from) : ic(iconv_open(to, from)) {
|
||||
if (ic == this->INVALID_IC) {
|
||||
string error_str = phosg::string_for_error(errno);
|
||||
throw runtime_error(std::format("failed to initialize {} -> {} text converter: {}", from, to, error_str));
|
||||
throw std::runtime_error(std::format("failed to initialize {} -> {} text converter: {}",
|
||||
from, to, phosg::string_for_error(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,32 +43,32 @@ TextTranscoder::Result TextTranscoder::operator()(
|
||||
if (ret == this->FAILURE_RESULT) {
|
||||
switch (errno) {
|
||||
case EILSEQ: {
|
||||
string custom_result = this->on_untranslatable(&src, &src_bytes);
|
||||
std::string custom_result = this->on_untranslatable(&src, &src_bytes);
|
||||
if (custom_result.empty()) {
|
||||
throw runtime_error(std::format("untranslatable character at position 0x{:X}", bytes_read));
|
||||
throw std::runtime_error(std::format("untranslatable character at position 0x{:X}", bytes_read));
|
||||
} else if (custom_result.size() <= dest_bytes) {
|
||||
memcpy(dest, custom_result.data(), custom_result.size());
|
||||
dest = reinterpret_cast<char*>(dest) + custom_result.size();
|
||||
dest_bytes -= custom_result.size();
|
||||
} else if (!truncate_oversize_result) {
|
||||
throw runtime_error("string does not fit in buffer");
|
||||
throw std::runtime_error("string does not fit in buffer");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EINVAL:
|
||||
throw runtime_error(std::format("incomplete multibyte sequence at position 0x{:X}", bytes_read));
|
||||
throw std::runtime_error(std::format("incomplete multibyte sequence at position 0x{:X}", bytes_read));
|
||||
case E2BIG:
|
||||
if (!truncate_oversize_result) {
|
||||
throw runtime_error("string does not fit in buffer");
|
||||
throw std::runtime_error("string does not fit in buffer");
|
||||
} else {
|
||||
src_bytes = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw runtime_error("transcoding failed: " + phosg::string_for_error(errno));
|
||||
throw std::runtime_error("transcoding failed: " + phosg::string_for_error(errno));
|
||||
}
|
||||
} else if (src_bytes_before == src_bytes) {
|
||||
throw runtime_error("could not transcode any characters");
|
||||
throw std::runtime_error("could not transcode any characters");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,15 +77,15 @@ TextTranscoder::Result TextTranscoder::operator()(
|
||||
return Result{.bytes_read = bytes_read, .bytes_written = bytes_written};
|
||||
}
|
||||
|
||||
string TextTranscoder::operator()(const void* src, size_t src_bytes) {
|
||||
std::string TextTranscoder::operator()(const void* src, size_t src_bytes) {
|
||||
// Clear any conversion state left over from the previous call
|
||||
iconv(this->ic, nullptr, nullptr, nullptr, nullptr);
|
||||
|
||||
const void* orig_src = src;
|
||||
deque<string> blocks;
|
||||
std::deque<std::string> blocks;
|
||||
while (src_bytes > 0) {
|
||||
// Assume 2x input size on average, but always allocate at least 8 bytes
|
||||
string& block = blocks.emplace_back(max<size_t>((src_bytes << 1), 8), '\0');
|
||||
std::string& block = blocks.emplace_back(std::max<size_t>((src_bytes << 1), 8), '\0');
|
||||
char* dest = block.data();
|
||||
size_t dest_size = block.size();
|
||||
size_t src_bytes_before = src_bytes;
|
||||
@@ -103,29 +101,29 @@ string TextTranscoder::operator()(const void* src, size_t src_bytes) {
|
||||
if (ret == this->FAILURE_RESULT) {
|
||||
switch (errno) {
|
||||
case EILSEQ: {
|
||||
string custom_result = this->on_untranslatable(&src, &src_bytes);
|
||||
std::string custom_result = this->on_untranslatable(&src, &src_bytes);
|
||||
if (custom_result.empty()) {
|
||||
throw runtime_error(std::format("untranslatable character at position {}", bytes_read));
|
||||
throw std::runtime_error(std::format("untranslatable character at position {}", bytes_read));
|
||||
}
|
||||
blocks.emplace_back(std::move(custom_result));
|
||||
break;
|
||||
}
|
||||
case EINVAL:
|
||||
throw runtime_error(std::format("incomplete multibyte sequence at position {}", bytes_read));
|
||||
throw std::runtime_error(std::format("incomplete multibyte sequence at position {}", bytes_read));
|
||||
case E2BIG:
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("transcoding failed: " + phosg::string_for_error(errno));
|
||||
throw std::runtime_error("transcoding failed: " + phosg::string_for_error(errno));
|
||||
}
|
||||
} else if (src_bytes_before == src_bytes) {
|
||||
throw runtime_error("could not transcode any characters");
|
||||
throw std::runtime_error("could not transcode any characters");
|
||||
}
|
||||
}
|
||||
|
||||
return phosg::join(blocks, "");
|
||||
}
|
||||
|
||||
string TextTranscoder::operator()(const string& data) {
|
||||
std::string TextTranscoder::operator()(const std::string& data) {
|
||||
return this->operator()(data.data(), data.size());
|
||||
}
|
||||
|
||||
@@ -136,7 +134,7 @@ std::string TextTranscoder::on_untranslatable(const void**, size_t*) const {
|
||||
TextTranscoderCustomSJISToUTF8::TextTranscoderCustomSJISToUTF8() : TextTranscoder("UTF-8", "SHIFT_JIS") {}
|
||||
|
||||
std::string encode_utf8_char(uint32_t ch) {
|
||||
string ret;
|
||||
std::string ret;
|
||||
if (ch < 0x80) {
|
||||
ret.push_back(ch);
|
||||
} else if (ch < 0x800) {
|
||||
@@ -152,14 +150,14 @@ std::string encode_utf8_char(uint32_t ch) {
|
||||
ret.push_back(0x80 | ((ch >> 6) & 0x3F));
|
||||
ret.push_back(0x80 | (ch & 0x3F));
|
||||
} else {
|
||||
throw runtime_error("unencodable Unicode code point");
|
||||
throw std::runtime_error("unencodable Unicode code point");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t decode_utf8_char(const void** vdata, size_t* size) {
|
||||
if (*size == 0) {
|
||||
throw runtime_error("incomplete UTF-8 character");
|
||||
throw std::runtime_error("incomplete UTF-8 character");
|
||||
}
|
||||
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(*vdata);
|
||||
@@ -169,27 +167,27 @@ uint32_t decode_utf8_char(const void** vdata, size_t* size) {
|
||||
return *data;
|
||||
} else if ((data[0] & 0xE0) == 0xC0) {
|
||||
if ((*size < 2) || ((data[1] & 0xC0) != 0x80)) {
|
||||
throw runtime_error("incomplete UTF-8 character");
|
||||
throw std::runtime_error("incomplete UTF-8 character");
|
||||
}
|
||||
(*size) -= 2;
|
||||
*vdata = data + 2;
|
||||
return ((data[0] & 0x1F) << 6) | (data[1] & 0x3F);
|
||||
} else if ((data[0] & 0xF0) == 0xE0) {
|
||||
if ((*size < 3) || ((data[1] & 0xC0) != 0x80) || ((data[2] & 0xC0) != 0x80)) {
|
||||
throw runtime_error("incomplete UTF-8 character");
|
||||
throw std::runtime_error("incomplete UTF-8 character");
|
||||
}
|
||||
(*size) -= 3;
|
||||
*vdata = data + 3;
|
||||
return ((data[0] & 0x0F) << 12) | ((data[1] & 0x3F) << 6) | (data[2] & 0x3F);
|
||||
} else if ((data[0] & 0xF8) == 0xF0) {
|
||||
if ((*size < 4) || ((data[1] & 0xC0) != 0x80) || ((data[2] & 0xC0) != 0x80) || ((data[3] & 0xC0) != 0x80)) {
|
||||
throw runtime_error("incomplete UTF-8 character");
|
||||
throw std::runtime_error("incomplete UTF-8 character");
|
||||
}
|
||||
(*size) -= 4;
|
||||
*vdata = data + 4;
|
||||
return ((data[0] & 0x07) << 18) | ((data[1] & 0x3F) << 12) | ((data[2] & 0x3F) << 6) | (data[3] & 0x3F);
|
||||
} else {
|
||||
throw runtime_error("invalid UTF-8 character");
|
||||
throw std::runtime_error("invalid UTF-8 character");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +206,7 @@ std::string TextTranscoderCustomSJISToUTF8::on_untranslatable(const void** vsrc,
|
||||
return "";
|
||||
}
|
||||
|
||||
string ret;
|
||||
std::string ret;
|
||||
if (src[1] < 0x40) {
|
||||
return "";
|
||||
} else if (src[1] == 0x40) { // F040 -> U+2665
|
||||
@@ -236,7 +234,7 @@ std::string TextTranscoderUTF8ToCustomSJIS::on_untranslatable(const void** src,
|
||||
uint32_t ch;
|
||||
try {
|
||||
ch = decode_utf8_char(src, size);
|
||||
} catch (const runtime_error&) {
|
||||
} catch (const std::runtime_error&) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -245,11 +243,11 @@ std::string TextTranscoderUTF8ToCustomSJIS::on_untranslatable(const void** src,
|
||||
} else if (ch == 0x24EA) { // U+24EA -> F041
|
||||
return "\xF0\x41";
|
||||
} else if (ch >= 0x2460 && ch <= 0x2468) { // U+2460-U+2468 -> F042-F04A
|
||||
string ret("\xF0");
|
||||
std::string ret("\xF0");
|
||||
ret.push_back(0x42 + (ch - 0x2460));
|
||||
return ret;
|
||||
} else if (ch >= 0x1D4D0 && ch <= 0x1D4E9) { // U+1D4D0-U+1D4E9 -> F04B-F064
|
||||
string ret("\xF0");
|
||||
std::string ret("\xF0");
|
||||
ret.push_back(0x4B + (ch - 0x1D4D0));
|
||||
return ret;
|
||||
} else {
|
||||
@@ -270,29 +268,29 @@ TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8");
|
||||
TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII");
|
||||
TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8");
|
||||
|
||||
string tt_encode_marked_optional(const string& utf8, Language default_language, bool is_utf16) {
|
||||
std::string tt_encode_marked_optional(const std::string& utf8, Language default_language, bool is_utf16) {
|
||||
if (is_utf16) {
|
||||
return tt_utf8_to_utf16(utf8);
|
||||
} else {
|
||||
if (default_language == Language::JAPANESE) {
|
||||
try {
|
||||
return tt_utf8_to_sega_sjis(utf8);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
return "\tE" + tt_utf8_to_8859(utf8);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return tt_utf8_to_8859(utf8);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
return "\tJ" + tt_utf8_to_sega_sjis(utf8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string tt_encode_marked(const string& utf8, Language default_language, bool is_utf16) {
|
||||
std::string tt_encode_marked(const std::string& utf8, Language default_language, bool is_utf16) {
|
||||
if (is_utf16) {
|
||||
string to_encode = "\t";
|
||||
std::string to_encode = "\t";
|
||||
to_encode += marker_for_language(default_language);
|
||||
to_encode += utf8;
|
||||
return tt_utf8_to_utf16(to_encode);
|
||||
@@ -300,22 +298,22 @@ string tt_encode_marked(const string& utf8, Language default_language, bool is_u
|
||||
if (default_language == Language::JAPANESE) {
|
||||
try {
|
||||
return "\tJ" + tt_utf8_to_sega_sjis(utf8);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
return "\tE" + tt_utf8_to_8859(utf8);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return "\tE" + tt_utf8_to_8859(utf8);
|
||||
} catch (const exception& e) {
|
||||
} catch (const std::exception& e) {
|
||||
return "\tJ" + tt_utf8_to_sega_sjis(utf8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string tt_decode_marked(const string& data, Language default_language, bool is_utf16) {
|
||||
std::string tt_decode_marked(const std::string& data, Language default_language, bool is_utf16) {
|
||||
if (is_utf16) {
|
||||
string ret = tt_utf16_to_utf8(data);
|
||||
std::string ret = tt_utf16_to_utf8(data);
|
||||
if (ret.size() >= 2 && ret[0] == '\t' && is_language_marker_utf16(ret[1])) {
|
||||
ret = ret.substr(2);
|
||||
}
|
||||
@@ -332,19 +330,19 @@ string tt_decode_marked(const string& data, Language default_language, bool is_u
|
||||
}
|
||||
}
|
||||
|
||||
string add_language_marker(const string& s, char marker) {
|
||||
std::string add_language_marker(const std::string& s, char marker) {
|
||||
if ((s.size() >= 2) && (s[0] == '\t') && (s[1] != 'C')) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string ret;
|
||||
std::string ret;
|
||||
ret.push_back('\t');
|
||||
ret.push_back(marker);
|
||||
ret += s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
string remove_language_marker(const string& s) {
|
||||
std::string remove_language_marker(const std::string& s) {
|
||||
if ((s.size() < 2) || (s[0] != '\t') || (s[1] == 'C')) {
|
||||
return s;
|
||||
}
|
||||
@@ -394,7 +392,7 @@ size_t add_color_inplace(char* a, size_t max_chars) {
|
||||
return d - orig_d;
|
||||
}
|
||||
|
||||
void add_color_inplace(string& s) {
|
||||
void add_color_inplace(std::string& s) {
|
||||
s.resize(add_color_inplace(s.data(), s.size()));
|
||||
}
|
||||
|
||||
@@ -425,7 +423,7 @@ void add_color(phosg::StringWriter& w, const char* src, size_t max_input_chars)
|
||||
}
|
||||
}
|
||||
|
||||
string add_color(const string& s) {
|
||||
std::string add_color(const std::string& s) {
|
||||
phosg::StringWriter w;
|
||||
add_color(w, s.data(), s.size());
|
||||
return std::move(w.str());
|
||||
@@ -453,14 +451,14 @@ void remove_color(phosg::StringWriter& w, const char* src, size_t max_input_char
|
||||
}
|
||||
}
|
||||
|
||||
string remove_color(const string& s) {
|
||||
std::string remove_color(const std::string& s) {
|
||||
phosg::StringWriter w;
|
||||
remove_color(w, s.data(), s.size());
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
string strip_color(const string& s) {
|
||||
string ret;
|
||||
std::string strip_color(const std::string& s) {
|
||||
std::string ret;
|
||||
for (size_t r = 0; r < s.size(); r++) {
|
||||
if ((s[r] == '$' || s[r] == '\t') &&
|
||||
(s[r + 1] == 'C') && (((s[r + 2] >= '0') && (s[r + 2] <= '9')) || (s[r + 2] == 'G') || (s[r + 2] == 'a'))) {
|
||||
@@ -472,7 +470,7 @@ string strip_color(const string& s) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
string escape_player_name(const string& name) {
|
||||
std::string escape_player_name(const std::string& name) {
|
||||
if (name.size() > 2 && name[0] == '\t' && name[1] != 'C') {
|
||||
return remove_color(name.substr(2));
|
||||
} else {
|
||||
|
||||
+47
-49
@@ -14,8 +14,6 @@
|
||||
#include "StaticGameData.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
TextSet::TextSet(const phosg::JSON& json) {
|
||||
for (const auto& coll_json : json.as_list()) {
|
||||
auto& collection = this->collections.emplace_back();
|
||||
@@ -57,7 +55,7 @@ const std::string& TextSet::get(size_t collection_index, size_t string_index) co
|
||||
return this->get(collection_index).at(string_index);
|
||||
}
|
||||
|
||||
const vector<string>& TextSet::get(size_t collection_index) const {
|
||||
const std::vector<std::string>& TextSet::get(size_t collection_index) const {
|
||||
return this->collections.at(collection_index);
|
||||
}
|
||||
|
||||
@@ -106,12 +104,12 @@ void TextSet::ensure_collection_exists(size_t collection_index) {
|
||||
}
|
||||
}
|
||||
|
||||
UnicodeTextSet::UnicodeTextSet(const string& prs_data) {
|
||||
string data = prs_decompress(prs_data);
|
||||
UnicodeTextSet::UnicodeTextSet(const std::string& prs_data) {
|
||||
std::string data = prs_decompress(prs_data);
|
||||
phosg::StringReader r(data);
|
||||
|
||||
uint32_t num_collections = r.get_u32l();
|
||||
deque<uint32_t> collection_sizes;
|
||||
std::deque<uint32_t> collection_sizes;
|
||||
while (collection_sizes.size() < num_collections) {
|
||||
collection_sizes.emplace_back(r.get_u32l());
|
||||
}
|
||||
@@ -134,7 +132,7 @@ UnicodeTextSet::UnicodeTextSet(const string& prs_data) {
|
||||
}
|
||||
}
|
||||
|
||||
string UnicodeTextSet::serialize() const {
|
||||
std::string UnicodeTextSet::serialize() const {
|
||||
phosg::StringWriter header_w;
|
||||
phosg::StringWriter data_w;
|
||||
|
||||
@@ -145,7 +143,7 @@ string UnicodeTextSet::serialize() const {
|
||||
total_num_strings += collection.size();
|
||||
}
|
||||
|
||||
unordered_map<string, uint32_t> encoded;
|
||||
std::unordered_map<std::string, uint32_t> encoded;
|
||||
|
||||
size_t data_base_offset = (total_num_strings * 4) + header_w.size();
|
||||
for (const auto& collection : this->collections) {
|
||||
@@ -154,8 +152,7 @@ string UnicodeTextSet::serialize() const {
|
||||
if (encoded_it == encoded.end()) {
|
||||
uint32_t offset = data_base_offset + data_w.size();
|
||||
encoded_it = encoded.emplace(s, offset).first;
|
||||
string s_utf16 = tt_utf8_to_utf16(s);
|
||||
data_w.write(s_utf16.data(), s_utf16.size());
|
||||
data_w.write(tt_utf8_to_utf16(s));
|
||||
data_w.put_u16(0);
|
||||
while (data_w.size() & 3) {
|
||||
data_w.put_u8(0);
|
||||
@@ -177,7 +174,7 @@ BinaryTextSet::BinaryTextSet(const std::string& pr2_data, size_t collection_coun
|
||||
// Annoyingly, there doesn't appear to be any bounds-checking on the language functions, so there are no counts of
|
||||
// strings in each collection. We have to figure out where each collection ends by collecting all the relevant
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
std::set<uint32_t> used_offsets;
|
||||
size_t root_offset = has_rel_footer
|
||||
? r.pget<RELFileFooter>(r.size() - 0x20).root_offset.load()
|
||||
: (r.size() - collection_count * sizeof(le_uint32_t));
|
||||
@@ -202,12 +199,12 @@ BinaryTextSet::BinaryTextSet(const std::string& pr2_data, size_t collection_coun
|
||||
string_offset_offset += 4) {
|
||||
collection.emplace_back(tt(r.pget_cstr(r.pget_u32l(string_offset_offset))));
|
||||
}
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryTextAndKeyboardsSet::BinaryTextAndKeyboardsSet(const string& pr2_data, bool big_endian, bool is_sjis) {
|
||||
BinaryTextAndKeyboardsSet::BinaryTextAndKeyboardsSet(const std::string& pr2_data, bool big_endian, bool is_sjis) {
|
||||
if (big_endian) {
|
||||
this->parse_t<true>(pr2_data, is_sjis);
|
||||
} else {
|
||||
@@ -224,7 +221,7 @@ BinaryTextAndKeyboardsSet::BinaryTextAndKeyboardsSet(const phosg::JSON& json) {
|
||||
}
|
||||
|
||||
for (const auto& keyboard_json : json.at("keyboards").as_list()) {
|
||||
auto& keyboard = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
auto& keyboard = this->keyboards.emplace_back(std::make_unique<Keyboard>());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
auto& row = keyboard->at(y);
|
||||
const auto& row_json = keyboard_json->at(y);
|
||||
@@ -267,14 +264,14 @@ void BinaryTextAndKeyboardsSet::set_keyboard(size_t kb_index, const Keyboard& kb
|
||||
if (kb_index >= this->keyboards.size()) {
|
||||
this->keyboards.resize(kb_index + 1);
|
||||
}
|
||||
this->keyboards[kb_index] = make_unique<Keyboard>(kb);
|
||||
this->keyboards[kb_index] = std::make_unique<Keyboard>(kb);
|
||||
}
|
||||
|
||||
void BinaryTextAndKeyboardsSet::resize_keyboards(size_t num_keyboards) {
|
||||
this->keyboards.resize(num_keyboards);
|
||||
}
|
||||
|
||||
pair<string, string> BinaryTextAndKeyboardsSet::serialize(bool big_endian, bool is_sjis) const {
|
||||
std::pair<std::string, std::string> BinaryTextAndKeyboardsSet::serialize(bool big_endian, bool is_sjis) const {
|
||||
if (big_endian) {
|
||||
return this->serialize_t<true>(is_sjis);
|
||||
} else {
|
||||
@@ -283,7 +280,7 @@ pair<string, string> BinaryTextAndKeyboardsSet::serialize(bool big_endian, bool
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void BinaryTextAndKeyboardsSet::parse_t(const string& pr2_data, bool is_sjis) {
|
||||
void BinaryTextAndKeyboardsSet::parse_t(const std::string& pr2_data, bool is_sjis) {
|
||||
auto& tt = is_sjis ? tt_sega_sjis_to_utf8 : tt_8859_to_utf8;
|
||||
|
||||
// The structure is as follows:
|
||||
@@ -308,7 +305,7 @@ void BinaryTextAndKeyboardsSet::parse_t(const string& pr2_data, bool is_sjis) {
|
||||
// Annoyingly, there doesn't appear to be any bounds-checking on the language functions, so there are no counts of
|
||||
// strings in each collection. We have to figure out where each collection ends by collecting all the relevant
|
||||
// offsets in the file instead.
|
||||
::set<uint32_t> used_offsets;
|
||||
std::set<uint32_t> used_offsets;
|
||||
used_offsets.emplace(r.size() - 8);
|
||||
|
||||
uint32_t keyboard_index_offset = r.pget<U32T<BE>>(r.size() - 8);
|
||||
@@ -320,7 +317,7 @@ void BinaryTextAndKeyboardsSet::parse_t(const string& pr2_data, bool is_sjis) {
|
||||
while (this->keyboards.size() < num_keyboards) {
|
||||
uint32_t keyboard_offset = r.pget<U32T<BE>>(keyboards_offset + 4 * this->keyboards.size());
|
||||
used_offsets.emplace(keyboard_offset);
|
||||
auto& kb = this->keyboards.emplace_back(make_unique<Keyboard>());
|
||||
auto& kb = this->keyboards.emplace_back(std::make_unique<Keyboard>());
|
||||
auto key_r = r.sub(keyboard_offset, sizeof(Keyboard));
|
||||
for (size_t y = 0; y < kb->size(); y++) {
|
||||
auto& row = kb->at(y);
|
||||
@@ -348,11 +345,11 @@ void BinaryTextAndKeyboardsSet::parse_t(const string& pr2_data, bool is_sjis) {
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
pair<string, string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const {
|
||||
std::pair<std::string, std::string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const {
|
||||
auto& tt = is_sjis ? tt_utf8_to_sega_sjis : tt_utf8_to_8859;
|
||||
|
||||
phosg::StringWriter w;
|
||||
::set<size_t> relocation_offsets;
|
||||
std::set<size_t> relocation_offsets;
|
||||
auto put_offset_u32 = [&](uint32_t v) {
|
||||
relocation_offsets.emplace(w.size());
|
||||
w.put<U32T<BE>>(v);
|
||||
@@ -360,7 +357,7 @@ pair<string, string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const
|
||||
|
||||
uint32_t collections_offset;
|
||||
{
|
||||
unordered_map<string, uint32_t> string_to_offset;
|
||||
std::unordered_map<std::string, uint32_t> string_to_offset;
|
||||
for (const auto& collection : this->collections) {
|
||||
for (const auto& s : collection) {
|
||||
if (string_to_offset.emplace(s, w.size()).second) {
|
||||
@@ -373,7 +370,7 @@ pair<string, string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const
|
||||
}
|
||||
}
|
||||
|
||||
vector<uint32_t> collection_offsets;
|
||||
std::vector<uint32_t> collection_offsets;
|
||||
for (const auto& collection : this->collections) {
|
||||
collection_offsets.emplace_back(w.size());
|
||||
for (const auto& s : collection) {
|
||||
@@ -389,7 +386,7 @@ pair<string, string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const
|
||||
|
||||
uint32_t keyboard_index_offset;
|
||||
{
|
||||
vector<uint32_t> keyboard_offsets;
|
||||
std::vector<uint32_t> keyboard_offsets;
|
||||
for (const auto& keyboard : this->keyboards) {
|
||||
keyboard_offsets.emplace_back(w.size());
|
||||
for (size_t y = 0; y < keyboard->size(); y++) {
|
||||
@@ -426,39 +423,40 @@ pair<string, string> BinaryTextAndKeyboardsSet::serialize_t(bool is_sjis) const
|
||||
size_t offset = 0;
|
||||
for (size_t reloc_offset : relocation_offsets) {
|
||||
if (reloc_offset & 3) {
|
||||
throw logic_error("misaligned relocation");
|
||||
throw std::logic_error("misaligned relocation");
|
||||
}
|
||||
size_t num_words = (reloc_offset - offset) >> 2;
|
||||
if (num_words > 0xFFFF) {
|
||||
throw runtime_error("relocation offset too far away");
|
||||
throw std::runtime_error("relocation offset too far away");
|
||||
}
|
||||
reloc_w.put<U16T<BE>>(num_words);
|
||||
offset = reloc_offset;
|
||||
}
|
||||
}
|
||||
|
||||
const string& pr2_data = w.str();
|
||||
const string& pr3_data = reloc_w.str();
|
||||
string pr2_compressed = prs_compress_optimal(pr2_data.data(), pr2_data.size());
|
||||
string pr3_compressed = prs_compress_optimal(pr3_data.data(), pr3_data.size());
|
||||
string pr2_ret = encrypt_pr2_data<BE>(pr2_compressed, pr2_data.size(), phosg::random_object<uint32_t>());
|
||||
string pr3_ret = encrypt_pr2_data<BE>(pr3_compressed, pr3_data.size(), phosg::random_object<uint32_t>());
|
||||
return make_pair(std::move(pr2_ret), std::move(pr3_ret));
|
||||
const std::string& pr2_data = w.str();
|
||||
const std::string& pr3_data = reloc_w.str();
|
||||
std::string pr2_compressed = prs_compress_optimal(pr2_data.data(), pr2_data.size());
|
||||
std::string pr3_compressed = prs_compress_optimal(pr3_data.data(), pr3_data.size());
|
||||
std::string pr2_ret = encrypt_pr2_data<BE>(pr2_compressed, pr2_data.size(), phosg::random_object<uint32_t>());
|
||||
std::string pr3_ret = encrypt_pr2_data<BE>(pr3_compressed, pr3_data.size(), phosg::random_object<uint32_t>());
|
||||
return std::make_pair(std::move(pr2_ret), std::move(pr3_ret));
|
||||
}
|
||||
|
||||
TextIndex::TextIndex(
|
||||
const string& directory, function<shared_ptr<const string>(Version, const string&)> get_patch_file)
|
||||
const std::string& directory,
|
||||
std::function<std::shared_ptr<const std::string>(Version, const std::string&)> get_patch_file)
|
||||
: log("[TextIndex] ", static_game_data_log.min_level) {
|
||||
if (!directory.empty()) {
|
||||
auto add_version = [&](Version version, const string& subdirectory, function<shared_ptr<TextSet>(const string&, bool)> make_set) -> void {
|
||||
static const map<string, Language> bintext_filenames({
|
||||
auto add_version = [&](Version version, const std::string& subdirectory, std::function<std::shared_ptr<TextSet>(const std::string&, bool)> make_set) -> void {
|
||||
static const std::map<std::string, Language> bintext_filenames({
|
||||
{"TextJapanese.pr2", Language::JAPANESE},
|
||||
{"TextEnglish.pr2", Language::ENGLISH},
|
||||
{"TextGerman.pr2", Language::GERMAN},
|
||||
{"TextFrench.pr2", Language::FRENCH},
|
||||
{"TextSpanish.pr2", Language::SPANISH},
|
||||
});
|
||||
static const map<string, Language> unitext_filenames({
|
||||
static const std::map<std::string, Language> unitext_filenames({
|
||||
{"unitxt_j.prs", Language::JAPANESE}, // PC/BB Japanese
|
||||
{"unitxt_e.prs", Language::ENGLISH}, // PC/BB English
|
||||
{"unitxt_g.prs", Language::GERMAN}, // PC/BB German
|
||||
@@ -473,11 +471,11 @@ TextIndex::TextIndex(
|
||||
});
|
||||
if (!uses_utf16(version)) {
|
||||
for (const auto& [base_filename, language] : bintext_filenames) {
|
||||
string file_path = directory + "/" + subdirectory + "/" + base_filename;
|
||||
string json_path = file_path + ".json";
|
||||
std::string file_path = directory + "/" + subdirectory + "/" + base_filename;
|
||||
std::string json_path = file_path + ".json";
|
||||
if (std::filesystem::is_regular_file(json_path)) {
|
||||
this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), name_for_language(language), json_path);
|
||||
this->add_set(version, language, make_shared<BinaryTextSet>(phosg::JSON::parse(phosg::load_file(json_path))));
|
||||
this->add_set(version, language, std::make_shared<BinaryTextSet>(phosg::JSON::parse(phosg::load_file(json_path))));
|
||||
} else if (std::filesystem::is_regular_file(file_path)) {
|
||||
this->log.debug_f("Loading {} {} binary text set from {}", phosg::name_for_enum(version), name_for_language(language), file_path);
|
||||
this->add_set(version, language, make_set(phosg::load_file(file_path), language == Language::JAPANESE));
|
||||
@@ -485,11 +483,11 @@ TextIndex::TextIndex(
|
||||
}
|
||||
} else {
|
||||
for (const auto& [base_filename, language] : unitext_filenames) {
|
||||
string file_path = directory + "/" + subdirectory + "/" + base_filename;
|
||||
string json_path = file_path + ".json";
|
||||
std::string file_path = directory + "/" + subdirectory + "/" + base_filename;
|
||||
std::string json_path = file_path + ".json";
|
||||
if (std::filesystem::is_regular_file(json_path)) {
|
||||
this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), name_for_language(language), json_path);
|
||||
this->add_set(version, language, make_shared<UnicodeTextSet>(phosg::JSON::parse(phosg::load_file(json_path))));
|
||||
this->add_set(version, language, std::make_shared<UnicodeTextSet>(phosg::JSON::parse(phosg::load_file(json_path))));
|
||||
} else {
|
||||
auto patch_file = get_patch_file ? get_patch_file(version, base_filename) : nullptr;
|
||||
if (patch_file) {
|
||||
@@ -506,12 +504,12 @@ TextIndex::TextIndex(
|
||||
}
|
||||
};
|
||||
|
||||
auto make_binary_dc112000 = +[](const string& data, bool is_sjis) { return make_shared<BinaryTextSet>(data, 21, true, is_sjis); };
|
||||
auto make_binary_dcnte_dcv1 = +[](const string& data, bool is_sjis) { return make_shared<BinaryTextSet>(data, 26, true, is_sjis); };
|
||||
auto make_binary_dcv2 = +[](const string& data, bool is_sjis) { return make_shared<BinaryTextSet>(data, 37, false, is_sjis); };
|
||||
auto make_binary_gc = +[](const string& data, bool is_sjis) { return make_shared<BinaryTextAndKeyboardsSet>(data, true, is_sjis); };
|
||||
auto make_binary_xb = +[](const string& data, bool is_sjis) { return make_shared<BinaryTextAndKeyboardsSet>(data, false, is_sjis); };
|
||||
auto make_unitxt = +[](const string& data, bool) { return make_shared<UnicodeTextSet>(data); };
|
||||
auto make_binary_dc112000 = +[](const std::string& data, bool is_sjis) { return std::make_shared<BinaryTextSet>(data, 21, true, is_sjis); };
|
||||
auto make_binary_dcnte_dcv1 = +[](const std::string& data, bool is_sjis) { return std::make_shared<BinaryTextSet>(data, 26, true, is_sjis); };
|
||||
auto make_binary_dcv2 = +[](const std::string& data, bool is_sjis) { return std::make_shared<BinaryTextSet>(data, 37, false, is_sjis); };
|
||||
auto make_binary_gc = +[](const std::string& data, bool is_sjis) { return std::make_shared<BinaryTextAndKeyboardsSet>(data, true, is_sjis); };
|
||||
auto make_binary_xb = +[](const std::string& data, bool is_sjis) { return std::make_shared<BinaryTextAndKeyboardsSet>(data, false, is_sjis); };
|
||||
auto make_unitxt = +[](const std::string& data, bool) { return std::make_shared<UnicodeTextSet>(data); };
|
||||
|
||||
add_version(Version::DC_NTE, "dc-nte", make_binary_dcnte_dcv1);
|
||||
add_version(Version::DC_11_2000, "dc-11-2000", make_binary_dc112000);
|
||||
|
||||
+9
-11
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "Client.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <>
|
||||
const char* phosg::name_for_enum<Version>(Version v) {
|
||||
switch (v) {
|
||||
@@ -40,7 +38,7 @@ const char* phosg::name_for_enum<Version>(Version v) {
|
||||
case Version::BB_V4:
|
||||
return "BB_V4";
|
||||
default:
|
||||
throw runtime_error("unknown version");
|
||||
throw std::runtime_error("unknown version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +73,7 @@ Version phosg::enum_for_name<Version>(const char* name) {
|
||||
} else if (!strcmp(name, "BB_V4") || !strcasecmp(name, "bb")) {
|
||||
return Version::BB_V4;
|
||||
} else {
|
||||
throw invalid_argument("incorrect version name");
|
||||
throw std::invalid_argument("incorrect version name");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +89,7 @@ const char* phosg::name_for_enum<ServerBehavior>(ServerBehavior behavior) {
|
||||
case ServerBehavior::PATCH_SERVER_BB:
|
||||
return "patch_server_bb";
|
||||
}
|
||||
throw logic_error("invalid server behavior");
|
||||
throw std::logic_error("invalid server behavior");
|
||||
}
|
||||
|
||||
template <>
|
||||
@@ -105,7 +103,7 @@ ServerBehavior phosg::enum_for_name<ServerBehavior>(const char* name) {
|
||||
} else if (!strcasecmp(name, "patch_server_bb") || !strcasecmp(name, "patch_bb")) {
|
||||
return ServerBehavior::PATCH_SERVER_BB;
|
||||
} else {
|
||||
throw invalid_argument(std::format("incorrect server behavior name: {}", name));
|
||||
throw std::invalid_argument(std::format("incorrect server behavior name: {}", name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +184,7 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
|
||||
default:
|
||||
return SPECIFIC_VERSION_GC_V3_INDETERMINATE; // 3O__; need to send VersionDetect
|
||||
}
|
||||
throw logic_error("this should be impossible");
|
||||
throw std::logic_error("this should be impossible");
|
||||
case Version::GC_EP3_NTE:
|
||||
return SPECIFIC_VERSION_GC_EP3_NTE; // 3SJT
|
||||
case Version::GC_EP3:
|
||||
@@ -261,8 +259,8 @@ uint32_t specific_version_for_str(const std::string& s) {
|
||||
}
|
||||
}
|
||||
|
||||
string str_for_specific_version(uint32_t specific_version) {
|
||||
string ret;
|
||||
std::string str_for_specific_version(uint32_t specific_version) {
|
||||
std::string ret;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
char ch = specific_version >> (24 - (z << 3));
|
||||
ret.push_back(isalnum(ch) ? ch : '_');
|
||||
@@ -301,7 +299,7 @@ const char* file_path_token_for_version(Version version) {
|
||||
case Version::BB_V4:
|
||||
return "bb-v4";
|
||||
default:
|
||||
throw runtime_error("invalid game version");
|
||||
throw std::runtime_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +323,6 @@ uint64_t generate_random_hardware_id(Version version) {
|
||||
case Version::BB_V4:
|
||||
return 0;
|
||||
default:
|
||||
throw runtime_error("invalid game version");
|
||||
throw std::runtime_error("invalid game version");
|
||||
}
|
||||
}
|
||||
|
||||
+29
-30
@@ -7,11 +7,9 @@
|
||||
|
||||
#include "Compression.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename RetT, typename ReadT>
|
||||
static vector<RetT> read_direct_table(const phosg::StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<RetT> ret;
|
||||
static std::vector<RetT> read_direct_table(const phosg::StringReader& base_r, size_t offset, size_t count) {
|
||||
std::vector<RetT> ret;
|
||||
auto entries_r = base_r.sub(offset, count * sizeof(ReadT));
|
||||
while (!entries_r.eof()) {
|
||||
ret.emplace_back(entries_r.get<ReadT>());
|
||||
@@ -20,8 +18,8 @@ static vector<RetT> read_direct_table(const phosg::StringReader& base_r, size_t
|
||||
}
|
||||
|
||||
template <typename RetT, typename ReadT, typename OffsetT>
|
||||
static vector<vector<RetT>> read_indirect_table(const phosg::StringReader& base_r, size_t offset, size_t count) {
|
||||
vector<vector<RetT>> ret;
|
||||
static std::vector<std::vector<RetT>> read_indirect_table(const phosg::StringReader& base_r, size_t offset, size_t count) {
|
||||
std::vector<std::vector<RetT>> ret;
|
||||
auto pointers_r = base_r.sub(offset, sizeof(OffsetT) * 2 * count);
|
||||
while (!pointers_r.eof()) {
|
||||
uint32_t sub_offset = pointers_r.get<OffsetT>();
|
||||
@@ -73,7 +71,7 @@ void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis)
|
||||
{
|
||||
auto string_offset_r = r.sub(root.strings_table, sizeof(U32T<BE>) * StringTableCount);
|
||||
while (!string_offset_r.eof()) {
|
||||
string raw_s = r.pget_cstr(string_offset_r.template get<U32T<BE>>());
|
||||
std::string raw_s = r.pget_cstr(string_offset_r.template get<U32T<BE>>());
|
||||
this->strings.emplace_back(use_sjis ? tt_sega_sjis_to_utf8(raw_s) : tt_8859_to_utf8(raw_s));
|
||||
}
|
||||
}
|
||||
@@ -89,7 +87,7 @@ void WordSelectSet::parse_non_windows_t(const std::string& data, bool use_sjis)
|
||||
template <typename RootT, size_t TokenCount>
|
||||
void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<std::string>* unitxt_collection) {
|
||||
if (!unitxt_collection) {
|
||||
throw runtime_error("a unitxt collection is required");
|
||||
throw std::runtime_error("a unitxt collection is required");
|
||||
}
|
||||
|
||||
phosg::StringReader r(data);
|
||||
@@ -104,13 +102,14 @@ void WordSelectSet::parse_windows_t(const std::string& data, const std::vector<s
|
||||
// this->table6 = read_indirect_table<uint16_t, le_uint16_t, le_uint32_t>(r, root.table6, Table6Count);
|
||||
}
|
||||
|
||||
WordSelectSet::WordSelectSet(const string& data, Version version, const vector<string>* unitxt_collection, bool use_sjis) {
|
||||
WordSelectSet::WordSelectSet(
|
||||
const std::string& data, Version version, const std::vector<std::string>* unitxt_collection, bool use_sjis) {
|
||||
switch (version) {
|
||||
case Version::DC_NTE: {
|
||||
if (data.size() < 4) {
|
||||
throw runtime_error("data is too small");
|
||||
throw std::runtime_error("data is too small");
|
||||
}
|
||||
string decrypted = data.substr(0, data.size() - 4);
|
||||
std::string decrypted = data.substr(0, data.size() - 4);
|
||||
uint32_t seed = *reinterpret_cast<const le_uint32_t*>(data.data() + data.size() - 4);
|
||||
PSOV2Encryption crypt(seed);
|
||||
crypt.decrypt(decrypted);
|
||||
@@ -145,11 +144,11 @@ WordSelectSet::WordSelectSet(const string& data, Version version, const vector<s
|
||||
this->parse_windows_t<BBRoot, 0x68C>(decrypt_and_decompress_pr2_data<false>(data), unitxt_collection);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("unsupported word select data version");
|
||||
throw std::runtime_error("unsupported word select data version");
|
||||
}
|
||||
}
|
||||
|
||||
const string& WordSelectSet::string_for_token(uint16_t token_id) const {
|
||||
const std::string& WordSelectSet::string_for_token(uint16_t token_id) const {
|
||||
return this->strings.at(this->token_id_to_string_id.at(token_id));
|
||||
}
|
||||
|
||||
@@ -179,9 +178,9 @@ WordSelectTable::WordSelectTable(
|
||||
const WordSelectSet& gc_ep3_ws,
|
||||
const WordSelectSet& xb_v3_ws,
|
||||
const WordSelectSet& bb_v4_ws,
|
||||
const vector<vector<string>>& name_alias_lists) {
|
||||
const std::vector<std::vector<std::string>>& name_alias_lists) {
|
||||
|
||||
unordered_map<string, string> name_to_canonical_name;
|
||||
std::unordered_map<std::string, std::string> name_to_canonical_name;
|
||||
for (const auto& alias_list : name_alias_lists) {
|
||||
if (alias_list.size() < 2) {
|
||||
continue;
|
||||
@@ -193,20 +192,20 @@ WordSelectTable::WordSelectTable(
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<Token>> dynamic_tokens;
|
||||
std::vector<std::shared_ptr<Token>> dynamic_tokens;
|
||||
{
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
auto& token = dynamic_tokens.emplace_back(std::make_shared<Token>());
|
||||
token->canonical_name = std::format("__PLAYER_{}_NAME__", z);
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
auto& token = dynamic_tokens.emplace_back(make_shared<Token>());
|
||||
auto& token = dynamic_tokens.emplace_back(std::make_shared<Token>());
|
||||
token->canonical_name = "__BLANK__";
|
||||
this->name_to_token.emplace(token->canonical_name, token);
|
||||
}
|
||||
|
||||
static_assert(NUM_NON_PATCH_VERSIONS == 12, "Don\'t forget to update the WordSelectTable constructor");
|
||||
array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
|
||||
std::array<const WordSelectSet*, NUM_NON_PATCH_VERSIONS> ws_sets = {
|
||||
&dc_nte_ws, &dc_112000_ws, &dc_v1_ws, &dc_v2_ws, &pc_nte_ws, &pc_v2_ws, &gc_nte_ws, &gc_v3_ws, &gc_ep3_nte_ws,
|
||||
&gc_ep3_ws, &xb_v3_ws, &bb_v4_ws};
|
||||
|
||||
@@ -217,18 +216,18 @@ WordSelectTable::WordSelectTable(
|
||||
|
||||
index.reserve(ws_set.num_tokens());
|
||||
for (size_t token_id = 0; token_id < ws_set.num_tokens(); token_id++) {
|
||||
const string& str = ws_set.string_for_token(token_id);
|
||||
const std::string& str = ws_set.string_for_token(token_id);
|
||||
|
||||
string canonical_name;
|
||||
std::string canonical_name;
|
||||
try {
|
||||
canonical_name = name_to_canonical_name.at(str);
|
||||
} catch (const out_of_range&) {
|
||||
} catch (const std::out_of_range&) {
|
||||
canonical_name = str;
|
||||
}
|
||||
|
||||
auto token_it = this->name_to_token.find(canonical_name);
|
||||
if (token_it == this->name_to_token.end()) {
|
||||
token_it = this->name_to_token.emplace(canonical_name, make_shared<Token>()).first;
|
||||
token_it = this->name_to_token.emplace(canonical_name, std::make_shared<Token>()).first;
|
||||
token_it->second->canonical_name = std::move(canonical_name);
|
||||
}
|
||||
token_it->second->slot_for_version(version) = token_id;
|
||||
@@ -255,8 +254,8 @@ void WordSelectTable::print(FILE* stream) const {
|
||||
phosg::fwrite_fmt(stream, "{:04X} ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = phosg::JSON(token->canonical_name).serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
phosg::fwrite_fmt(stream, "{}\n", serialized);
|
||||
phosg::fwrite_fmt(stream, "{}\n",
|
||||
phosg::JSON(token->canonical_name).serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,8 +272,8 @@ void WordSelectTable::print_index(FILE* stream, Version v) const {
|
||||
phosg::fwrite_fmt(stream, "{:04X} ", token->values_by_version[z]);
|
||||
}
|
||||
}
|
||||
string serialized = phosg::JSON(token->canonical_name).serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY);
|
||||
phosg::fwrite_fmt(stream, "{}\n", serialized);
|
||||
phosg::fwrite_fmt(stream, "{}\n",
|
||||
phosg::JSON(token->canonical_name).serialize(phosg::JSON::SerializeOption::ESCAPE_CONTROLS_ONLY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +286,7 @@ void WordSelectTable::validate(const WordSelectMessage& msg, Version version) co
|
||||
}
|
||||
const auto& token = index.at(msg.tokens[z]);
|
||||
if (!token) {
|
||||
throw runtime_error(std::format("token {:04X} does not exist in the index", msg.tokens[z]));
|
||||
throw std::runtime_error(std::format("token {:04X} does not exist in the index", msg.tokens[z]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,11 +302,11 @@ WordSelectMessage WordSelectTable::translate(
|
||||
} else {
|
||||
const auto& token = index.at(msg.tokens[z]);
|
||||
if (!token) {
|
||||
throw runtime_error(std::format("token {:04X} does not exist in the index", msg.tokens[z]));
|
||||
throw std::runtime_error(std::format("token {:04X} does not exist in the index", msg.tokens[z]));
|
||||
}
|
||||
ret.tokens[z] = token->slot_for_version(to_version);
|
||||
if (ret.tokens[z] == 0xFFFF) {
|
||||
throw runtime_error(std::format("token {:04X} has no translation", msg.tokens[z]));
|
||||
throw std::runtime_error(std::format("token {:04X} has no translation", msg.tokens[z]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user