switch to coroutine execution model
This commit is contained in:
+41
-39
@@ -2,6 +2,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Random.hh>
|
||||
@@ -292,7 +293,7 @@ phosg::JSON Account::json() const {
|
||||
}
|
||||
|
||||
string Account::str() const {
|
||||
std::string ret = phosg::string_printf("Account: %010" PRIu32 "/%08" PRIX32 "\n", this->account_id, this->account_id);
|
||||
std::string ret = std::format("Account: {:010}/{:08X}\n", this->account_id, this->account_id);
|
||||
|
||||
if (this->flags) {
|
||||
string flags_str = "";
|
||||
@@ -336,10 +337,10 @@ string Account::str() const {
|
||||
}
|
||||
if (flags_str.empty()) {
|
||||
flags_str = "none";
|
||||
} else if (phosg::ends_with(flags_str, ",")) {
|
||||
} else if (flags_str.ends_with(",")) {
|
||||
flags_str.pop_back();
|
||||
}
|
||||
ret += phosg::string_printf(" Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str());
|
||||
ret += std::format(" Flags: {:08X} ({})\n", this->flags, flags_str);
|
||||
}
|
||||
|
||||
if (this->user_flags) {
|
||||
@@ -349,56 +350,56 @@ string Account::str() const {
|
||||
}
|
||||
if (user_flags_str.empty()) {
|
||||
user_flags_str = "none";
|
||||
} else if (phosg::ends_with(user_flags_str, ",")) {
|
||||
} else if (user_flags_str.ends_with(",")) {
|
||||
user_flags_str.pop_back();
|
||||
}
|
||||
ret += phosg::string_printf(" User flags: %08" PRIX32 " (%s)\n", this->user_flags, user_flags_str.c_str());
|
||||
ret += std::format(" User flags: {:08X} ({})\n", this->user_flags, user_flags_str);
|
||||
}
|
||||
|
||||
if (this->ban_end_time) {
|
||||
string time_str = phosg::format_time(this->ban_end_time);
|
||||
ret += phosg::string_printf(" Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str());
|
||||
ret += std::format(" Banned until: {} ({})\n", this->ban_end_time, time_str);
|
||||
}
|
||||
if (this->ep3_current_meseta || this->ep3_total_meseta_earned) {
|
||||
ret += phosg::string_printf(" Episode 3 meseta: %" PRIu32 " (total earned: %" PRIu32 ")\n",
|
||||
ret += std::format(" Episode 3 meseta: {} (total earned: {})\n",
|
||||
this->ep3_current_meseta, this->ep3_total_meseta_earned);
|
||||
}
|
||||
if (!this->last_player_name.empty()) {
|
||||
ret += phosg::string_printf(" Last player name: \"%s\"\n", this->last_player_name.c_str());
|
||||
ret += std::format(" Last player name: \"{}\"\n", this->last_player_name);
|
||||
}
|
||||
if (!this->auto_reply_message.empty()) {
|
||||
ret += phosg::string_printf(" Auto reply message: \"%s\"\n", this->auto_reply_message.c_str());
|
||||
ret += std::format(" Auto reply message: \"{}\"\n", this->auto_reply_message);
|
||||
}
|
||||
if (this->bb_team_id) {
|
||||
ret += phosg::string_printf(" BB team ID: %08" PRIX32 "\n", this->bb_team_id);
|
||||
ret += std::format(" BB team ID: {:08X}\n", this->bb_team_id);
|
||||
}
|
||||
if (this->is_temporary) {
|
||||
ret += phosg::string_printf(" Is temporary license: true\n");
|
||||
ret += std::format(" Is temporary license: true\n");
|
||||
}
|
||||
|
||||
for (const auto& it : this->dc_nte_licenses) {
|
||||
ret += phosg::string_printf(" DC NTE license: serial_number=%s access_key=%s\n",
|
||||
it.second->serial_number.c_str(), it.second->access_key.c_str());
|
||||
ret += std::format(" DC NTE license: serial_number={} access_key={}\n",
|
||||
it.second->serial_number, it.second->access_key);
|
||||
}
|
||||
for (const auto& it : this->dc_licenses) {
|
||||
ret += phosg::string_printf(" DC license: serial_number=%" PRIX32 " access_key=%s\n",
|
||||
it.second->serial_number, it.second->access_key.c_str());
|
||||
ret += std::format(" DC license: serial_number={:X} access_key={}\n",
|
||||
it.second->serial_number, it.second->access_key);
|
||||
}
|
||||
for (const auto& it : this->pc_licenses) {
|
||||
ret += phosg::string_printf(" PC license: serial_number=%" PRIX32 " access_key=%s\n",
|
||||
it.second->serial_number, it.second->access_key.c_str());
|
||||
ret += std::format(" PC license: serial_number={:X} access_key={}\n",
|
||||
it.second->serial_number, it.second->access_key);
|
||||
}
|
||||
for (const auto& it : this->gc_licenses) {
|
||||
ret += phosg::string_printf(" GC license: serial_number=%010" PRIu32 " access_key=%s password=%s\n",
|
||||
it.second->serial_number, it.second->access_key.c_str(), it.second->password.c_str());
|
||||
ret += std::format(" GC license: serial_number={:010} access_key={} password={}\n",
|
||||
it.second->serial_number, it.second->access_key, it.second->password);
|
||||
}
|
||||
for (const auto& it : this->xb_licenses) {
|
||||
ret += phosg::string_printf(" XB license: gamertag=%s user_id=%016" PRIX64 " account_id=%016" PRIX64 "\n",
|
||||
it.second->gamertag.c_str(), it.second->user_id, it.second->account_id);
|
||||
ret += std::format(" XB license: gamertag={} user_id={:016X} account_id={:016X}\n",
|
||||
it.second->gamertag, it.second->user_id, it.second->account_id);
|
||||
}
|
||||
for (const auto& it : this->bb_licenses) {
|
||||
ret += phosg::string_printf(" BB license: username=%s password=%s\n",
|
||||
it.second->username.c_str(), it.second->password.c_str());
|
||||
ret += std::format(" BB license: username={} password={}\n",
|
||||
it.second->username, it.second->password);
|
||||
}
|
||||
|
||||
phosg::strip_trailing_whitespace(ret);
|
||||
@@ -409,13 +410,13 @@ void Account::save() const {
|
||||
if (!this->is_temporary) {
|
||||
auto json = this->json();
|
||||
string json_data = json.serialize(phosg::JSON::SerializeOption::FORMAT | phosg::JSON::SerializeOption::HEX_INTEGERS);
|
||||
string filename = phosg::string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||
phosg::save_file(filename, json_data);
|
||||
}
|
||||
}
|
||||
|
||||
void Account::delete_file() const {
|
||||
string filename = phosg::string_printf("system/licenses/%010" PRIu32 ".json", this->account_id);
|
||||
string filename = std::format("system/licenses/{:010}.json", this->account_id);
|
||||
remove(filename.c_str());
|
||||
}
|
||||
|
||||
@@ -440,24 +441,24 @@ uint64_t Login::proxy_session_id() const {
|
||||
}
|
||||
|
||||
string Login::str() const {
|
||||
string ret = phosg::string_printf("Account:%08" PRIX32, this->account->account_id);
|
||||
string ret = std::format("Account:{:08X}", this->account->account_id);
|
||||
if (this->account_was_created) {
|
||||
ret += " (new)";
|
||||
}
|
||||
if (this->dc_nte_license) {
|
||||
ret += phosg::string_printf(" via DC NTE serial number %s", this->dc_nte_license->serial_number.c_str());
|
||||
ret += std::format(" via DC NTE serial number {}", this->dc_nte_license->serial_number);
|
||||
} else if (this->dc_license) {
|
||||
ret += phosg::string_printf(" via DC serial number %08" PRIX32, this->dc_license->serial_number);
|
||||
ret += std::format(" via DC serial number {:08X}", this->dc_license->serial_number);
|
||||
} else if (this->pc_license) {
|
||||
ret += phosg::string_printf(" via PC serial number %08" PRIX32, this->pc_license->serial_number);
|
||||
ret += std::format(" via PC serial number {:08X}", this->pc_license->serial_number);
|
||||
} else if (this->gc_license) {
|
||||
ret += phosg::string_printf(" via GC serial number %010" PRIu32, this->gc_license->serial_number);
|
||||
ret += std::format(" via GC serial number {:010}", this->gc_license->serial_number);
|
||||
} else if (this->xb_license) {
|
||||
ret += phosg::string_printf(" via XB user ID %016" PRIX64, this->xb_license->user_id);
|
||||
ret += std::format(" via XB user ID {:016X}", this->xb_license->user_id);
|
||||
} else if (this->bb_license) {
|
||||
ret += phosg::string_printf(" via BB username %s", this->bb_license->username.c_str());
|
||||
ret += std::format(" via BB username {}", this->bb_license->username);
|
||||
} else {
|
||||
ret += phosg::string_printf(" artificially");
|
||||
ret += std::format(" artificially");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1043,16 +1044,17 @@ shared_ptr<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||
AccountIndex::AccountIndex(bool force_all_temporary)
|
||||
: force_all_temporary(force_all_temporary) {
|
||||
if (!this->force_all_temporary) {
|
||||
if (!phosg::isdir("system/licenses")) {
|
||||
mkdir("system/licenses", 0755);
|
||||
if (!std::filesystem::is_directory("system/licenses")) {
|
||||
std::filesystem::create_directories("system/licenses");
|
||||
} else {
|
||||
for (const auto& item : phosg::list_directory("system/licenses")) {
|
||||
if (phosg::ends_with(item, ".json")) {
|
||||
for (const auto& item : std::filesystem::directory_iterator("system/licenses")) {
|
||||
string filename = item.path().filename().string();
|
||||
if (filename.ends_with(".json")) {
|
||||
try {
|
||||
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + item));
|
||||
phosg::JSON json = phosg::JSON::parse(phosg::load_file("system/licenses/" + filename));
|
||||
this->add(make_shared<Account>(json));
|
||||
} catch (const exception& e) {
|
||||
phosg::log_error("Failed to index account %s", item.c_str());
|
||||
phosg::log_error_f("Failed to index account {}", filename);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
+44
-42
@@ -1,6 +1,7 @@
|
||||
#include "AddressTranslator.hh"
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <future>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
@@ -113,42 +114,43 @@ public:
|
||||
AddressTranslator(const string& directory)
|
||||
: log("[addr-trans] "),
|
||||
directory(directory) {
|
||||
while (phosg::ends_with(this->directory, "/")) {
|
||||
while (this->directory.ends_with("/")) {
|
||||
this->directory.pop_back();
|
||||
}
|
||||
for (const auto& filename : phosg::list_directory(this->directory)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(this->directory)) {
|
||||
string filename = item.path().filename().string();
|
||||
if (filename.size() < 4) {
|
||||
continue;
|
||||
}
|
||||
string name = filename.substr(0, filename.size() - 4);
|
||||
string path = directory + "/" + filename;
|
||||
|
||||
if (phosg::ends_with(filename, ".dol")) {
|
||||
if (filename.ends_with(".dol")) {
|
||||
ResourceDASM::DOLFile dol(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
dol.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->ppc_mems.emplace(mem);
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
} else if (phosg::ends_with(filename, ".xbe")) {
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".xbe")) {
|
||||
ResourceDASM::XBEFile xbe(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
xbe.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
} else if (phosg::ends_with(filename, ".exe")) {
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".exe")) {
|
||||
ResourceDASM::PEFile pe(path.c_str());
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
pe.load_into(mem);
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
} else if (phosg::ends_with(filename, ".bin")) {
|
||||
this->log.info_f("Loaded {}", name);
|
||||
} else if (filename.ends_with(".bin")) {
|
||||
string data = phosg::load_file(path);
|
||||
auto mem = make_shared<ResourceDASM::MemoryContext>();
|
||||
mem->allocate_at(0x8C010000, data.size());
|
||||
mem->memcpy(0x8C010000, data.data(), data.size());
|
||||
this->mems.emplace(name, mem);
|
||||
this->log.info("Loaded %s", name.c_str());
|
||||
this->log.info_f("Loaded {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,14 +204,14 @@ public:
|
||||
}
|
||||
}
|
||||
if (r2_low_found && r2_high_found) {
|
||||
fprintf(stderr, "(%s) r2 = %08" PRIX32 "\n", it.first.c_str(), r2);
|
||||
phosg::fwrite_fmt(stderr, "({}) r2 = {:08X}\n", it.first, r2);
|
||||
} else {
|
||||
fprintf(stderr, "(%s) r2 = __MISSING__\n", it.first.c_str());
|
||||
phosg::fwrite_fmt(stderr, "({}) r2 = __MISSING__\n", it.first);
|
||||
}
|
||||
if (r13_low_found && r13_high_found) {
|
||||
fprintf(stderr, "(%s) r13 = %08" PRIX32 "\n", it.first.c_str(), r13);
|
||||
phosg::fwrite_fmt(stderr, "({}) r13 = {:08X}\n", it.first, r13);
|
||||
} else {
|
||||
fprintf(stderr, "(%s) r13 = __MISSING__\n", it.first.c_str());
|
||||
phosg::fwrite_fmt(stderr, "({}) r13 = __MISSING__\n", it.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,16 +359,16 @@ public:
|
||||
}
|
||||
|
||||
for (const auto& [type, constructor_to_area_ranges] : table) {
|
||||
fprintf(stdout, "%04" PRIX32 " =>", type);
|
||||
phosg::fwrite_fmt(stdout, "{:04X} =>", type);
|
||||
for (const auto& [constructor, area_ranges] : constructor_to_area_ranges) {
|
||||
fprintf(stdout, " %08" PRIX32, constructor);
|
||||
phosg::fwrite_fmt(stdout, " {:08X}", constructor);
|
||||
bool is_first = true;
|
||||
for (const auto& [start, end] : area_ranges) {
|
||||
fputc(is_first ? ':' : ',', stdout);
|
||||
if (start == end) {
|
||||
fprintf(stdout, "%02zX", start);
|
||||
phosg::fwrite_fmt(stdout, "{:02X}", start);
|
||||
} else {
|
||||
fprintf(stdout, "%02zX-%02zX", start, end);
|
||||
phosg::fwrite_fmt(stdout, "{:02X}-{:02X}", start, end);
|
||||
}
|
||||
is_first = false;
|
||||
}
|
||||
@@ -403,17 +405,17 @@ public:
|
||||
if (!cell_data.empty()) {
|
||||
cell_data.push_back(' ');
|
||||
}
|
||||
cell_data += phosg::string_printf("%08" PRIX32, constructor);
|
||||
cell_data += std::format("{:08X}", constructor);
|
||||
if (print_area_masks) {
|
||||
cell_data += phosg::string_printf(":%016" PRIX64, this->area_mask_for_ranges(area_ranges));
|
||||
cell_data += std::format(":{:016X}", this->area_mask_for_ranges(area_ranges));
|
||||
} else {
|
||||
bool is_first = true;
|
||||
for (const auto& [start, end] : area_ranges) {
|
||||
cell_data.push_back(is_first ? ':' : ',');
|
||||
if (start == end) {
|
||||
cell_data += phosg::string_printf("%02zX", start);
|
||||
cell_data += std::format("{:02X}", start);
|
||||
} else {
|
||||
cell_data += phosg::string_printf("%02zX-%02zX", start, end);
|
||||
cell_data += std::format("{:02X}-{:02X}", start, end);
|
||||
}
|
||||
is_first = false;
|
||||
}
|
||||
@@ -438,7 +440,7 @@ public:
|
||||
header_line += " NAME";
|
||||
|
||||
for (const auto& [type, formatted_cells] : formatted_cells_for_type) {
|
||||
string line = phosg::string_printf("%04" PRIX32 " =>", type);
|
||||
string line = std::format("{:04X} =>", type);
|
||||
for (const auto& spec : specs) {
|
||||
size_t width = version_widths.at(spec.src_name);
|
||||
try {
|
||||
@@ -465,7 +467,7 @@ public:
|
||||
|
||||
for (auto& line : formatted_lines) {
|
||||
phosg::strip_trailing_whitespace(line);
|
||||
fprintf(stdout, "%s\n", line.c_str());
|
||||
phosg::fwrite_fmt(stdout, "{}\n", line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +502,7 @@ public:
|
||||
size_t src_offset = src_addr - src_section.first;
|
||||
size_t src_bytes_available_before = src_offset;
|
||||
size_t src_bytes_available_after = src_section.second - src_offset - 4;
|
||||
this->log.info("(find_match/%s) Source offset = %08zX with %zX/%zX bytes available before/after",
|
||||
this->log.info_f("(find_match/{}) Source offset = {:08X} with {:X}/{:X} bytes available before/after",
|
||||
method_token, src_offset, src_bytes_available_before, src_bytes_available_after);
|
||||
|
||||
size_t match_bytes_before = 0;
|
||||
@@ -570,7 +572,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
this->log.info("(find_match/%s) For match length %zX, %zu matches found", method_token, match_length, num_matches);
|
||||
this->log.info_f("(find_match/{}) For match length {:X}, {} matches found", method_token, match_length, num_matches);
|
||||
if (num_matches == 1) {
|
||||
return last_match_address;
|
||||
} else if (num_matches == 0) {
|
||||
@@ -641,7 +643,7 @@ public:
|
||||
map<string, uint32_t> results;
|
||||
for (const auto& it : this->mems) {
|
||||
if (it.second == this->src_mem) {
|
||||
log.info("(%s) %08" PRIX32 " (from source)", it.first.c_str(), src_addr);
|
||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||
results.emplace(it.first, src_addr);
|
||||
|
||||
} else {
|
||||
@@ -673,24 +675,24 @@ public:
|
||||
const char* method_name = this->name_for_expand_method(methods[z]);
|
||||
try {
|
||||
uint32_t ret = futures[z].get();
|
||||
log.info("(%s) (%s) %08" PRIX32, it.first.c_str(), method_name, ret);
|
||||
log.info_f("({}) ({}) {:08X}", it.first, method_name, ret);
|
||||
match_addrs.emplace(ret);
|
||||
} catch (const exception& e) {
|
||||
log.error("(%s) (%s) failed: %s", it.first.c_str(), method_name, e.what());
|
||||
log.error_f("({}) ({}) failed: {}", it.first, method_name, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (match_addrs.empty()) {
|
||||
log.error("(%s) no match found", it.first.c_str());
|
||||
log.error_f("({}) no match found", it.first);
|
||||
} else if (match_addrs.size() > 1) {
|
||||
log.error("(%s) different matches found by different methods", it.first.c_str());
|
||||
log.error_f("({}) different matches found by different methods", it.first);
|
||||
} else {
|
||||
results.emplace(it.first, *match_addrs.begin());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : results) {
|
||||
fprintf(stdout, "%s => %08" PRIX32 "\n", it.first.c_str(), it.second);
|
||||
phosg::fwrite_fmt(stdout, "{} => {:08X}\n", it.first, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +751,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
this->log.info("... For match length %zX, %zu matches found", match_length, num_matches);
|
||||
this->log.info_f("... For match length {:X}, {} matches found", match_length, num_matches);
|
||||
if (num_matches == 1) {
|
||||
return last_match_address;
|
||||
} else if (num_matches == 0) {
|
||||
@@ -778,27 +780,27 @@ public:
|
||||
map<string, uint32_t> results;
|
||||
for (const auto& it : this->mems) {
|
||||
if (it.second == this->src_mem) {
|
||||
log.info("(%s) %08" PRIX32 " (from source)", it.first.c_str(), src_addr);
|
||||
log.info_f("({}) {:08X} (from source)", it.first, src_addr);
|
||||
results.emplace(it.first, src_addr);
|
||||
|
||||
} else {
|
||||
uint32_t ret = 0;
|
||||
try {
|
||||
ret = this->find_be_to_le_data_match(it.second, src_addr, src_size);
|
||||
log.info("(%s) %08" PRIX32, it.first.c_str(), ret);
|
||||
log.info_f("({}) {:08X}", it.first, ret);
|
||||
} catch (const exception& e) {
|
||||
log.error("(%s) failed: %s", it.first.c_str(), e.what());
|
||||
log.error_f("({}) failed: {}", it.first, e.what());
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
log.error("(%s) no match found", it.first.c_str());
|
||||
log.error_f("({}) no match found", it.first);
|
||||
} else {
|
||||
results.emplace(it.first, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& it : results) {
|
||||
fprintf(stdout, "%s => %08" PRIX32 "\n", it.first.c_str(), it.second);
|
||||
phosg::fwrite_fmt(stdout, "{} => {:08X}\n", it.first, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +810,7 @@ public:
|
||||
uint32_t last_addr = sec_addr + sec_size - data.size();
|
||||
for (uint32_t addr = sec_addr; addr < last_addr; addr++) {
|
||||
if (!mem->memcmp(addr, data.data(), data.size())) {
|
||||
fprintf(stderr, "%s => %08" PRIX32 "\n", name.c_str(), addr);
|
||||
phosg::fwrite_fmt(stderr, "{} => {:08X}\n", name, addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -849,9 +851,9 @@ public:
|
||||
void run_shell() {
|
||||
while (!feof(stdin)) {
|
||||
if (!this->src_filename.empty()) {
|
||||
fprintf(stdout, "addr-trans:%s/%s> ", this->directory.c_str(), this->src_filename.c_str());
|
||||
phosg::fwrite_fmt(stdout, "addr-trans:{}/{}> ", this->directory, this->src_filename);
|
||||
} else {
|
||||
fprintf(stdout, "addr-trans:%s> ", this->directory.c_str());
|
||||
phosg::fwrite_fmt(stdout, "addr-trans:{}> ", this->directory);
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
@@ -859,7 +861,7 @@ public:
|
||||
try {
|
||||
this->handle_command(command);
|
||||
} catch (const exception& e) {
|
||||
this->log.error("Failed: %s", e.what());
|
||||
this->log.error_f("Failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
#include "AsyncHTTPServer.hh"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "AsyncUtils.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "Revision.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const unordered_map<int, const char*> explanation_for_response_code{
|
||||
{100, "Continue"},
|
||||
{101, "Switching Protocols"},
|
||||
{102, "Processing"},
|
||||
{200, "OK"},
|
||||
{201, "Created"},
|
||||
{202, "Accepted"},
|
||||
{203, "Non-Authoritative Information"},
|
||||
{204, "No Content"},
|
||||
{205, "Reset Content"},
|
||||
{206, "Partial Content"},
|
||||
{207, "Multi-Status"},
|
||||
{208, "Already Reported"},
|
||||
{226, "IM Used"},
|
||||
{300, "Multiple Choices"},
|
||||
{301, "Moved Permanently"},
|
||||
{302, "Found"},
|
||||
{303, "See Other"},
|
||||
{304, "Not Modified"},
|
||||
{305, "Use Proxy"},
|
||||
{307, "Temporary Redirect"},
|
||||
{308, "Permanent Redirect"},
|
||||
{400, "Bad Request"},
|
||||
{401, "Unathorized"},
|
||||
{402, "Payment Required"},
|
||||
{403, "Forbidden"},
|
||||
{404, "Not Found"},
|
||||
{405, "Method Not Allowed"},
|
||||
{406, "Not Acceptable"},
|
||||
{407, "Proxy Authentication Required"},
|
||||
{408, "Request Timeout"},
|
||||
{409, "Conflict"},
|
||||
{410, "Gone"},
|
||||
{411, "Length Required"},
|
||||
{412, "Precondition Failed"},
|
||||
{413, "Request Entity Too Large"},
|
||||
{414, "Request-URI Too Long"},
|
||||
{415, "Unsupported Media Type"},
|
||||
{416, "Requested Range Not Satisfiable"},
|
||||
{417, "Expectation Failed"},
|
||||
{418, "I\'m a Teapot"},
|
||||
{420, "Enhance Your Calm"},
|
||||
{422, "Unprocessable Entity"},
|
||||
{423, "Locked"},
|
||||
{424, "Failed Dependency"},
|
||||
{426, "Upgrade Required"},
|
||||
{428, "Precondition Required"},
|
||||
{429, "Too Many Requests"},
|
||||
{431, "Request Header Fields Too Large"},
|
||||
{444, "No Response"},
|
||||
{449, "Retry With"},
|
||||
{451, "Unavailable For Legal Reasons"},
|
||||
{500, "Internal Server Error"},
|
||||
{501, "Not Implemented"},
|
||||
{502, "Bad Gateway"},
|
||||
{503, "Service Unavailable"},
|
||||
{504, "Gateway Timeout"},
|
||||
{505, "HTTP Version Not Supported"},
|
||||
{506, "Variant Also Negotiates"},
|
||||
{507, "Insufficient Storage"},
|
||||
{508, "Loop Detected"},
|
||||
{509, "Bandwidth Limit Exceeded"},
|
||||
{510, "Not Extended"},
|
||||
{511, "Network Authentication Required"},
|
||||
{598, "Network Read Timeout Error"},
|
||||
{599, "Network Connect Timeout Error"},
|
||||
};
|
||||
|
||||
HTTPError::HTTPError(int code, const std::string& what)
|
||||
: std::runtime_error(what), code(code) {}
|
||||
|
||||
const std::string* HTTPRequest::get_header(const std::string& name) const {
|
||||
auto its = this->headers.equal_range(name);
|
||||
if (its.first == its.second) {
|
||||
return nullptr;
|
||||
}
|
||||
const string* ret = &its.first->second;
|
||||
its.first++;
|
||||
if (its.first != its.second) {
|
||||
throw std::out_of_range("Header appears multiple times: " + name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::string* HTTPRequest::get_query_param(const std::string& name) const {
|
||||
auto its = this->query_params.equal_range(name);
|
||||
if (its.first == its.second) {
|
||||
return nullptr;
|
||||
}
|
||||
const string* ret = &its.first->second;
|
||||
its.first++;
|
||||
if (its.first != its.second) {
|
||||
throw std::out_of_range("Query parameter appears multiple times: " + name);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void url_decode_inplace(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)) {
|
||||
s[write_offset] =
|
||||
static_cast<char>(phosg::value_for_hex_char(s[read_offset + 1]) << 4) |
|
||||
static_cast<char>(phosg::value_for_hex_char(s[read_offset + 2]));
|
||||
read_offset += 3;
|
||||
} else if (s[write_offset] == '+') {
|
||||
s[write_offset] = ' ';
|
||||
read_offset++;
|
||||
} else {
|
||||
s[write_offset] = s[read_offset];
|
||||
read_offset++;
|
||||
}
|
||||
}
|
||||
s.resize(write_offset);
|
||||
}
|
||||
|
||||
HTTPClient::HTTPClient(asio::ip::tcp::socket&& sock) : r(std::move(sock)) {}
|
||||
|
||||
asio::awaitable<HTTPRequest> HTTPClient::recv_http_request(size_t max_line_size, size_t max_body_size) {
|
||||
HTTPRequest req;
|
||||
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");
|
||||
}
|
||||
const auto& method_token = line_tokens[0];
|
||||
if (method_token == "GET") {
|
||||
req.method = HTTPRequest::Method::GET;
|
||||
} else if (method_token == "POST") {
|
||||
req.method = HTTPRequest::Method::POST;
|
||||
} else if (method_token == "DELETE") {
|
||||
req.method = HTTPRequest::Method::DELETE;
|
||||
} else if (method_token == "HEAD") {
|
||||
req.method = HTTPRequest::Method::HEAD;
|
||||
} else if (method_token == "PATCH") {
|
||||
req.method = HTTPRequest::Method::PATCH;
|
||||
} else if (method_token == "PUT") {
|
||||
req.method = HTTPRequest::Method::PUT;
|
||||
} else if (method_token == "UPDATE") {
|
||||
req.method = HTTPRequest::Method::UPDATE;
|
||||
} else if (method_token == "OPTIONS") {
|
||||
req.method = HTTPRequest::Method::OPTIONS;
|
||||
} else if (method_token == "CONNECT") {
|
||||
req.method = HTTPRequest::Method::CONNECT;
|
||||
} else if (method_token == "TRACE") {
|
||||
req.method = HTTPRequest::Method::TRACE;
|
||||
} else {
|
||||
throw HTTPError(400, "unknown request method");
|
||||
}
|
||||
|
||||
req.http_version = std::move(line_tokens[2]);
|
||||
|
||||
size_t fragment_start_offset = line_tokens[1].find('#');
|
||||
if (fragment_start_offset != 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) {
|
||||
query = line_tokens[1].substr(query_start_offset + 1);
|
||||
line_tokens[1].resize(query_start_offset);
|
||||
}
|
||||
|
||||
req.path = std::move(line_tokens[1]);
|
||||
if (req.path.empty()) {
|
||||
throw std::runtime_error("request path is missing");
|
||||
}
|
||||
|
||||
auto query_tokens = phosg::split(query, '&');
|
||||
for (auto& token : query_tokens) {
|
||||
size_t equals_pos = token.find('=');
|
||||
if (equals_pos == 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);
|
||||
url_decode_inplace(key);
|
||||
url_decode_inplace(value);
|
||||
req.query_params.emplace(std::move(key), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
auto prev_header_it = req.headers.end();
|
||||
for (;;) {
|
||||
std::string line = co_await this->r.read_line("\r\n", max_line_size);
|
||||
if (line.empty()) {
|
||||
break;
|
||||
}
|
||||
if (line[0] == ' ' || line[0] == '\t') {
|
||||
if (prev_header_it == req.headers.end()) {
|
||||
throw std::runtime_error("received header continuation line before any header");
|
||||
} else {
|
||||
phosg::strip_whitespace(line);
|
||||
prev_header_it->second.append(1, ' ');
|
||||
prev_header_it->second += line;
|
||||
}
|
||||
} else {
|
||||
size_t colon_pos = line.find(':');
|
||||
if (colon_pos == string::npos) {
|
||||
throw runtime_error("malformed header line");
|
||||
}
|
||||
string key = line.substr(0, colon_pos);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
auto transfer_encoding_header = req.get_header("transfer-encoding");
|
||||
if (transfer_encoding_header && phosg::tolower(*transfer_encoding_header) == "chunked") {
|
||||
deque<string> chunks;
|
||||
size_t total_data_bytes = 0;
|
||||
for (;;) {
|
||||
auto line = co_await this->r.read_line("\r\n", 0x20);
|
||||
size_t parse_offset = 0;
|
||||
size_t chunk_size = stoull(line, &parse_offset, 16);
|
||||
if (parse_offset != line.size()) {
|
||||
throw HTTPError(400, "invalid chunk header during chunked encoding");
|
||||
}
|
||||
if (chunk_size == 0) {
|
||||
break;
|
||||
}
|
||||
total_data_bytes += chunk_size;
|
||||
if (total_data_bytes > max_body_size) {
|
||||
throw HTTPError(400, "request data size too large");
|
||||
}
|
||||
chunks.emplace_back(co_await this->r.read_data(chunk_size));
|
||||
auto after_chunk_data = co_await this->r.read_line("\r\n", 0x20);
|
||||
if (!after_chunk_data.empty()) {
|
||||
throw HTTPError(400, "incorrect trailing sequence after chunk data");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto content_length_header = req.get_header("content-length");
|
||||
size_t content_length = content_length_header ? stoull(*content_length_header) : 0;
|
||||
if (content_length > max_body_size) {
|
||||
throw HTTPError(400, "request data size too large");
|
||||
} else if (content_length > 0) {
|
||||
req.data = co_await this->r.read_data(content_length);
|
||||
}
|
||||
}
|
||||
|
||||
co_return req;
|
||||
}
|
||||
|
||||
asio::awaitable<void> HTTPClient::send_http_response(const HTTPResponse& resp) {
|
||||
AsyncWriteCollector w;
|
||||
w.add(std::format("{} {} {}\r\n",
|
||||
resp.http_version, resp.response_code, explanation_for_response_code.at(resp.response_code)));
|
||||
for (const auto& it : resp.headers) {
|
||||
w.add(it.first + ": " + it.second + "\r\n");
|
||||
}
|
||||
if (!resp.data.empty()) {
|
||||
w.add(std::format("Content-Length: {}\r\n", resp.data.size()));
|
||||
}
|
||||
w.add("\r\n");
|
||||
if (!resp.data.empty()) {
|
||||
w.add_reference(resp.data.data(), resp.data.size());
|
||||
}
|
||||
co_await w.write(this->r.get_socket());
|
||||
}
|
||||
|
||||
asio::awaitable<WebSocketMessage> HTTPClient::recv_websocket_message(size_t max_data_size) {
|
||||
WebSocketMessage prev_msg;
|
||||
bool prev_msg_present = false;
|
||||
|
||||
while (this->r.get_socket().is_open()) {
|
||||
WebSocketMessage msg;
|
||||
|
||||
// We need at most 10 bytes to determine if there's a valid frame, or as
|
||||
// little as 2
|
||||
co_await this->r.read_data_into(msg.header, 2);
|
||||
|
||||
// Get the payload size
|
||||
bool has_mask = msg.header[1] & 0x80;
|
||||
size_t payload_size = msg.header[1] & 0x7F;
|
||||
if (payload_size == 0x7F) {
|
||||
phosg::be_uint64_t wire_size;
|
||||
co_await this->r.read_data_into(&wire_size, sizeof(wire_size));
|
||||
payload_size = wire_size;
|
||||
} else if (payload_size == 0x7E) {
|
||||
phosg::be_uint16_t wire_size;
|
||||
co_await this->r.read_data_into(&wire_size, sizeof(wire_size));
|
||||
payload_size = wire_size;
|
||||
}
|
||||
|
||||
if (payload_size > max_data_size) {
|
||||
throw runtime_error("Incoming WebSocket message exceeds size limit");
|
||||
}
|
||||
|
||||
// Read the masking key if present
|
||||
if (has_mask) {
|
||||
co_await this->r.read_data_into(msg.mask_key, sizeof(msg.mask_key));
|
||||
}
|
||||
|
||||
// Read and unmask message data
|
||||
msg.data = co_await this->r.read_data(payload_size);
|
||||
if (has_mask) {
|
||||
for (size_t x = 0; x < msg.data.size(); x++) {
|
||||
msg.data[x] ^= msg.mask_key[x & 3];
|
||||
}
|
||||
}
|
||||
|
||||
this->last_communication_time = phosg::now();
|
||||
|
||||
// If the current message is a control message, respond appropriately
|
||||
// (these can be sent in the middle of fragmented messages)
|
||||
uint8_t opcode = msg.header[0] & 0x0F;
|
||||
if (opcode & 0x08) {
|
||||
if (opcode == 0x0A) {
|
||||
// Ping response; ignore it
|
||||
|
||||
} else if (opcode == 0x08) {
|
||||
// Close message
|
||||
co_await this->send_websocket_message(msg.data, msg.opcode);
|
||||
this->r.get_socket().close();
|
||||
|
||||
} else if (opcode == 0x09) {
|
||||
// Ping message
|
||||
co_await this->send_websocket_message(msg.data, 0x0A);
|
||||
|
||||
} else {
|
||||
// Unknown control message type
|
||||
this->r.get_socket().close();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there's an existing fragment, the current message's opcode should be
|
||||
// zero; if there's no pending message, it must not be zero
|
||||
if (prev_msg_present == (opcode != 0)) {
|
||||
this->r.get_socket().close();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the message opcode, if present, and append the frame data
|
||||
if (!prev_msg_present) {
|
||||
prev_msg = std::move(msg);
|
||||
} else {
|
||||
prev_msg.header[0] = msg.header[0];
|
||||
prev_msg.header[1] = msg.header[1];
|
||||
if (opcode) {
|
||||
prev_msg.opcode = msg.opcode;
|
||||
}
|
||||
if (has_mask) {
|
||||
prev_msg.mask_key[0] = msg.mask_key[0];
|
||||
prev_msg.mask_key[1] = msg.mask_key[1];
|
||||
prev_msg.mask_key[2] = msg.mask_key[2];
|
||||
prev_msg.mask_key[3] = msg.mask_key[3];
|
||||
}
|
||||
prev_msg.data += msg.data;
|
||||
}
|
||||
|
||||
// If the FIN bit is set, then the frame is complete - append the payload
|
||||
// to any pending payloads and call the message handler. If the FIN bit
|
||||
// isn't set, we need to receive at least one continuation frame to
|
||||
// complete the message.
|
||||
if (prev_msg.header[0] & 0x80) {
|
||||
co_return prev_msg;
|
||||
}
|
||||
}
|
||||
|
||||
throw logic_error("failed to receive websocket message");
|
||||
}
|
||||
|
||||
asio::awaitable<void> HTTPClient::send_websocket_message(const void* data, size_t size, uint8_t opcode) {
|
||||
phosg::StringWriter w;
|
||||
w.put_u8(0x80 | (opcode & 0x0F));
|
||||
if (size > 0xFFFF) {
|
||||
w.put_u8(0x7F);
|
||||
w.put_u64b(size);
|
||||
} else if (size > 0x7D) {
|
||||
w.put_u8(0x7E);
|
||||
w.put_u16b(size);
|
||||
} else {
|
||||
w.put_u8(size);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
asio::awaitable<void> HTTPClient::send_websocket_message(const std::string& data, uint8_t opcode) {
|
||||
return this->send_websocket_message(data.data(), data.size(), opcode);
|
||||
}
|
||||
|
||||
const HTTPServerLimits DEFAULT_HTTP_LIMITS;
|
||||
@@ -0,0 +1,228 @@
|
||||
#pragma once
|
||||
|
||||
#include "WindowsPlatform.hh"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
#include <string>
|
||||
|
||||
#include "AsyncUtils.hh"
|
||||
#include "Server.hh"
|
||||
|
||||
struct HTTPRequest {
|
||||
enum class Method {
|
||||
GET = 0,
|
||||
POST,
|
||||
DELETE,
|
||||
HEAD,
|
||||
PATCH,
|
||||
PUT,
|
||||
UPDATE,
|
||||
OPTIONS,
|
||||
CONNECT,
|
||||
TRACE,
|
||||
};
|
||||
std::string http_version;
|
||||
Method method;
|
||||
std::string path;
|
||||
std::string fragment;
|
||||
std::unordered_multimap<std::string, std::string> headers; // Header names converted to all lowercase
|
||||
std::unordered_multimap<std::string, std::string> query_params;
|
||||
std::string data;
|
||||
|
||||
// Header name should be entirely lowercase for this function. Returns
|
||||
// nullptr if the header doesn't exist; throws http_error(400) if multiple
|
||||
// instances of it exist.
|
||||
const std::string* get_header(const std::string& name) const;
|
||||
|
||||
const std::string* get_query_param(const std::string& name) const;
|
||||
};
|
||||
|
||||
struct HTTPResponse {
|
||||
std::string http_version;
|
||||
int response_code = 200;
|
||||
// Content-Length should NOT be specified in headers; it is automatically
|
||||
// added in async_write() if data is not blank.
|
||||
std::unordered_multimap<std::string, std::string> headers;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
struct WebSocketMessage {
|
||||
uint8_t header[2] = {0, 0};
|
||||
uint8_t opcode = 0x01;
|
||||
uint8_t mask_key[4] = {0, 0, 0, 0};
|
||||
std::string data;
|
||||
};
|
||||
|
||||
class HTTPError : public std::runtime_error {
|
||||
public:
|
||||
HTTPError(int code, const std::string& what);
|
||||
int code;
|
||||
};
|
||||
|
||||
struct HTTPClient {
|
||||
AsyncSocketReader r;
|
||||
uint64_t last_communication_time = 0;
|
||||
bool is_websocket = false;
|
||||
|
||||
HTTPClient(asio::ip::tcp::socket&& sock);
|
||||
|
||||
asio::awaitable<HTTPRequest> recv_http_request(size_t max_line_size, size_t max_body_size);
|
||||
asio::awaitable<void> send_http_response(const HTTPResponse& resp);
|
||||
|
||||
asio::awaitable<WebSocketMessage> recv_websocket_message(size_t max_data_size);
|
||||
asio::awaitable<void> send_websocket_message(const void* data, size_t size, uint8_t opcode = 0x01);
|
||||
asio::awaitable<void> send_websocket_message(const std::string& data, uint8_t opcode = 0x01);
|
||||
};
|
||||
|
||||
struct HTTPServerLimits {
|
||||
size_t max_http_request_line_size = 0x1000; // 4KB
|
||||
size_t max_http_data_size = 0x200000; // 2MB
|
||||
size_t max_http_keepalive_idle_usecs = 300 * 1000 * 1000; // 5 minutes (0 = no limit)
|
||||
size_t max_websocket_message_size = 0x200000; // 2MB
|
||||
size_t max_websocket_idle_usecs = 300 * 1000 * 1000; // 5 minutes (0 = no limit)
|
||||
};
|
||||
|
||||
extern const HTTPServerLimits DEFAULT_HTTP_LIMITS;
|
||||
|
||||
template <typename ClientT = HTTPClient>
|
||||
class AsyncHTTPServer : public Server<ClientT, ServerSocket> {
|
||||
public:
|
||||
explicit AsyncHTTPServer(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
const std::string& log_prefix = "[AsyncHTTPServer] ",
|
||||
const HTTPServerLimits& limits = DEFAULT_HTTP_LIMITS)
|
||||
: Server<ClientT, ServerSocket>(io_context, log_prefix), limits(limits) {}
|
||||
AsyncHTTPServer(const AsyncHTTPServer&) = delete;
|
||||
AsyncHTTPServer(AsyncHTTPServer&&) = delete;
|
||||
AsyncHTTPServer& operator=(const AsyncHTTPServer&) = delete;
|
||||
AsyncHTTPServer& operator=(AsyncHTTPServer&&) = delete;
|
||||
virtual ~AsyncHTTPServer() = default;
|
||||
|
||||
void listen(const std::string& addr, int port) {
|
||||
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 = std::make_shared<ServerSocket>();
|
||||
sock->name = std::format("http:{}:{}", addr, port);
|
||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||
this->add_socket(std::move(sock));
|
||||
}
|
||||
|
||||
protected:
|
||||
HTTPServerLimits limits;
|
||||
|
||||
// Attempts to switch the client to WebSockets. Returns true if this is done
|
||||
// successfully (and the caller should then receive/send WebSocket messages),
|
||||
// or false if this failed (and the caller should send an HTTP response).
|
||||
asio::awaitable<bool> enable_websockets(std::shared_ptr<ClientT> c, const HTTPRequest& req) {
|
||||
if (req.method != HTTPRequest::Method::GET) {
|
||||
co_return false;
|
||||
}
|
||||
|
||||
auto connection_header = req.get_header("connection");
|
||||
if (!connection_header || phosg::tolower(*connection_header) != "upgrade") {
|
||||
co_return false;
|
||||
}
|
||||
auto upgrade_header = req.get_header("upgrade");
|
||||
if (!upgrade_header || phosg::tolower(*upgrade_header) != "websocket") {
|
||||
co_return false;
|
||||
}
|
||||
auto sec_websocket_key_header = req.get_header("sec-websocket-key");
|
||||
if (!sec_websocket_key_header) {
|
||||
co_return false;
|
||||
}
|
||||
|
||||
std::string sec_websocket_accept_data = *sec_websocket_key_header + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
std::string sec_websocket_accept = phosg::base64_encode(phosg::SHA1(sec_websocket_accept_data).bin());
|
||||
|
||||
HTTPResponse resp;
|
||||
resp.http_version = req.http_version;
|
||||
resp.response_code = 101;
|
||||
resp.headers.emplace("Upgrade", "websocket");
|
||||
resp.headers.emplace("Connection", "upgrade");
|
||||
resp.headers.emplace("Sec-WebSocket-Accept", std::move(sec_websocket_accept));
|
||||
co_await c->send_http_response(resp);
|
||||
|
||||
c->is_websocket = true;
|
||||
co_return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual std::shared_ptr<ClientT> create_client(
|
||||
std::shared_ptr<ServerSocket>, asio::ip::tcp::socket&& client_sock) {
|
||||
return std::make_shared<HTTPClient>(std::move(client_sock));
|
||||
}
|
||||
|
||||
// handle_request must do one of the following three things:
|
||||
// 1. Return an HTTP response.
|
||||
// 2. Call enable_websockets, and if it returns true, return nullptr. After
|
||||
// this point, handle_request will not be called again for this client;
|
||||
// handle_websocket_message will be called instead when any WebSocket
|
||||
// messages are received. If enable_websockets returns false,
|
||||
// handle_request must still return an HTTP response.
|
||||
// 3. Throw an exception. In this case, the client receives an HTTP 500
|
||||
// response.
|
||||
virtual asio::awaitable<std::unique_ptr<HTTPResponse>> handle_request(std::shared_ptr<ClientT> c, HTTPRequest&& req) = 0;
|
||||
virtual asio::awaitable<void> handle_websocket_message(std::shared_ptr<ClientT>, WebSocketMessage&&) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
virtual asio::awaitable<void> handle_client(std::shared_ptr<ClientT> c) {
|
||||
asio::steady_timer idle_timer(*this->io_context);
|
||||
while (c->r.get_socket().is_open()) {
|
||||
if (c->is_websocket) {
|
||||
WebSocketMessage msg = co_await c->recv_websocket_message(this->limits.max_websocket_message_size);
|
||||
idle_timer.cancel();
|
||||
try {
|
||||
co_await this->handle_websocket_message(c, std::move(msg));
|
||||
} catch (const std::exception& e) {
|
||||
c->r.close();
|
||||
}
|
||||
|
||||
} else {
|
||||
HTTPRequest req = co_await c->recv_http_request(
|
||||
this->limits.max_http_request_line_size, this->limits.max_http_data_size);
|
||||
idle_timer.cancel();
|
||||
std::unique_ptr<HTTPResponse> resp;
|
||||
try {
|
||||
resp = co_await this->handle_request(c, std::move(req));
|
||||
} catch (const std::exception& e) {
|
||||
resp = std::make_unique<HTTPResponse>();
|
||||
resp->http_version = req.http_version;
|
||||
resp->response_code = 500;
|
||||
resp->headers.emplace("Content-Type", "text/plain");
|
||||
resp->data = "Internal server error:\n";
|
||||
resp->data += e.what();
|
||||
}
|
||||
if (resp) {
|
||||
co_await c->send_http_response(*resp);
|
||||
}
|
||||
auto* conn_header = req.get_header("connection");
|
||||
if (!conn_header || (*conn_header != "keep-alive")) {
|
||||
c->r.close();
|
||||
}
|
||||
}
|
||||
|
||||
size_t idle_usecs_limit = c->is_websocket
|
||||
? this->limits.max_websocket_idle_usecs
|
||||
: this->limits.max_http_keepalive_idle_usecs;
|
||||
if (idle_usecs_limit && c->r.get_socket().is_open()) {
|
||||
idle_timer.expires_after(std::chrono::microseconds(idle_usecs_limit));
|
||||
idle_timer.async_wait([c](std::error_code ec) {
|
||||
if (!ec) {
|
||||
c->r.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
idle_timer.cancel();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
#include "AsyncUtils.hh"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
AsyncEvent::AsyncEvent(asio::any_io_executor ex)
|
||||
: executor(ex), is_set(false) {}
|
||||
|
||||
void AsyncEvent::set() {
|
||||
lock_guard g(this->lock);
|
||||
this->is_set = true;
|
||||
for (auto& waiter : this->waiters) {
|
||||
asio::post(this->executor,
|
||||
[handler = std::move(waiter)]() mutable {
|
||||
(*handler)();
|
||||
});
|
||||
}
|
||||
this->waiters.clear();
|
||||
}
|
||||
|
||||
void AsyncEvent::clear() {
|
||||
lock_guard g(this->lock);
|
||||
this->is_set = false;
|
||||
}
|
||||
|
||||
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);
|
||||
if (this->is_set) {
|
||||
handler();
|
||||
} else {
|
||||
this->waiters.emplace_back(make_unique<asio::detail::awaitable_handler<asio::any_io_executor>>(std::move(handler)));
|
||||
}
|
||||
},
|
||||
token);
|
||||
}
|
||||
|
||||
AsyncSocketReader::AsyncSocketReader(asio::ip::tcp::socket&& sock)
|
||||
: sock(std::move(sock)) {}
|
||||
|
||||
asio::awaitable<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");
|
||||
}
|
||||
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))) {
|
||||
size_t pre_size = this->pending_data.size();
|
||||
this->pending_data.resize(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);
|
||||
this->pending_data.resize(pre_size + bytes_read);
|
||||
delimiter_pos = this->pending_data.find(
|
||||
delimiter,
|
||||
(delimiter_backup_bytes > pre_size) ? 0 : (pre_size - delimiter_backup_bytes));
|
||||
}
|
||||
|
||||
if (delimiter_pos == string::npos) {
|
||||
throw 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);
|
||||
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;
|
||||
if (this->pending_data.size() == size) {
|
||||
this->pending_data.swap(ret);
|
||||
} else if (this->pending_data.size() > size) {
|
||||
ret = this->pending_data.substr(0, size);
|
||||
this->pending_data = this->pending_data.substr(size);
|
||||
} else {
|
||||
size_t bytes_to_read = size - this->pending_data.size();
|
||||
this->pending_data.swap(ret);
|
||||
ret.resize(size);
|
||||
co_await asio::async_read(this->sock, asio::buffer(ret.data() + size - bytes_to_read, bytes_to_read), asio::use_awaitable);
|
||||
}
|
||||
co_return ret;
|
||||
}
|
||||
|
||||
asio::awaitable<void> AsyncSocketReader::read_data_into(void* data, size_t size) {
|
||||
if (this->pending_data.size() == size) {
|
||||
memcpy(data, this->pending_data.data(), size);
|
||||
this->pending_data.clear();
|
||||
} else if (this->pending_data.size() > size) {
|
||||
memcpy(data, this->pending_data.data(), size);
|
||||
this->pending_data = this->pending_data.substr(size);
|
||||
} else {
|
||||
memcpy(data, this->pending_data.data(), this->pending_data.size());
|
||||
size_t bytes_to_read = size - this->pending_data.size();
|
||||
this->pending_data.clear();
|
||||
void* read_buf = reinterpret_cast<uint8_t*>(data) + size - bytes_to_read;
|
||||
co_await asio::async_read(this->sock, asio::buffer(read_buf, bytes_to_read), asio::use_awaitable);
|
||||
}
|
||||
}
|
||||
|
||||
void AsyncWriteCollector::add(string&& data) {
|
||||
const auto& item = this->owned_data.emplace_back(std::move(data));
|
||||
bufs.emplace_back(asio::buffer(item.data(), item.size()));
|
||||
}
|
||||
|
||||
void AsyncWriteCollector::add_reference(const void* data, size_t size) {
|
||||
bufs.emplace_back(asio::buffer(data, size));
|
||||
}
|
||||
|
||||
asio::awaitable<void> AsyncWriteCollector::write(asio::ip::tcp::socket& sock) {
|
||||
deque<string> local_owned_data;
|
||||
local_owned_data.swap(this->owned_data);
|
||||
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::steady_timer timer(co_await asio::this_coro::executor, duration);
|
||||
co_await timer.async_wait(asio::use_awaitable);
|
||||
}
|
||||
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(uint32_t ipv4_addr, uint16_t port) {
|
||||
uint8_t octets[4] = {
|
||||
static_cast<uint8_t>(ipv4_addr >> 24),
|
||||
static_cast<uint8_t>(ipv4_addr >> 16),
|
||||
static_cast<uint8_t>(ipv4_addr >> 8),
|
||||
static_cast<uint8_t>(ipv4_addr)};
|
||||
return async_connect_tcp(std::format("{}.{}.{}.{}", octets[0], octets[1], octets[2], octets[3]), port);
|
||||
}
|
||||
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(const std::string& host, uint16_t port) {
|
||||
auto executor = co_await asio::this_coro::executor;
|
||||
|
||||
asio::ip::tcp::resolver resolver(executor);
|
||||
auto endpoints = co_await resolver.async_resolve(host, std::format("{}", port), asio::use_awaitable);
|
||||
|
||||
asio::ip::tcp::socket sock(executor);
|
||||
co_await asio::async_connect(sock, endpoints, asio::use_awaitable);
|
||||
|
||||
co_return sock;
|
||||
}
|
||||
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(const asio::ip::tcp::endpoint& ep) {
|
||||
auto executor = co_await asio::this_coro::executor;
|
||||
asio::ip::tcp::socket sock(executor);
|
||||
co_await sock.async_connect(ep, asio::use_awaitable);
|
||||
co_return sock;
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
#pragma once
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <asio/experimental/parallel_group.hpp>
|
||||
#include <asio/experimental/promise.hpp>
|
||||
#include <deque>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <phosg/Strings.hh>
|
||||
|
||||
template <typename T>
|
||||
class AsyncPromise {
|
||||
public:
|
||||
AsyncPromise() = default;
|
||||
|
||||
asio::awaitable<T> get() {
|
||||
if (!this->exc && !this->val.has_value()) {
|
||||
auto executor = co_await asio::this_coro::executor;
|
||||
co_await asio::async_initiate<decltype(asio::use_awaitable), void(std::error_code)>(
|
||||
[this, &executor](auto&& new_handler) {
|
||||
this->resolver_ref.emplace(ResolverRef{.resolve = std::move(new_handler), .executor = &executor});
|
||||
},
|
||||
asio::use_awaitable);
|
||||
}
|
||||
|
||||
if (this->exc) {
|
||||
std::rethrow_exception(this->exc);
|
||||
} else if (this->val.has_value()) {
|
||||
co_return *this->val;
|
||||
} else {
|
||||
throw std::logic_error("AsyncPromise await resolved but did not have a value or exception");
|
||||
}
|
||||
}
|
||||
|
||||
void set_value(T&& result) {
|
||||
if (this->exc || this->val.has_value()) {
|
||||
throw std::logic_error("attempted to set value on completed promise");
|
||||
}
|
||||
this->val = result;
|
||||
this->resolve();
|
||||
}
|
||||
|
||||
void set_exception(std::exception_ptr ex) {
|
||||
if (this->exc || this->val.has_value()) {
|
||||
throw std::logic_error("attempted to set value on completed promise");
|
||||
}
|
||||
this->exc = ex;
|
||||
this->resolve();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
this->set_exception(std::make_exception_ptr(std::runtime_error("AsyncPromise cancelled")));
|
||||
}
|
||||
|
||||
bool done() const {
|
||||
return this->exc || this->val.has_value();
|
||||
}
|
||||
|
||||
private:
|
||||
struct ResolverRef {
|
||||
asio::detail::awaitable_handler<asio::any_io_executor, std::error_code> resolve;
|
||||
asio::any_io_executor* executor;
|
||||
};
|
||||
std::optional<T> val;
|
||||
std::exception_ptr exc;
|
||||
std::optional<ResolverRef> resolver_ref;
|
||||
|
||||
void resolve() {
|
||||
if (this->resolver_ref.has_value()) {
|
||||
auto* executor = this->resolver_ref->executor;
|
||||
asio::post(*executor, [ref = std::move(this->resolver_ref)]() mutable -> void {
|
||||
ref->resolve(std::error_code{});
|
||||
});
|
||||
this->resolver_ref.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class AsyncPromise<void> {
|
||||
public:
|
||||
AsyncPromise() = default;
|
||||
|
||||
asio::awaitable<void> get() {
|
||||
if (!this->exc && !this->returned) {
|
||||
auto executor = co_await asio::this_coro::executor;
|
||||
co_await asio::async_initiate<decltype(asio::use_awaitable), void(std::error_code)>(
|
||||
[this, &executor](auto&& new_handler) {
|
||||
this->resolver_ref.emplace(ResolverRef{.resolve = std::move(new_handler), .executor = &executor});
|
||||
},
|
||||
asio::use_awaitable);
|
||||
}
|
||||
|
||||
if (this->exc) {
|
||||
std::rethrow_exception(this->exc);
|
||||
} else if (this->returned) {
|
||||
co_return;
|
||||
} else {
|
||||
throw std::logic_error("AsyncPromise await resolved but did not have a value or exception");
|
||||
}
|
||||
}
|
||||
|
||||
void set_value() {
|
||||
if (this->exc || this->returned) {
|
||||
throw std::logic_error("attempted to set value on completed promise");
|
||||
}
|
||||
this->returned = true;
|
||||
this->resolve();
|
||||
}
|
||||
|
||||
void set_exception(std::exception_ptr ex) {
|
||||
if (this->exc || this->returned) {
|
||||
throw std::logic_error("attempted to set value on completed promise");
|
||||
}
|
||||
this->exc = ex;
|
||||
this->resolve();
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
this->set_exception(std::make_exception_ptr(std::runtime_error("AsyncPromise cancelled")));
|
||||
}
|
||||
|
||||
bool done() const {
|
||||
return this->exc || this->returned;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ResolverRef {
|
||||
asio::detail::awaitable_handler<asio::any_io_executor, std::error_code> resolve;
|
||||
asio::any_io_executor* executor;
|
||||
};
|
||||
bool returned;
|
||||
std::exception_ptr exc;
|
||||
std::optional<ResolverRef> resolver_ref;
|
||||
|
||||
void resolve() {
|
||||
if (this->resolver_ref.has_value()) {
|
||||
auto* executor = this->resolver_ref->executor;
|
||||
asio::post(*executor, [ref = std::move(this->resolver_ref)]() mutable -> void {
|
||||
ref->resolve(std::error_code{});
|
||||
});
|
||||
this->resolver_ref.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AsyncEvent {
|
||||
public:
|
||||
AsyncEvent(asio::any_io_executor ex);
|
||||
AsyncEvent(const AsyncEvent&) = delete;
|
||||
AsyncEvent(AsyncEvent&&) = delete;
|
||||
AsyncEvent& operator=(const AsyncEvent&) = delete;
|
||||
AsyncEvent& operator=(AsyncEvent&&) = delete;
|
||||
|
||||
void set();
|
||||
void clear();
|
||||
asio::awaitable<void> wait();
|
||||
|
||||
private:
|
||||
asio::any_io_executor executor;
|
||||
bool is_set;
|
||||
std::mutex lock;
|
||||
std::vector<std::unique_ptr<asio::detail::awaitable_handler<asio::any_io_executor>>> waiters;
|
||||
};
|
||||
|
||||
class AsyncSocketReader {
|
||||
public:
|
||||
explicit AsyncSocketReader(asio::ip::tcp::socket&& sock);
|
||||
AsyncSocketReader(const AsyncSocketReader&) = delete;
|
||||
AsyncSocketReader(AsyncSocketReader&&) = delete;
|
||||
AsyncSocketReader& operator=(const AsyncSocketReader&) = delete;
|
||||
AsyncSocketReader& operator=(AsyncSocketReader&&) = delete;
|
||||
~AsyncSocketReader() = default;
|
||||
|
||||
// Reads one line from the socket, buffering any extra data read. The
|
||||
// delimiter is not included in the returned line. max_length = 0 means no
|
||||
// maximum length is enforced.
|
||||
asio::awaitable<std::string> read_line(
|
||||
const char* delimiter = "\n", size_t max_length = 0);
|
||||
asio::awaitable<std::string> read_data(size_t size);
|
||||
asio::awaitable<void> read_data_into(void* data, size_t size);
|
||||
|
||||
// The caller cannot know what the socket's read state is, so this should
|
||||
// only be used when the caller intends to write to the socket, not read
|
||||
inline asio::ip::tcp::socket& get_socket() {
|
||||
return this->sock;
|
||||
}
|
||||
|
||||
inline void close() {
|
||||
this->sock.close();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string pending_data; // Data read but not yet returned to the caller
|
||||
asio::ip::tcp::socket sock;
|
||||
};
|
||||
|
||||
class AsyncWriteCollector {
|
||||
public:
|
||||
AsyncWriteCollector() = default;
|
||||
AsyncWriteCollector(const AsyncWriteCollector&) = delete;
|
||||
AsyncWriteCollector(AsyncWriteCollector&&) = delete;
|
||||
AsyncWriteCollector& operator=(const AsyncWriteCollector&) = delete;
|
||||
AsyncWriteCollector& operator=(AsyncWriteCollector&&) = delete;
|
||||
~AsyncWriteCollector() = default;
|
||||
|
||||
void add(std::string&& data);
|
||||
|
||||
// When using add_reference, it is the caller's responsibility to ensure that
|
||||
// the buffer is valid until *this is destroyed or write() returns.
|
||||
void add_reference(const void* data, size_t size);
|
||||
|
||||
asio::awaitable<void> write(asio::ip::tcp::socket& sock);
|
||||
|
||||
private:
|
||||
std::deque<std::string> owned_data;
|
||||
std::vector<asio::const_buffer> bufs;
|
||||
};
|
||||
|
||||
asio::awaitable<void> async_sleep(std::chrono::steady_clock::duration duration);
|
||||
|
||||
inline asio::ip::tcp::endpoint make_endpoint_ipv4(uint32_t addr, uint16_t port) {
|
||||
return asio::ip::tcp::endpoint(asio::ip::address_v4(addr), port);
|
||||
}
|
||||
|
||||
inline std::string str_for_endpoint(const asio::ip::tcp::endpoint& ep) {
|
||||
return ep.address().to_string() + std::format(":{}", ep.port());
|
||||
}
|
||||
|
||||
inline uint32_t ipv4_addr_for_asio_addr(const asio::ip::address& addr) {
|
||||
if (!addr.is_v4()) {
|
||||
throw std::runtime_error("Address is not IPv4");
|
||||
}
|
||||
return ntohl(addr.to_v4().to_uint());
|
||||
}
|
||||
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(uint32_t ipv4_addr, uint16_t port);
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(const std::string& host, uint16_t port);
|
||||
asio::awaitable<asio::ip::tcp::socket> async_connect_tcp(const asio::ip::tcp::endpoint& ep);
|
||||
|
||||
template <typename FnT, typename... ArgTs>
|
||||
asio::awaitable<std::invoke_result_t<FnT, ArgTs...>> call_on_thread_pool(asio::thread_pool& pool, FnT&& f, ArgTs&&... args) {
|
||||
using ReturnT = std::invoke_result_t<FnT, ArgTs...>;
|
||||
auto bound = std::bind(std::forward<FnT>(f), std::forward<ArgTs>(args)...);
|
||||
AsyncPromise<ReturnT> promise;
|
||||
|
||||
asio::post(pool, [&promise, &bound]() -> void {
|
||||
promise.set_value(bound());
|
||||
});
|
||||
co_return co_await promise.get();
|
||||
}
|
||||
+16
-16
@@ -11,25 +11,25 @@ using namespace std;
|
||||
|
||||
void BattleParamsIndex::Table::print(FILE* stream) const {
|
||||
auto print_entry = +[](FILE* stream, const PlayerStats& e) {
|
||||
fprintf(stream,
|
||||
"%5hu %5hu %5hu %5hu %5hu %5hu %5hu %5hu %5" PRIu32 " %5" PRIu32,
|
||||
e.char_stats.atp.load(),
|
||||
e.char_stats.mst.load(),
|
||||
e.char_stats.evp.load(),
|
||||
e.char_stats.hp.load(),
|
||||
e.char_stats.dfp.load(),
|
||||
e.char_stats.ata.load(),
|
||||
e.char_stats.lck.load(),
|
||||
e.esp.load(),
|
||||
e.experience.load(),
|
||||
e.meseta.load());
|
||||
phosg::fwrite_fmt(stream,
|
||||
"{:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5} {:5}",
|
||||
e.char_stats.atp,
|
||||
e.char_stats.mst,
|
||||
e.char_stats.evp,
|
||||
e.char_stats.hp,
|
||||
e.char_stats.dfp,
|
||||
e.char_stats.ata,
|
||||
e.char_stats.lck,
|
||||
e.esp,
|
||||
e.experience,
|
||||
e.meseta);
|
||||
};
|
||||
|
||||
for (size_t diff = 0; diff < 4; diff++) {
|
||||
fprintf(stream, "%c ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
|
||||
phosg::fwrite_fmt(stream, "{} ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF\n",
|
||||
abbreviation_for_difficulty(diff));
|
||||
for (size_t z = 0; z < 0x60; z++) {
|
||||
fprintf(stream, " %02zX ", z);
|
||||
phosg::fwrite_fmt(stream, " {:02X} ", z);
|
||||
print_entry(stream, this->stats[diff][z]);
|
||||
fputc('\n', stream);
|
||||
}
|
||||
@@ -54,8 +54,8 @@ BattleParamsIndex::BattleParamsIndex(
|
||||
for (uint8_t episode = 0; episode < 3; episode++) {
|
||||
auto& file = this->files[is_solo][episode];
|
||||
if (file.data->size() < sizeof(Table)) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"battle params table size is incorrect (expected %zX bytes, have %zX bytes; is_solo=%hhu, episode=%hhu)",
|
||||
throw 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));
|
||||
}
|
||||
file.table = reinterpret_cast<const Table*>(file.data->data());
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
#include "CatSession.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CatSession::exit_shell::exit_shell() : runtime_error("shell exited") {}
|
||||
|
||||
CatSession::CatSession(
|
||||
shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file)
|
||||
: log(phosg::string_printf("[CatSession:%s] ", phosg::name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
read_event(event_new(this->base.get(), 0, EV_READ | EV_PERSIST, CatSession::dispatch_read_stdin, this), event_free),
|
||||
channel(version, 1, CatSession::dispatch_on_channel_input, CatSession::dispatch_on_channel_error, this, "CatSession"),
|
||||
bb_key_file(bb_key_file) {
|
||||
|
||||
if (remote.ss_family != AF_INET) {
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = phosg::render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
|
||||
event_add(this->read_event.get(), nullptr);
|
||||
this->poll.add(0, POLLIN);
|
||||
}
|
||||
|
||||
void CatSession::execute_command(const std::string& command) {
|
||||
string full_cmd = phosg::parse_data_string(command, nullptr, phosg::ParseDataFlags::ALLOW_FILES);
|
||||
send_command_with_header(this->channel, full_cmd.data(), full_cmd.size());
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_input(
|
||||
Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_input(command, flag, data);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_input(
|
||||
uint16_t command, uint32_t flag, std::string& data) {
|
||||
if (!uses_v4_encryption(this->channel.version)) {
|
||||
if (command == 0x02 || command == 0x17 || command == 0x91 || command == 0x9B) {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else { // PC, DC, or patch server
|
||||
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
}
|
||||
}
|
||||
} else { // BB
|
||||
if (command == 0x03 || command == 0x9B) {
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 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->log.info("Enabled BB encryption");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
phosg::print_data(stdout, full_cmd, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
|
||||
void CatSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<CatSession*>(ch.context_obj);
|
||||
session->on_channel_error(events);
|
||||
}
|
||||
|
||||
void CatSession::on_channel_error(short events) {
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
this->log.info("Channel connected");
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log.warning("Error %d (%s) in unlinked client stream", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log.info("Session endpoint has disconnected");
|
||||
this->channel.disconnect();
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CatSession::dispatch_read_stdin(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<CatSession*>(ctx)->read_stdin();
|
||||
}
|
||||
|
||||
void CatSession::read_stdin() {
|
||||
bool any_command_read = false;
|
||||
for (;;) {
|
||||
auto poll_result = this->poll.poll();
|
||||
short fd_events = 0;
|
||||
try {
|
||||
fd_events = poll_result.at(0);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
|
||||
if (!(fd_events & POLLIN)) {
|
||||
break;
|
||||
}
|
||||
|
||||
string command(2048, '\0');
|
||||
if (!fgets(command.data(), command.size(), stdin)) {
|
||||
if (!any_command_read) {
|
||||
// ctrl+d probably; we should exit
|
||||
fputc('\n', stderr);
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} else {
|
||||
break; // probably not EOF; just no more commands for now
|
||||
}
|
||||
}
|
||||
|
||||
// trim the extra data off the string
|
||||
size_t len = strlen(command.c_str());
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
if (command[len - 1] == '\n') {
|
||||
len--;
|
||||
}
|
||||
command.resize(len);
|
||||
any_command_read = true;
|
||||
|
||||
try {
|
||||
execute_command(command);
|
||||
} catch (const exit_shell&) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
return;
|
||||
} catch (const exception& e) {
|
||||
fprintf(stderr, "FAILED: %s\n", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class CatSession {
|
||||
public:
|
||||
CatSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
Version version,
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file);
|
||||
CatSession(const CatSession&) = delete;
|
||||
CatSession(CatSession&&) = delete;
|
||||
CatSession& operator=(const CatSession&) = delete;
|
||||
CatSession& operator=(CatSession&&) = delete;
|
||||
virtual ~CatSession() = default;
|
||||
|
||||
protected:
|
||||
phosg::PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> read_event;
|
||||
phosg::Poll poll;
|
||||
|
||||
Channel channel;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
|
||||
class exit_shell : public std::runtime_error {
|
||||
public:
|
||||
exit_shell();
|
||||
~exit_shell() = default;
|
||||
};
|
||||
|
||||
virtual void execute_command(const std::string& command);
|
||||
|
||||
static void dispatch_read_stdin(evutil_socket_t fd, short events, void* ctx);
|
||||
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||
void on_channel_error(short events);
|
||||
void read_stdin();
|
||||
};
|
||||
+226
-249
@@ -1,9 +1,6 @@
|
||||
#include "Channel.hh"
|
||||
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -17,230 +14,17 @@ using namespace std;
|
||||
|
||||
extern bool use_terminal_colors;
|
||||
|
||||
static void flush_and_free_bufferevent(struct bufferevent* bev) {
|
||||
bufferevent_flush(bev, EV_READ | EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
virtual_network_id(0),
|
||||
version(version),
|
||||
: version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
}
|
||||
|
||||
Channel::Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: bev(nullptr, flush_and_free_bufferevent),
|
||||
version(version),
|
||||
language(language),
|
||||
name(name),
|
||||
terminal_send_color(terminal_send_color),
|
||||
terminal_recv_color(terminal_recv_color),
|
||||
on_command_received(on_command_received),
|
||||
on_error(on_error),
|
||||
context_obj(context_obj) {
|
||||
this->set_bufferevent(bev, virtual_network_id);
|
||||
}
|
||||
|
||||
void Channel::replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name) {
|
||||
this->set_bufferevent(other.bev.release(), other.virtual_network_id);
|
||||
this->local_addr = other.local_addr;
|
||||
this->remote_addr = other.remote_addr;
|
||||
this->version = other.version;
|
||||
this->language = other.language;
|
||||
this->crypt_in = other.crypt_in;
|
||||
this->crypt_out = other.crypt_out;
|
||||
this->name = name;
|
||||
this->terminal_send_color = other.terminal_send_color;
|
||||
this->terminal_recv_color = other.terminal_recv_color;
|
||||
this->on_command_received = on_command_received;
|
||||
this->on_error = on_error;
|
||||
this->context_obj = context_obj;
|
||||
other.disconnect(); // Clears crypts, addrs, etc.
|
||||
}
|
||||
|
||||
void Channel::set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id) {
|
||||
this->bev.reset(bev);
|
||||
this->virtual_network_id = virtual_network_id;
|
||||
|
||||
if (this->bev.get()) {
|
||||
int fd = bufferevent_getfd(this->bev.get());
|
||||
if (fd < 0) {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
} else {
|
||||
phosg::get_socket_addresses(fd, &this->local_addr, &this->remote_addr);
|
||||
}
|
||||
|
||||
bufferevent_setcb(this->bev.get(), &Channel::dispatch_on_input, nullptr, &Channel::dispatch_on_error, this);
|
||||
bufferevent_enable(this->bev.get(), EV_READ | EV_WRITE);
|
||||
|
||||
} else {
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::disconnect() {
|
||||
if (this->bev.get()) {
|
||||
// If the output buffer is not empty, move the bufferevent into the draining
|
||||
// pool instead of disconnecting it, to make sure all the data gets sent.
|
||||
struct evbuffer* out_buffer = bufferevent_get_output(this->bev.get());
|
||||
if (evbuffer_get_length(out_buffer) == 0) {
|
||||
this->bev.reset(); // Destructor flushes and frees the bufferevent
|
||||
} else {
|
||||
// The callbacks will free it when all the data is sent or the client
|
||||
// disconnects
|
||||
|
||||
auto on_output = +[](struct bufferevent* bev, void*) -> void {
|
||||
flush_and_free_bufferevent(bev);
|
||||
};
|
||||
|
||||
auto on_error = +[](struct bufferevent* bev, short events, void*) -> void {
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
channel_exceptions_log.warning(
|
||||
"Disconnecting channel caused error %d (%s)", err,
|
||||
evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
bufferevent_flush(bev, EV_WRITE, BEV_FINISHED);
|
||||
bufferevent_free(bev);
|
||||
}
|
||||
};
|
||||
|
||||
struct bufferevent* bev = this->bev.release();
|
||||
bufferevent_setcb(bev, nullptr, on_output, on_error, bev);
|
||||
bufferevent_disable(bev, EV_READ);
|
||||
}
|
||||
}
|
||||
|
||||
memset(&this->local_addr, 0, sizeof(this->local_addr));
|
||||
memset(&this->remote_addr, 0, sizeof(this->remote_addr));
|
||||
this->virtual_network_id = false;
|
||||
this->crypt_in.reset();
|
||||
this->crypt_out.reset();
|
||||
}
|
||||
|
||||
Channel::Message Channel::recv() {
|
||||
struct evbuffer* buf = bufferevent_get_input(this->bev.get());
|
||||
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
PSOCommandHeader header;
|
||||
if (evbuffer_copyout(buf, &header, header_size) < static_cast<ssize_t>(header_size)) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(&header, header_size, false);
|
||||
}
|
||||
|
||||
size_t command_logical_size = header.size(version);
|
||||
|
||||
// If encryption is enabled, BB pads commands to 8-byte boundaries, and this
|
||||
// is not reflected in the size field. This logic does not occur if encryption
|
||||
// is not yet enabled.
|
||||
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
|
||||
? ((command_logical_size + 7) & ~7)
|
||||
: command_logical_size;
|
||||
if (evbuffer_get_length(buf) < command_physical_size) {
|
||||
throw out_of_range("no command available");
|
||||
}
|
||||
|
||||
// If we get here, then there is a full command in the buffer. Some encryption
|
||||
// algorithms' advancement depends on the decrypted data, so we have to
|
||||
// actually decrypt the header again (with advance=true) to keep them in a
|
||||
// consistent state.
|
||||
|
||||
string header_data(header_size, '\0');
|
||||
if (evbuffer_remove(buf, header_data.data(), header_data.size()) < static_cast<ssize_t>(header_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(header_data.data(), header_data.size());
|
||||
}
|
||||
|
||||
string command_data(command_physical_size - header_size, '\0');
|
||||
if (evbuffer_remove(buf, command_data.data(), command_data.size()) < static_cast<ssize_t>(command_data.size())) {
|
||||
throw logic_error("enough bytes available, but could not remove them");
|
||||
}
|
||||
|
||||
if (this->crypt_in.get()) {
|
||||
// Some versions of PSO DC can send commands whose sizes are not a multiple
|
||||
// of 4, but the server is expected to always use a multiple of 4 bytes when
|
||||
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
|
||||
// we have to round up the size for DC commands here.
|
||||
size_t orig_size = command_data.size();
|
||||
command_data.resize((orig_size + 3) & (~3), 0);
|
||||
this->crypt_in->decrypt(command_data.data(), command_data.size());
|
||||
command_data.resize(orig_size);
|
||||
}
|
||||
command_data.resize(command_logical_size - header_size);
|
||||
|
||||
if (command_data_log.should_log(phosg::LogLevel::INFO) && (this->terminal_recv_color != phosg::TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
} else {
|
||||
command_data_log.info(
|
||||
"Received from %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(),
|
||||
phosg::name_for_enum(this->version),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
}
|
||||
|
||||
vector<struct iovec> iovs;
|
||||
iovs.emplace_back(iovec{.iov_base = header_data.data(), .iov_len = header_data.size()});
|
||||
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
|
||||
phosg::print_data(stderr, iovs, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
phosg::print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
.command = header.command(this->version),
|
||||
.flag = header.flag(this->version),
|
||||
.data = std::move(command_data),
|
||||
};
|
||||
terminal_recv_color(terminal_recv_color) {
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
|
||||
@@ -249,7 +33,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, bool silent) {
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<const void*, size_t>> blocks, bool silent) {
|
||||
if (!this->connected()) {
|
||||
channel_exceptions_log.warning("Attempted to send command on closed channel; dropping data");
|
||||
channel_exceptions_log.warning_f("Attempted to send command on closed channel; dropping data");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -342,16 +126,16 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
|
||||
}
|
||||
send_data.resize(send_data_size, '\0');
|
||||
|
||||
if (!silent && (command_data_log.should_log(phosg::LogLevel::INFO)) && (this->terminal_send_color != phosg::TerminalFormat::END)) {
|
||||
if (!silent && (command_data_log.should_log(phosg::LogLevel::L_INFO)) && (this->terminal_send_color != phosg::TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_send_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info("Sending to %s (version=BB command=%04hX flag=%08" PRIX32 ")",
|
||||
this->name.c_str(), cmd, flag);
|
||||
command_data_log.info_f("Sending to {} (version=BB command={:04X} flag={:08X})",
|
||||
this->name, cmd, flag);
|
||||
} else {
|
||||
command_data_log.info("Sending to %s (version=%s command=%02hX flag=%02" PRIX32 ")",
|
||||
this->name.c_str(), phosg::name_for_enum(version), cmd, flag);
|
||||
command_data_log.info_f("Sending to {} (version={} command={:02X} flag={:02X})",
|
||||
this->name, phosg::name_for_enum(version), cmd, flag);
|
||||
}
|
||||
phosg::print_data(stderr, send_data.data(), logical_size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
if (use_terminal_colors && this->terminal_send_color != phosg::TerminalFormat::NORMAL) {
|
||||
@@ -363,8 +147,7 @@ void Channel::send(uint16_t cmd, uint32_t flag, const std::vector<std::pair<cons
|
||||
this->crypt_out->encrypt(send_data.data(), send_data.size());
|
||||
}
|
||||
|
||||
struct evbuffer* buf = bufferevent_get_output(this->bev.get());
|
||||
evbuffer_add(buf, send_data.data(), send_data.size());
|
||||
this->send_raw(std::move(send_data));
|
||||
}
|
||||
|
||||
void Channel::send(uint16_t cmd, uint32_t flag, const void* data, size_t size, bool silent) {
|
||||
@@ -387,35 +170,229 @@ void Channel::send(const void* data, size_t size, bool silent) {
|
||||
}
|
||||
|
||||
void Channel::send(const string& data, bool silent) {
|
||||
return this->send(data.data(), data.size(), silent);
|
||||
this->send(data.data(), data.size(), silent);
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_input(struct bufferevent*, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
// The client can be disconnected during on_command_received, so we have to
|
||||
// make sure ch->bev is valid every time before calling recv()
|
||||
while (ch->bev.get()) {
|
||||
Message msg;
|
||||
try {
|
||||
msg = ch->recv();
|
||||
} catch (const out_of_range&) {
|
||||
break;
|
||||
} catch (const exception& e) {
|
||||
channel_exceptions_log.warning("Error receiving on channel: %s", e.what());
|
||||
ch->on_error(*ch, BEV_EVENT_ERROR);
|
||||
break;
|
||||
asio::awaitable<Channel::Message> Channel::recv() {
|
||||
size_t header_size = (this->version == Version::BB_V4) ? 8 : 4;
|
||||
PSOCommandHeader header;
|
||||
co_await this->recv_raw(&header, header_size);
|
||||
if (this->crypt_in.get()) {
|
||||
this->crypt_in->decrypt(&header, header_size);
|
||||
}
|
||||
|
||||
size_t command_logical_size = header.size(version);
|
||||
if (command_logical_size < header_size) {
|
||||
throw 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 logic does not occur if encryption
|
||||
// is not yet enabled.
|
||||
size_t command_physical_size = (this->crypt_in.get() && (version == Version::BB_V4))
|
||||
? ((command_logical_size + 7) & ~7)
|
||||
: command_logical_size;
|
||||
|
||||
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()) {
|
||||
// Some versions of PSO DC can send commands whose sizes are not a multiple
|
||||
// of 4, but the server is expected to always use a multiple of 4 bytes when
|
||||
// decrypting (the extra cipher bytes are lost). To emulate this behavior,
|
||||
// we have to round up the size for DC commands here.
|
||||
size_t orig_size = command_data.size();
|
||||
command_data.resize((orig_size + 3) & (~3), 0);
|
||||
this->crypt_in->decrypt(command_data.data(), command_data.size());
|
||||
command_data.resize(orig_size);
|
||||
}
|
||||
command_data.resize(command_logical_size - header_size);
|
||||
|
||||
if (command_data_log.should_log(phosg::LogLevel::L_INFO) && (this->terminal_recv_color != phosg::TerminalFormat::END)) {
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
print_color_escape(stderr, this->terminal_recv_color, phosg::TerminalFormat::BOLD, phosg::TerminalFormat::END);
|
||||
}
|
||||
if (ch->on_command_received) {
|
||||
ch->on_command_received(*ch, msg.command, msg.flag, msg.data);
|
||||
|
||||
if (version == Version::BB_V4) {
|
||||
command_data_log.info_f(
|
||||
"Received from {} (version=BB command={:04X} flag={:08X})",
|
||||
this->name,
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
} else {
|
||||
command_data_log.info_f(
|
||||
"Received from {} (version={} command={:02X} flag={:02X})",
|
||||
this->name,
|
||||
phosg::name_for_enum(this->version),
|
||||
header.command(this->version),
|
||||
header.flag(this->version));
|
||||
}
|
||||
|
||||
vector<struct iovec> iovs;
|
||||
iovs.emplace_back(iovec{.iov_base = &header, .iov_len = header_size});
|
||||
iovs.emplace_back(iovec{.iov_base = command_data.data(), .iov_len = command_data.size()});
|
||||
phosg::print_data(stderr, iovs, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
|
||||
if (use_terminal_colors && this->terminal_recv_color != phosg::TerminalFormat::NORMAL) {
|
||||
phosg::print_color_escape(stderr, phosg::TerminalFormat::NORMAL, phosg::TerminalFormat::END);
|
||||
}
|
||||
}
|
||||
|
||||
co_return Message{
|
||||
.command = header.command(this->version),
|
||||
.flag = header.flag(this->version),
|
||||
.data = std::move(command_data),
|
||||
};
|
||||
}
|
||||
|
||||
shared_ptr<SocketChannel> SocketChannel::create(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color) {
|
||||
shared_ptr<SocketChannel> ret(new SocketChannel(
|
||||
io_context, std::move(sock), version, language, name, terminal_send_color, terminal_recv_color));
|
||||
asio::co_spawn(*io_context, ret->send_task(), asio::detached);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SocketChannel::SocketChannel(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: Channel(version, language, name, terminal_send_color, terminal_recv_color),
|
||||
sock(std::move(sock)),
|
||||
local_addr(this->sock->local_endpoint()),
|
||||
remote_addr(this->sock->remote_endpoint()),
|
||||
send_buffer_nonempty_signal(io_context->get_executor()) {}
|
||||
|
||||
std::string SocketChannel::default_name() const {
|
||||
return "ip:" + str_for_endpoint(this->remote_addr);
|
||||
}
|
||||
|
||||
bool SocketChannel::connected() const {
|
||||
return !this->should_disconnect && this->sock && this->sock->is_open();
|
||||
}
|
||||
|
||||
void SocketChannel::disconnect() {
|
||||
this->should_disconnect = true;
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
|
||||
void SocketChannel::send_raw(string&& data) {
|
||||
if (this->sock && !this->should_disconnect) {
|
||||
this->outbound_data.emplace_back(std::move(data));
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
co_await asio::async_read(*this->sock, asio::buffer(data, size), asio::use_awaitable);
|
||||
}
|
||||
|
||||
asio::awaitable<void> SocketChannel::send_task() {
|
||||
// Ensure *this doesn't get deleted while the socket is open
|
||||
auto this_sh = this->shared_from_this();
|
||||
|
||||
while (this->sock->is_open()) {
|
||||
deque<string> to_send;
|
||||
to_send.swap(this->outbound_data);
|
||||
|
||||
if (!to_send.empty()) {
|
||||
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()));
|
||||
}
|
||||
co_await asio::async_write(*this->sock, bufs, asio::use_awaitable);
|
||||
}
|
||||
|
||||
if (this->outbound_data.empty()) {
|
||||
if (this->should_disconnect) {
|
||||
this->sock->close();
|
||||
} else {
|
||||
this->send_buffer_nonempty_signal.clear();
|
||||
co_await this->send_buffer_nonempty_signal.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Channel::dispatch_on_error(struct bufferevent*, short events, void* ctx) {
|
||||
Channel* ch = reinterpret_cast<Channel*>(ctx);
|
||||
if (ch->on_error) {
|
||||
ch->on_error(*ch, events);
|
||||
} else {
|
||||
ch->disconnect();
|
||||
PeerChannel::PeerChannel(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color)
|
||||
: Channel(version, language, name, terminal_send_color, terminal_recv_color),
|
||||
send_buffer_nonempty_signal(io_context->get_executor()) {}
|
||||
|
||||
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");
|
||||
}
|
||||
peer1->peer = peer2;
|
||||
peer2->peer = peer1;
|
||||
}
|
||||
|
||||
std::string PeerChannel::default_name() const {
|
||||
return std::format("peer:{}->{}", reinterpret_cast<const void*>(this), reinterpret_cast<const void*>(this->peer.lock().get()));
|
||||
}
|
||||
|
||||
bool PeerChannel::connected() const {
|
||||
return (!this->inbound_data.empty()) || (this->peer.lock() != nullptr);
|
||||
}
|
||||
|
||||
void PeerChannel::disconnect() {
|
||||
auto peer = this->peer.lock();
|
||||
if (peer) {
|
||||
peer->peer.reset();
|
||||
peer->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
this->peer.reset();
|
||||
this->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
|
||||
void PeerChannel::send_raw(string&& data) {
|
||||
auto peer = this->peer.lock();
|
||||
if (peer) {
|
||||
peer->inbound_data.emplace_back(std::move(data));
|
||||
peer->send_buffer_nonempty_signal.set();
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> PeerChannel::recv_raw(void* data, size_t size) {
|
||||
while (size > 0) {
|
||||
while (this->inbound_data.empty() && this->peer.lock()) {
|
||||
this->send_buffer_nonempty_signal.clear();
|
||||
co_await this->send_buffer_nonempty_signal.wait();
|
||||
}
|
||||
|
||||
if (!this->inbound_data.empty()) {
|
||||
auto& front_block = this->inbound_data.front();
|
||||
if (size < front_block.size()) {
|
||||
memcpy(data, front_block.data(), size);
|
||||
front_block = front_block.substr(size);
|
||||
size = 0;
|
||||
} else {
|
||||
memcpy(data, front_block.data(), front_block.size());
|
||||
size -= front_block.size();
|
||||
data = reinterpret_cast<uint8_t*>(data) + front_block.size();
|
||||
this->inbound_data.pop_front();
|
||||
}
|
||||
} else if (!this->peer.lock()) {
|
||||
throw runtime_error("Channel peer has disconnected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+134
-58
@@ -1,20 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "AsyncUtils.hh"
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
struct Channel {
|
||||
std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)> bev;
|
||||
struct sockaddr_storage local_addr;
|
||||
struct sockaddr_storage remote_addr;
|
||||
uint64_t virtual_network_id; // 0 = normal TCP connection
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
Version version;
|
||||
uint8_t language;
|
||||
std::shared_ptr<PSOEncryption> crypt_in;
|
||||
@@ -28,58 +24,46 @@ struct Channel {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
std::string data;
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(size_t min_size, size_t max_size) const {
|
||||
return ::check_size_t<const T>(this->data.data(), this->data.size(), min_size, max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(size_t min_size, size_t max_size) {
|
||||
return ::check_size_t<T>(this->data.data(), this->data.size(), min_size, max_size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t(size_t max_size) const {
|
||||
return ::check_size_t<const T>(this->data.data(), this->data.size(), sizeof(T), max_size);
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t(size_t max_size) {
|
||||
return ::check_size_t<T>(this->data.data(), this->data.size(), sizeof(T), max_size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& check_size_t() const {
|
||||
return ::check_size_t<const T>(this->data.data(), this->data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
template <typename T>
|
||||
T& check_size_t() {
|
||||
return ::check_size_t<T>(this->data.data(), this->data.size(), sizeof(T), sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
typedef void (*on_command_received_t)(Channel&, uint16_t, uint32_t, std::string&);
|
||||
typedef void (*on_error_t)(Channel&, short);
|
||||
virtual ~Channel() = default;
|
||||
|
||||
on_command_received_t on_command_received;
|
||||
on_error_t on_error;
|
||||
void* context_obj;
|
||||
virtual std::string default_name() const = 0;
|
||||
|
||||
// Creates an unconnected channel
|
||||
Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
// Creates a connected channel
|
||||
Channel(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "",
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
Channel(const Channel& other) = delete;
|
||||
Channel(Channel&& other) = delete;
|
||||
Channel& operator=(const Channel& other) = delete;
|
||||
Channel& operator=(Channel&& other) = delete;
|
||||
// Returns whether the channel is connected or not.
|
||||
virtual bool connected() const = 0;
|
||||
|
||||
void replace_with(
|
||||
Channel&& other,
|
||||
on_command_received_t on_command_received,
|
||||
on_error_t on_error,
|
||||
void* context_obj,
|
||||
const std::string& name = "");
|
||||
|
||||
void set_bufferevent(struct bufferevent* bev, uint64_t virtual_network_id);
|
||||
|
||||
inline bool connected() const {
|
||||
return this->bev.get() != nullptr;
|
||||
}
|
||||
void disconnect();
|
||||
|
||||
// Receives a message. Throws std::out_of_range if no messages are available.
|
||||
Message recv();
|
||||
// Disconnects the channel. Any pending data will still be sent before the
|
||||
// underlying transport (e.g. socket) is closed, but further send calls will
|
||||
// do nothing.
|
||||
virtual void disconnect() = 0;
|
||||
|
||||
// Sends a message with an automatically-constructed header.
|
||||
void send(uint16_t cmd, uint32_t flag = 0, bool silent = false);
|
||||
@@ -97,7 +81,99 @@ struct Channel {
|
||||
void send(const void* data, size_t size, bool silent = false);
|
||||
void send(const std::string& data, bool silent = false);
|
||||
|
||||
private:
|
||||
static void dispatch_on_input(struct bufferevent*, void* ctx);
|
||||
static void dispatch_on_error(struct bufferevent*, short events, void* ctx);
|
||||
// Receives a message. Throws std::out_of_range if no messages are available.
|
||||
asio::awaitable<Message> recv();
|
||||
|
||||
protected:
|
||||
Channel(
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
Channel(const Channel& other) = delete;
|
||||
Channel(Channel&& other) = delete;
|
||||
Channel& operator=(const Channel& other) = delete;
|
||||
Channel& operator=(Channel&& other) = delete;
|
||||
|
||||
// Sends raw data on the underlying transport. If the channel is already
|
||||
// disconnected, silently drops the data.
|
||||
virtual void send_raw(std::string&& data) = 0;
|
||||
// Receives raw data on the underlying transport. Raises when the channel is
|
||||
// disconnected.
|
||||
virtual asio::awaitable<void> recv_raw(void* data, size_t size) = 0;
|
||||
};
|
||||
|
||||
// Standard channel type, used for most PSO clients. Represents an open TCP
|
||||
// socket.
|
||||
class SocketChannel : public Channel, public std::enable_shared_from_this<SocketChannel> {
|
||||
public:
|
||||
std::unique_ptr<asio::ip::tcp::socket> sock;
|
||||
asio::ip::tcp::endpoint local_addr;
|
||||
asio::ip::tcp::endpoint remote_addr;
|
||||
|
||||
// SocketChannel has a static constructor because it has an internal task,
|
||||
// which is necessary to support flushing before disconnection (for example)
|
||||
// and also to make send_raw not a coroutine, which keeps the rest of the
|
||||
// code cleaner. The task needs to hold a shared_ptr to the SocketChannel
|
||||
// whilc it's open
|
||||
static std::shared_ptr<SocketChannel> create(std::shared_ptr<asio::io_context> io_context,
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name = "",
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
|
||||
virtual std::string default_name() const;
|
||||
|
||||
virtual bool connected() const;
|
||||
virtual void disconnect();
|
||||
|
||||
virtual void send_raw(std::string&& data);
|
||||
virtual asio::awaitable<void> recv_raw(void* data, size_t size);
|
||||
|
||||
private:
|
||||
SocketChannel(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
std::unique_ptr<asio::ip::tcp::socket>&& sock,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name,
|
||||
phosg::TerminalFormat terminal_send_color,
|
||||
phosg::TerminalFormat terminal_recv_color);
|
||||
|
||||
std::deque<std::string> outbound_data;
|
||||
bool should_disconnect = false;
|
||||
AsyncEvent send_buffer_nonempty_signal;
|
||||
|
||||
asio::awaitable<void> send_task();
|
||||
};
|
||||
|
||||
// In-process peer channel, used for replay testing.
|
||||
class PeerChannel : public Channel {
|
||||
public:
|
||||
std::weak_ptr<PeerChannel> peer;
|
||||
|
||||
PeerChannel(
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name = "",
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
|
||||
static void link_peers(std::shared_ptr<PeerChannel> peer1, std::shared_ptr<PeerChannel> peer2);
|
||||
|
||||
virtual std::string default_name() const;
|
||||
|
||||
virtual bool connected() const;
|
||||
virtual void disconnect();
|
||||
|
||||
virtual void send_raw(std::string&& data);
|
||||
virtual asio::awaitable<void> recv_raw(void* data, size_t size);
|
||||
|
||||
private:
|
||||
AsyncEvent send_buffer_nonempty_signal;
|
||||
std::deque<std::string> inbound_data;
|
||||
};
|
||||
|
||||
+1232
-1349
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "Lobby.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "ProxySession.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_chat_command(std::shared_ptr<Client> c, const std::string& text, bool check_permissions);
|
||||
void on_chat_command(std::shared_ptr<ProxyServer::LinkedSession> ses, const std::string& text, bool check_permissions);
|
||||
asio::awaitable<void> on_chat_command(std::shared_ptr<Client> c, const std::string& text, bool check_permissions);
|
||||
|
||||
+3
-3
@@ -31,12 +31,12 @@ struct ChoiceSearchConfigT {
|
||||
|
||||
operator ChoiceSearchConfigT<!BE>() const {
|
||||
ChoiceSearchConfigT<!BE> ret;
|
||||
ret.disabled = this->disabled.load();
|
||||
ret.disabled = this->disabled;
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
auto& ret_e = ret.entries[z];
|
||||
const auto& this_e = this->entries[z];
|
||||
ret_e.parent_choice_id = this_e.parent_choice_id.load();
|
||||
ret_e.choice_id = this_e.choice_id.load();
|
||||
ret_e.parent_choice_id = this_e.parent_choice_id;
|
||||
ret_e.choice_id = this_e.choice_id;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
+155
-200
@@ -1,16 +1,15 @@
|
||||
#include "Client.hh"
|
||||
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "GameServer.hh"
|
||||
#include "IPStackSimulator.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "SendCommands.hh"
|
||||
@@ -23,7 +22,7 @@ const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
|
||||
|
||||
static atomic<uint64_t> next_id(1);
|
||||
|
||||
void Client::Config::set_flags_for_version(Version version, int64_t sub_version) {
|
||||
void Client::set_flags_for_version(Version version, int64_t sub_version) {
|
||||
this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||
|
||||
// BB shares some sub_version values with GC Episode 3, so we handle it
|
||||
@@ -153,17 +152,17 @@ void Client::Config::set_flags_for_version(Version version, int64_t sub_version)
|
||||
this->set_flag(Flag::CAN_RECEIVE_ENABLE_B2_QUEST);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error(phosg::string_printf("unknown sub_version %" PRIX64, sub_version));
|
||||
throw runtime_error(std::format("unknown sub_version {:X}", sub_version));
|
||||
}
|
||||
}
|
||||
|
||||
Client::ItemDropNotificationMode Client::Config::get_drop_notification_mode() const {
|
||||
Client::ItemDropNotificationMode Client::get_drop_notification_mode() const {
|
||||
uint8_t mode_s = (this->check_flag(Flag::ITEM_DROP_NOTIFICATIONS_1) ? 1 : 0) |
|
||||
(this->check_flag(Flag::ITEM_DROP_NOTIFICATIONS_2) ? 2 : 0);
|
||||
return static_cast<Client::ItemDropNotificationMode>(mode_s);
|
||||
}
|
||||
|
||||
void Client::Config::set_drop_notification_mode(ItemDropNotificationMode new_mode) {
|
||||
void Client::set_drop_notification_mode(ItemDropNotificationMode new_mode) {
|
||||
uint8_t mode_s = static_cast<uint8_t>(new_mode);
|
||||
if (mode_s & 1) {
|
||||
this->set_flag(Client::Flag::ITEM_DROP_NOTIFICATIONS_1);
|
||||
@@ -177,133 +176,121 @@ void Client::Config::set_drop_notification_mode(ItemDropNotificationMode new_mod
|
||||
}
|
||||
}
|
||||
|
||||
bool Client::Config::should_update_vs(const Config& other) const {
|
||||
constexpr uint64_t mask = static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK);
|
||||
return ((this->enabled_flags ^ other.enabled_flags) & mask) ||
|
||||
(this->specific_version != other.specific_version) ||
|
||||
(this->override_random_seed != other.override_random_seed) ||
|
||||
(this->override_section_id != other.override_section_id) ||
|
||||
(this->override_lobby_event != other.override_lobby_event) ||
|
||||
(this->override_lobby_number != other.override_lobby_number) ||
|
||||
(this->proxy_destination_address != other.proxy_destination_address) ||
|
||||
(this->proxy_destination_port != other.proxy_destination_port);
|
||||
}
|
||||
|
||||
Client::Client(
|
||||
shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
shared_ptr<GameServer> server,
|
||||
shared_ptr<Channel> channel,
|
||||
ServerBehavior server_behavior)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(phosg::string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, virtual_network_id, version, 1, nullptr, nullptr, this, "", phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::FG_GREEN),
|
||||
log(std::format("[C-{:X}] ", this->id), client_log.min_level),
|
||||
channel(channel),
|
||||
server_behavior(server_behavior),
|
||||
should_disconnect(false),
|
||||
should_send_to_lobby_server(false),
|
||||
should_send_to_proxy_server(false),
|
||||
bb_connection_phase(0xFF),
|
||||
ping_start_time(0),
|
||||
sub_version(-1),
|
||||
floor(0),
|
||||
lobby_client_id(0),
|
||||
lobby_arrow_color(0),
|
||||
preferred_lobby_id(-1),
|
||||
save_game_data_event(
|
||||
event_new(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST,
|
||||
&Client::dispatch_save_game_data, this),
|
||||
event_free),
|
||||
send_ping_event(
|
||||
event_new(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT,
|
||||
&Client::dispatch_send_ping, this),
|
||||
event_free),
|
||||
idle_timeout_event(
|
||||
event_new(
|
||||
bufferevent_get_base(bev), -1, EV_TIMEOUT,
|
||||
&Client::dispatch_idle_timeout, this),
|
||||
event_free),
|
||||
card_battle_table_number(-1),
|
||||
card_battle_table_seat_number(0),
|
||||
card_battle_table_seat_state(0),
|
||||
last_game_info_requested(0),
|
||||
should_update_play_time(false),
|
||||
bb_character_index(-1),
|
||||
next_exp_value(0),
|
||||
can_chat(true),
|
||||
dol_base_addr(0),
|
||||
external_bank_character_index(-1),
|
||||
last_play_time_update(0) {
|
||||
save_game_data_timer(*server->get_io_context()),
|
||||
send_ping_timer(*server->get_io_context()),
|
||||
idle_timeout_timer(*server->get_io_context()),
|
||||
should_update_play_time(false) {
|
||||
this->update_channel_name();
|
||||
|
||||
this->config.set_flags_for_version(version, -1);
|
||||
// Don't print data sent to patch clients to the logs. The patch server
|
||||
// protocol is fully understood and data logs for patch clients are generally
|
||||
// more annoying than helpful at this point.
|
||||
auto s = server->get_state();
|
||||
if (is_v1_or_v2(this->version()) ? s->default_rare_notifs_enabled_v1_v2 : s->default_rare_notifs_enabled_v3_v4) {
|
||||
this->config.set_drop_notification_mode(ItemDropNotificationMode::RARES_ONLY);
|
||||
if (is_patch(this->version()) && s->hide_download_commands) {
|
||||
this->channel->terminal_recv_color = phosg::TerminalFormat::END;
|
||||
this->channel->terminal_send_color = phosg::TerminalFormat::END;
|
||||
} else {
|
||||
this->channel->terminal_recv_color = phosg::TerminalFormat::FG_GREEN;
|
||||
this->channel->terminal_send_color = phosg::TerminalFormat::FG_YELLOW;
|
||||
}
|
||||
this->config.specific_version = default_specific_version_for_version(version, -1);
|
||||
|
||||
memset(&this->next_connection_addr, 0, sizeof(this->next_connection_addr));
|
||||
this->set_flags_for_version(this->version(), -1);
|
||||
if (is_v1_or_v2(this->version()) ? s->default_rare_notifs_enabled_v1_v2 : s->default_rare_notifs_enabled_v3_v4) {
|
||||
this->set_drop_notification_mode(ItemDropNotificationMode::RARES_ONLY);
|
||||
}
|
||||
this->specific_version = default_specific_version_for_version(this->version(), -1);
|
||||
|
||||
this->reschedule_save_game_data_event();
|
||||
this->reschedule_ping_and_timeout_events();
|
||||
this->reschedule_save_game_data_timer();
|
||||
this->reschedule_ping_and_timeout_timers();
|
||||
|
||||
// Don't print data sent to patch clients to the logs. The patch server
|
||||
// protocol is fully understood and data logs for patch clients are generally
|
||||
// more annoying than helpful at this point.
|
||||
if ((s->hide_download_commands) &&
|
||||
((this->channel.version == Version::PC_PATCH) || (this->channel.version == Version::BB_PATCH))) {
|
||||
this->channel.terminal_recv_color = phosg::TerminalFormat::END;
|
||||
this->channel.terminal_send_color = phosg::TerminalFormat::END;
|
||||
((this->version() == Version::PC_PATCH) || (this->version() == Version::BB_PATCH))) {
|
||||
this->channel->terminal_recv_color = phosg::TerminalFormat::END;
|
||||
this->channel->terminal_send_color = phosg::TerminalFormat::END;
|
||||
} else {
|
||||
this->channel->terminal_send_color = phosg::TerminalFormat::FG_YELLOW;
|
||||
this->channel->terminal_recv_color = phosg::TerminalFormat::FG_GREEN;
|
||||
}
|
||||
|
||||
this->log.info("Created");
|
||||
this->log.info_f("Created");
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
if (!this->disconnect_hooks.empty()) {
|
||||
this->log.warning("Disconnect hooks pending at client destruction time:");
|
||||
this->log.warning_f("Disconnect hooks pending at client destruction time:");
|
||||
for (const auto& it : this->disconnect_hooks) {
|
||||
this->log.warning(" %s", it.first.c_str());
|
||||
this->log.warning_f(" {}", it.first);
|
||||
}
|
||||
}
|
||||
|
||||
if ((this->version() == Version::BB_V4) && (this->character_data.get())) {
|
||||
this->save_all();
|
||||
}
|
||||
this->log.info("Deleted");
|
||||
this->log.info_f("Deleted");
|
||||
}
|
||||
|
||||
void Client::update_channel_name() {
|
||||
string ip_str = this->require_server_state()->format_address_for_channel_name(
|
||||
this->channel.remote_addr, this->channel.virtual_network_id);
|
||||
string default_name = this->channel->default_name();
|
||||
|
||||
auto player = this->character(false, false);
|
||||
if (player) {
|
||||
string name_str = player->disp.name.decode(this->language());
|
||||
size_t level = player->disp.stats.level + 1;
|
||||
this->channel.name = phosg::string_printf("C-%" PRIX64 " (%s Lv.%zu) @ %s", this->id, name_str.c_str(), level, ip_str.c_str());
|
||||
this->channel->name = std::format("C-{:X} ({} Lv.{}) @ {}",
|
||||
this->id, name_str, level, default_name);
|
||||
} else {
|
||||
this->channel.name = phosg::string_printf("C-%" PRIX64 " @ %s", this->id, ip_str.c_str());
|
||||
this->channel->name = std::format("C-{:X} @ {}", this->id, default_name);
|
||||
}
|
||||
this->log.info("Channel name updated from player data: %s", this->channel.name.c_str());
|
||||
this->log.info_f("Channel name updated: {}", this->channel->name);
|
||||
}
|
||||
|
||||
void Client::reschedule_save_game_data_event() {
|
||||
if (this->version() == Version::BB_V4) {
|
||||
struct timeval tv = phosg::usecs_to_timeval(60000000); // 1 minute
|
||||
event_add(this->save_game_data_event.get(), &tv);
|
||||
void Client::reschedule_save_game_data_timer() {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
return;
|
||||
}
|
||||
this->save_game_data_timer.expires_after(std::chrono::seconds(60));
|
||||
this->idle_timeout_timer.async_wait([this](std::error_code ec) {
|
||||
if (!ec) {
|
||||
if (this->character(false)) {
|
||||
this->save_all();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Client::reschedule_ping_and_timeout_events() {
|
||||
void Client::reschedule_ping_and_timeout_timers() {
|
||||
auto s = this->require_server_state();
|
||||
struct timeval ping_tv = phosg::usecs_to_timeval(s->client_ping_interval_usecs);
|
||||
event_add(this->send_ping_event.get(), &ping_tv);
|
||||
struct timeval idle_tv = phosg::usecs_to_timeval(s->client_idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
if (!is_patch(this->version())) {
|
||||
this->send_ping_timer.expires_after(std::chrono::microseconds(s->client_ping_interval_usecs));
|
||||
this->send_ping_timer.async_wait([this](std::error_code ec) {
|
||||
if (!ec) {
|
||||
this->log.info_f("Sending ping command");
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this->idle_timeout_timer.expires_after(std::chrono::microseconds(s->client_idle_timeout_usecs));
|
||||
this->idle_timeout_timer.async_wait([this](std::error_code ec) {
|
||||
if (!ec) {
|
||||
this->log.info_f("Idle timeout expired");
|
||||
this->channel->disconnect();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Client::convert_account_to_temporary_if_nte() {
|
||||
@@ -312,7 +299,7 @@ void Client::convert_account_to_temporary_if_nte() {
|
||||
// replace it with a temporary account.
|
||||
auto s = this->require_server_state();
|
||||
if (s->use_temp_accounts_for_prototypes && this->login->account_was_created && is_any_nte(this->version())) {
|
||||
this->log.info("Client is a prototype version and the account was created during this session; converting permanent account to temporary account");
|
||||
this->log.info_f("Client is a prototype version and the account was created during this session; converting permanent account to temporary account");
|
||||
this->login->account->is_temporary = true;
|
||||
this->login->account->delete_file();
|
||||
this->login->account_was_created = false;
|
||||
@@ -348,7 +335,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
auto s = this->require_server_state();
|
||||
auto team = s->team_index->get_by_id(this->login->account->bb_team_id);
|
||||
if (!team) {
|
||||
this->log.info("Account contains a team ID, but the team does not exist; clearing team ID from account");
|
||||
this->log.info_f("Account contains a team ID, but the team does not exist; clearing team ID from account");
|
||||
this->login->account->bb_team_id = 0;
|
||||
this->login->account->save();
|
||||
return nullptr;
|
||||
@@ -356,7 +343,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
|
||||
auto member_it = team->members.find(this->login->account->account_id);
|
||||
if (member_it == team->members.end()) {
|
||||
this->log.info("Account contains a team ID, but the team does not contain this member; clearing team ID from account");
|
||||
this->log.info_f("Account contains a team ID, but the team does not contain this member; clearing team ID from account");
|
||||
this->login->account->bb_team_id = 0;
|
||||
this->login->account->save();
|
||||
return nullptr;
|
||||
@@ -368,7 +355,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
||||
auto& m = member_it->second;
|
||||
string name = p->disp.name.decode(this->language());
|
||||
if (m.name != name) {
|
||||
this->log.info("Updating player name in team config");
|
||||
this->log.info_f("Updating player name in team config");
|
||||
s->team_index->update_member_name(this->login->account->account_id, name);
|
||||
}
|
||||
}
|
||||
@@ -402,9 +389,9 @@ bool Client::evaluate_quest_availability_expression(
|
||||
.v1_present = v1_present,
|
||||
};
|
||||
int64_t ret = expr->evaluate(env);
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
string expr_str = expr->str();
|
||||
this->log.info("Evaluated integral expression %s => %s", expr_str.c_str(), ret ? "TRUE" : "FALSE");
|
||||
this->log.info_f("Evaluated integral expression {} => {}", expr_str, ret ? "TRUE" : "FALSE");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -448,62 +435,11 @@ bool Client::can_use_chat_commands() const {
|
||||
return this->require_server_state()->enable_chat_commands;
|
||||
}
|
||||
|
||||
void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->save_game_data();
|
||||
}
|
||||
|
||||
void Client::save_game_data() {
|
||||
if (this->version() != Version::BB_V4) {
|
||||
throw logic_error("save_game_data called for non-BB client");
|
||||
}
|
||||
if (this->character(false)) {
|
||||
this->save_all();
|
||||
}
|
||||
}
|
||||
|
||||
void Client::dispatch_send_ping(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->send_ping();
|
||||
}
|
||||
|
||||
void Client::send_ping() {
|
||||
if (!is_patch(this->version())) {
|
||||
this->log.info("Sending ping command");
|
||||
// The game doesn't use this timestamp; we only use it for debugging purposes
|
||||
be_uint64_t timestamp = phosg::now();
|
||||
try {
|
||||
this->channel.send(0x1D, 0x00, ×tamp, sizeof(be_uint64_t));
|
||||
} catch (const exception& e) {
|
||||
this->log.info("Failed to send ping: %s", e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->idle_timeout();
|
||||
}
|
||||
|
||||
void Client::idle_timeout() {
|
||||
this->log.info("Idle timeout expired");
|
||||
auto s = this->server.lock();
|
||||
if (s) {
|
||||
auto c = this->shared_from_this();
|
||||
s->disconnect_client(c);
|
||||
} else {
|
||||
this->log.info("Server is deleted; cannot disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
void Client::suspend_timeouts() {
|
||||
event_del(this->send_ping_event.get());
|
||||
event_del(this->idle_timeout_event.get());
|
||||
this->log.info("Timeouts suspended");
|
||||
}
|
||||
|
||||
void Client::set_login(shared_ptr<Login> login) {
|
||||
this->login = login;
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
string login_str = this->login->str();
|
||||
this->log.info("Login: %s", login_str.c_str());
|
||||
this->log.info_f("Login: {}", login_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,7 +605,7 @@ string Client::system_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return phosg::string_printf("system/players/system_%s.psosys", this->login->bb_license->username.c_str());
|
||||
return std::format("system/players/system_{}.psosys", this->login->bb_license->username);
|
||||
}
|
||||
|
||||
string Client::character_filename(const std::string& bb_username, ssize_t index) {
|
||||
@@ -679,11 +615,11 @@ string Client::character_filename(const std::string& bb_username, ssize_t index)
|
||||
if (index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
}
|
||||
return phosg::string_printf("system/players/player_%s_%zd.psochar", bb_username.c_str(), index);
|
||||
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) {
|
||||
return phosg::string_printf("system/players/backup_player_%" PRIu32 "_%zu.%s",
|
||||
return std::format("system/players/backup_player_{}_{}.{}",
|
||||
account_id, index, is_ep3 ? "pso3char" : "psochar");
|
||||
}
|
||||
|
||||
@@ -704,7 +640,7 @@ string Client::guild_card_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return phosg::string_printf("system/players/guild_cards_%s.psocard", this->login->bb_license->username.c_str());
|
||||
return std::format("system/players/guild_cards_{}.psocard", this->login->bb_license->username);
|
||||
}
|
||||
|
||||
string Client::shared_bank_filename() const {
|
||||
@@ -714,7 +650,7 @@ string Client::shared_bank_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return phosg::string_printf("system/players/shared_bank_%s.psobank", this->login->bb_license->username.c_str());
|
||||
return std::format("system/players/shared_bank_{}.psobank", this->login->bb_license->username);
|
||||
}
|
||||
|
||||
string Client::legacy_account_filename() const {
|
||||
@@ -724,7 +660,7 @@ string Client::legacy_account_filename() const {
|
||||
if (!this->login || !this->login->bb_license) {
|
||||
throw logic_error("client is not logged in");
|
||||
}
|
||||
return phosg::string_printf("system/players/account_%s.nsa", this->login->bb_license->username.c_str());
|
||||
return std::format("system/players/account_{}.nsa", this->login->bb_license->username);
|
||||
}
|
||||
|
||||
string Client::legacy_player_filename() const {
|
||||
@@ -737,9 +673,9 @@ string Client::legacy_player_filename() const {
|
||||
if (this->bb_character_index < 0) {
|
||||
throw logic_error("character index is not set");
|
||||
}
|
||||
return phosg::string_printf(
|
||||
"system/players/player_%s_%zd.nsc",
|
||||
this->login->bb_license->username.c_str(),
|
||||
return std::format(
|
||||
"system/players/player_{}_{}.nsc",
|
||||
this->login->bb_license->username,
|
||||
static_cast<ssize_t>(this->bb_character_index + 1));
|
||||
}
|
||||
|
||||
@@ -772,25 +708,25 @@ void Client::load_all_files() {
|
||||
string sys_filename = this->system_filename();
|
||||
this->system_data = files_manager->get_system(sys_filename);
|
||||
if (this->system_data) {
|
||||
player_data_log.info("Using loaded system file %s", sys_filename.c_str());
|
||||
} else if (phosg::isfile(sys_filename)) {
|
||||
player_data_log.info_f("Using loaded system file {}", sys_filename);
|
||||
} else if (std::filesystem::is_regular_file(sys_filename)) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(phosg::load_object_file<PSOBBBaseSystemFile>(sys_filename, true));
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded system data from %s", sys_filename.c_str());
|
||||
player_data_log.info_f("Loaded system data from {}", sys_filename);
|
||||
} else {
|
||||
player_data_log.info("System file is missing: %s", sys_filename.c_str());
|
||||
player_data_log.info_f("System file is missing: {}", sys_filename);
|
||||
}
|
||||
|
||||
if (this->bb_character_index >= 0) {
|
||||
string char_filename = this->character_filename();
|
||||
this->character_data = files_manager->get_character(char_filename);
|
||||
if (this->character_data) {
|
||||
player_data_log.info("Using loaded character file %s", char_filename.c_str());
|
||||
} else if (phosg::isfile(char_filename)) {
|
||||
player_data_log.info_f("Using loaded character file {}", char_filename);
|
||||
} else if (std::filesystem::is_regular_file(char_filename)) {
|
||||
auto psochar = PSOCHARFile::load_shared(char_filename, !this->system_data);
|
||||
this->character_data = psochar.character_file;
|
||||
files_manager->set_character(char_filename, this->character_data);
|
||||
player_data_log.info("Loaded character data from %s", char_filename.c_str());
|
||||
player_data_log.info_f("Loaded character data from {}", char_filename);
|
||||
|
||||
// If there was no .psosys file, use the system file from the .psochar
|
||||
// file instead
|
||||
@@ -800,34 +736,34 @@ void Client::load_all_files() {
|
||||
}
|
||||
this->system_data = psochar.system_file;
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded system data from %s", char_filename.c_str());
|
||||
player_data_log.info_f("Loaded system data from {}", char_filename);
|
||||
}
|
||||
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
this->system_data->language = this->language();
|
||||
|
||||
} else {
|
||||
player_data_log.info("Character file is missing: %s", char_filename.c_str());
|
||||
player_data_log.info_f("Character file is missing: {}", char_filename);
|
||||
}
|
||||
}
|
||||
|
||||
string card_filename = this->guild_card_filename();
|
||||
this->guild_card_data = files_manager->get_guild_card(card_filename);
|
||||
if (this->guild_card_data) {
|
||||
player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str());
|
||||
} else if (phosg::isfile(card_filename)) {
|
||||
player_data_log.info_f("Using loaded Guild Card file {}", card_filename);
|
||||
} else if (std::filesystem::is_regular_file(card_filename)) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>(phosg::load_object_file<PSOBBGuildCardFile>(card_filename));
|
||||
files_manager->set_guild_card(card_filename, this->guild_card_data);
|
||||
player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str());
|
||||
player_data_log.info_f("Loaded Guild Card data from {}", card_filename);
|
||||
} else {
|
||||
player_data_log.info("Guild Card file is missing: %s", card_filename.c_str());
|
||||
player_data_log.info_f("Guild Card file is missing: {}", card_filename);
|
||||
}
|
||||
|
||||
// If any of the above files were 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;
|
||||
if (phosg::isfile(nsa_filename)) {
|
||||
if (std::filesystem::is_regular_file(nsa_filename)) {
|
||||
nsa_data = 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");
|
||||
@@ -835,12 +771,12 @@ void Client::load_all_files() {
|
||||
if (!this->system_data) {
|
||||
this->system_data = make_shared<PSOBBBaseSystemFile>(nsa_data->system_file);
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str());
|
||||
player_data_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);
|
||||
files_manager->set_guild_card(card_filename, this->guild_card_data);
|
||||
player_data_log.info("Loaded legacy Guild Card data from %s", nsa_filename.c_str());
|
||||
player_data_log.info_f("Loaded legacy Guild Card data from {}", nsa_filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,12 +790,12 @@ void Client::load_all_files() {
|
||||
this->system_data->joystick_config = *s->bb_default_joystick_config;
|
||||
}
|
||||
files_manager->set_system(sys_filename, this->system_data);
|
||||
player_data_log.info("Created new system data");
|
||||
player_data_log.info_f("Created new system data");
|
||||
}
|
||||
if (!this->guild_card_data) {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
||||
files_manager->set_guild_card(card_filename, this->guild_card_data);
|
||||
player_data_log.info("Created new Guild Card data");
|
||||
player_data_log.info_f("Created new Guild Card data");
|
||||
}
|
||||
|
||||
if (!this->character_data && (this->bb_character_index >= 0)) {
|
||||
@@ -900,9 +836,9 @@ void Client::load_all_files() {
|
||||
this->character_data->option_flags = nsa_data->option_flags;
|
||||
this->character_data->symbol_chats = nsa_data->symbol_chats;
|
||||
this->character_data->shortcuts = nsa_data->shortcuts;
|
||||
player_data_log.info("Loaded legacy player data from %s and %s", nsa_filename.c_str(), nsc_filename.c_str());
|
||||
player_data_log.info_f("Loaded legacy player data from {} and {}", nsa_filename, nsc_filename);
|
||||
} else {
|
||||
player_data_log.info("Loaded legacy player data from %s", nsc_filename.c_str());
|
||||
player_data_log.info_f("Loaded legacy player data from {}", nsc_filename);
|
||||
}
|
||||
this->update_character_data_after_load(this->character_data);
|
||||
}
|
||||
@@ -928,7 +864,7 @@ void Client::update_character_data_after_load(shared_ptr<PSOBBCharacterFile> cha
|
||||
charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version()));
|
||||
|
||||
uint8_t lang = this->language();
|
||||
player_data_log.info("Overriding language fields in save files with %02hhX (%c)", lang, char_for_language_code(lang));
|
||||
player_data_log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang));
|
||||
charfile->inventory.language = lang;
|
||||
charfile->guild_card.language = lang;
|
||||
}
|
||||
@@ -946,7 +882,7 @@ void Client::save_all() {
|
||||
if (this->external_bank) {
|
||||
string filename = this->shared_bank_filename();
|
||||
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
player_data_log.info("Saved shared bank file %s", filename.c_str());
|
||||
player_data_log.info_f("Saved shared bank file {}", filename);
|
||||
}
|
||||
if (this->external_bank_character) {
|
||||
this->save_character_file(
|
||||
@@ -962,7 +898,7 @@ void Client::save_system_file() const {
|
||||
}
|
||||
string filename = this->system_filename();
|
||||
phosg::save_object_file(filename, *this->system_data);
|
||||
player_data_log.info("Saved system file %s", filename.c_str());
|
||||
player_data_log.info_f("Saved system file {}", filename);
|
||||
}
|
||||
|
||||
void Client::save_character_file(
|
||||
@@ -970,14 +906,14 @@ void Client::save_character_file(
|
||||
shared_ptr<const PSOBBBaseSystemFile> system,
|
||||
shared_ptr<const PSOBBCharacterFile> character) {
|
||||
PSOCHARFile::save(filename, system, character);
|
||||
player_data_log.info("Saved character file %s", filename.c_str());
|
||||
player_data_log.info_f("Saved character file {}", filename);
|
||||
}
|
||||
|
||||
void Client::save_ep3_character_file(
|
||||
const string& filename,
|
||||
const PSOGCEp3CharacterFile::Character& character) {
|
||||
phosg::save_file(filename, &character, sizeof(character));
|
||||
player_data_log.info("Saved Episode 3 character file %s", filename.c_str());
|
||||
player_data_log.info_f("Saved Episode 3 character file {}", filename);
|
||||
}
|
||||
|
||||
void Client::save_character_file() {
|
||||
@@ -993,7 +929,7 @@ void Client::save_character_file() {
|
||||
uint64_t t = phosg::now();
|
||||
uint64_t seconds = (t - this->last_play_time_update) / 1000000;
|
||||
this->character_data->play_time_seconds += seconds;
|
||||
player_data_log.info("Added %" PRIu64 " seconds to play time", seconds);
|
||||
player_data_log.info_f("Added {} seconds to play time", seconds);
|
||||
this->last_play_time_update = t;
|
||||
}
|
||||
|
||||
@@ -1006,7 +942,7 @@ void Client::save_guild_card_file() const {
|
||||
}
|
||||
string filename = this->guild_card_filename();
|
||||
phosg::save_object_file(filename, *this->guild_card_data);
|
||||
player_data_log.info("Saved Guild Card file %s", filename.c_str());
|
||||
player_data_log.info_f("Saved Guild Card file {}", filename);
|
||||
}
|
||||
|
||||
void Client::load_backup_character(uint32_t account_id, size_t index) {
|
||||
@@ -1030,7 +966,7 @@ void Client::save_and_unload_character() {
|
||||
if (this->character_data) {
|
||||
this->save_character_file();
|
||||
this->character_data.reset();
|
||||
this->log.info("Unloaded character");
|
||||
this->log.info_f("Unloaded character");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,13 +992,13 @@ void Client::use_default_bank() {
|
||||
string filename = this->shared_bank_filename();
|
||||
phosg::save_object_file<PlayerBank200>(filename, *this->external_bank);
|
||||
this->external_bank.reset();
|
||||
player_data_log.info("Detached shared bank %s", filename.c_str());
|
||||
player_data_log.info_f("Detached shared bank {}", filename);
|
||||
}
|
||||
if (this->external_bank_character) {
|
||||
string filename = this->character_filename(this->external_bank_character_index);
|
||||
this->save_character_file(filename, this->system_data, this->external_bank_character);
|
||||
this->external_bank_character.reset();
|
||||
player_data_log.info("Detached character %s from bank", filename.c_str());
|
||||
player_data_log.info_f("Detached character {} from bank", filename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,17 +1009,17 @@ bool Client::use_shared_bank() {
|
||||
auto files_manager = this->require_server_state()->player_files_manager;
|
||||
this->external_bank = files_manager->get_bank(filename);
|
||||
if (this->external_bank) {
|
||||
player_data_log.info("Using loaded shared bank %s", filename.c_str());
|
||||
player_data_log.info_f("Using loaded shared bank {}", filename);
|
||||
return true;
|
||||
} else if (phosg::isfile(filename)) {
|
||||
} else if (std::filesystem::is_regular_file(filename)) {
|
||||
this->external_bank = make_shared<PlayerBank200>(phosg::load_object_file<PlayerBank200>(filename));
|
||||
files_manager->set_bank(filename, this->external_bank);
|
||||
player_data_log.info("Loaded shared bank %s", filename.c_str());
|
||||
player_data_log.info_f("Loaded shared bank {}", filename);
|
||||
return true;
|
||||
} else {
|
||||
this->external_bank = make_shared<PlayerBank200>();
|
||||
files_manager->set_bank(filename, this->external_bank);
|
||||
player_data_log.info("Created shared bank for %s", filename.c_str());
|
||||
player_data_log.info_f("Created shared bank for {}", filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1097,13 +1033,13 @@ void Client::use_character_bank(ssize_t index) {
|
||||
this->external_bank_character = files_manager->get_character(filename);
|
||||
if (this->external_bank_character) {
|
||||
this->external_bank_character_index = index;
|
||||
player_data_log.info("Using loaded character file %s for external bank", filename.c_str());
|
||||
} else if (phosg::isfile(filename)) {
|
||||
player_data_log.info_f("Using loaded character file {} for external bank", filename);
|
||||
} else if (std::filesystem::is_regular_file(filename)) {
|
||||
this->external_bank_character = PSOCHARFile::load_shared(filename, false).character_file;
|
||||
this->update_character_data_after_load(this->external_bank_character);
|
||||
this->external_bank_character_index = index;
|
||||
files_manager->set_character(filename, this->external_bank_character);
|
||||
player_data_log.info("Loaded character data from %s for external bank", filename.c_str());
|
||||
player_data_log.info_f("Loaded character data from {} for external bank", filename);
|
||||
} else {
|
||||
throw runtime_error("character does not exist");
|
||||
}
|
||||
@@ -1113,26 +1049,45 @@ void Client::use_character_bank(ssize_t index) {
|
||||
void Client::print_inventory(FILE* stream) const {
|
||||
auto s = this->require_server_state();
|
||||
auto p = this->character();
|
||||
fprintf(stream, "[PlayerInventory] Meseta: %" PRIu32 "\n", p->disp.stats.meseta.load());
|
||||
fprintf(stream, "[PlayerInventory] %hhu items\n", p->inventory.num_items);
|
||||
phosg::fwrite_fmt(stream, "[PlayerInventory] Meseta: {}\n", p->disp.stats.meseta);
|
||||
phosg::fwrite_fmt(stream, "[PlayerInventory] {} items\n", p->inventory.num_items);
|
||||
for (size_t x = 0; x < p->inventory.num_items; x++) {
|
||||
const auto& item = p->inventory.items[x];
|
||||
auto hex = item.data.hex();
|
||||
auto name = s->describe_item(this->version(), item.data, false);
|
||||
fprintf(stream, "[PlayerInventory] %2zu: [+%08" PRIX32 "] %s (%s)\n", x, item.flags.load(), hex.c_str(), name.c_str());
|
||||
phosg::fwrite_fmt(stream, "[PlayerInventory] {:2}: [+{:08X}] {} ({})\n", x, item.flags, hex, name);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::print_bank(FILE* stream) const {
|
||||
auto s = this->require_server_state();
|
||||
auto bank = this->current_bank();
|
||||
fprintf(stream, "[PlayerBank] Meseta: %" PRIu32 "\n", bank.meseta.load());
|
||||
fprintf(stream, "[PlayerBank] %" PRIu32 " items\n", bank.num_items.load());
|
||||
phosg::fwrite_fmt(stream, "[PlayerBank] Meseta: {}\n", bank.meseta);
|
||||
phosg::fwrite_fmt(stream, "[PlayerBank] {} items\n", bank.num_items);
|
||||
for (size_t x = 0; x < bank.num_items; x++) {
|
||||
const auto& item = bank.items[x];
|
||||
const char* present_token = item.present ? "" : " (missing present flag)";
|
||||
auto hex = item.data.hex();
|
||||
auto name = s->describe_item(this->version(), item.data, false);
|
||||
fprintf(stream, "[PlayerBank] %3zu: %s (%s) (x%hu)%s\n", x, hex.c_str(), name.c_str(), item.amount.load(), present_token);
|
||||
phosg::fwrite_fmt(stream, "[PlayerBank] {:3}: {} ({}) (x{}){}\n", x, hex, name, item.amount, present_token);
|
||||
}
|
||||
}
|
||||
|
||||
void Client::cancel_pending_promises() {
|
||||
for (const auto& promise : this->function_call_response_queue) {
|
||||
if (!promise->done()) {
|
||||
promise->cancel();
|
||||
}
|
||||
}
|
||||
this->function_call_response_queue.clear();
|
||||
|
||||
if (this->character_data_ready_promise && !this->character_data_ready_promise->done()) {
|
||||
this->character_data_ready_promise->cancel();
|
||||
}
|
||||
this->character_data_ready_promise.reset();
|
||||
|
||||
if (this->enable_save_promise && !this->enable_save_promise->done()) {
|
||||
this->enable_save_promise->cancel();
|
||||
}
|
||||
this->enable_save_promise.reset();
|
||||
}
|
||||
|
||||
+104
-153
@@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "AsyncUtils.hh"
|
||||
#include "Channel.hh"
|
||||
#include "CommandFormats.hh"
|
||||
#include "Episode3/BattleRecord.hh"
|
||||
@@ -15,6 +14,7 @@
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "ProxySession.hh"
|
||||
#include "Quest.hh"
|
||||
#include "QuestScript.hh"
|
||||
#include "TeamIndex.hh"
|
||||
@@ -22,21 +22,22 @@
|
||||
|
||||
extern const uint64_t CLIENT_CONFIG_MAGIC;
|
||||
|
||||
class Server;
|
||||
class GameServer;
|
||||
struct Lobby;
|
||||
class Parsed6x70Data;
|
||||
|
||||
struct GetPlayerInfoResult {
|
||||
// Exactly one of the following two 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
|
||||
};
|
||||
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
enum class Flag : uint64_t {
|
||||
// clang-format off
|
||||
|
||||
// This mask specifies which flags are sent to the client
|
||||
// TODO: It'd be nice to use a pattern here (e.g. all server-side flags are
|
||||
// in the high bits) but that would require re-recording or manually
|
||||
// rewriting all the tests
|
||||
CLIENT_SIDE_MASK = 0xEF3CFFFF7C0BFFFB,
|
||||
|
||||
// Version-related flags
|
||||
CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002,
|
||||
NO_D6_AFTER_LOBBY = 0x0000000000000100,
|
||||
@@ -61,8 +62,6 @@ public:
|
||||
SAVE_ENABLED = 0x0000000004000000,
|
||||
HAS_EP3_CARD_DEFS = 0x0000000008000000,
|
||||
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
|
||||
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
|
||||
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
|
||||
HAS_AUTO_PATCHES = 0x0000004000000000,
|
||||
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
|
||||
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
|
||||
@@ -86,16 +85,10 @@ public:
|
||||
PROXY_SAVE_FILES = 0x0000001000000000,
|
||||
PROXY_CHAT_COMMANDS_ENABLED = 0x0000002000000000,
|
||||
PROXY_PLAYER_NOTIFICATIONS_ENABLED = 0x0000008000000000,
|
||||
PROXY_SUPPRESS_CLIENT_PINGS = 0x0000010000000000,
|
||||
PROXY_SUPPRESS_REMOTE_LOGIN = 0x0000020000000000,
|
||||
PROXY_ZERO_REMOTE_GUILD_CARD = 0x0000040000000000,
|
||||
PROXY_EP3_INFINITE_MESETA_ENABLED = 0x0000080000000000,
|
||||
PROXY_EP3_INFINITE_TIME_ENABLED = 0x0000100000000000,
|
||||
PROXY_RED_NAME_ENABLED = 0x0000200000000000,
|
||||
PROXY_BLANK_NAME_ENABLED = 0x0000400000000000,
|
||||
PROXY_BLOCK_FUNCTION_CALLS = 0x0000800000000000,
|
||||
PROXY_EP3_UNMASK_WHISPERS = 0x0008000000000000,
|
||||
PROXY_VIRTUAL_CLIENT = 0x0400000000000000,
|
||||
// clang-format on
|
||||
};
|
||||
enum class ItemDropNotificationMode {
|
||||
@@ -107,123 +100,70 @@ public:
|
||||
|
||||
static constexpr uint64_t DEFAULT_FLAGS = static_cast<uint64_t>(Flag::PROXY_CHAT_COMMANDS_ENABLED);
|
||||
|
||||
struct Config {
|
||||
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
|
||||
uint32_t specific_version = 0;
|
||||
int32_t override_random_seed = 0;
|
||||
uint8_t override_section_id = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_event = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||
uint32_t proxy_destination_address = 0;
|
||||
uint16_t proxy_destination_port = 0;
|
||||
|
||||
Config() = default;
|
||||
|
||||
bool operator==(const Config& other) const = default;
|
||||
bool operator!=(const Config& other) const = default;
|
||||
|
||||
bool should_update_vs(const Config& other) const;
|
||||
|
||||
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
|
||||
return !!(enabled_flags & static_cast<uint64_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return this->check_flag(this->enabled_flags, flag);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint64_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint64_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint64_t>(flag);
|
||||
}
|
||||
|
||||
void set_flags_for_version(Version version, int64_t sub_version);
|
||||
|
||||
ItemDropNotificationMode get_drop_notification_mode() const;
|
||||
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
|
||||
|
||||
template <size_t Bytes>
|
||||
void parse_from(const parray<uint8_t, Bytes>& data) {
|
||||
phosg::StringReader r(data.data(), data.size());
|
||||
if (r.get_u32l() != CLIENT_CONFIG_MAGIC) {
|
||||
throw std::invalid_argument("config signature is incorrect");
|
||||
}
|
||||
this->specific_version = r.get_u32l();
|
||||
this->enabled_flags = r.get_u64l();
|
||||
this->override_random_seed = r.get_u32l();
|
||||
this->proxy_destination_address = r.get_u32b();
|
||||
this->proxy_destination_port = r.get_u16l();
|
||||
this->override_section_id = r.get_u8();
|
||||
this->override_lobby_event = r.get_u8();
|
||||
this->override_lobby_number = r.get_u8();
|
||||
}
|
||||
|
||||
template <size_t Bytes>
|
||||
void serialize_into(parray<uint8_t, Bytes>& data) const {
|
||||
phosg::StringWriter w;
|
||||
w.put_u32l(CLIENT_CONFIG_MAGIC);
|
||||
w.put_u32l(this->specific_version);
|
||||
w.put_u64l(this->enabled_flags & static_cast<uint64_t>(Flag::CLIENT_SIDE_MASK));
|
||||
w.put_u32l(this->override_random_seed);
|
||||
w.put_u32b(this->proxy_destination_address);
|
||||
w.put_u16l(this->proxy_destination_port);
|
||||
w.put_u8(this->override_section_id);
|
||||
w.put_u8(this->override_lobby_event);
|
||||
w.put_u8(this->override_lobby_number);
|
||||
|
||||
const auto& s = w.str();
|
||||
for (size_t z = 0; z < s.size(); z++) {
|
||||
data[z] = s[z];
|
||||
}
|
||||
data.clear_after(s.size(), 0xFF);
|
||||
}
|
||||
};
|
||||
|
||||
std::weak_ptr<Server> server;
|
||||
std::weak_ptr<GameServer> server;
|
||||
uint64_t id;
|
||||
phosg::PrefixedLogger log;
|
||||
|
||||
// Account information (not all of these are used; depends on game version)
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string email_address;
|
||||
uint64_t hardware_id = 0;
|
||||
int32_t sub_version = 0;
|
||||
uint8_t bb_client_code = 0;
|
||||
uint8_t bb_connection_phase = 0xFF;
|
||||
ssize_t bb_character_index = -1; // -1 = not set
|
||||
uint32_t bb_security_token = 0;
|
||||
parray<uint8_t, 0x28> bb_client_config;
|
||||
std::string login_character_name;
|
||||
std::string serial_number;
|
||||
std::string access_key;
|
||||
std::string serial_number2;
|
||||
std::string access_key2;
|
||||
std::string v1_serial_number;
|
||||
std::string v1_access_key;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_unknown_a1a;
|
||||
uint64_t xb_user_id = 0;
|
||||
uint32_t xb_unknown_a1b = 0;
|
||||
std::shared_ptr<Login> login;
|
||||
std::shared_ptr<ProxySession> proxy_session;
|
||||
|
||||
// Patch server state (only used for PC_PATCH and BB_PATCH versions)
|
||||
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
|
||||
|
||||
// Network
|
||||
Channel channel;
|
||||
struct sockaddr_storage next_connection_addr;
|
||||
std::shared_ptr<Channel> channel;
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> bb_detector_crypt;
|
||||
ServerBehavior server_behavior;
|
||||
bool should_disconnect;
|
||||
bool should_send_to_lobby_server;
|
||||
bool should_send_to_proxy_server;
|
||||
std::unordered_map<std::string, std::function<void()>> disconnect_hooks;
|
||||
std::shared_ptr<XBNetworkLocation> xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
uint8_t bb_connection_phase;
|
||||
uint64_t ping_start_time;
|
||||
uint64_t ping_start_time = 0;
|
||||
|
||||
// Lobby/positioning
|
||||
Config config;
|
||||
Config synced_config;
|
||||
// Basic state
|
||||
uint64_t enabled_flags = DEFAULT_FLAGS; // Client::Flag enum
|
||||
uint32_t specific_version = 0;
|
||||
uint8_t override_section_id = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_event = 0xFF; // FF = no override
|
||||
uint8_t override_lobby_number = 0x80; // 80 = no override
|
||||
int64_t override_random_seed = -1;
|
||||
std::unique_ptr<Variations> override_variations;
|
||||
int32_t sub_version;
|
||||
VectorXZF pos;
|
||||
uint32_t floor;
|
||||
uint32_t floor = 0x0F;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
uint8_t lobby_client_id;
|
||||
uint8_t lobby_arrow_color;
|
||||
int64_t preferred_lobby_id; // <0 = no preference
|
||||
uint8_t lobby_client_id = 0;
|
||||
uint8_t lobby_arrow_color = 0;
|
||||
int64_t preferred_lobby_id = -1; // <0 = no preference
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> save_game_data_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> send_ping_event;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
int16_t card_battle_table_number;
|
||||
uint16_t card_battle_table_seat_number;
|
||||
uint16_t card_battle_table_seat_state;
|
||||
asio::steady_timer save_game_data_timer;
|
||||
asio::steady_timer send_ping_timer;
|
||||
asio::steady_timer idle_timeout_timer;
|
||||
int16_t card_battle_table_number = -1;
|
||||
uint16_t card_battle_table_seat_number = 0;
|
||||
uint16_t card_battle_table_seat_state = 0;
|
||||
std::weak_ptr<Episode3::Tournament::Team> ep3_tournament_team;
|
||||
std::shared_ptr<const Episode3::BattleRecord> ep3_prev_battle_record;
|
||||
std::shared_ptr<const Menu> last_menu_sent;
|
||||
uint32_t last_game_info_requested;
|
||||
uint32_t last_game_info_requested = 0;
|
||||
struct JoinCommand {
|
||||
uint16_t command;
|
||||
uint32_t flag;
|
||||
@@ -246,52 +186,72 @@ public:
|
||||
std::unordered_set<uint32_t> blocked_senders;
|
||||
std::unique_ptr<PlayerDispDataDCPCV3> v1_v2_last_reported_disp;
|
||||
std::shared_ptr<Parsed6x70Data> last_reported_6x70;
|
||||
// These are null unless the client is within the trade sequence (D0-D4 or EE commands)
|
||||
// These are null unless the client is within the trade sequence (D0-D4 or EE
|
||||
// commands)
|
||||
std::unique_ptr<PendingItemTrade> pending_item_trade;
|
||||
std::unique_ptr<PendingCardTrade> pending_card_trade;
|
||||
uint32_t telepipe_lobby_id;
|
||||
uint32_t telepipe_lobby_id = 0;
|
||||
TelepipeState telepipe_state;
|
||||
std::shared_ptr<Episode3::PlayerConfig> ep3_config; // Null for non-Ep3
|
||||
ssize_t bb_character_index; // -1 = not set
|
||||
ItemData bb_identify_result;
|
||||
std::array<std::vector<ItemData>, 3> bb_shop_contents;
|
||||
|
||||
// Miscellaneous (used by chat commands)
|
||||
uint32_t next_exp_value; // next EXP value to give
|
||||
bool can_chat;
|
||||
struct PendingCharacterExport {
|
||||
std::shared_ptr<const Account> dest_account;
|
||||
ssize_t character_index = -1;
|
||||
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
|
||||
};
|
||||
std::unique_ptr<PendingCharacterExport> pending_character_export;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
uint32_t next_exp_value = 0; // next EXP value to give
|
||||
bool can_chat = true;
|
||||
// NOTE: If you add any new optional promises here, make sure to also add
|
||||
// them to cancel_pending_promises.
|
||||
// NOTE: Entries in this queue can be nullptr; that represents a B2 command
|
||||
// sent by the remote server during a proxy session. We can't just omit those
|
||||
// from the queue entirely, because if we did, we could end up sending the
|
||||
// wrong B3 response back.
|
||||
std::deque<std::shared_ptr<AsyncPromise<C_ExecuteCodeResult_B3>>> function_call_response_queue;
|
||||
std::shared_ptr<AsyncPromise<GetPlayerInfoResult>> character_data_ready_promise;
|
||||
std::shared_ptr<AsyncPromise<void>> enable_save_promise;
|
||||
|
||||
// File loading state
|
||||
uint32_t dol_base_addr;
|
||||
std::shared_ptr<DOLFileIndex::File> loading_dol_file;
|
||||
std::unordered_map<std::string, std::shared_ptr<const std::string>> sending_files;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<Server> server,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
Version version,
|
||||
std::shared_ptr<GameServer> server,
|
||||
std::shared_ptr<Channel> channel,
|
||||
ServerBehavior server_behavior);
|
||||
~Client();
|
||||
|
||||
void update_channel_name();
|
||||
|
||||
void reschedule_save_game_data_event();
|
||||
void reschedule_ping_and_timeout_events();
|
||||
void reschedule_save_game_data_timer();
|
||||
void reschedule_ping_and_timeout_timers();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
return this->channel->version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->channel.language;
|
||||
return this->channel->language;
|
||||
}
|
||||
|
||||
[[nodiscard]] static inline bool check_flag(uint64_t enabled_flags, Flag flag) {
|
||||
return !!(enabled_flags & static_cast<uint64_t>(flag));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool check_flag(Flag flag) const {
|
||||
return this->check_flag(this->enabled_flags, flag);
|
||||
}
|
||||
inline void set_flag(Flag flag) {
|
||||
this->enabled_flags |= static_cast<uint64_t>(flag);
|
||||
}
|
||||
inline void clear_flag(Flag flag) {
|
||||
this->enabled_flags &= (~static_cast<uint64_t>(flag));
|
||||
}
|
||||
inline void toggle_flag(Flag flag) {
|
||||
this->enabled_flags ^= static_cast<uint64_t>(flag);
|
||||
}
|
||||
|
||||
void set_flags_for_version(Version version, int64_t sub_version);
|
||||
|
||||
ItemDropNotificationMode get_drop_notification_mode() const;
|
||||
void set_drop_notification_mode(ItemDropNotificationMode new_mode);
|
||||
|
||||
void convert_account_to_temporary_if_nte();
|
||||
|
||||
void sync_config();
|
||||
@@ -325,15 +285,6 @@ public:
|
||||
|
||||
bool can_use_chat_commands() const;
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
void save_game_data();
|
||||
static void dispatch_send_ping(evutil_socket_t, short, void* ctx);
|
||||
void send_ping();
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
|
||||
void suspend_timeouts();
|
||||
|
||||
void set_login(std::shared_ptr<Login> login);
|
||||
|
||||
void create_battle_overlay(std::shared_ptr<const BattleRules> rules, std::shared_ptr<const LevelTable> level_table);
|
||||
@@ -397,6 +348,8 @@ public:
|
||||
void print_inventory(FILE* stream) const;
|
||||
void print_bank(FILE* stream) const;
|
||||
|
||||
void cancel_pending_promises();
|
||||
|
||||
private:
|
||||
// The overlay character data is used in battle and challenge modes, when
|
||||
// character data is temporarily replaced in-game. In other play modes and in
|
||||
@@ -407,10 +360,8 @@ private:
|
||||
std::shared_ptr<PSOBBGuildCardFile> guild_card_data;
|
||||
std::shared_ptr<PlayerBank200> external_bank;
|
||||
std::shared_ptr<PSOBBCharacterFile> external_bank_character;
|
||||
ssize_t external_bank_character_index;
|
||||
uint64_t last_play_time_update;
|
||||
|
||||
void save_and_clear_external_bank();
|
||||
ssize_t external_bank_character_index = -1;
|
||||
uint64_t last_play_time_update = 0;
|
||||
|
||||
void load_all_files();
|
||||
void update_character_data_after_load(std::shared_ptr<PSOBBCharacterFile> character_data);
|
||||
|
||||
+51
-38
@@ -172,7 +172,7 @@ struct C_Login_Patch_04 {
|
||||
parray<le_uint32_t, 3> unused;
|
||||
pstring<TextEncoding::ASCII, 0x10> username;
|
||||
pstring<TextEncoding::ASCII, 0x10> password;
|
||||
pstring<TextEncoding::ASCII, 0x40> email;
|
||||
pstring<TextEncoding::ASCII, 0x40> email_address;
|
||||
} __packed_ws__(C_Login_Patch_04, 0x6C);
|
||||
|
||||
// 05 (S->C): Disconnect
|
||||
@@ -514,13 +514,23 @@ struct S_UpdateClientConfig_BB_04 {
|
||||
// appears in PSO v1 and v2, but it is not used, implying that at some point
|
||||
// there was a separate command to send the block list, but it was scrapped.
|
||||
// Perhaps this was used for command A1, which is identical to 07 and A0 in all
|
||||
// versions of PSO (except DC NTE).
|
||||
// versions of PSO (except DC NTE, whch uses 07/8E/8F instead).
|
||||
|
||||
// This command sets the interaction mode, which affects which objects can be
|
||||
// interacted with and what certain controls do. It also affects some in-game
|
||||
// behaviors; for example, if the leader's interaction mode is set incorrectly
|
||||
// in a game, the leader will not send the game state to joining players, so
|
||||
// they will wait forever and not be able to actually join.
|
||||
// In all PSO versions except DC NTE and 11/2000, this command sets the
|
||||
// interaction mode, which affects which objects can be interacted with and
|
||||
// what certain controls do. It also affects some in-game behaviors; for
|
||||
// example, if the leader's interaction mode is set incorrectly in a game, the
|
||||
// leader will not send the game state to joining players, so they will wait
|
||||
// forever and not be able to actually join.
|
||||
|
||||
// In DC NTE and 11/2000, a side effect of this command not setting the
|
||||
// interaction mode is that the menu doesn't work properly unless another
|
||||
// command that sets the correct interaction mode (1) is sent before it. The
|
||||
// user-visible effects of the interaction mode not being set are that the
|
||||
// "Please select a ship" message doesn't appear, the X button does nothing,
|
||||
// and selecting an item from the menu doesn't dismiss the menu and instead
|
||||
// softlocks, since the client doesn't send anything.) One such command that
|
||||
// sets the correct interaction mode is command 04 with an error code of 0.
|
||||
|
||||
// The menu is titled "Ship Select" unless the first menu item begins with the
|
||||
// text "BLOCK" (all caps), in which case it is titled "Block Select".
|
||||
@@ -1627,7 +1637,7 @@ struct C_ConnectionInfo_DCNTE_8A {
|
||||
le_uint32_t unused = 0;
|
||||
pstring<TextEncoding::ASCII, 0x30> username;
|
||||
pstring<TextEncoding::ASCII, 0x30> password;
|
||||
pstring<TextEncoding::ASCII, 0x30> email_address; // From Sylverant documentation
|
||||
pstring<TextEncoding::ASCII, 0x30> email_address;
|
||||
} __packed_ws__(C_ConnectionInfo_DCNTE_8A, 0xA0);
|
||||
|
||||
// 8A (S->C): Connection information result (DC NTE only)
|
||||
@@ -1661,7 +1671,7 @@ struct C_Login_DCNTE_8B {
|
||||
pstring<TextEncoding::ASCII, 0x11> access_key;
|
||||
pstring<TextEncoding::ASCII, 0x30> username;
|
||||
pstring<TextEncoding::ASCII, 0x30> password;
|
||||
pstring<TextEncoding::ASCII, 0x10> name;
|
||||
pstring<TextEncoding::ASCII, 0x10> login_character_name;
|
||||
parray<uint8_t, 2> unused;
|
||||
} __packed_ws__(C_Login_DCNTE_8B, 0xAC);
|
||||
|
||||
@@ -1720,7 +1730,7 @@ struct C_RegisterV1_DC_92 {
|
||||
parray<uint8_t, 2> unused2;
|
||||
pstring<TextEncoding::ASCII, 0x30> serial_number2;
|
||||
pstring<TextEncoding::ASCII, 0x30> access_key2;
|
||||
pstring<TextEncoding::ASCII, 0x30> email; // According to Sylverant documentation
|
||||
pstring<TextEncoding::ASCII, 0x30> email_address;
|
||||
} __packed_ws__(C_RegisterV1_DC_92, 0xA0);
|
||||
|
||||
// 92 (S->C): Register result (non-BB)
|
||||
@@ -1741,7 +1751,7 @@ struct C_LoginV1_DC_93 {
|
||||
/* 29 */ pstring<TextEncoding::ASCII, 0x11> access_key;
|
||||
/* 3A */ pstring<TextEncoding::ASCII, 0x30> serial_number2;
|
||||
/* 6A */ pstring<TextEncoding::ASCII, 0x30> access_key2;
|
||||
/* 9A */ pstring<TextEncoding::ASCII, 0x10> name;
|
||||
/* 9A */ pstring<TextEncoding::ASCII, 0x10> login_character_name;
|
||||
/* AA */ parray<uint8_t, 2> unused2;
|
||||
/* AC */
|
||||
} __packed_ws__(C_LoginV1_DC_93, 0xAC);
|
||||
@@ -1753,11 +1763,11 @@ struct C_LoginExtendedV1_DC_93 : C_LoginV1_DC_93 {
|
||||
// 93 (C->S): Log in (BB)
|
||||
|
||||
struct C_LoginBase_BB_93 {
|
||||
le_uint32_t player_tag = 0x00010000;
|
||||
le_uint32_t guild_card_number = 0;
|
||||
le_uint32_t sub_version = 0;
|
||||
uint8_t language = 0;
|
||||
int8_t character_slot = 0;
|
||||
/* 00 */ le_uint32_t player_tag = 0x00010000;
|
||||
/* 04 */ le_uint32_t guild_card_number = 0;
|
||||
/* 08 */ le_uint32_t sub_version = 0;
|
||||
/* 0C */ uint8_t language = 0;
|
||||
/* 0D */ int8_t character_slot = 0;
|
||||
// Values for connection_phase:
|
||||
// 00 - initial connection (client will request system file, characters, etc.)
|
||||
// 01 - choose character
|
||||
@@ -1766,17 +1776,18 @@ struct C_LoginBase_BB_93 {
|
||||
// 04 - login server
|
||||
// 05 - lobby server
|
||||
// 06 - lobby server (with Meet User fields specified)
|
||||
uint8_t connection_phase = 0;
|
||||
uint8_t client_code = 0;
|
||||
le_uint32_t security_token = 0;
|
||||
pstring<TextEncoding::ASCII, 0x30> username;
|
||||
pstring<TextEncoding::ASCII, 0x30> password;
|
||||
/* 0E */ uint8_t connection_phase = 0;
|
||||
/* 0F */ uint8_t client_code = 0;
|
||||
/* 10 */ le_uint32_t security_token = 0;
|
||||
/* 14 */ pstring<TextEncoding::ASCII, 0x30> username;
|
||||
/* 44 */ pstring<TextEncoding::ASCII, 0x30> password;
|
||||
|
||||
// These fields map to the same fields in SC_MeetUserExtensionT. There is no
|
||||
// equivalent of the name field from that structure on BB (though newserv
|
||||
// doesn't use it anyway).
|
||||
le_uint32_t menu_id = 0;
|
||||
le_uint32_t preferred_lobby_id = 0;
|
||||
/* 74 */ le_uint32_t menu_id = 0;
|
||||
/* 78 */ le_uint32_t preferred_lobby_id = 0;
|
||||
/* 7C */
|
||||
} __packed_ws__(C_LoginBase_BB_93, 0x7C);
|
||||
|
||||
struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 {
|
||||
@@ -1784,14 +1795,16 @@ struct C_LoginWithoutHardwareInfo_BB_93 : C_LoginBase_BB_93 {
|
||||
// config at connect time. So the first time the server gets this command, it
|
||||
// will be something like "Ver. 1.24.3". This format is used on older client
|
||||
// versions (before 1.23.8?)
|
||||
parray<uint8_t, 0x28> client_config;
|
||||
/* 7C */ parray<uint8_t, 0x28> client_config;
|
||||
/* A4 */
|
||||
} __packed_ws__(C_LoginWithoutHardwareInfo_BB_93, 0xA4);
|
||||
|
||||
struct C_LoginWithHardwareInfo_BB_93 : C_LoginBase_BB_93 {
|
||||
// See the comment in the above structure. This format is used on newer client
|
||||
// versions.
|
||||
parray<le_uint32_t, 2> hardware_info;
|
||||
parray<uint8_t, 0x28> client_config;
|
||||
/* 7C */ be_uint64_t hardware_id;
|
||||
/* 84 */ parray<uint8_t, 0x28> client_config;
|
||||
/* AC */
|
||||
} __packed_ws__(C_LoginWithHardwareInfo_BB_93, 0xAC);
|
||||
|
||||
// 94: Invalid command
|
||||
@@ -1975,7 +1988,7 @@ struct C_Login_DC_PC_GC_9D {
|
||||
/* 48 */ pstring<TextEncoding::ASCII, 0x10> access_key; // On XB, this is the XBL user ID
|
||||
/* 58 */ pstring<TextEncoding::ASCII, 0x30> serial_number2; // On DCv2, this is the hardware ID; on XB, this is the XBL gamertag
|
||||
/* 88 */ pstring<TextEncoding::ASCII, 0x30> access_key2; // On XB, this is the XBL user ID
|
||||
/* B8 */ pstring<TextEncoding::ASCII, 0x10> name;
|
||||
/* B8 */ pstring<TextEncoding::ASCII, 0x10> login_character_name;
|
||||
/* C8 */
|
||||
} __packed_ws__(C_Login_DC_PC_GC_9D, 0xC8);
|
||||
|
||||
@@ -2006,13 +2019,13 @@ struct C_LoginExtended_GC_9E : C_Login_GC_9E {
|
||||
|
||||
struct C_Login_XB_9E : C_Login_DC_PC_GC_9D {
|
||||
/* 00C8 */ parray<uint8_t, 0x20> unknown_a4;
|
||||
/* 00E8 */ XBNetworkLocation netloc;
|
||||
/* 00E8 */ XBNetworkLocation xb_netloc;
|
||||
// By default, this array is initialized with [0x5A2773DD, 0xD82B2345,
|
||||
// 0x4FF904D5] on 4OEU (US TU version).
|
||||
/* 0118 */ parray<le_uint32_t, 3> unknown_a1a;
|
||||
/* 0118 */ parray<le_uint32_t, 3> xb_unknown_a1a;
|
||||
/* 0124 */ le_uint32_t xb_user_id_high = 0;
|
||||
/* 0128 */ le_uint32_t xb_user_id_low = 0;
|
||||
/* 012C */ le_uint32_t unknown_a1b = 0;
|
||||
/* 012C */ le_uint32_t xb_unknown_a1b = 0;
|
||||
/* 0130 */
|
||||
} __packed_ws__(C_Login_XB_9E, 0x130);
|
||||
|
||||
@@ -2045,8 +2058,7 @@ struct C_LoginExtended_BB_9E {
|
||||
|
||||
// 9F (C->S): Client config / security data response (V3/BB)
|
||||
// The data is opaque to the client, as described at the top of this file.
|
||||
// If newserv ever sent a 9F command (it currently does not). On BB, this
|
||||
// command does not work during the data server phase.
|
||||
// On BB, this command does not work during the data server phase.
|
||||
|
||||
struct C_ClientConfig_V3_9F {
|
||||
parray<uint8_t, 0x20> data;
|
||||
@@ -3444,7 +3456,7 @@ struct SC_TeamChat_BB_07EA {
|
||||
struct S_TeamMemberList_BB_09EA {
|
||||
le_uint32_t entry_count = 0;
|
||||
struct Entry {
|
||||
// This is displayed as "<%04d> %s" % (rank, name)
|
||||
// This is displayed as "<{:04}> {}" % (rank, name)
|
||||
le_uint32_t rank = 0;
|
||||
le_uint32_t privilege_level = 0; // 0x10 or 0x20 = green, 0x30 = blue, 0x40 = red, anything else = white
|
||||
le_uint32_t guild_card_number = 0;
|
||||
@@ -4257,11 +4269,11 @@ struct G_UseItem_6x27 {
|
||||
|
||||
// 6x28: Feed MAG (protected on V3/V4)
|
||||
|
||||
struct G_FeedMAG_6x28 {
|
||||
struct G_FeedMag_6x28 {
|
||||
G_ClientIDHeader header;
|
||||
le_uint32_t mag_item_id = 0;
|
||||
le_uint32_t fed_item_id = 0;
|
||||
} __packed_ws__(G_FeedMAG_6x28, 0x0C);
|
||||
} __packed_ws__(G_FeedMag_6x28, 0x0C);
|
||||
|
||||
// 6x29: Delete inventory item (via bank deposit / sale / feeding MAG) (protected on V3 but not V4)
|
||||
// This subcommand is also used for reducing the size of stacks - if amount is
|
||||
@@ -7351,9 +7363,10 @@ struct G_TournamentMatchResult_Ep3_6xB4x51 {
|
||||
le_uint16_t num_players_per_team = 0;
|
||||
le_uint16_t winner_team_id = 0;
|
||||
le_uint32_t meseta_amount = 0;
|
||||
// This field apparently is supposed to contain a %s token (as for printf)
|
||||
// which is replaced with meseta_amount. The results screen animates this text
|
||||
// counting up from 0 to meseta_amount.
|
||||
// This field apparently is supposed to contain a %s token (as for sprintf)
|
||||
// which is replaced with meseta_amount. (Yes, %s, not %d; it seems they
|
||||
// manually convert it to a string for some reason before calling sprintf.)
|
||||
// The results screen animates this text counting up from 0 to meseta_amount.
|
||||
pstring<TextEncoding::MARKED, 0x20> meseta_reward_text;
|
||||
} __packed_ws__(G_TournamentMatchResult_Ep3_6xB4x51, 0xF4);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ struct VectorXZF {
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXZF x=%g z=%g]", this->x.load(), this->z.load());
|
||||
return std::format("[VectorXZF x={:g} z={:g}]", this->x, this->z);
|
||||
}
|
||||
} __packed_ws__(VectorXZF, 0x08);
|
||||
|
||||
@@ -109,7 +109,7 @@ struct VectorXYZF {
|
||||
}
|
||||
|
||||
inline std::string str() const {
|
||||
return phosg::string_printf("[VectorXYZF x=%g y=%g z=%g]", this->x.load(), this->y.load(), this->z.load());
|
||||
return std::format("[VectorXYZF x={:g} y={:g} z={:g}]", this->x, this->y, this->z);
|
||||
}
|
||||
} __packed_ws__(VectorXYZF, 0x0C);
|
||||
|
||||
@@ -143,12 +143,11 @@ struct RELFileFooterT {
|
||||
static constexpr bool IsBE = BE;
|
||||
// Relocations is a list of words (le_uint16_t on DC/PC/XB/BB, be_uint16_t on
|
||||
// GC) containing the number of doublewords (uint32_t) to skip for each
|
||||
// relocation. The relocation pointer starts immediately after the
|
||||
// checksum_size field in the header, and advances by the value of one
|
||||
// relocation word (times 4) before each relocation. At each relocated
|
||||
// doubleword, the address of the first byte of the code (after checksum_size)
|
||||
// is added to the existing value.
|
||||
// For example, if the code segment contains the following data (where R
|
||||
// relocation. The relocation pointer starts at the beginning of the file
|
||||
// data, and advances by the value of one relocation word (times 4) before
|
||||
// each relocation. At each relocated doubleword, the address of the first
|
||||
// byte of the file is added to the existing value.
|
||||
// For example, if the file data contains the following data (where R
|
||||
// specifies doublewords to relocate):
|
||||
// RR RR RR RR ?? ?? ?? ?? ?? ?? ?? ?? RR RR RR RR
|
||||
// RR RR RR RR ?? ?? ?? ?? RR RR RR RR
|
||||
|
||||
+49
-49
@@ -129,7 +129,7 @@ CommonItemSet::Table::Table(const phosg::JSON& json, Episode episode)
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = phosg::string_printf("%s:%s", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
string name = std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
from_json_into(*enemy_meseta_ranges_json.at(name), this->enemy_meseta_ranges[z]);
|
||||
this->enemy_type_drop_probs[z] = enemy_type_drop_probs_json.at(name)->as_int();
|
||||
this->enemy_item_classes[z] = enemy_item_classes_json.at(name)->as_int();
|
||||
@@ -163,8 +163,8 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
const auto& meseta_ranges = this->enemy_meseta_ranges;
|
||||
const auto& drop_probs = this->enemy_type_drop_probs;
|
||||
const auto& item_classes = this->enemy_item_classes;
|
||||
fprintf(stream, "Enemy tables:\n");
|
||||
fprintf(stream, " ## $LOW $HIGH DAR%% ITEM ENEMIES\n");
|
||||
phosg::fwrite_fmt(stream, "Enemy tables:\n");
|
||||
phosg::fwrite_fmt(stream, " ## $LOW $HIGH DAR% ITEM ENEMIES\n");
|
||||
for (size_t z = 0; z < 0x64; z++) {
|
||||
string enemies_str;
|
||||
for (EnemyType enemy_type : enemy_types_for_rare_table_index(this->episode, z)) {
|
||||
@@ -174,11 +174,11 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
enemies_str += phosg::name_for_enum(enemy_type);
|
||||
}
|
||||
if (drop_probs[z]) {
|
||||
fprintf(stream, " %02zX %5hu %5hu %3hhu%% %02hX:%s %s\n",
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:5} {:5} {:3}% {:02X}:{} {}\n",
|
||||
z, meseta_ranges[z].min, meseta_ranges[z].max, drop_probs[z], item_classes[z],
|
||||
name_for_common_item_class(item_classes[z]), enemies_str.c_str());
|
||||
name_for_common_item_class(item_classes[z]), enemies_str);
|
||||
} else {
|
||||
fprintf(stream, " %02zX ----- ----- 0%% -- %s\n", z, enemies_str.c_str());
|
||||
phosg::fwrite_fmt(stream, " {:02X} ----- ----- 0% -- {}\n", z, enemies_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,8 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
"ROD ",
|
||||
"WAND ",
|
||||
};
|
||||
fprintf(stream, "Base weapon config:\n");
|
||||
fprintf(stream, " TYPE PROB [SB AL] FLOORS\n");
|
||||
phosg::fwrite_fmt(stream, "Base weapon config:\n");
|
||||
phosg::fwrite_fmt(stream, " TYPE PROB [SB AL] FLOORS\n");
|
||||
for (size_t z = 0; z < 12; z++) {
|
||||
uint8_t floor_to_class[10];
|
||||
if (this->subtype_base_table[z] < 0) {
|
||||
@@ -213,17 +213,17 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
floor_to_class[x] = this->subtype_base_table[z] + (x / this->subtype_area_length_table[z]);
|
||||
}
|
||||
}
|
||||
fprintf(stream, " %02zX:%s %3hhu%% [%02hhX %02hhX] %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX\n",
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{} {:3}% [{:02X} {:02X}] {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}\n",
|
||||
z, base_weapon_type_names[z], this->base_weapon_type_prob_table[z],
|
||||
this->subtype_base_table[z], this->subtype_area_length_table[z],
|
||||
floor_to_class[0], floor_to_class[1], floor_to_class[2], floor_to_class[3], floor_to_class[4],
|
||||
floor_to_class[5], floor_to_class[6], floor_to_class[7], floor_to_class[8], floor_to_class[9]);
|
||||
}
|
||||
|
||||
fprintf(stream, "Box configuration:\n");
|
||||
fprintf(stream, " AR $LOW $HIGH WEP%% ARM%% SHD%% UNI%% TL%% MST%% NO%%\n");
|
||||
phosg::fwrite_fmt(stream, "Box configuration:\n");
|
||||
phosg::fwrite_fmt(stream, " AR $LOW $HIGH WEP% ARM% SHD% UNI% TL% MST% NO%\n");
|
||||
for (size_t z = 0; z < 10; z++) {
|
||||
fprintf(stream, " %02zX %5hu %5hu %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%%\n",
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:5} {:5} {:3}% {:3}% {:3}% {:3}% {:3}% {:3}% {:3}%\n",
|
||||
z, this->box_meseta_ranges[z].min, this->box_meseta_ranges[z].max,
|
||||
this->box_item_class_prob_table[0][z],
|
||||
this->box_item_class_prob_table[1][z],
|
||||
@@ -234,43 +234,43 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
this->box_item_class_prob_table[6][z]);
|
||||
}
|
||||
|
||||
fprintf(stream, "Weapon drops:\n");
|
||||
fprintf(stream, " Grinds:\n");
|
||||
fprintf(stream, " GD AR0%% AR1%% AR2%% AR3%%\n");
|
||||
phosg::fwrite_fmt(stream, "Weapon drops:\n");
|
||||
phosg::fwrite_fmt(stream, " Grinds:\n");
|
||||
phosg::fwrite_fmt(stream, " GD AR0% AR1% AR2% AR3%\n");
|
||||
for (size_t z = 0; z < 9; z++) {
|
||||
fprintf(stream, " +%zu %3hhd%% %3hhd%% %3hhd%% %3hhd%%\n", z,
|
||||
phosg::fwrite_fmt(stream, " +{} {:3}% {:3}% {:3}% {:3}%\n", z,
|
||||
this->grind_prob_table[z][0], this->grind_prob_table[z][1],
|
||||
this->grind_prob_table[z][2], this->grind_prob_table[z][3]);
|
||||
}
|
||||
fprintf(stream, " Bonus value table:\n");
|
||||
fprintf(stream, " ID");
|
||||
phosg::fwrite_fmt(stream, " Bonus value table:\n");
|
||||
phosg::fwrite_fmt(stream, " ID");
|
||||
for (int8_t v = -10; v <= 100; v += 5) {
|
||||
fprintf(stream, " %5hhd%%", v);
|
||||
phosg::fwrite_fmt(stream, " {:5}%", v);
|
||||
}
|
||||
fputc('\n', stream);
|
||||
for (size_t z = 0; z < (this->has_rare_bonus_value_prob_table ? 6 : 5); z++) {
|
||||
fprintf(stream, " %02zX", z);
|
||||
phosg::fwrite_fmt(stream, " {:02X}", z);
|
||||
for (size_t x = 0; x < 0x17; x++) {
|
||||
fprintf(stream, " %5hu#", this->bonus_value_prob_table[x][z]);
|
||||
phosg::fwrite_fmt(stream, " {:5}#", this->bonus_value_prob_table[x][z]);
|
||||
}
|
||||
fputc('\n', stream);
|
||||
}
|
||||
fprintf(stream, " Area config tables:\n");
|
||||
fprintf(stream, " AR BONUS SP NO%% NTV%% AB%% MAC%% DRK%% HIT%% SM SPC%%\n");
|
||||
phosg::fwrite_fmt(stream, " Area config tables:\n");
|
||||
phosg::fwrite_fmt(stream, " AR BONUS SP NO% NTV% AB% MAC% DRK% HIT% SM SPC%\n");
|
||||
for (size_t z = 0; z < 10; z++) {
|
||||
fprintf(stream, " %02zX %02hhX %02hhX %02hhX %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %3hhu%% %02hhX %3hhu%%\n",
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:02X} {:02X} {:02X} {:3}% {:3}% {:3}% {:3}% {:3}% {:3}% {:02X} {:3}%\n",
|
||||
z, this->nonrare_bonus_prob_spec[0][z], this->nonrare_bonus_prob_spec[1][z], this->nonrare_bonus_prob_spec[2][z],
|
||||
this->bonus_type_prob_table[0][z], this->bonus_type_prob_table[1][z], this->bonus_type_prob_table[2][z],
|
||||
this->bonus_type_prob_table[3][z], this->bonus_type_prob_table[4][z], this->bonus_type_prob_table[5][z],
|
||||
this->special_mult[z], this->special_percent[z]);
|
||||
}
|
||||
|
||||
fprintf(stream, " Tool class table:\n");
|
||||
fprintf(stream, " CS A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n");
|
||||
phosg::fwrite_fmt(stream, " Tool class table:\n");
|
||||
phosg::fwrite_fmt(stream, " CS A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n");
|
||||
for (size_t tool_class = 0; tool_class < this->tool_class_prob_table.size(); tool_class++) {
|
||||
fprintf(stream, " %02zX", tool_class);
|
||||
phosg::fwrite_fmt(stream, " {:02X}", tool_class);
|
||||
for (size_t area_norm = 0; area_norm < 10; area_norm++) {
|
||||
fprintf(stream, " %5hu", this->tool_class_prob_table[tool_class][area_norm]);
|
||||
phosg::fwrite_fmt(stream, " {:5}", this->tool_class_prob_table[tool_class][area_norm]);
|
||||
}
|
||||
fputc('\n', stream);
|
||||
}
|
||||
@@ -297,42 +297,42 @@ void CommonItemSet::Table::print(FILE* stream) const {
|
||||
"MEGID ",
|
||||
};
|
||||
|
||||
fprintf(stream, " Technique table:\n");
|
||||
fprintf(stream, " TECH A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n");
|
||||
phosg::fwrite_fmt(stream, " Technique table:\n");
|
||||
phosg::fwrite_fmt(stream, " TECH A1 A2 A3 A4 A5 A6 A7 A8 A9 A10\n");
|
||||
for (size_t tech_num = 0; tech_num < this->technique_index_prob_table.size(); tech_num++) {
|
||||
fprintf(stream, " %02zX:%s", tech_num, technique_names[tech_num]);
|
||||
phosg::fwrite_fmt(stream, " {:02X}:{}", tech_num, technique_names[tech_num]);
|
||||
for (size_t area_norm = 0; area_norm < 10; area_norm++) {
|
||||
uint16_t prob = this->technique_index_prob_table[tech_num][area_norm];
|
||||
if (prob) {
|
||||
const auto& level_range = this->technique_level_ranges[tech_num][area_norm];
|
||||
size_t min_level = level_range.min + 1;
|
||||
size_t max_level = level_range.max + 1;
|
||||
fprintf(stream, " %5hu[%2zu-%2zu]", prob, min_level, max_level);
|
||||
phosg::fwrite_fmt(stream, " {:5}[{:2}-{:2}]", prob, min_level, max_level);
|
||||
} else {
|
||||
fprintf(stream, " 0[-----]");
|
||||
phosg::fwrite_fmt(stream, " 0[-----]");
|
||||
}
|
||||
}
|
||||
fputc('\n', stream);
|
||||
}
|
||||
|
||||
fprintf(stream, " Armor/shield type bias: %hhu\n", this->armor_or_shield_type_bias);
|
||||
phosg::fwrite_fmt(stream, " Armor/shield type bias: {}\n", this->armor_or_shield_type_bias);
|
||||
|
||||
fprintf(stream, " Armor/shield type index table:\n");
|
||||
fprintf(stream, " TY PROB\n");
|
||||
phosg::fwrite_fmt(stream, " Armor/shield type index table:\n");
|
||||
phosg::fwrite_fmt(stream, " TY PROB\n");
|
||||
for (size_t z = 0; z < 5; z++) {
|
||||
fprintf(stream, " %02zX %3hhu%%\n", z, this->armor_shield_type_index_prob_table[z]);
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:3}%\n", z, this->armor_shield_type_index_prob_table[z]);
|
||||
}
|
||||
|
||||
fprintf(stream, " Armor/shield slot count table:\n");
|
||||
fprintf(stream, " #S PROB\n");
|
||||
phosg::fwrite_fmt(stream, " Armor/shield slot count table:\n");
|
||||
phosg::fwrite_fmt(stream, " #S PROB\n");
|
||||
for (size_t z = 0; z < 5; z++) {
|
||||
fprintf(stream, " %02zX %3hhu%%\n", z, this->armor_slot_count_prob_table[z]);
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:3}%\n", z, this->armor_slot_count_prob_table[z]);
|
||||
}
|
||||
|
||||
fprintf(stream, " Unit maximum stars table:\n");
|
||||
fprintf(stream, " AR #*\n");
|
||||
phosg::fwrite_fmt(stream, " Unit maximum stars table:\n");
|
||||
phosg::fwrite_fmt(stream, " AR #*\n");
|
||||
for (size_t z = 0; z < 10; z++) {
|
||||
fprintf(stream, " %02zX %3hhu\n", z, this->unit_max_stars_table[z]);
|
||||
phosg::fwrite_fmt(stream, " {:02X} {:3}\n", z, this->unit_max_stars_table[z]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +344,7 @@ phosg::JSON CommonItemSet::Table::json() const {
|
||||
static const array<Episode, 3> episodes = {Episode::EP1, Episode::EP2, Episode::EP4};
|
||||
for (Episode episode : episodes) {
|
||||
for (auto type : enemy_types_for_rare_table_index(episode, z)) {
|
||||
string name = phosg::string_printf("%s:%s", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
string name = std::format("{}:{}", abbreviation_for_episode(episode), phosg::name_for_enum(type));
|
||||
enemy_meseta_ranges_json.emplace(name, to_json(this->enemy_meseta_ranges[z]));
|
||||
enemy_type_drop_probs_json.emplace(name, this->enemy_type_drop_probs[z]);
|
||||
enemy_item_classes_json.emplace(name, this->enemy_item_classes[z]);
|
||||
@@ -413,7 +413,7 @@ void CommonItemSet::print(FILE* stream) const {
|
||||
for (uint8_t section_id = 0; section_id < 10; section_id++) {
|
||||
try {
|
||||
auto table = this->get_table(episode, mode, difficulty, section_id);
|
||||
fprintf(stream, "============ %s %s %s %s\n",
|
||||
phosg::fwrite_fmt(stream, "============ {} {} {} {}\n",
|
||||
name_for_mode(mode), name_for_episode(episode), name_for_difficulty(difficulty), name_for_section_id(section_id));
|
||||
table->print(stream);
|
||||
} catch (const runtime_error&) {
|
||||
@@ -503,7 +503,7 @@ shared_ptr<const CommonItemSet::Table> CommonItemSet::get_table(
|
||||
try {
|
||||
return this->tables.at(this->key_for_table(episode, mode, difficulty, secid));
|
||||
} catch (const out_of_range&) {
|
||||
throw runtime_error(phosg::string_printf("common item table not available for episode=%s, mode=%s, difficulty=%hu, secid=%hu",
|
||||
throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}",
|
||||
name_for_episode(episode), name_for_mode(mode), difficulty, secid));
|
||||
}
|
||||
}
|
||||
@@ -554,11 +554,11 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr<const std::string> gs
|
||||
default:
|
||||
throw runtime_error("invalid episode");
|
||||
}
|
||||
return phosg::string_printf(
|
||||
"ItemPT%s%s%c%1hhu.rel",
|
||||
return std::format(
|
||||
"ItemPT{}{}{}{}.rel",
|
||||
is_challenge ? "c" : "",
|
||||
episode_token,
|
||||
tolower(abbreviation_for_difficulty(difficulty)),
|
||||
static_cast<char>(tolower(abbreviation_for_difficulty(difficulty))),
|
||||
section_id);
|
||||
};
|
||||
|
||||
|
||||
@@ -311,22 +311,22 @@ struct ProbabilityTable {
|
||||
return this->items[--this->count];
|
||||
}
|
||||
|
||||
void shuffle(std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) {
|
||||
void shuffle(std::shared_ptr<RandomGenerator> rand_crypt) {
|
||||
for (size_t z = 1; z < this->count; z++) {
|
||||
size_t other_z = random_from_optional_crypt(opt_rand_crypt) % (z + 1);
|
||||
size_t other_z = rand_crypt->next() % (z + 1);
|
||||
ItemT t = this->items[z];
|
||||
this->items[z] = this->items[other_z];
|
||||
this->items[other_z] = t;
|
||||
}
|
||||
}
|
||||
|
||||
ItemT sample(std::shared_ptr<PSOLFGEncryption> opt_rand_crypt) const {
|
||||
ItemT sample(std::shared_ptr<RandomGenerator> rand_crypt) const {
|
||||
if (this->count == 0) {
|
||||
throw std::runtime_error("sample from empty probability table");
|
||||
} else if (this->count == 1) {
|
||||
return this->items[0];
|
||||
} else {
|
||||
return this->items[random_from_optional_crypt(opt_rand_crypt) % this->count];
|
||||
return this->items[rand_crypt->next() % this->count];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+7
-7
@@ -1018,7 +1018,7 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
uint8_t buffered_bits = cr.buffered_bits();
|
||||
if (cr.read()) {
|
||||
uint8_t literal_value = r.get_u8();
|
||||
fprintf(stream, "[%zX] %hhu> 1 %02hhX literal %02hhX\n",
|
||||
phosg::fwrite_fmt(stream, "[{:X}] {}> 1 {:02X} literal {:02X}\n",
|
||||
output_bytes, buffered_bits, literal_value, literal_value);
|
||||
output_bytes++;
|
||||
|
||||
@@ -1030,19 +1030,19 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
uint16_t a = (a_high << 8) | a_low;
|
||||
ssize_t offset = (a >> 3) | (~0x1FFF);
|
||||
if (offset == ~0x1FFF) {
|
||||
fprintf(stream, "[%zX] end\n", output_bytes);
|
||||
phosg::fwrite_fmt(stream, "[{:X}] end\n", output_bytes);
|
||||
break;
|
||||
}
|
||||
if (a & 7) {
|
||||
count = (a & 7) + 2;
|
||||
read_offset = output_bytes + offset;
|
||||
fprintf(stream, "[%zX] %hhu> 01 %02hhX %02hhX long copy from %zd (offset=%zX) size=%zX\n",
|
||||
phosg::fwrite_fmt(stream, "[{:X}] {}> 01 {:02X} {:02X} long copy from {} (offset={:X}) size={:X}\n",
|
||||
output_bytes, buffered_bits, a_low, a_high, offset, read_offset, count);
|
||||
} else {
|
||||
uint8_t count_u8 = r.get_u8();
|
||||
count = count_u8 + 1;
|
||||
read_offset = output_bytes + offset;
|
||||
fprintf(stream, "[%zX] %hhu> 01 %02hhX %02hhX %02hhX extended copy from %zd (offset=%zX) size=%zX\n",
|
||||
phosg::fwrite_fmt(stream, "[{:X}] {}> 01 {:02X} {:02X} {:02X} extended copy from {} (offset={:X}) size={:X}\n",
|
||||
output_bytes, buffered_bits, a_low, a_high, count_u8, offset, read_offset, count);
|
||||
}
|
||||
|
||||
@@ -1053,7 +1053,7 @@ void prs_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
count = ((first_bit ? 2 : 0) | (second_bit ? 1 : 0)) + 2;
|
||||
ssize_t offset = offset_u8 | (~0xFF);
|
||||
read_offset = output_bytes + offset;
|
||||
fprintf(stream, "[%zX] %hhu> 00%c%c %02hhX short copy from %zd (offset=%zX) size=%zX\n",
|
||||
phosg::fwrite_fmt(stream, "[{:X}] {}> 00{}{} {:02X} short copy from {} (offset={:X}) size={:X}\n",
|
||||
output_bytes, buffered_bits, first_bit ? '1' : '0', second_bit ? '1' : '0', offset_u8, offset, read_offset, count);
|
||||
}
|
||||
|
||||
@@ -1346,11 +1346,11 @@ void bc0_disassemble(FILE* stream, const void* data, size_t size) {
|
||||
uint8_t a2 = r.get_u8();
|
||||
size_t count = (a2 & 0x0F) + 3;
|
||||
// size_t backreference_offset = a1 | ((a2 << 4) & 0xF00);
|
||||
fprintf(stream, "[%zX] backreference %02zX\n", output_bytes, count);
|
||||
phosg::fwrite_fmt(stream, "[{:X}] backreference {:02X}\n", output_bytes, count);
|
||||
output_bytes += count;
|
||||
|
||||
} else {
|
||||
fprintf(stream, "[%zX] literal %02hhX\n", output_bytes, r.get_u8());
|
||||
phosg::fwrite_fmt(stream, "[{:X}] literal {:02X}\n", output_bytes, r.get_u8());
|
||||
output_bytes++;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -1304,7 +1304,7 @@ 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 = phosg::string_printf("%08X", value);
|
||||
string s = std::format("{:08X}", value);
|
||||
|
||||
string ret;
|
||||
for (char ch : s) {
|
||||
@@ -1336,7 +1336,7 @@ unordered_map<uint32_t, string> generate_all_dc_serial_numbers(uint8_t domain, u
|
||||
while ((serial_number = iter.next()) != 0) {
|
||||
ret[serial_number].push_back(((iter.domain << 2) & 3) | (iter.subdomain & 3));
|
||||
if (iter.index3 == 0) {
|
||||
fprintf(stderr, "... (it) domain=%hhu subdomain=%hhu index2=%hu results=%zu (0x%zX)\n", iter.domain, iter.subdomain, iter.index2, ret.size(), ret.size());
|
||||
phosg::fwrite_fmt(stderr, "... (it) domain={} subdomain={} index2={} results={} (0x{:X})\n", iter.domain, iter.subdomain, iter.index2, ret.size(), ret.size());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -1389,14 +1389,14 @@ size_t DCSerialNumberIterator::progress() const {
|
||||
|
||||
void dc_serial_number_speed_test(uint64_t seed) {
|
||||
uint32_t effective_seed = (seed & 0xFFFFFFFF00000000) ? phosg::random_object<uint32_t>() : seed;
|
||||
fprintf(stderr, "Product speed test with seed=%08" PRIX32 "\n", effective_seed);
|
||||
phosg::fwrite_fmt(stderr, "Product speed test with seed={:08X}\n", effective_seed);
|
||||
PSOV2Encryption crypt(effective_seed);
|
||||
uint64_t time_slow = 0;
|
||||
uint64_t time_fast = 0;
|
||||
size_t num_disagreements = 0;
|
||||
static constexpr size_t count = 0x1000;
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
string s = phosg::string_printf("%08X", crypt.next());
|
||||
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);
|
||||
@@ -1407,17 +1407,17 @@ void dc_serial_number_speed_test(uint64_t seed) {
|
||||
time_slow += phosg::now() - start;
|
||||
|
||||
if (((z & 0xF) == 0) || is_valid_slow || is_valid_fast) {
|
||||
fprintf(stderr, "... %02zX: %s => %s %s%s\n", z, s.c_str(), is_valid_slow ? "SLOW" : "----", is_valid_fast ? "FAST" : "----", is_valid_slow != is_valid_fast ? " !!!" : "");
|
||||
phosg::fwrite_fmt(stderr, "... {:02X}: {} => {} {}{}\n", z, s, is_valid_slow ? "SLOW" : "----", is_valid_fast ? "FAST" : "----", is_valid_slow != is_valid_fast ? " !!!" : "");
|
||||
}
|
||||
if (is_valid_fast != is_valid_slow) {
|
||||
num_disagreements++;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "Total time (slow): %" PRId64 " usecs (%" PRIu64 " per serial number)\n", time_slow, time_slow / count);
|
||||
fprintf(stderr, "Total time (fast): %" PRId64 " usecs (%" PRIu64 " per serial number)\n", time_fast, time_fast / count);
|
||||
fprintf(stderr, "Fast vs. slow speedup: %zux\n", static_cast<size_t>(time_slow / time_fast));
|
||||
fprintf(stderr, "Disagreements: %zu\n", num_disagreements);
|
||||
phosg::fwrite_fmt(stderr, "Total time (slow): {} usecs ({} per serial number)\n", time_slow, time_slow / count);
|
||||
phosg::fwrite_fmt(stderr, "Total time (fast): {} usecs ({} per serial number)\n", time_fast, time_fast / count);
|
||||
phosg::fwrite_fmt(stderr, "Fast vs. slow speedup: {}x\n", static_cast<size_t>(time_slow / time_fast));
|
||||
phosg::fwrite_fmt(stderr, "Disagreements: {}\n", num_disagreements);
|
||||
}
|
||||
|
||||
string decrypt_dp_address_jpn(
|
||||
@@ -1480,7 +1480,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");
|
||||
}
|
||||
phosg::log_info("Determined %08" PRIX64 " to be the most likely mask key", mask_key);
|
||||
phosg::log_info_f("Determined {:08X} to be the most likely mask key", mask_key);
|
||||
r.go(0);
|
||||
}
|
||||
|
||||
|
||||
+23
-68
@@ -1,7 +1,5 @@
|
||||
#include "DNSServer.hh"
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@@ -14,48 +12,23 @@
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "NetworkAddresses.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
DNSServer::DNSServer(
|
||||
shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
shared_ptr<const IPV4RangeSet> banned_ipv4_ranges)
|
||||
: base(base),
|
||||
local_connect_address(local_connect_address),
|
||||
external_connect_address(external_connect_address),
|
||||
banned_ipv4_ranges(banned_ipv4_ranges) {}
|
||||
|
||||
DNSServer::~DNSServer() {
|
||||
for (const auto& it : this->fd_to_receive_event) {
|
||||
close(it.first);
|
||||
}
|
||||
}
|
||||
|
||||
void DNSServer::listen(const std::string& socket_path) {
|
||||
this->add_socket(phosg::listen(socket_path, 0, 0));
|
||||
}
|
||||
DNSServer::DNSServer(shared_ptr<ServerState> state)
|
||||
: state(state) {}
|
||||
|
||||
void DNSServer::listen(const std::string& addr, int port) {
|
||||
this->add_socket(phosg::listen(addr, port, 0));
|
||||
}
|
||||
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);
|
||||
asio::ip::udp::endpoint endpoint(asio_addr, port);
|
||||
auto sock = make_shared<asio::ip::udp::socket>(*this->state->io_context, endpoint);
|
||||
this->sockets.emplace(sock);
|
||||
|
||||
void DNSServer::listen(int port) {
|
||||
this->add_socket(phosg::listen("", port, 0));
|
||||
}
|
||||
|
||||
void DNSServer::add_socket(int fd) {
|
||||
unique_ptr<struct event, void (*)(struct event*)> e(
|
||||
event_new(this->base.get(), fd, EV_READ | EV_PERSIST, &DNSServer::dispatch_on_receive_message, this),
|
||||
event_free);
|
||||
event_add(e.get(), nullptr);
|
||||
this->fd_to_receive_event.emplace(fd, std::move(e));
|
||||
}
|
||||
|
||||
void DNSServer::dispatch_on_receive_message(evutil_socket_t fd,
|
||||
short events, void* ctx) {
|
||||
reinterpret_cast<DNSServer*>(ctx)->on_receive_message(fd, events);
|
||||
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) {
|
||||
@@ -77,45 +50,27 @@ 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) {
|
||||
string DNSServer::response_for_query(const string& query, uint32_t resolved_address) {
|
||||
return DNSServer::response_for_query(query.data(), query.size(), resolved_address);
|
||||
}
|
||||
|
||||
void DNSServer::on_receive_message(int fd, short) {
|
||||
asio::awaitable<void> DNSServer::dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock) {
|
||||
for (;;) {
|
||||
struct sockaddr_storage remote;
|
||||
socklen_t remote_size = sizeof(sockaddr_in);
|
||||
memset(&remote, 0, remote_size);
|
||||
|
||||
string input(2048, 0);
|
||||
ssize_t bytes = recvfrom(fd, const_cast<char*>(input.data()), input.size(),
|
||||
0, reinterpret_cast<sockaddr*>(&remote), &remote_size);
|
||||
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());
|
||||
|
||||
if (bytes < 0) {
|
||||
if (errno != EAGAIN) {
|
||||
dns_server_log.error("input error %d", errno);
|
||||
throw runtime_error("cannot read from udp socket");
|
||||
}
|
||||
break;
|
||||
|
||||
} else if (bytes == 0) {
|
||||
break;
|
||||
|
||||
} else if (bytes < 0x0C) {
|
||||
dns_server_log.warning("input query too small");
|
||||
if (bytes < 0x0C) {
|
||||
dns_server_log.warning_f("input query too small");
|
||||
phosg::print_data(stderr, input.data(), bytes);
|
||||
|
||||
} else if (!this->banned_ipv4_ranges->check(remote)) {
|
||||
} else if (!this->state->banned_ipv4_ranges->check(sender_addr)) {
|
||||
input.resize(bytes);
|
||||
const sockaddr_in* remote_sin = reinterpret_cast<const sockaddr_in*>(&remote);
|
||||
uint32_t remote_address = ntohl(remote_sin->sin_addr.s_addr);
|
||||
uint32_t connect_address = is_local_address(remote_address)
|
||||
? this->local_connect_address
|
||||
: this->external_connect_address;
|
||||
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);
|
||||
sendto(fd, response.data(), response.size(), 0,
|
||||
reinterpret_cast<const sockaddr*>(&remote), remote_size);
|
||||
co_await sock->async_send_to(asio::buffer(response.data(), response.size()), sender_ep, asio::use_awaitable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-22
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -9,36 +8,25 @@
|
||||
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
struct ServerState;
|
||||
|
||||
class DNSServer {
|
||||
public:
|
||||
DNSServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
uint32_t local_connect_address,
|
||||
uint32_t external_connect_address,
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges);
|
||||
explicit DNSServer(std::shared_ptr<ServerState> state);
|
||||
DNSServer(const DNSServer&) = delete;
|
||||
DNSServer(DNSServer&&) = delete;
|
||||
virtual ~DNSServer();
|
||||
DNSServer& operator=(const DNSServer&) = delete;
|
||||
DNSServer& operator=(DNSServer&&) = delete;
|
||||
virtual ~DNSServer() = default;
|
||||
|
||||
inline void set_banned_ipv4_ranges(std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges) {
|
||||
this->banned_ipv4_ranges = banned_ipv4_ranges;
|
||||
}
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
static std::string response_for_query(const void* vdata, size_t size, uint32_t resolved_address);
|
||||
static std::string response_for_query(const std::string& query, uint32_t resolved_address);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unordered_map<int, std::unique_ptr<struct event, void (*)(struct event*)>> fd_to_receive_event;
|
||||
uint32_t local_connect_address;
|
||||
uint32_t external_connect_address;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::unordered_set<std::shared_ptr<asio::ip::udp::socket>> sockets;
|
||||
|
||||
static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx);
|
||||
void on_receive_message(int fd, short event);
|
||||
asio::awaitable<void> dns_server_task(std::shared_ptr<asio::ip::udp::socket> sock);
|
||||
};
|
||||
|
||||
+219
-272
@@ -1,13 +1,7 @@
|
||||
#include "DownloadSession.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -24,7 +18,6 @@
|
||||
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ProxyCommands.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
#include "ReceiveSubcommands.hh"
|
||||
#include "SendCommands.hh"
|
||||
@@ -42,8 +35,9 @@ static string random_name() {
|
||||
}
|
||||
|
||||
DownloadSession::DownloadSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
const std::string& remote_host,
|
||||
uint16_t remote_port,
|
||||
const std::string& output_dir,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
@@ -61,10 +55,15 @@ DownloadSession::DownloadSession(
|
||||
const std::vector<std::string>& on_request_complete_commands,
|
||||
bool interactive,
|
||||
bool show_command_data)
|
||||
: output_dir(output_dir),
|
||||
: remote_host(remote_host),
|
||||
remote_port(remote_port),
|
||||
output_dir(output_dir),
|
||||
version(version),
|
||||
language(language),
|
||||
show_command_data(show_command_data),
|
||||
bb_key_file(bb_key_file),
|
||||
serial_number2(serial_number2),
|
||||
serial_number(serial_number),
|
||||
serial_number2(serial_number2),
|
||||
access_key(access_key),
|
||||
username(username),
|
||||
password(password),
|
||||
@@ -75,33 +74,16 @@ DownloadSession::DownloadSession(
|
||||
ship_menu_selections(ship_menu_selections),
|
||||
on_request_complete_commands(on_request_complete_commands),
|
||||
interactive(interactive),
|
||||
log(phosg::string_printf("[DownloadSession:%s] ", phosg::name_for_enum(version)), proxy_server_log.min_level),
|
||||
base(base),
|
||||
channel(
|
||||
version,
|
||||
language,
|
||||
DownloadSession::dispatch_on_channel_input,
|
||||
DownloadSession::dispatch_on_channel_error,
|
||||
this,
|
||||
phosg::render_sockaddr_storage(remote),
|
||||
show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END,
|
||||
show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END),
|
||||
hardware_id(generate_random_hardware_id(this->channel.version)),
|
||||
guild_card_number(0),
|
||||
log(std::format("[DownloadSession:{}] ", phosg::name_for_enum(version)), proxy_server_log.min_level),
|
||||
io_context(io_context),
|
||||
hardware_id(generate_random_hardware_id(version)),
|
||||
prev_cmd_data(0),
|
||||
client_config(0),
|
||||
sent_96(false),
|
||||
should_request_category_list(true),
|
||||
current_request(0),
|
||||
current_game_config_index(0),
|
||||
in_game(false),
|
||||
bin_complete(false),
|
||||
dat_complete(false) {
|
||||
client_config(0) {
|
||||
if (this->output_dir.empty()) {
|
||||
this->output_dir = ".";
|
||||
}
|
||||
|
||||
switch (this->channel.version) {
|
||||
switch (version) {
|
||||
case Version::DC_V1:
|
||||
case Version::DC_V2:
|
||||
if (this->serial_number2 == 0 || this->serial_number == 0 || this->access_key.empty()) {
|
||||
@@ -132,106 +114,102 @@ DownloadSession::DownloadSession(
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
this->character->inventory.language = this->channel.language;
|
||||
|
||||
if (remote.ss_family != AF_INET) {
|
||||
throw runtime_error("remote is not AF_INET");
|
||||
}
|
||||
|
||||
string netloc_str = phosg::render_sockaddr_storage(remote);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(
|
||||
this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(),
|
||||
reinterpret_cast<const sockaddr*>(&remote), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->character->inventory.language = language;
|
||||
}
|
||||
|
||||
void DownloadSession::dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data) {
|
||||
auto* session = reinterpret_cast<DownloadSession*>(ch.context_obj);
|
||||
session->on_channel_input(command, flag, data);
|
||||
asio::awaitable<void> DownloadSession::run() {
|
||||
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));
|
||||
this->channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
std::move(sock),
|
||||
this->version,
|
||||
this->language,
|
||||
netloc_str,
|
||||
this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END,
|
||||
this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END);
|
||||
this->log.info_f("Server channel connected");
|
||||
|
||||
while (this->channel->connected()) {
|
||||
auto msg = co_await this->channel->recv();
|
||||
co_await this->on_message(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::send_93_9D_9E(bool extended) {
|
||||
if (is_v1(this->channel.version)) {
|
||||
if (is_v1(this->version)) {
|
||||
C_LoginExtendedV1_DC_93 ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.language = this->language;
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.serial_number2.encode(phosg::string_printf("%08" PRIX32, this->serial_number2));
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
this->channel.send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93));
|
||||
ret.serial_number2.encode(std::format("{:08X}", this->serial_number2));
|
||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
||||
this->channel->send(0x93, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_LoginV1_DC_93));
|
||||
|
||||
} else if (is_v2(this->channel.version)) {
|
||||
} else if (is_v2(this->version)) {
|
||||
C_LoginExtended_PC_9D ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.language = this->language;
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
||||
size_t data_size = extended
|
||||
? ((this->channel.version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D))
|
||||
? ((this->version == Version::PC_V2) ? sizeof(ret) : sizeof(C_LoginExtended_DC_GC_9D))
|
||||
: sizeof(C_Login_DC_PC_GC_9D);
|
||||
this->channel.send(0x9D, 0x01, &ret, data_size);
|
||||
this->channel->send(0x9D, 0x01, &ret, data_size);
|
||||
|
||||
} else if (this->channel.version == Version::GC_V3) {
|
||||
} else if (this->version == Version::GC_V3) {
|
||||
C_LoginExtended_GC_9E ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.language = this->language;
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
||||
ret.client_config = this->client_config;
|
||||
this->channel.send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_GC_9E));
|
||||
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_GC_9E));
|
||||
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
} else if (this->version == Version::XB_V3) {
|
||||
C_LoginExtended_XB_9E ret;
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.is_extended = extended ? 1 : 0;
|
||||
ret.language = this->channel.language;
|
||||
ret.language = this->language;
|
||||
ret.serial_number.encode(this->xb_gamertag);
|
||||
ret.access_key.encode(phosg::string_printf("%016" PRIX64, this->xb_user_id));
|
||||
ret.access_key.encode(std::format("{:016X}", this->xb_user_id));
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.name.encode(this->character->disp.name.decode());
|
||||
ret.netloc.internal_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.external_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.port = 9500;
|
||||
phosg::random_data(&ret.netloc.mac_address, sizeof(ret.netloc.mac_address));
|
||||
ret.netloc.sg_ip_address = phosg::random_object<uint32_t>();
|
||||
ret.netloc.spi = phosg::random_object<uint32_t>();
|
||||
ret.netloc.account_id = this->xb_account_id;
|
||||
ret.netloc.unknown_a3.clear(0);
|
||||
ret.login_character_name.encode(this->character->disp.name.decode());
|
||||
ret.xb_netloc.internal_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.xb_netloc.external_ipv4_address = phosg::random_object<uint32_t>();
|
||||
ret.xb_netloc.port = 9500;
|
||||
phosg::random_data(&ret.xb_netloc.mac_address, sizeof(ret.xb_netloc.mac_address));
|
||||
ret.xb_netloc.sg_ip_address = phosg::random_object<uint32_t>();
|
||||
ret.xb_netloc.spi = phosg::random_object<uint32_t>();
|
||||
ret.xb_netloc.account_id = this->xb_account_id;
|
||||
ret.xb_netloc.unknown_a3.clear(0);
|
||||
ret.xb_user_id_high = this->xb_user_id >> 32;
|
||||
ret.xb_user_id_low = this->xb_user_id;
|
||||
this->channel.send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
||||
this->channel->send(0x9E, 0x01, &ret, extended ? sizeof(ret) : sizeof(C_Login_DC_PC_GC_9D));
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
@@ -241,31 +219,31 @@ void DownloadSession::send_93_9D_9E(bool extended) {
|
||||
void DownloadSession::send_61_98(bool is_98) {
|
||||
uint8_t command = is_98 ? 0x98 : 0x61;
|
||||
|
||||
if (is_v1(this->channel.version)) {
|
||||
if (is_v1(this->version)) {
|
||||
C_CharacterData_DCv1_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
this->channel.send(command, 0x01, ret);
|
||||
this->channel->send(command, 0x01, ret);
|
||||
|
||||
} else if (this->channel.version == Version::DC_V2) {
|
||||
} else if (this->version == Version::DC_V2) {
|
||||
C_CharacterData_DCv2_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
this->channel.send(command, 0x02, ret);
|
||||
this->channel->send(command, 0x02, ret);
|
||||
|
||||
} else if (this->channel.version == Version::PC_V2) {
|
||||
} else if (this->version == Version::PC_V2) {
|
||||
C_CharacterData_PC_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
ret.records.challenge = this->character->challenge_records;
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
this->channel.send(command, 0x02, ret);
|
||||
this->channel->send(command, 0x02, ret);
|
||||
|
||||
} else if (is_v3(this->channel.version)) {
|
||||
} else if (is_v3(this->version)) {
|
||||
C_CharacterData_V3_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = convert_player_disp_data<PlayerDispDataDCPCV3, PlayerDispDataBB>(this->character->disp, 1, 1);
|
||||
@@ -273,9 +251,9 @@ void DownloadSession::send_61_98(bool is_98) {
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
ret.info_board.encode(this->character->info_board.decode());
|
||||
this->channel.send(command, 0x03, ret);
|
||||
this->channel->send(command, 0x03, ret);
|
||||
|
||||
} else if (this->channel.version == Version::BB_V4) {
|
||||
} else if (this->version == Version::BB_V4) {
|
||||
C_CharacterData_BB_61_98 ret;
|
||||
ret.inventory = this->character->inventory;
|
||||
ret.disp = this->character->disp;
|
||||
@@ -283,34 +261,30 @@ void DownloadSession::send_61_98(bool is_98) {
|
||||
ret.records.battle = this->character->battle_records;
|
||||
ret.choice_search_config = this->character->choice_search_config;
|
||||
ret.info_board.encode(this->character->info_board.decode());
|
||||
this->channel.send(command, 0x04, ret);
|
||||
this->channel->send(command, 0x04, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::string& data) {
|
||||
// TODO: Use the iovec form of print_data here instead of
|
||||
// prepend_command_header (which copies the string)
|
||||
string full_cmd = prepend_command_header(this->channel.version, this->channel.crypt_in.get(), command, flag, data);
|
||||
|
||||
for (size_t z = 0; z < 0x28 && z < data.size(); z++) {
|
||||
this->prev_cmd_data[z] = data[z];
|
||||
asio::awaitable<void> DownloadSession::on_message(Channel::Message& msg) {
|
||||
for (size_t z = 0; z < 0x28 && z < msg.data.size(); z++) {
|
||||
this->prev_cmd_data[z] = msg.data[z];
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
switch (msg.command) {
|
||||
case 0x03: {
|
||||
if (this->channel.version != Version::BB_V4) {
|
||||
if (this->version != Version::BB_V4) {
|
||||
throw runtime_error("BB server sent non-BB encryption command");
|
||||
}
|
||||
if (!this->bb_key_file) {
|
||||
throw runtime_error("BB encryption requires a key file");
|
||||
}
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_BB_03_9B>(data, 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->log.info("Enabled BB encryption");
|
||||
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->log.info_f("Enabled BB encryption");
|
||||
throw runtime_error("not yet implemented"); // Send 93
|
||||
break;
|
||||
}
|
||||
@@ -319,54 +293,54 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
case 0x17:
|
||||
case 0x91:
|
||||
case 0x9B: {
|
||||
const auto& cmd = check_size_t<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(data, 0xFFFF);
|
||||
if (uses_v3_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV3Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV3Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V3 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
} else if (!uses_v4_encryption(this->channel.version)) {
|
||||
this->channel.crypt_in = make_shared<PSOV2Encryption>(cmd.server_key);
|
||||
this->channel.crypt_out = make_shared<PSOV2Encryption>(cmd.client_key);
|
||||
this->log.info("Enabled V2 encryption (server key %08" PRIX32 ", client key %08" PRIX32 ")",
|
||||
cmd.server_key.load(), cmd.client_key.load());
|
||||
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->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->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");
|
||||
}
|
||||
|
||||
if (command == 0x02) {
|
||||
bool is_extended = (this->channel.version == Version::XB_V3);
|
||||
if (msg.command == 0x02) {
|
||||
bool is_extended = (this->version == Version::XB_V3);
|
||||
this->send_93_9D_9E(is_extended);
|
||||
|
||||
} else {
|
||||
if (is_v1(this->channel.version)) {
|
||||
if (is_v1(this->version)) {
|
||||
C_LoginV1_DC_PC_V3_90 ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
this->channel.send(0x90, 0x00, ret);
|
||||
this->channel->send(0x90, 0x00, ret);
|
||||
|
||||
} else if (is_v2(this->channel.version)) {
|
||||
} else if (is_v2(this->version)) {
|
||||
C_Login_DC_PC_V3_9A ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.player_tag = this->guild_card_number ? 0xFFFF0000 : 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
this->channel.send(0x9A, 0x00, ret);
|
||||
this->channel->send(0x9A, 0x00, ret);
|
||||
|
||||
} else if (this->channel.version == Version::GC_V3) {
|
||||
} else if (this->version == Version::GC_V3) {
|
||||
C_VerifyAccount_V3_DB ret;
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.serial_number2 = ret.serial_number;
|
||||
ret.access_key2 = ret.access_key;
|
||||
ret.password.encode(this->password);
|
||||
this->channel.send(0xDB, 0x00, ret);
|
||||
this->channel->send(0xDB, 0x00, ret);
|
||||
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
} else if (this->version == Version::XB_V3) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
@@ -379,36 +353,36 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
|
||||
case 0x90:
|
||||
case 0x9A: {
|
||||
if (flag == 1) {
|
||||
if (is_v1(this->channel.version)) {
|
||||
if (msg.flag == 1) {
|
||||
if (is_v1(this->version)) {
|
||||
C_RegisterV1_DC_92 ret;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.language = this->channel.language;
|
||||
ret.serial_number2.encode(phosg::string_printf("%08" PRIX32, this->serial_number2));
|
||||
this->channel.send(0x92, 0x00, ret);
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.language = this->language;
|
||||
ret.serial_number2.encode(std::format("{:08X}", this->serial_number2));
|
||||
this->channel->send(0x92, 0x00, ret);
|
||||
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
} else if (!is_v4(this->version)) {
|
||||
C_Register_DC_PC_V3_9C ret;
|
||||
ret.hardware_id = this->hardware_id;
|
||||
ret.sub_version = default_sub_version_for_version(this->channel.version);
|
||||
ret.language = this->channel.language;
|
||||
if (this->channel.version == Version::XB_V3) {
|
||||
ret.sub_version = default_sub_version_for_version(this->version);
|
||||
ret.language = this->language;
|
||||
if (this->version == Version::XB_V3) {
|
||||
ret.serial_number.encode(this->xb_gamertag);
|
||||
ret.access_key.encode(phosg::string_printf("%016" PRIX64, this->xb_user_id));
|
||||
ret.access_key.encode(std::format("{:016X}", this->xb_user_id));
|
||||
ret.password.encode("xbox-pso");
|
||||
} else {
|
||||
ret.serial_number.encode(phosg::string_printf("%08" PRIX32, this->serial_number));
|
||||
ret.serial_number.encode(std::format("{:08X}", this->serial_number));
|
||||
ret.access_key.encode(this->access_key);
|
||||
ret.password.encode(this->password);
|
||||
}
|
||||
this->channel.send(0x9C, 0x00, ret);
|
||||
this->channel->send(0x9C, 0x00, ret);
|
||||
|
||||
} else {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
|
||||
} else if (flag == 0 || flag == 2) {
|
||||
} else if (msg.flag == 0 || msg.flag == 2) {
|
||||
this->send_93_9D_9E(true);
|
||||
|
||||
} else {
|
||||
@@ -419,17 +393,17 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
|
||||
case 0x92:
|
||||
case 0x9C:
|
||||
if (flag == 0) {
|
||||
if (msg.flag == 0) {
|
||||
throw runtime_error("server rejected login credentials");
|
||||
}
|
||||
this->send_93_9D_9E(true);
|
||||
break;
|
||||
|
||||
case 0x9F: {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
if (is_v1_or_v2(this->version)) {
|
||||
throw runtime_error("invalid command");
|
||||
}
|
||||
this->channel.send(0x9F, 0x00, this->client_config);
|
||||
this->channel->send(0x9F, 0x00, this->client_config);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -437,16 +411,16 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
C_ExecuteCodeResult_B3 ret;
|
||||
ret.checksum = 0;
|
||||
ret.return_value = 0;
|
||||
this->channel.send(0xB3, 0x00, ret);
|
||||
this->channel->send(0xB3, 0x00, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x04: {
|
||||
const auto& cmd = check_size_t<S_UpdateClientConfig_V3_04>(data, 0x08, sizeof(S_UpdateClientConfig_V3_04));
|
||||
if (!is_v1_or_v2(this->channel.version)) {
|
||||
const auto& cmd = msg.check_size_t<S_UpdateClientConfig_V3_04>(0x08, sizeof(S_UpdateClientConfig_V3_04));
|
||||
if (!is_v1_or_v2(this->version)) {
|
||||
for (size_t z = 0; z < 0x20; z++) {
|
||||
size_t read_index = z + 8;
|
||||
this->client_config[z] = (read_index < data.size()) ? data[read_index] : this->prev_cmd_data[read_index];
|
||||
this->client_config[z] = (read_index < msg.data.size()) ? msg.data[read_index] : this->prev_cmd_data[read_index];
|
||||
}
|
||||
}
|
||||
this->guild_card_number = cmd.guild_card_number;
|
||||
@@ -454,14 +428,14 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
C_CharSaveInfo_DCv2_PC_V3_BB_96 ret;
|
||||
ret.creation_timestamp = this->character->creation_timestamp;
|
||||
ret.event_counter = this->character->save_count;
|
||||
this->channel.send(0x96, 0x00, ret);
|
||||
this->channel->send(0x96, 0x00, ret);
|
||||
this->sent_96 = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x97:
|
||||
this->channel.send(0xB1, 0x00);
|
||||
this->channel->send(0xB1, 0x00);
|
||||
break;
|
||||
|
||||
case 0x95:
|
||||
@@ -469,13 +443,13 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
break;
|
||||
|
||||
case 0xB1:
|
||||
this->channel.send(0x99, 0x00);
|
||||
this->channel->send(0x99, 0x00);
|
||||
break;
|
||||
|
||||
case 0x1A:
|
||||
case 0xD5:
|
||||
if (is_v3(this->channel.version)) {
|
||||
this->channel.send(0xD6, 0x00);
|
||||
if (is_v3(this->version)) {
|
||||
this->channel->send(0xD6, 0x00);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -486,21 +460,21 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
C_MenuSelection_10_Flag00 ret;
|
||||
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto* items = check_size_vec_t<CmdT>(data, flag + 1);
|
||||
const auto* items = check_size_vec_t<CmdT>(msg.data, msg.flag + 1);
|
||||
size_t item_index;
|
||||
this->log.info("Ship Select menu:");
|
||||
for (item_index = 1; item_index <= flag; item_index++) {
|
||||
this->log.info_f("Ship Select menu:");
|
||||
for (item_index = 1; item_index <= msg.flag; item_index++) {
|
||||
const auto& item = items[item_index];
|
||||
auto text = strip_color(item.name.decode());
|
||||
this->log.info("%zu: (%08" PRIX32 " %08" PRIX32 ") %s", item_index, item.menu_id.load(), item.item_id.load(), text.c_str());
|
||||
this->log.info_f("{}: ({:08X} {:08X}) {}", item_index, item.menu_id, item.item_id, text);
|
||||
if (this->ship_menu_selections.count(text)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (item_index > flag) {
|
||||
if (item_index > msg.flag) {
|
||||
if (this->interactive) {
|
||||
while (item_index == 0 || item_index > flag) {
|
||||
this->log.info("Choose response index:");
|
||||
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);
|
||||
}
|
||||
@@ -512,13 +486,13 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
ret.item_id = items[item_index].item_id;
|
||||
};
|
||||
|
||||
if (uses_utf16(this->channel.version)) {
|
||||
if (uses_utf16(this->version)) {
|
||||
handle_command.operator()<S_MenuItem_PC_BB_08>();
|
||||
} else {
|
||||
handle_command.operator()<S_MenuItem_DC_V3_08_Ep3_E6>();
|
||||
}
|
||||
|
||||
this->channel.send(0x10, 0x00, ret);
|
||||
this->channel->send(0x10, 0x00, ret);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -538,39 +512,32 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
break;
|
||||
|
||||
case 0x1D:
|
||||
this->channel.send(0x1D, 0x00);
|
||||
this->channel->send(0x1D, 0x00);
|
||||
break;
|
||||
|
||||
case 0x19: {
|
||||
const auto& cmd = check_size_t<S_Reconnect_19>(data, sizeof(S_Reconnect_19), 0xFFFF);
|
||||
|
||||
sockaddr_storage ss;
|
||||
auto* sin = reinterpret_cast<sockaddr_in*>(&ss);
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = htonl(cmd.address);
|
||||
sin->sin_port = htons(cmd.port);
|
||||
string netloc_str = phosg::render_sockaddr_storage(ss);
|
||||
this->log.info("Connecting to %s", netloc_str.c_str());
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
if (!bev) {
|
||||
throw runtime_error(phosg::string_printf("failed to open socket (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
this->channel.set_bufferevent(bev, 0);
|
||||
this->channel.crypt_in.reset();
|
||||
this->channel.crypt_out.reset();
|
||||
|
||||
if (bufferevent_socket_connect(this->channel.bev.get(), reinterpret_cast<const sockaddr*>(&ss), sizeof(struct sockaddr_in)) != 0) {
|
||||
throw runtime_error(phosg::string_printf("failed to connect (%d)", EVUTIL_SOCKET_ERROR()));
|
||||
}
|
||||
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);
|
||||
this->log.info_f("Connecting to {}", netloc_str);
|
||||
auto sock = make_unique<asio::ip::tcp::socket>(co_await async_connect_tcp(new_ep));
|
||||
this->channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
std::move(sock),
|
||||
this->version,
|
||||
this->language,
|
||||
netloc_str,
|
||||
this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END,
|
||||
this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END);
|
||||
this->log.info_f("Server channel connected");
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x83: {
|
||||
const auto* items = check_size_vec_t<S_LobbyListEntry_83>(data, flag, true);
|
||||
const auto* items = check_size_vec_t<S_LobbyListEntry_83>(msg.data, msg.flag, true);
|
||||
this->lobby_menu_items.clear();
|
||||
for (size_t z = 0; z < flag; z++) {
|
||||
for (size_t z = 0; z < msg.flag; z++) {
|
||||
this->lobby_menu_items.emplace_back(items[z]);
|
||||
}
|
||||
break;
|
||||
@@ -581,7 +548,7 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
// be able to see that we didn't, so we don't bother
|
||||
|
||||
const auto& game_config = this->game_configs[this->current_game_config_index];
|
||||
if (this->channel.version == Version::PC_V2) {
|
||||
if (this->version == Version::PC_V2) {
|
||||
C_CreateGame_PC_C1 ret;
|
||||
ret.name.encode(random_name());
|
||||
ret.password.encode(random_name());
|
||||
@@ -589,16 +556,16 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
ret.battle_mode = (game_config.mode == GameMode::BATTLE);
|
||||
ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE);
|
||||
ret.episode = 1;
|
||||
this->channel.send(0xC1, 0x00, ret);
|
||||
this->channel->send(0xC1, 0x00, ret);
|
||||
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
} else if (!is_v4(this->version)) {
|
||||
C_CreateGame_DC_V3_0C_C1_Ep3_EC ret;
|
||||
ret.name.encode(random_name());
|
||||
ret.password.encode(random_name());
|
||||
ret.difficulty = 0;
|
||||
ret.battle_mode = (game_config.mode == GameMode::BATTLE);
|
||||
ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE);
|
||||
if (is_v1(this->channel.version)) {
|
||||
if (is_v1(this->version)) {
|
||||
ret.episode = 0;
|
||||
} else if (game_config.episode == Episode::EP1) {
|
||||
ret.episode = 1;
|
||||
@@ -609,7 +576,7 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
} else {
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
this->channel.send(is_v1(this->channel.version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
this->channel->send(is_v1(this->version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
|
||||
} else {
|
||||
C_CreateGame_BB_C1 ret;
|
||||
@@ -628,7 +595,7 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
throw std::logic_error("invalid episode");
|
||||
}
|
||||
ret.solo_mode = (game_config.mode == GameMode::SOLO);
|
||||
this->channel.send(is_v1(this->channel.version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
this->channel->send(is_v1(this->version) ? 0x0C : 0xC1, 0x00, ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -641,32 +608,32 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
this->character->inventory.items[z].data.id = 0x00010000 + z;
|
||||
}
|
||||
|
||||
if (!is_v1(this->channel.version)) {
|
||||
this->channel.send(0x8A, 0x00);
|
||||
if (!is_v1(this->version)) {
|
||||
this->channel->send(0x8A, 0x00);
|
||||
}
|
||||
this->channel.send(0x6F, 0x00);
|
||||
this->channel->send(0x6F, 0x00);
|
||||
this->send_next_request();
|
||||
break;
|
||||
}
|
||||
|
||||
case 0xA2: {
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto* items = check_size_vec_t<CmdT>(data, flag);
|
||||
for (size_t z = 0; z < flag; z++) {
|
||||
const auto* items = check_size_vec_t<CmdT>(msg.data, msg.flag);
|
||||
for (size_t z = 0; z < msg.flag; z++) {
|
||||
const auto& item = items[z];
|
||||
uint64_t request = (static_cast<uint64_t>(item.menu_id) << 32) | static_cast<uint64_t>(item.item_id);
|
||||
if (!this->done_requests.count(request)) {
|
||||
this->log.info("Adding request %016" PRIX64, request);
|
||||
this->log.info_f("Adding request {:016X}", request);
|
||||
this->pending_requests.emplace(request, item.name.decode());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this->channel.version == Version::PC_V2) {
|
||||
if (this->version == Version::PC_V2) {
|
||||
handle_command.operator()<S_QuestMenuEntry_PC_A2_A4>();
|
||||
} else if (this->channel.version == Version::XB_V3) {
|
||||
} else if (this->version == Version::XB_V3) {
|
||||
handle_command.operator()<S_QuestMenuEntry_XB_A2_A4>();
|
||||
} else if (this->channel.version == Version::BB_V4) {
|
||||
} else if (this->version == Version::BB_V4) {
|
||||
handle_command.operator()<S_QuestMenuEntry_BB_A2_A4>();
|
||||
} else {
|
||||
handle_command.operator()<S_QuestMenuEntry_DC_GC_A2_A4>();
|
||||
@@ -677,26 +644,26 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
case 0x44:
|
||||
case 0xA6: {
|
||||
auto handle_command = [&]<typename CmdT>() {
|
||||
const auto& cmd = check_size_t<CmdT>(data, 0xFFFF);
|
||||
const auto& cmd = msg.check_size_t<CmdT>(0xFFFF);
|
||||
string internal_name = cmd.filename.decode();
|
||||
string filtered_name;
|
||||
for (char ch : internal_name) {
|
||||
filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_');
|
||||
}
|
||||
string local_filename = phosg::string_printf(
|
||||
"%s/%016" PRIX64 "_%" PRIu64 "_%s_%c_%s",
|
||||
this->output_dir.c_str(),
|
||||
string local_filename = std::format(
|
||||
"{}/{:016X}_{}_{}_{}_{}",
|
||||
this->output_dir,
|
||||
this->current_request,
|
||||
phosg::now(),
|
||||
phosg::name_for_enum(this->channel.version),
|
||||
char_for_language_code(this->channel.language),
|
||||
filtered_name.c_str());
|
||||
phosg::name_for_enum(this->version),
|
||||
char_for_language_code(this->language),
|
||||
filtered_name);
|
||||
this->open_files.emplace(internal_name, OpenFile{.request = this->current_request, .filename = local_filename, .total_size = cmd.file_size, .data = ""});
|
||||
};
|
||||
|
||||
if (is_dc(this->channel.version)) {
|
||||
if (is_dc(this->version)) {
|
||||
handle_command.operator()<S_OpenFile_DC_44_A6>();
|
||||
} else if (!is_v4(this->channel.version)) {
|
||||
} else if (!is_v4(this->version)) {
|
||||
handle_command.operator()<S_OpenFile_PC_GC_44_A6>();
|
||||
} else {
|
||||
handle_command.operator()<S_OpenFile_BB_44_A6>();
|
||||
@@ -705,22 +672,22 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
}
|
||||
case 0x13:
|
||||
case 0xA7: {
|
||||
const auto& cmd = check_size_t<S_WriteFile_13_A7>(data);
|
||||
const auto& cmd = msg.check_size_t<S_WriteFile_13_A7>();
|
||||
string internal_filename = cmd.filename.decode();
|
||||
|
||||
if (!is_v1_or_v2(this->channel.version)) {
|
||||
if (!is_v1_or_v2(this->version)) {
|
||||
C_WriteFileConfirmation_V3_BB_13_A7 ret;
|
||||
ret.filename.encode(internal_filename);
|
||||
this->channel.send(command, flag, ret);
|
||||
this->channel->send(msg.command, msg.flag, ret);
|
||||
}
|
||||
|
||||
auto f_it = this->open_files.find(internal_filename.c_str());
|
||||
auto f_it = this->open_files.find(internal_filename);
|
||||
if (f_it == this->open_files.end()) {
|
||||
this->log.warning("Received data for non-open file %s", internal_filename.c_str());
|
||||
this->log.warning_f("Received data for non-open file {}", internal_filename);
|
||||
break;
|
||||
}
|
||||
auto& f = this->open_files.at(cmd.filename.decode());
|
||||
size_t block_offset = flag * 0x400;
|
||||
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;
|
||||
@@ -732,25 +699,25 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
memcpy(f.data.data() + block_offset, cmd.data.data(), data_size);
|
||||
if (cmd.data_size != 0x400) {
|
||||
phosg::save_file(f.filename, f.data);
|
||||
this->log.info("Wrote file %s (%zu bytes)", f.filename.c_str(), f.data.size());
|
||||
this->log.info_f("Wrote file {} ({} bytes)", f.filename, f.data.size());
|
||||
this->open_files.erase(internal_filename);
|
||||
if (phosg::ends_with(internal_filename, ".bin")) {
|
||||
if (internal_filename.ends_with(".bin")) {
|
||||
this->bin_complete = true;
|
||||
} else if (phosg::ends_with(internal_filename, ".dat")) {
|
||||
} else if (internal_filename.ends_with(".dat")) {
|
||||
this->dat_complete = true;
|
||||
}
|
||||
if (this->open_files.empty() && this->bin_complete && this->dat_complete) {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
if (is_v1_or_v2(this->version)) {
|
||||
this->on_request_complete();
|
||||
} else {
|
||||
this->channel.send(0xAC, 0x00);
|
||||
this->channel->send(0xAC, 0x00);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xAC: {
|
||||
if (is_v1_or_v2(this->channel.version)) {
|
||||
if (is_v1_or_v2(this->version)) {
|
||||
throw runtime_error("unsupported version");
|
||||
}
|
||||
this->on_request_complete();
|
||||
@@ -761,24 +728,24 @@ void DownloadSession::on_channel_input(uint16_t command, uint32_t flag, std::str
|
||||
|
||||
void DownloadSession::send_next_request() {
|
||||
if (should_request_category_list) {
|
||||
this->log.info("Requesting quest list");
|
||||
this->channel.send(0xA2, 0x00);
|
||||
if (is_v4(this->channel.version)) {
|
||||
this->channel.send(0xA2, 0x01);
|
||||
this->log.info_f("Requesting quest list");
|
||||
this->channel->send(0xA2, 0x00);
|
||||
if (is_v4(this->version)) {
|
||||
this->channel->send(0xA2, 0x01);
|
||||
}
|
||||
this->should_request_category_list = false;
|
||||
|
||||
} else if (!this->pending_requests.empty()) {
|
||||
if (interactive) {
|
||||
const auto& config = this->game_configs[this->current_game_config_index];
|
||||
this->log.info("Items available to expand (mode=%s, episode=%s):", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
this->log.info_f("Items available to expand (mode={}, episode={}):", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
for (const auto& it : this->pending_requests) {
|
||||
this->log.info("%016" PRIX64 ": %s", it.first, it.second.c_str());
|
||||
this->log.info_f("{:016X}: {}", it.first, it.second);
|
||||
}
|
||||
this->log.info("Choose item to expand by ID (q to quit; s to skip to next config):");
|
||||
this->log.info_f("Choose item to expand by ID (q to quit; s to skip to next config):");
|
||||
string input = phosg::fgets(stdin);
|
||||
if (input.empty() || (input == "q\n")) {
|
||||
this->channel.disconnect();
|
||||
this->channel->disconnect();
|
||||
return;
|
||||
} else if (input == "s\n") {
|
||||
this->pending_requests.clear();
|
||||
@@ -794,22 +761,22 @@ void DownloadSession::send_next_request() {
|
||||
this->current_request = item_it->first;
|
||||
this->done_requests.emplace(this->current_request);
|
||||
this->pending_requests.erase(item_it);
|
||||
this->log.info("Sending request %016" PRIX64, this->current_request);
|
||||
this->log.info_f("Sending request {:016X}", this->current_request);
|
||||
}
|
||||
|
||||
C_MenuSelection_10_Flag00 cmd;
|
||||
cmd.menu_id = (this->current_request >> 32) & 0xFFFFFFFF;
|
||||
cmd.item_id = this->current_request & 0xFFFFFFFF;
|
||||
this->channel.send(0x10, 0x00, cmd);
|
||||
this->channel->send(0x10, 0x00, cmd);
|
||||
} else {
|
||||
this->log.info("No pending requests with current parameters");
|
||||
this->log.info_f("No pending requests with current parameters");
|
||||
this->on_request_complete();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::on_request_complete() {
|
||||
for (const auto& data : this->on_request_complete_commands) {
|
||||
this->channel.send(data);
|
||||
this->channel->send(data);
|
||||
}
|
||||
|
||||
this->send_61_98(true);
|
||||
@@ -819,14 +786,14 @@ void DownloadSession::on_request_complete() {
|
||||
C_LobbySelection_84 ret84;
|
||||
ret84.menu_id = item.menu_id;
|
||||
ret84.item_id = item.item_id;
|
||||
this->channel.send(0x84, 0x00, ret84);
|
||||
this->channel->send(0x84, 0x00, ret84);
|
||||
|
||||
if (this->pending_requests.empty()) {
|
||||
// Advance to next mode/episode combination
|
||||
this->current_game_config_index++;
|
||||
bool v1 = is_v1(this->channel.version);
|
||||
bool v2 = is_v2(this->channel.version);
|
||||
bool v3 = is_v3(this->channel.version);
|
||||
bool v1 = is_v1(this->version);
|
||||
bool v2 = is_v2(this->version);
|
||||
bool v3 = is_v3(this->version);
|
||||
while ((this->current_game_config_index < this->game_configs.size()) &&
|
||||
((v1 && !this->game_configs[this->current_game_config_index].v1) ||
|
||||
(v2 && !this->game_configs[this->current_game_config_index].v2) ||
|
||||
@@ -834,36 +801,16 @@ void DownloadSession::on_request_complete() {
|
||||
this->current_game_config_index++;
|
||||
}
|
||||
if (this->current_game_config_index >= this->game_configs.size()) {
|
||||
this->log.info("All modes complete");
|
||||
this->channel.disconnect();
|
||||
this->log.info_f("All modes complete");
|
||||
this->channel->disconnect();
|
||||
} else {
|
||||
const auto& config = this->game_configs[this->current_game_config_index];
|
||||
this->log.info("Advancing to %s mode in %s", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
this->log.info_f("Advancing to {} mode in {}", name_for_mode(config.mode), name_for_episode(config.episode));
|
||||
this->should_request_category_list = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadSession::dispatch_on_channel_error(Channel& ch, short events) {
|
||||
auto* session = reinterpret_cast<DownloadSession*>(ch.context_obj);
|
||||
session->on_channel_error(events);
|
||||
}
|
||||
|
||||
void DownloadSession::on_channel_error(short events) {
|
||||
if (events & BEV_EVENT_CONNECTED) {
|
||||
this->log.info("Server channel connected");
|
||||
}
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
this->log.warning("Error %d (%s) in server stream", err, evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
|
||||
this->log.info("Server endpoint has disconnected");
|
||||
this->channel.disconnect();
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<DownloadSession::GameConfig> DownloadSession::game_configs({
|
||||
{.mode = GameMode::NORMAL, .episode = Episode::EP1, .v1 = true, .v2 = true, .v3 = true},
|
||||
{.mode = GameMode::NORMAL, .episode = Episode::EP2, .v1 = false, .v2 = false, .v3 = true},
|
||||
|
||||
+24
-24
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -18,8 +16,9 @@
|
||||
class DownloadSession {
|
||||
public:
|
||||
DownloadSession(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
const struct sockaddr_storage& remote,
|
||||
std::shared_ptr<asio::io_context> io_context,
|
||||
const std::string& remote_host,
|
||||
uint16_t remote_port,
|
||||
const std::string& output_dir,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
@@ -43,12 +42,19 @@ public:
|
||||
DownloadSession& operator=(DownloadSession&&) = delete;
|
||||
virtual ~DownloadSession() = default;
|
||||
|
||||
asio::awaitable<void> run();
|
||||
|
||||
protected:
|
||||
// Config (must be set by caller)
|
||||
std::string remote_host;
|
||||
uint16_t remote_port;
|
||||
std::string output_dir;
|
||||
Version version;
|
||||
uint8_t language;
|
||||
bool show_command_data;
|
||||
std::shared_ptr<const PSOBBEncryption::KeyFile> bb_key_file;
|
||||
uint32_t serial_number2;
|
||||
uint32_t serial_number;
|
||||
uint32_t serial_number2;
|
||||
std::string access_key;
|
||||
std::string username;
|
||||
std::string password;
|
||||
@@ -62,17 +68,17 @@ protected:
|
||||
|
||||
// State (set during session)
|
||||
phosg::PrefixedLogger log;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
Channel channel;
|
||||
std::shared_ptr<asio::io_context> io_context;
|
||||
std::shared_ptr<Channel> channel;
|
||||
uint64_t hardware_id;
|
||||
uint32_t guild_card_number;
|
||||
uint32_t guild_card_number = 0;
|
||||
parray<uint8_t, 0x28> prev_cmd_data;
|
||||
parray<uint8_t, 0x20> client_config;
|
||||
bool sent_96;
|
||||
bool sent_96 = false;
|
||||
std::vector<S_LobbyListEntry_83> lobby_menu_items;
|
||||
|
||||
bool should_request_category_list;
|
||||
uint64_t current_request;
|
||||
bool should_request_category_list = true;
|
||||
uint64_t current_request = 0;
|
||||
std::map<uint64_t, std::string> pending_requests;
|
||||
std::unordered_set<uint64_t> done_requests;
|
||||
|
||||
@@ -92,20 +98,14 @@ protected:
|
||||
bool v3;
|
||||
};
|
||||
static const std::vector<GameConfig> game_configs;
|
||||
size_t current_game_config_index;
|
||||
bool in_game;
|
||||
bool bin_complete;
|
||||
bool dat_complete;
|
||||
size_t current_game_config_index = 0;
|
||||
bool in_game = false;
|
||||
bool bin_complete = false;
|
||||
bool dat_complete = false;
|
||||
|
||||
static void dispatch_on_channel_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void dispatch_on_channel_error(Channel& ch, short events);
|
||||
void on_channel_input(uint16_t command, uint32_t flag, std::string& msg);
|
||||
void on_channel_error(short events);
|
||||
|
||||
void send_next_request();
|
||||
void on_request_complete();
|
||||
|
||||
void assign_item_ids(uint32_t base_item_id);
|
||||
void send_93_9D_9E(bool extended);
|
||||
void send_61_98(bool is_98);
|
||||
asio::awaitable<void> on_message(Channel::Message& msg);
|
||||
void send_next_request();
|
||||
void on_request_complete();
|
||||
};
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ EnemyType phosg::enum_for_name<EnemyType>(const char* name) {
|
||||
if (index.empty()) {
|
||||
for (const auto& def : type_defs) {
|
||||
if (!index.emplace(def.enum_name, def.type).second) {
|
||||
throw logic_error(phosg::string_printf("duplicate enemy enum name: %s", def.enum_name));
|
||||
throw logic_error(std::format("duplicate enemy enum name: {}", def.enum_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,19 +82,19 @@ void BattleRecord::Event::serialize(phosg::StringWriter& w) const {
|
||||
|
||||
void BattleRecord::Event::print(FILE* stream) const {
|
||||
string time_str = phosg::format_time(this->timestamp);
|
||||
fprintf(stream, "Event @%016" PRIX64 " (%s) ", this->timestamp, time_str.c_str());
|
||||
phosg::fwrite_fmt(stream, "Event @{:016X} ({}) ", this->timestamp, time_str);
|
||||
switch (this->type) {
|
||||
case Type::PLAYER_JOIN:
|
||||
fprintf(stream, "PLAYER_JOIN %02" PRIX32 "\n", this->players[0].lobby_data.client_id.load());
|
||||
phosg::fwrite_fmt(stream, "PLAYER_JOIN {:02X}\n", this->players[0].lobby_data.client_id);
|
||||
this->players[0].print(stream);
|
||||
break;
|
||||
case Type::PLAYER_LEAVE:
|
||||
fprintf(stream, "PLAYER_LEAVE %02hhu\n", this->leaving_client_id);
|
||||
phosg::fwrite_fmt(stream, "PLAYER_LEAVE {:02}\n", this->leaving_client_id);
|
||||
break;
|
||||
case Type::SET_INITIAL_PLAYERS:
|
||||
fprintf(stream, "SET_INITIAL_PLAYERS");
|
||||
phosg::fwrite_fmt(stream, "SET_INITIAL_PLAYERS");
|
||||
for (const auto& player : this->players) {
|
||||
fprintf(stream, " %02" PRIX32, player.lobby_data.client_id.load());
|
||||
phosg::fwrite_fmt(stream, " {:02X}", player.lobby_data.client_id);
|
||||
}
|
||||
fputc('\n', stream);
|
||||
for (const auto& player : this->players) {
|
||||
@@ -102,23 +102,23 @@ void BattleRecord::Event::print(FILE* stream) const {
|
||||
}
|
||||
break;
|
||||
case Type::BATTLE_COMMAND:
|
||||
fprintf(stream, "BATTLE_COMMAND\n");
|
||||
phosg::fwrite_fmt(stream, "BATTLE_COMMAND\n");
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::GAME_COMMAND:
|
||||
fprintf(stream, "GAME_COMMAND\n");
|
||||
phosg::fwrite_fmt(stream, "GAME_COMMAND\n");
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::EP3_GAME_COMMAND:
|
||||
fprintf(stream, "EP3_GAME_COMMAND\n");
|
||||
phosg::fwrite_fmt(stream, "EP3_GAME_COMMAND\n");
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::CHAT_MESSAGE:
|
||||
fprintf(stream, "CHAT_MESSAGE %08" PRIX32 "\n", this->guild_card_number);
|
||||
phosg::fwrite_fmt(stream, "CHAT_MESSAGE {:08X}\n", this->guild_card_number);
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
case Type::SERVER_DATA_COMMAND:
|
||||
fprintf(stream, "SERVER_DATA_COMMAND\n");
|
||||
phosg::fwrite_fmt(stream, "SERVER_DATA_COMMAND\n");
|
||||
phosg::print_data(stream, this->data, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
break;
|
||||
default:
|
||||
@@ -363,26 +363,24 @@ 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);
|
||||
fprintf(stream, "BattleRecord %s behavior_flags=%08" PRIX32 " start=%016" PRIX64 " (%s) end=%016" PRIX64 " (%s); %zu events\n",
|
||||
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.c_str(),
|
||||
start_str,
|
||||
this->battle_end_timestamp,
|
||||
end_str.c_str(), this->events.size());
|
||||
end_str, this->events.size());
|
||||
for (const auto& event : this->events) {
|
||||
event.print(stream);
|
||||
}
|
||||
}
|
||||
|
||||
BattleRecordPlayer::BattleRecordPlayer(
|
||||
shared_ptr<const BattleRecord> rec,
|
||||
shared_ptr<struct event_base> base)
|
||||
: record(rec),
|
||||
BattleRecordPlayer::BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, shared_ptr<const BattleRecord> rec)
|
||||
: io_context(io_context),
|
||||
record(rec),
|
||||
event_it(this->record->events.begin()),
|
||||
play_start_timestamp(0),
|
||||
base(base),
|
||||
next_command_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &BattleRecordPlayer::dispatch_schedule_events, this), event_free) {}
|
||||
next_command_timer(*this->io_context) {}
|
||||
|
||||
shared_ptr<const BattleRecord> BattleRecordPlayer::get_record() const {
|
||||
return this->record;
|
||||
@@ -395,40 +393,37 @@ void BattleRecordPlayer::set_lobby(shared_ptr<Lobby> l) {
|
||||
void BattleRecordPlayer::start() {
|
||||
if (this->play_start_timestamp == 0) {
|
||||
this->play_start_timestamp = phosg::now();
|
||||
this->schedule_events();
|
||||
asio::co_spawn(*this->io_context, this->play_task(), asio::detached);
|
||||
}
|
||||
}
|
||||
|
||||
void BattleRecordPlayer::dispatch_schedule_events(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<BattleRecordPlayer*>(ctx)->schedule_events();
|
||||
}
|
||||
|
||||
void BattleRecordPlayer::schedule_events() {
|
||||
// If the lobby is destroyed, we can't replay anything - just return without
|
||||
// rescheduling
|
||||
asio::awaitable<void> BattleRecordPlayer::play_task() {
|
||||
auto l = this->lobby.lock();
|
||||
if (!l) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
uint64_t relative_ts = phosg::now() - this->play_start_timestamp + this->record->battle_start_timestamp;
|
||||
|
||||
// If the lobby is destroyed, we can't replay anything
|
||||
if (!l) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
if (this->event_it == this->record->events.end()) {
|
||||
if (relative_ts >= this->record->battle_end_timestamp) {
|
||||
// If the record is complete and the end timestamp has been reached,
|
||||
// send exit commands to all players in the lobby, and don't reschedule
|
||||
// the event (it will be deleted along with the Player when the lobby is
|
||||
// destroyed, when the last client leaves)
|
||||
// the event (it will be deleted along with the Player when the lobby
|
||||
// is destroyed, when the last client leaves)
|
||||
send_command(l, 0xED, 0x00);
|
||||
break;
|
||||
|
||||
} else {
|
||||
// There are no more events to play, but the battle has not officially
|
||||
// ended yet - reschedule the event for the end time
|
||||
auto tv = phosg::usecs_to_timeval(this->record->battle_end_timestamp - relative_ts);
|
||||
event_add(this->next_command_ev.get(), &tv);
|
||||
// There are no more events to play, but the battle has not actually
|
||||
// ended yet; wait until the end time
|
||||
this->next_command_timer.expires_after(std::chrono::microseconds(this->record->battle_end_timestamp - relative_ts));
|
||||
co_await this->next_command_timer.async_wait(asio::use_awaitable);
|
||||
l = this->lobby.lock();
|
||||
}
|
||||
break;
|
||||
|
||||
} else {
|
||||
if (this->event_it->timestamp <= relative_ts) {
|
||||
@@ -464,11 +459,10 @@ void BattleRecordPlayer::schedule_events() {
|
||||
this->event_it++;
|
||||
|
||||
} else {
|
||||
// The next event should not occur yet, so reschedule for the time when
|
||||
// it should occur
|
||||
auto tv = phosg::usecs_to_timeval(this->event_it->timestamp - relative_ts);
|
||||
event_add(this->next_command_ev.get(), &tv);
|
||||
break;
|
||||
// The next event should not occur yet, so wait until its time
|
||||
this->next_command_timer.expires_after(std::chrono::microseconds(this->event_it->timestamp - relative_ts));
|
||||
co_await this->next_command_timer.async_wait(asio::use_awaitable);
|
||||
l = this->lobby.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <phosg/Strings.hh>
|
||||
@@ -108,7 +108,7 @@ private:
|
||||
|
||||
class BattleRecordPlayer {
|
||||
public:
|
||||
BattleRecordPlayer(std::shared_ptr<const BattleRecord> rec, std::shared_ptr<struct event_base> base);
|
||||
BattleRecordPlayer(std::shared_ptr<asio::io_context> io_context, std::shared_ptr<const BattleRecord> rec);
|
||||
~BattleRecordPlayer() = default;
|
||||
|
||||
std::shared_ptr<const BattleRecord> get_record() const;
|
||||
@@ -117,16 +117,14 @@ public:
|
||||
void start();
|
||||
|
||||
private:
|
||||
static void dispatch_schedule_events(evutil_socket_t, short, void* ctx);
|
||||
void schedule_events();
|
||||
|
||||
std::shared_ptr<asio::io_context> io_context;
|
||||
std::shared_ptr<const BattleRecord> record;
|
||||
std::deque<BattleRecord::Event>::const_iterator event_it;
|
||||
uint64_t play_start_timestamp;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::weak_ptr<Lobby> lobby;
|
||||
std::shared_ptr<struct event> next_command_ev;
|
||||
phosg::StringReader random_r;
|
||||
asio::steady_timer next_command_timer;
|
||||
|
||||
asio::awaitable<void> play_task();
|
||||
};
|
||||
|
||||
} // namespace Episode3
|
||||
|
||||
+84
-84
@@ -123,7 +123,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
int8_t dice_roll_value,
|
||||
int8_t random_percent) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("apply_abnormal_condition(%02hhX, @%04X, @%04X, %hd, %hhd, %hhd): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent));
|
||||
auto log = s->log_stack(std::format("apply_abnormal_condition({:02X}, @{:04X}, @{:04X}, {}, {}, {}): ", def_effect_index, target_card_ref, sc_card_ref, value, dice_roll_value, random_percent));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
ssize_t existing_cond_index;
|
||||
@@ -150,13 +150,13 @@ ssize_t Card::apply_abnormal_condition(
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.debug("existing_cond_index < 0 (new condition) => cond_index = %zd", cond_index);
|
||||
log.debug_f("existing_cond_index < 0 (new condition) => cond_index = {}", cond_index);
|
||||
} else {
|
||||
log.debug("existing_cond_index = %zd (existing condition)", existing_cond_index);
|
||||
log.debug_f("existing_cond_index = {} (existing condition)", existing_cond_index);
|
||||
}
|
||||
|
||||
if (cond_index < 0) {
|
||||
log.debug("no space for condition");
|
||||
log.debug_f("no space for condition");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
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);
|
||||
log.debug("MV_BONUS combines => existing_cond_value = %hd", existing_cond_value);
|
||||
log.debug_f("MV_BONUS combines => existing_cond_value = {}", existing_cond_value);
|
||||
}
|
||||
|
||||
s->card_special->apply_stat_deltas_to_card_from_condition_and_clear_cond(cond, this->shared_from_this());
|
||||
@@ -205,7 +205,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
}
|
||||
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("wrote condition %zd => %s", cond_index, cond_str.c_str());
|
||||
log.debug_f("wrote condition {} => {}", cond_index, cond_str);
|
||||
|
||||
if (!is_nte) {
|
||||
s->card_special->update_condition_orders(this->shared_from_this());
|
||||
@@ -214,7 +214,7 @@ ssize_t Card::apply_abnormal_condition(
|
||||
continue;
|
||||
}
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("sorted conditions: [%zu] => %s", z, cond_str.c_str());
|
||||
log.debug_f("sorted conditions: [{}] => {}", z, cond_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,13 +298,13 @@ void Card::commit_attack(
|
||||
size_t strike_number,
|
||||
int16_t* out_effective_damage) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("commit_attack(@%04hX #%04hX, @%04hX #%04hX => %hd (str%zu)): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number));
|
||||
auto log = s->log_stack(std::format("commit_attack(@{:04X} #{:04X}, @{:04X} #{:04X} => {} (str{})): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id(), damage, strike_number));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
int16_t effective_damage = damage;
|
||||
s->card_special->adjust_attack_damage_due_to_conditions(
|
||||
this->shared_from_this(), &effective_damage, attacker_card->get_card_ref());
|
||||
log.debug("adjusted damage = %hd", effective_damage);
|
||||
log.debug_f("adjusted damage = {}", effective_damage);
|
||||
|
||||
size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id);
|
||||
for (size_t z = 0; z < num_assists; z++) {
|
||||
@@ -324,36 +324,36 @@ void Card::commit_attack(
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("after assists = %hd", effective_damage);
|
||||
log.debug_f("after assists = {}", effective_damage);
|
||||
|
||||
if (this->action_metadata.check_flag(0x10)) {
|
||||
effective_damage = 0;
|
||||
log.debug("flag 0x10 => effective damage = %hd", effective_damage);
|
||||
log.debug_f("flag 0x10 => effective damage = {}", effective_damage);
|
||||
}
|
||||
|
||||
auto attacker_ps = attacker_card->player_state();
|
||||
attacker_ps->stats.damage_given += effective_damage;
|
||||
this->player_state()->stats.damage_taken += effective_damage;
|
||||
log.debug("updated stats");
|
||||
log.debug_f("updated stats");
|
||||
|
||||
this->current_hp = clamp<int16_t>(this->current_hp - effective_damage, 0, this->max_hp);
|
||||
log.debug("hp set to %hd", this->current_hp);
|
||||
log.debug_f("hp set to {}", this->current_hp);
|
||||
|
||||
if ((effective_damage > 0) &&
|
||||
(attacker_ps->stats.max_attack_damage < effective_damage)) {
|
||||
attacker_ps->stats.max_attack_damage = effective_damage;
|
||||
log.debug("attacker new max damage %hd", effective_damage);
|
||||
log.debug_f("attacker new max damage {}", effective_damage);
|
||||
}
|
||||
|
||||
this->last_attack_final_damage = effective_damage;
|
||||
log.debug("last attack final damage = %hd", effective_damage);
|
||||
log.debug_f("last attack final damage = {}", effective_damage);
|
||||
if (effective_damage > 0) {
|
||||
this->card_flags = this->card_flags | 4;
|
||||
log.debug("set flag 4");
|
||||
log.debug_f("set flag 4");
|
||||
}
|
||||
if (this->current_hp < 1) {
|
||||
this->destroy_set_card(attacker_card);
|
||||
log.debug("card destroyed");
|
||||
log.debug_f("card destroyed");
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd_to_send;
|
||||
@@ -507,19 +507,19 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
}
|
||||
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("execute_attack(@%04X #%04X, @%04X #%04X): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id()));
|
||||
auto log = s->log_stack(std::format("execute_attack(@{:04X} #{:04X}, @{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id(), attacker_card->get_card_ref(), attacker_card->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
this->card_flags &= 0xFFFFFFF3;
|
||||
int16_t attack_ap = this->action_metadata.attack_bonus;
|
||||
int16_t attack_tp = 0;
|
||||
int16_t defense_power = is_nte ? 0 : this->compute_defense_power_for_attacker_card(attacker_card);
|
||||
log.debug("ap=%hd, tp=%hd", attack_ap, attack_tp);
|
||||
log.debug_f("ap={}, tp={}", attack_ap, attack_tp);
|
||||
if (!is_nte && (attack_ap == 0) && !this->action_metadata.check_flag(0x20)) {
|
||||
log.debug("ap == 0 and flag 0x20 not set");
|
||||
log.debug_f("ap == 0 and flag 0x20 not set");
|
||||
return;
|
||||
} else {
|
||||
log.debug("ap != 0 or flag 0x20 set; continuing...");
|
||||
log.debug_f("ap != 0 or flag 0x20 set; continuing...");
|
||||
}
|
||||
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
@@ -542,7 +542,7 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
|
||||
if (is_nte) {
|
||||
defense_power = this->compute_defense_power_for_attacker_card(attacker_card);
|
||||
log.debug("ap=%hd, tp=%hd, defense=%hd", attack_ap, attack_tp, defense_power);
|
||||
log.debug_f("ap={}, tp={}, defense={}", attack_ap, attack_tp, defense_power);
|
||||
attacker_card->compute_action_chain_results(true, false);
|
||||
attack_ap = attacker_card->action_chain.chain.damage;
|
||||
if (this->action_chain.chain.attack_medium == AttackMedium::TECH) {
|
||||
@@ -553,14 +553,14 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
}
|
||||
|
||||
s->card_special->compute_attack_ap(this->shared_from_this(), &attack_ap, attacker_card->get_card_ref());
|
||||
log.debug("computed ap %hd", attack_ap);
|
||||
log.debug_f("computed ap {}", attack_ap);
|
||||
this->apply_ap_and_tp_adjust_assists_to_attack(attacker_card, &attack_ap, &defense_power, &attack_tp);
|
||||
log.debug("assist adjusts ap=%hd, defense=%hd", attack_ap, defense_power);
|
||||
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;
|
||||
this->last_attack_preliminary_damage = preliminary_damage;
|
||||
log.debug("raw_damage=%hd, preliminary_damange=%hd", raw_damage, preliminary_damage);
|
||||
log.debug_f("raw_damage={}, preliminary_damange={}", raw_damage, preliminary_damage);
|
||||
|
||||
uint32_t unknown_a9 = 0;
|
||||
auto target = s->card_special->compute_replaced_target_based_on_conditions(
|
||||
@@ -568,19 +568,19 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
|
||||
if (!target) {
|
||||
target = this->shared_from_this();
|
||||
log.debug("target is not replaced");
|
||||
log.debug_f("target is not replaced");
|
||||
} else {
|
||||
log.debug("target replaced with @%04hX #%04hX", target->get_card_ref(), target->get_card_id());
|
||||
log.debug_f("target replaced with @{:04X} #{:04X}", target->get_card_ref(), target->get_card_id());
|
||||
}
|
||||
|
||||
if (!is_nte) {
|
||||
if (unknown_a9 != 0) {
|
||||
preliminary_damage = 0;
|
||||
log.debug("a9 nonzero; preliminary_damage = 0");
|
||||
log.debug_f("a9 nonzero; preliminary_damage = 0");
|
||||
}
|
||||
if (!(this->card_flags & 2) && (!attacker_card || !(attacker_card->card_flags & 2))) {
|
||||
s->card_special->check_for_defense_interference(attacker_card, this->shared_from_this(), &preliminary_damage);
|
||||
log.debug("checked for defense interference");
|
||||
log.debug_f("checked for defense interference");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,19 +592,19 @@ void Card::execute_attack(shared_ptr<Card> attacker_card) {
|
||||
ps->stats.num_attacks_taken++;
|
||||
|
||||
if (!(target->card_flags & 2)) {
|
||||
log.debug("flag 2 not set");
|
||||
log.debug_f("flag 2 not set");
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
log.debug("flag 2 set; committing zero-damage attack");
|
||||
log.debug_f("flag 2 set; committing zero-damage attack");
|
||||
target->commit_attack(0, attacker_card, &cmd, 0, nullptr);
|
||||
}
|
||||
|
||||
if (!is_nte && (this != target.get())) {
|
||||
log.debug("target was replaced; committing zero-damage attack on original card");
|
||||
log.debug_f("target was replaced; committing zero-damage attack on original card");
|
||||
this->commit_attack(0, attacker_card, &cmd, 0, nullptr);
|
||||
}
|
||||
|
||||
@@ -906,7 +906,7 @@ void Card::clear_action_chain_and_metadata_and_most_flags() {
|
||||
|
||||
void Card::compute_action_chain_results(bool apply_action_conditions, bool ignore_this_card_ap_tp) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("compute_action_chain_results(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
auto log = s->log_stack(std::format("compute_action_chain_results(@{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
this->action_chain.compute_attack_medium(s);
|
||||
@@ -914,7 +914,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
this->action_chain.chain.ap_effect_bonus = 0;
|
||||
this->action_chain.chain.tp_effect_bonus = 0;
|
||||
|
||||
log.debug("(initial) medium=%s, strike_count=%hhu, ap_effect_bonus=%hhd, tp_effect_bonus=%hhd",
|
||||
log.debug_f("(initial) medium={}, strike_count={}, ap_effect_bonus={}, tp_effect_bonus={}",
|
||||
phosg::name_for_enum(this->action_chain.chain.attack_medium),
|
||||
this->action_chain.chain.strike_count,
|
||||
this->action_chain.chain.ap_effect_bonus,
|
||||
@@ -929,9 +929,9 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
stat_swap_type = StatSwapType::NONE;
|
||||
} else {
|
||||
stat_swap_type = s->card_special->compute_stat_swap_type(this->shared_from_this());
|
||||
log.debug("stat_swap_type = %zu (0=none, 1=a/t, 2=a/h)", static_cast<size_t>(stat_swap_type));
|
||||
log.debug_f("stat_swap_type = {} (0=none, 1=a/t, 2=a/h)", static_cast<size_t>(stat_swap_type));
|
||||
s->card_special->get_effective_ap_tp(stat_swap_type, &effective_ap, &effective_tp, this->get_current_hp(), this->ap, this->tp);
|
||||
log.debug("effective_ap = %hd, effective_tp = %hd", effective_ap, effective_tp);
|
||||
log.debug_f("effective_ap = {}, effective_tp = {}", effective_ap, effective_tp);
|
||||
}
|
||||
|
||||
// This option doesn't exist in NTE
|
||||
@@ -942,7 +942,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
if (ce) {
|
||||
effective_ap += ce->def.ap.stat;
|
||||
effective_tp += ce->def.tp.stat;
|
||||
log.debug("(action card @%04hX) updated effective_ap = %hd, effective_tp = %hd", this->action_chain.chain.attack_action_card_refs[z].load(), effective_ap, effective_tp);
|
||||
log.debug_f("(action card @{:04X}) updated effective_ap = {}, effective_tp = {}", this->action_chain.chain.attack_action_card_refs[z], effective_ap, effective_tp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -957,7 +957,7 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
stat_swap_type, &card_ap, &card_tp, card->get_current_hp(), card->ap, card->tp);
|
||||
effective_ap += card_ap;
|
||||
effective_tp += card_tp;
|
||||
log.debug("(mag card set_index %zu @%04hX) updated effective_ap = %hd, effective_tp = %hd",
|
||||
log.debug_f("(mag card set_index {} @{:04X}) updated effective_ap = {}, effective_tp = {}",
|
||||
set_index, card->get_card_ref(), effective_ap, effective_tp);
|
||||
}
|
||||
}
|
||||
@@ -968,25 +968,25 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
sc_card->compute_action_chain_results(apply_action_conditions, true);
|
||||
effective_ap += sc_card->action_chain.chain.effective_ap + sc_card->action_chain.chain.ap_effect_bonus;
|
||||
effective_tp += sc_card->action_chain.chain.effective_tp + sc_card->action_chain.chain.tp_effect_bonus;
|
||||
log.debug("(item is attacking; adding SC stats) updated effective_ap = %hd, effective_tp = %hd",
|
||||
log.debug_f("(item is attacking; adding SC stats) updated effective_ap = {}, effective_tp = {}",
|
||||
effective_ap, effective_tp);
|
||||
}
|
||||
|
||||
if (!this->action_chain.check_flag(0x10)) {
|
||||
this->action_chain.chain.effective_ap = is_nte ? effective_ap : min<int16_t>(effective_ap, 99);
|
||||
log.debug("set chain effective_ap = %hd", this->action_chain.chain.effective_ap);
|
||||
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);
|
||||
log.debug("set chain effective_tp = %hd", this->action_chain.chain.effective_tp);
|
||||
log.debug_f("set chain effective_tp = {}", this->action_chain.chain.effective_tp);
|
||||
}
|
||||
|
||||
if (apply_action_conditions) {
|
||||
auto this_sh = this->shared_from_this();
|
||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, this_sh, this_sh, 1, nullptr);
|
||||
log.debug("applied action conditions (1)");
|
||||
log.debug_f("applied action conditions (1)");
|
||||
} else {
|
||||
log.debug("skipped applying action conditions (1)");
|
||||
log.debug_f("skipped applying action conditions (1)");
|
||||
}
|
||||
|
||||
size_t num_assists = s->assist_server->compute_num_assist_effects_for_client(this->client_id);
|
||||
@@ -1106,29 +1106,29 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
int16_t damage = 0;
|
||||
if (this->action_chain.chain.attack_medium == AttackMedium::TECH) {
|
||||
damage = this->action_chain.chain.effective_tp + this->action_chain.chain.tp_effect_bonus;
|
||||
log.debug("(tech) damage = %hhd (eff) + %hhd (bonus) = %hd", this->action_chain.chain.effective_tp, this->action_chain.chain.tp_effect_bonus, damage);
|
||||
log.debug_f("(tech) damage = {} (eff) + {} (bonus) = {}", this->action_chain.chain.effective_tp, this->action_chain.chain.tp_effect_bonus, damage);
|
||||
} else if (this->action_chain.chain.attack_medium == AttackMedium::PHYSICAL) {
|
||||
damage = this->action_chain.chain.effective_ap + this->action_chain.chain.ap_effect_bonus;
|
||||
log.debug("(physical) damage = %hhd (eff) + %hhd (bonus) = %hd", this->action_chain.chain.effective_ap, this->action_chain.chain.ap_effect_bonus, damage);
|
||||
log.debug_f("(physical) damage = {} (eff) + {} (bonus) = {}", this->action_chain.chain.effective_ap, this->action_chain.chain.ap_effect_bonus, damage);
|
||||
} else {
|
||||
log.debug("(unknown attack medium) damage = 0");
|
||||
log.debug_f("(unknown attack medium) damage = 0");
|
||||
}
|
||||
|
||||
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);
|
||||
log.debug("overall chain damage = %hd (base) * %hhd (mult) = %hhd", damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
|
||||
log.debug_f("overall chain damage = {} (base) * {} (mult) = {}", damage, this->action_chain.chain.damage_multiplier, this->action_chain.chain.damage);
|
||||
|
||||
if (apply_action_conditions) {
|
||||
auto this_sh = this->shared_from_this();
|
||||
s->card_special->apply_action_conditions(EffectWhen::BEFORE_ANY_CARD_ATTACK, this_sh, this_sh, 2, nullptr);
|
||||
log.debug("applied action conditions (2)");
|
||||
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);
|
||||
log.debug("(has flag 0x100) chain damage = %hhd", this->action_chain.chain.damage);
|
||||
log.debug_f("(has flag 0x100) chain damage = {}", this->action_chain.chain.damage);
|
||||
}
|
||||
} else {
|
||||
log.debug("skipped applying action conditions (2)");
|
||||
log.debug_f("skipped applying action conditions (2)");
|
||||
}
|
||||
|
||||
if (!is_nte) {
|
||||
@@ -1156,9 +1156,9 @@ void Card::compute_action_chain_results(bool apply_action_conditions, bool ignor
|
||||
}
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string chain_str = this->action_chain.str(s);
|
||||
log.debug("result computed as %s", chain_str.c_str());
|
||||
log.debug_f("result computed as {}", chain_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,24 +1226,24 @@ void Card::move_phase_before() {
|
||||
|
||||
void Card::unknown_80236374(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("unknown_80236374(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id()));
|
||||
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::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
log.debug_f("as = {}", as_str);
|
||||
} else {
|
||||
log.debug("as = null");
|
||||
log.debug_f("as = null");
|
||||
}
|
||||
}
|
||||
|
||||
auto check_card = [&](shared_ptr<Card> card) -> void {
|
||||
if (card) {
|
||||
if (!card->unknown_80236554(other_card, as)) {
|
||||
log.debug("check_card @%04hX #%04hX => false", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("check_card @{:04X} #{:04X} => false", card->get_card_ref(), card->get_card_id());
|
||||
card->action_metadata.clear_flags(0x20);
|
||||
} else {
|
||||
log.debug("check_card @%04hX #%04hX => true", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("check_card @{:04X} #{:04X} => true", card->get_card_ref(), card->get_card_id());
|
||||
card->action_metadata.set_flags(0x20);
|
||||
}
|
||||
}
|
||||
@@ -1378,14 +1378,14 @@ bool Card::is_guard_item() const {
|
||||
bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(other_card
|
||||
? phosg::string_printf("unknown_80236554(@%04hX #%04hX, @%04hX #%04hX): ", this->get_card_ref(), this->get_card_id(), other_card->get_card_ref(), other_card->get_card_id())
|
||||
: phosg::string_printf("unknown_80236554(@%04hX #%04hX, null): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
? 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())
|
||||
: std::format("unknown_80236554(@{:04X} #{:04X}, null): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
if (as) {
|
||||
string as_str = as->str(s);
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
log.debug_f("as = {}", as_str);
|
||||
} else {
|
||||
log.debug("as = null");
|
||||
log.debug_f("as = null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,7 +1398,7 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
if (other_card->action_chain.chain.target_card_refs[z] == this->get_card_ref()) {
|
||||
attack_bonus = other_card->action_chain.chain.damage;
|
||||
ret = true;
|
||||
log.debug("attack_bonus = %hd (matched other_card->action_chain.chain.target_card_refs)", attack_bonus);
|
||||
log.debug_f("attack_bonus = {} (matched other_card->action_chain.chain.target_card_refs)", attack_bonus);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1406,7 +1406,7 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
for (size_t z = 0; (z < 4 * 9) && (as->target_card_refs[z] != 0xFFFF); z++) {
|
||||
if (as->target_card_refs[z] == this->get_card_ref()) {
|
||||
attack_bonus = other_card->action_chain.chain.damage;
|
||||
log.debug("attack_bonus = %hd (matched as->target_card_refs)", attack_bonus);
|
||||
log.debug_f("attack_bonus = {} (matched as->target_card_refs)", attack_bonus);
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
@@ -1415,26 +1415,26 @@ bool Card::unknown_80236554(shared_ptr<Card> other_card, const ActionState* as)
|
||||
}
|
||||
|
||||
this->action_metadata.attack_bonus = max<int16_t>(attack_bonus, 0);
|
||||
log.debug("attack_bonus = %hhd", this->action_metadata.attack_bonus);
|
||||
log.debug_f("attack_bonus = {}", this->action_metadata.attack_bonus);
|
||||
this->last_attack_preliminary_damage = 0;
|
||||
this->last_attack_final_damage = 0;
|
||||
log.debug("last attack damage stats cleared");
|
||||
log.debug_f("last attack damage stats cleared");
|
||||
|
||||
if (other_card) {
|
||||
log.debug("applying BEFORE_ANY_CARD_ATTACK conditions");
|
||||
log.debug_f("applying BEFORE_ANY_CARD_ATTACK conditions");
|
||||
s->card_special->apply_action_conditions(
|
||||
EffectWhen::BEFORE_ANY_CARD_ATTACK, other_card, this->shared_from_this(), 0x20, as);
|
||||
log.debug("applying BEFORE_THIS_CARD_ATTACKED conditions");
|
||||
log.debug_f("applying BEFORE_THIS_CARD_ATTACKED conditions");
|
||||
s->card_special->apply_action_conditions(
|
||||
EffectWhen::BEFORE_THIS_CARD_ATTACKED, other_card, this->shared_from_this(), 0x40, as);
|
||||
if (other_card->action_chain.check_flag(0x20000)) {
|
||||
log.debug("attack_bonus cleared due to cancellation");
|
||||
log.debug_f("attack_bonus cleared due to cancellation");
|
||||
this->action_metadata.attack_bonus = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (this->card_flags & 2) {
|
||||
log.debug("attack_bonus cleared due to destruction");
|
||||
log.debug_f("attack_bonus cleared due to destruction");
|
||||
this->action_metadata.attack_bonus = 0;
|
||||
}
|
||||
return ret;
|
||||
@@ -1464,7 +1464,7 @@ void Card::apply_attack_result() {
|
||||
auto ps = this->player_state();
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
auto log = s->log_stack(phosg::string_printf("apply_attack_result(@%04hX #%04hX): ", this->get_card_ref(), this->get_card_id()));
|
||||
auto log = s->log_stack(std::format("apply_attack_result(@{:04X} #{:04X}): ", this->get_card_ref(), this->get_card_id()));
|
||||
if (!this->action_chain.can_apply_attack()) {
|
||||
return;
|
||||
}
|
||||
@@ -1573,9 +1573,9 @@ void Card::apply_attack_result() {
|
||||
}
|
||||
}
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string as_str = as.str(s);
|
||||
log.debug("as constructed as %s", as_str.c_str());
|
||||
log.debug_f("as constructed as {}", as_str);
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < this->action_chain.chain.target_card_ref_count; z++) {
|
||||
@@ -1583,36 +1583,36 @@ void Card::apply_attack_result() {
|
||||
if (card) {
|
||||
card->current_defense_power = card->action_metadata.attack_bonus;
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("unknown_8024A6DC(@%04hX #%04hX) ...", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("unknown_8024A6DC(@{:04X} #{:04X}) ...", card->get_card_ref(), card->get_card_id());
|
||||
s->card_special->unknown_8024A6DC(this->shared_from_this(), card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("compute_action_chain_results 1 ...");
|
||||
log.debug_f("compute_action_chain_results 1 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("apply_effects_before_attack ...");
|
||||
log.debug_f("apply_effects_before_attack ...");
|
||||
s->card_special->apply_effects_before_attack(this->shared_from_this());
|
||||
}
|
||||
if (!(this->card_flags & 2)) {
|
||||
log.debug("compute_action_chain_results 2 ...");
|
||||
log.debug_f("compute_action_chain_results 2 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
log.debug("check_for_attack_interference ...");
|
||||
log.debug_f("check_for_attack_interference ...");
|
||||
s->card_special->check_for_attack_interference(this->shared_from_this());
|
||||
}
|
||||
log.debug("compute_action_chain_results 3 ...");
|
||||
log.debug_f("compute_action_chain_results 3 ...");
|
||||
this->compute_action_chain_results(true, false);
|
||||
|
||||
log.debug("unknown_80236374 ...");
|
||||
log.debug_f("unknown_80236374 ...");
|
||||
this->unknown_80236374(this->shared_from_this(), nullptr);
|
||||
log.debug("execute_attack_on_all_valid_targets ...");
|
||||
log.debug_f("execute_attack_on_all_valid_targets ...");
|
||||
this->execute_attack_on_all_valid_targets(this->shared_from_this());
|
||||
}
|
||||
|
||||
if (!this->action_chain.check_flag(0x40)) {
|
||||
log.debug("apply_effects_after_attack ...");
|
||||
log.debug_f("apply_effects_after_attack ...");
|
||||
s->card_special->apply_effects_after_attack(this->shared_from_this());
|
||||
}
|
||||
ps->stats.num_attacks_given++;
|
||||
@@ -1625,7 +1625,7 @@ void Card::apply_attack_result() {
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = s->player_states[client_id];
|
||||
if (ps) {
|
||||
log.debug("unknown_8023C110(%zu) ...", client_id);
|
||||
log.debug_f("unknown_8023C110({}) ...", client_id);
|
||||
ps->unknown_8023C110();
|
||||
}
|
||||
}
|
||||
|
||||
+195
-195
@@ -21,7 +21,7 @@ static string refs_str_for_cards_vector(const vector<shared_ptr<T>>& cards) {
|
||||
if (!ret.empty()) {
|
||||
ret += ", ";
|
||||
}
|
||||
ret += phosg::string_printf("@%04hX", ref_for_card(card));
|
||||
ret += std::format("@{:04X}", ref_for_card(card));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -89,45 +89,45 @@ uint32_t CardSpecial::AttackEnvStats::at(size_t index) const {
|
||||
}
|
||||
|
||||
void CardSpecial::AttackEnvStats::print(FILE* stream) const {
|
||||
fprintf(stream, "(a) total_num_set_cards = %" PRIu32 "\n", this->total_num_set_cards);
|
||||
fprintf(stream, "(ab) num_a_beast_creatures = %" PRIu32 "\n", this->num_a_beast_creatures);
|
||||
fprintf(stream, "(ac) player_num_atk_points = %" PRIu32 "\n", this->player_num_atk_points);
|
||||
fprintf(stream, "(adm) sc_effective_ap = %" PRIu32 "\n", this->sc_effective_ap);
|
||||
fprintf(stream, "(ap) effective_ap = %" PRIu32 "\n", this->effective_ap);
|
||||
fprintf(stream, "(bi) num_native_creatures = %" PRIu32 "\n", this->num_native_creatures);
|
||||
fprintf(stream, "(cs) card_cost = %" PRIu32 "\n", this->card_cost);
|
||||
fprintf(stream, "(d) dice_roll_value1 = %" PRIu32 "\n", this->dice_roll_value1);
|
||||
fprintf(stream, "(dc) dice_roll_value2 = %" PRIu32 "\n", this->dice_roll_value2);
|
||||
fprintf(stream, "(ddm) attack_bonus = %" PRIu32 "\n", this->attack_bonus);
|
||||
fprintf(stream, "(df) num_destroyed_ally_fcs = %" PRIu32 "\n", this->num_destroyed_ally_fcs);
|
||||
fprintf(stream, "(dk) num_dark_creatures = %" PRIu32 "\n", this->num_dark_creatures);
|
||||
fprintf(stream, "(dm) effective_ap_if_not_tech = %" PRIu32 "\n", this->effective_ap_if_not_tech);
|
||||
fprintf(stream, "(dn) unknown_a1 = %" PRIu32 "\n", this->unknown_a1);
|
||||
fprintf(stream, "(edm) target_attack_bonus = %" PRIu32 "\n", this->target_attack_bonus);
|
||||
fprintf(stream, "(ef) non_target_team_num_set_cards = %" PRIu32 "\n", this->non_target_team_num_set_cards);
|
||||
fprintf(stream, "(ehp) target_current_hp = %" PRIu32 "\n", this->target_current_hp);
|
||||
fprintf(stream, "(f) num_set_cards = %" PRIu32 "\n", this->num_set_cards);
|
||||
fprintf(stream, "(fdm) final_last_attack_damage = %" PRIu32 "\n", this->final_last_attack_damage);
|
||||
fprintf(stream, "(ff) target_team_num_set_cards = %" PRIu32 "\n", this->target_team_num_set_cards);
|
||||
fprintf(stream, "(gn) num_gun_type_items = %" PRIu32 "\n", this->num_gun_type_items);
|
||||
fprintf(stream, "(hf) num_item_or_creature_cards_in_hand = %" PRIu32 "\n", this->num_item_or_creature_cards_in_hand);
|
||||
fprintf(stream, "(hp) current_hp = %" PRIu32 "\n", this->current_hp);
|
||||
fprintf(stream, "(kap) action_cards_ap = %" PRIu32 "\n", this->action_cards_ap);
|
||||
fprintf(stream, "(ktp) action_cards_tp = %" PRIu32 "\n", this->action_cards_tp);
|
||||
fprintf(stream, "(ldm) last_attack_preliminary_damage = %" PRIu32 "\n", this->last_attack_preliminary_damage);
|
||||
fprintf(stream, "(lv) team_dice_bonus = %" PRIu32 "\n", this->team_dice_bonus);
|
||||
fprintf(stream, "(mc) num_machine_creatures = %" PRIu32 "\n", this->num_machine_creatures);
|
||||
fprintf(stream, "(mhp) max_hp = %" PRIu32 "\n", this->max_hp);
|
||||
fprintf(stream, "(ndm) last_attack_damage_count = %" PRIu32 "\n", this->last_attack_damage_count);
|
||||
fprintf(stream, "(php) defined_max_hp = %" PRIu32 "\n", this->defined_max_hp);
|
||||
fprintf(stream, "(rdm) last_attack_damage = %" PRIu32 "\n", this->last_attack_damage);
|
||||
fprintf(stream, "(sa) num_sword_type_items = %" PRIu32 "\n", this->num_sword_type_items);
|
||||
fprintf(stream, "(sat) num_sword_type_items_on_team = %" PRIu32 "\n", this->num_sword_type_items_on_team);
|
||||
fprintf(stream, "(tdm) effective_ap_if_not_physical = %" PRIu32 "\n", this->effective_ap_if_not_physical);
|
||||
fprintf(stream, "(tf) player_num_destroyed_fcs = %" PRIu32 "\n", this->player_num_destroyed_fcs);
|
||||
fprintf(stream, "(tp) effective_tp = %" PRIu32 "\n", this->effective_tp);
|
||||
fprintf(stream, "(tt) effective_ap_if_not_tech2 = %" PRIu32 "\n", this->effective_ap_if_not_tech2);
|
||||
fprintf(stream, "(wd) num_cane_type_items = %" PRIu32 "\n", this->num_cane_type_items);
|
||||
phosg::fwrite_fmt(stream, "(a) total_num_set_cards = {}\n", this->total_num_set_cards);
|
||||
phosg::fwrite_fmt(stream, "(ab) num_a_beast_creatures = {}\n", this->num_a_beast_creatures);
|
||||
phosg::fwrite_fmt(stream, "(ac) player_num_atk_points = {}\n", this->player_num_atk_points);
|
||||
phosg::fwrite_fmt(stream, "(adm) sc_effective_ap = {}\n", this->sc_effective_ap);
|
||||
phosg::fwrite_fmt(stream, "(ap) effective_ap = {}\n", this->effective_ap);
|
||||
phosg::fwrite_fmt(stream, "(bi) num_native_creatures = {}\n", this->num_native_creatures);
|
||||
phosg::fwrite_fmt(stream, "(cs) card_cost = {}\n", this->card_cost);
|
||||
phosg::fwrite_fmt(stream, "(d) dice_roll_value1 = {}\n", this->dice_roll_value1);
|
||||
phosg::fwrite_fmt(stream, "(dc) dice_roll_value2 = {}\n", this->dice_roll_value2);
|
||||
phosg::fwrite_fmt(stream, "(ddm) attack_bonus = {}\n", this->attack_bonus);
|
||||
phosg::fwrite_fmt(stream, "(df) num_destroyed_ally_fcs = {}\n", this->num_destroyed_ally_fcs);
|
||||
phosg::fwrite_fmt(stream, "(dk) num_dark_creatures = {}\n", this->num_dark_creatures);
|
||||
phosg::fwrite_fmt(stream, "(dm) effective_ap_if_not_tech = {}\n", this->effective_ap_if_not_tech);
|
||||
phosg::fwrite_fmt(stream, "(dn) unknown_a1 = {}\n", this->unknown_a1);
|
||||
phosg::fwrite_fmt(stream, "(edm) target_attack_bonus = {}\n", this->target_attack_bonus);
|
||||
phosg::fwrite_fmt(stream, "(ef) non_target_team_num_set_cards = {}\n", this->non_target_team_num_set_cards);
|
||||
phosg::fwrite_fmt(stream, "(ehp) target_current_hp = {}\n", this->target_current_hp);
|
||||
phosg::fwrite_fmt(stream, "(f) num_set_cards = {}\n", this->num_set_cards);
|
||||
phosg::fwrite_fmt(stream, "(fdm) final_last_attack_damage = {}\n", this->final_last_attack_damage);
|
||||
phosg::fwrite_fmt(stream, "(ff) target_team_num_set_cards = {}\n", this->target_team_num_set_cards);
|
||||
phosg::fwrite_fmt(stream, "(gn) num_gun_type_items = {}\n", this->num_gun_type_items);
|
||||
phosg::fwrite_fmt(stream, "(hf) num_item_or_creature_cards_in_hand = {}\n", this->num_item_or_creature_cards_in_hand);
|
||||
phosg::fwrite_fmt(stream, "(hp) current_hp = {}\n", this->current_hp);
|
||||
phosg::fwrite_fmt(stream, "(kap) action_cards_ap = {}\n", this->action_cards_ap);
|
||||
phosg::fwrite_fmt(stream, "(ktp) action_cards_tp = {}\n", this->action_cards_tp);
|
||||
phosg::fwrite_fmt(stream, "(ldm) last_attack_preliminary_damage = {}\n", this->last_attack_preliminary_damage);
|
||||
phosg::fwrite_fmt(stream, "(lv) team_dice_bonus = {}\n", this->team_dice_bonus);
|
||||
phosg::fwrite_fmt(stream, "(mc) num_machine_creatures = {}\n", this->num_machine_creatures);
|
||||
phosg::fwrite_fmt(stream, "(mhp) max_hp = {}\n", this->max_hp);
|
||||
phosg::fwrite_fmt(stream, "(ndm) last_attack_damage_count = {}\n", this->last_attack_damage_count);
|
||||
phosg::fwrite_fmt(stream, "(php) defined_max_hp = {}\n", this->defined_max_hp);
|
||||
phosg::fwrite_fmt(stream, "(rdm) last_attack_damage = {}\n", this->last_attack_damage);
|
||||
phosg::fwrite_fmt(stream, "(sa) num_sword_type_items = {}\n", this->num_sword_type_items);
|
||||
phosg::fwrite_fmt(stream, "(sat) num_sword_type_items_on_team = {}\n", this->num_sword_type_items_on_team);
|
||||
phosg::fwrite_fmt(stream, "(tdm) effective_ap_if_not_physical = {}\n", this->effective_ap_if_not_physical);
|
||||
phosg::fwrite_fmt(stream, "(tf) player_num_destroyed_fcs = {}\n", this->player_num_destroyed_fcs);
|
||||
phosg::fwrite_fmt(stream, "(tp) effective_tp = {}\n", this->effective_tp);
|
||||
phosg::fwrite_fmt(stream, "(tt) effective_ap_if_not_tech2 = {}\n", this->effective_ap_if_not_tech2);
|
||||
phosg::fwrite_fmt(stream, "(wd) num_cane_type_items = {}\n", this->num_cane_type_items);
|
||||
}
|
||||
|
||||
CardSpecial::CardSpecial(shared_ptr<Server> server) : w_server(server) {}
|
||||
@@ -251,14 +251,14 @@ void CardSpecial::apply_action_conditions(
|
||||
if (attacker_card == defender_card) {
|
||||
temp_as = this->create_attack_state_from_card_action_chain(attacker_card);
|
||||
if (as) {
|
||||
log.debug("using action state from override");
|
||||
log.debug_f("using action state from override");
|
||||
temp_as = *as;
|
||||
} else {
|
||||
log.debug("using action state from attacker card");
|
||||
log.debug_f("using action state from attacker card");
|
||||
}
|
||||
} else {
|
||||
temp_as = this->create_defense_state_for_card_pair_action_chains(attacker_card, defender_card);
|
||||
log.debug("using action state from card pair");
|
||||
log.debug_f("using action state from card pair");
|
||||
}
|
||||
|
||||
this->apply_defense_conditions(temp_as, when, defender_card, flags);
|
||||
@@ -307,9 +307,9 @@ bool CardSpecial::apply_defense_condition(
|
||||
bool is_nte = s->options.is_nte();
|
||||
auto log = s->log_stack("apply_defense_condition: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug(
|
||||
"when=%s, cond_index=%hhu, defender_card=(@%04hX #%04hX), flags=%08" PRIX32 ", p8=%s",
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
log.debug_f(
|
||||
"when={}, cond_index={}, defender_card=(@{:04X} #{:04X}), flags={:08X}, p8={}",
|
||||
phosg::name_for_enum(when),
|
||||
cond_index,
|
||||
defender_card->get_card_ref(),
|
||||
@@ -318,32 +318,32 @@ bool CardSpecial::apply_defense_condition(
|
||||
unknown_p8 ? "true" : "false");
|
||||
auto defender_cond_str = defender_cond->str(s);
|
||||
auto defense_state_str = defense_state.str(s);
|
||||
log.debug("defender_cond = %s", defender_cond_str.c_str());
|
||||
log.debug("defense_state = %s", defense_state_str.c_str());
|
||||
log.debug_f("defender_cond = {}", defender_cond_str);
|
||||
log.debug_f("defense_state = {}", defense_state_str);
|
||||
}
|
||||
|
||||
if (defender_cond->type == ConditionType::NONE) {
|
||||
log.debug("no condition");
|
||||
log.debug_f("no condition");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto orig_eff = this->original_definition_for_condition(*defender_cond);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
auto orig_eff_str = orig_eff->str();
|
||||
log.debug("orig_eff = %s", orig_eff_str.c_str());
|
||||
log.debug_f("orig_eff = {}", orig_eff_str);
|
||||
}
|
||||
|
||||
uint16_t attacker_card_ref = defense_state.attacker_card_ref;
|
||||
if (attacker_card_ref == 0xFFFF) {
|
||||
attacker_card_ref = defense_state.original_attacker_card_ref;
|
||||
}
|
||||
log.debug("attacker_card_ref = @%04hX", attacker_card_ref);
|
||||
log.debug_f("attacker_card_ref = @{:04X}", attacker_card_ref);
|
||||
|
||||
bool defender_has_ability_trap = !is_nte && this->card_ref_has_ability_trap(*defender_cond);
|
||||
log.debug("defender_has_ability_trap = %s", defender_has_ability_trap ? "true" : "false");
|
||||
log.debug_f("defender_has_ability_trap = {}", defender_has_ability_trap ? "true" : "false");
|
||||
|
||||
if ((is_nte || (flags & 4)) && !this->is_card_targeted_by_condition(*defender_cond, defense_state, defender_card)) {
|
||||
log.debug("not targeted by condition");
|
||||
log.debug_f("not targeted by condition");
|
||||
if (defender_cond->type != ConditionType::NONE) {
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x04;
|
||||
@@ -359,7 +359,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if ((when == EffectWhen::AFTER_ANY_CARD_ATTACK) && (defender_cond->type == ConditionType::GUOM) && (flags & 4)) {
|
||||
log.debug("deleting guom condition");
|
||||
log.debug_f("deleting guom condition");
|
||||
CardShortStatus stat = defender_card->get_short_status();
|
||||
if (stat.card_flags & 4) {
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
@@ -396,7 +396,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if ((when == EffectWhen::BEFORE_DICE_PHASE_THIS_TEAM_TURN) && (flags & 4) && !defender_has_ability_trap && (defender_cond->type == ConditionType::ACID)) {
|
||||
log.debug("applying acid");
|
||||
log.debug_f("applying acid");
|
||||
int16_t hp = defender_card->get_current_hp();
|
||||
if (hp > 0) {
|
||||
this->send_6xB4x06_for_stat_delta(defender_card, defender_cond->card_ref, 0x20, -1, 0, 1);
|
||||
@@ -406,11 +406,11 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if (!orig_eff || (orig_eff->when != when)) {
|
||||
log.debug("unsetting flag 4");
|
||||
log.debug_f("unsetting flag 4");
|
||||
flags &= ~4;
|
||||
}
|
||||
if ((flags == 0) || defender_has_ability_trap) {
|
||||
log.debug("no condition remains to apply");
|
||||
log.debug_f("no condition remains to apply");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -427,10 +427,10 @@ bool CardSpecial::apply_defense_condition(
|
||||
|
||||
string expr = orig_eff->expr.decode();
|
||||
int16_t expr_value = this->evaluate_effect_expr(astats, expr.c_str(), dice_roll);
|
||||
log.debug("execute_effect ...");
|
||||
log.debug_f("execute_effect ...");
|
||||
this->execute_effect(*defender_cond, defender_card, expr_value, defender_cond->value, orig_eff->type, flags, attacker_card_ref);
|
||||
if (flags & 4) {
|
||||
log.debug("recomputing action chaing results");
|
||||
log.debug_f("recomputing action chaing results");
|
||||
if (is_nte || !(defender_card->card_flags & 2)) {
|
||||
defender_card->compute_action_chain_results(true, false);
|
||||
}
|
||||
@@ -440,7 +440,7 @@ bool CardSpecial::apply_defense_condition(
|
||||
}
|
||||
|
||||
if (dice_roll.value_used_in_expr && !(original_cond_flags & 1) && !unknown_p8) {
|
||||
log.debug("dice roll was used; setting dice display flag");
|
||||
log.debug_f("dice roll was used; setting dice display flag");
|
||||
defender_cond->flags |= 1;
|
||||
G_ApplyConditionEffect_Ep3_6xB4x06 cmd;
|
||||
cmd.effect.flags = 0x08;
|
||||
@@ -491,11 +491,11 @@ bool CardSpecial::apply_stat_deltas_to_all_cards_from_all_conditions_with_card_r
|
||||
|
||||
bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condition& cond, shared_ptr<Card> card) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("apply_stat_deltas_to_card_from_condition_and_clear_cond(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(std::format("apply_stat_deltas_to_card_from_condition_and_clear_cond(@{:04X} #{:04X}): ", card->get_card_ref(), card->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("cond: %s", cond_str.c_str());
|
||||
log.debug_f("cond: {}", cond_str);
|
||||
|
||||
ConditionType cond_type = cond.type;
|
||||
int16_t cond_value = is_nte ? cond.value.load() : clamp<int16_t>(cond.value, -99, 99);
|
||||
@@ -508,7 +508,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
if (cond_flags & 2) {
|
||||
int16_t ap = clamp<int16_t>(card->ap, -99, 99);
|
||||
int16_t tp = clamp<int16_t>(card->tp, -99, 99);
|
||||
log.debug("A_T_SWAP_0C: swapping AP (%hd) and TP (%hd)", ap, tp);
|
||||
log.debug_f("A_T_SWAP_0C: swapping AP ({}) and TP ({})", ap, tp);
|
||||
if (!is_nte) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, tp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, ap - tp, 0, 0);
|
||||
@@ -516,7 +516,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
card->ap = tp;
|
||||
card->tp = ap;
|
||||
} else {
|
||||
log.debug("A_T_SWAP_0C: required flag is missing");
|
||||
log.debug_f("A_T_SWAP_0C: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::A_H_SWAP:
|
||||
@@ -524,7 +524,7 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
int16_t ap = clamp<int16_t>(card->ap, -99, 99);
|
||||
int16_t hp = clamp<int16_t>(card->get_current_hp(), -99, 99);
|
||||
if (hp != ap) {
|
||||
log.debug("A_H_SWAP: swapping AP (%hd) and HP (%hd)", ap, hp);
|
||||
log.debug_f("A_H_SWAP: swapping AP ({}) and HP ({})", ap, hp);
|
||||
if (!is_nte) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, hp - ap, 0, 0);
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x20, ap - hp, 0, 0);
|
||||
@@ -533,10 +533,10 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
card->ap = hp;
|
||||
this->destroy_card_if_hp_zero(card, cond_card_ref);
|
||||
} else {
|
||||
log.debug("A_H_SWAP: AP (%hd) == HP (%hd)", ap, hp);
|
||||
log.debug_f("A_H_SWAP: AP ({}) == HP ({})", ap, hp);
|
||||
}
|
||||
} else {
|
||||
log.debug("A_H_SWAP: required flag is missing");
|
||||
log.debug_f("A_H_SWAP: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::AP_OVERRIDE:
|
||||
@@ -550,12 +550,12 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
}
|
||||
card->ap = max<int16_t>(card->ap - cond_value, 0);
|
||||
log.debug("AP_OVERRIDE: subtracting %hd from AP => %hd", cond_value, card->ap);
|
||||
log.debug_f("AP_OVERRIDE: subtracting {} from AP => {}", cond_value, card->ap);
|
||||
} else {
|
||||
other_cond->value = clamp<int16_t>(other_cond->value + cond_value, -99, 99);
|
||||
}
|
||||
} else {
|
||||
log.debug("AP_OVERRIDE: required flag is missing");
|
||||
log.debug_f("AP_OVERRIDE: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::TP_OVERRIDE:
|
||||
@@ -567,12 +567,12 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
}
|
||||
card->tp = max<int16_t>(card->tp - cond_value, 0);
|
||||
log.debug("TP_OVERRIDE: subtracting %hd from TP => %hd", cond_value, card->tp);
|
||||
log.debug_f("TP_OVERRIDE: subtracting {} from TP => {}", cond_value, card->tp);
|
||||
} else {
|
||||
other_cond->value = clamp<int16_t>(other_cond->value + cond_value, -99, 99);
|
||||
}
|
||||
} else {
|
||||
log.debug("TP_OVERRIDE: required flag is missing");
|
||||
log.debug_f("TP_OVERRIDE: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::MISC_AP_BONUSES:
|
||||
@@ -581,9 +581,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, -cond_value, 0, 0);
|
||||
}
|
||||
card->ap = max<int16_t>(card->ap - cond_value, 0);
|
||||
log.debug("MISC_AP_BONUSES: subtracting %hd from AP => %hd", cond_value, card->ap);
|
||||
log.debug_f("MISC_AP_BONUSES: subtracting {} from AP => {}", cond_value, card->ap);
|
||||
} else {
|
||||
log.debug("MISC_AP_BONUSES: required flag is missing");
|
||||
log.debug_f("MISC_AP_BONUSES: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::MISC_TP_BONUSES:
|
||||
@@ -592,9 +592,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, -cond_value, 0, 0);
|
||||
}
|
||||
card->tp = max<int16_t>(card->tp - cond_value, 0);
|
||||
log.debug("MISC_TP_BONUSES: subtracting %hd from TP => %hd", cond_value, card->tp);
|
||||
log.debug_f("MISC_TP_BONUSES: subtracting {} from TP => {}", cond_value, card->tp);
|
||||
} else {
|
||||
log.debug("MISC_TP_BONUSES: required flag is missing");
|
||||
log.debug_f("MISC_TP_BONUSES: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::AP_SILENCE:
|
||||
@@ -604,9 +604,9 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0xA0, cond_value, 0, 0);
|
||||
card->ap = max<int16_t>(card->ap + cond_value, 0);
|
||||
log.debug("AP_SILENCE: adding %hd to AP => %hd", cond_value, card->ap);
|
||||
log.debug_f("AP_SILENCE: adding {} to AP => {}", cond_value, card->ap);
|
||||
} else {
|
||||
log.debug("AP_SILENCE: required flag is missing");
|
||||
log.debug_f("AP_SILENCE: required flag is missing");
|
||||
}
|
||||
break;
|
||||
case ConditionType::TP_SILENCE:
|
||||
@@ -616,14 +616,14 @@ bool CardSpecial::apply_stat_deltas_to_card_from_condition_and_clear_cond(Condit
|
||||
if (cond_flags & 2) {
|
||||
this->send_6xB4x06_for_stat_delta(card, cond_card_ref, 0x80, cond_value, 0, 0);
|
||||
card->tp = max<int16_t>(card->tp + cond_value, 0);
|
||||
log.debug("TP_SILENCE: adding %hd to TP => %hd", cond_value, card->tp);
|
||||
log.debug_f("TP_SILENCE: adding {} to TP => {}", cond_value, card->tp);
|
||||
} else {
|
||||
log.debug("TP_SILENCE: required flag is missing");
|
||||
log.debug_f("TP_SILENCE: required flag is missing");
|
||||
}
|
||||
break;
|
||||
trial_unimplemented:
|
||||
default:
|
||||
log.debug("%s: no adjustments for condition type", phosg::name_for_enum(cond_type));
|
||||
log.debug_f("{}: no adjustments for condition type", phosg::name_for_enum(cond_type));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -741,22 +741,22 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
string pa_str = pa.str(s);
|
||||
log.debug("pa=%s, card=@%04hX #%04hX, dice_roll=%hhu, target=@%04hX, condition_giver=@%04hX", pa_str.c_str(), card->get_card_ref(), card->get_card_id(), dice_roll.value, target_card_ref, condition_giver_card_ref);
|
||||
log.debug_f("pa={}, card=@{:04X} #{:04X}, dice_roll={}, target=@{:04X}, condition_giver=@{:04X}", pa_str, card->get_card_ref(), card->get_card_id(), dice_roll.value, target_card_ref, condition_giver_card_ref);
|
||||
|
||||
auto attacker_card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (!attacker_card && (pa.original_attacker_card_ref != 0xFFFF)) {
|
||||
attacker_card = s->card_for_set_card_ref(pa.original_attacker_card_ref);
|
||||
log.debug("attacker=@%04hX #%04hX (from original)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
log.debug_f("attacker=@{:04X} #{:04X} (from original)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
} else if (attacker_card) {
|
||||
log.debug("attacker=@%04hX #%04hX (from set)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
log.debug_f("attacker=@{:04X} #{:04X} (from set)", attacker_card->get_card_ref(), attacker_card->get_card_id());
|
||||
} else {
|
||||
log.debug("attacker=null (from set)");
|
||||
log.debug_f("attacker=null (from set)");
|
||||
}
|
||||
|
||||
AttackEnvStats ast;
|
||||
|
||||
auto ps = card->player_state();
|
||||
log.debug("base ps = %hhu", ps->client_id);
|
||||
log.debug_f("base ps = {}", ps->client_id);
|
||||
ast.num_set_cards = is_nte ? ps->count_set_cards_for_env_stats_nte() : ps->count_set_cards();
|
||||
auto condition_giver_card = s->card_for_set_card_ref(condition_giver_card_ref);
|
||||
auto target_card = s->card_for_set_card_ref(target_card_ref);
|
||||
@@ -871,8 +871,8 @@ CardSpecial::AttackEnvStats CardSpecial::compute_attack_env_stats(
|
||||
// Note: The (z < 8) conditions in these two loops are not present in the
|
||||
// original code.
|
||||
for (z = 0;
|
||||
((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF));
|
||||
z++) {
|
||||
((target_card_ref != z_ref) && (z < 8) && ((z_ref = pa.action_card_refs[z]) != 0xFFFF));
|
||||
z++) {
|
||||
}
|
||||
|
||||
ast.action_cards_ap = 0;
|
||||
@@ -1227,9 +1227,9 @@ shared_ptr<Card> CardSpecial::compute_replaced_target_based_on_conditions(
|
||||
|
||||
StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr<const Card> card) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("compute_stat_swap_type(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(std::format("compute_stat_swap_type(@{:04X} #{:04X}): ", card->get_card_ref(), card->get_card_id()));
|
||||
if (!card) {
|
||||
log.debug("card is missing");
|
||||
log.debug_f("card is missing");
|
||||
return StatSwapType::NONE;
|
||||
}
|
||||
|
||||
@@ -1237,33 +1237,33 @@ StatSwapType CardSpecial::compute_stat_swap_type(shared_ptr<const Card> card) co
|
||||
for (size_t cond_index = 0; cond_index < 9; cond_index++) {
|
||||
auto& cond = card->action_chain.conditions[cond_index];
|
||||
if (cond.type != ConditionType::NONE) {
|
||||
auto cond_log = log.sub(phosg::string_printf("(%zu) ", cond_index));
|
||||
auto cond_log = log.sub(std::format("({}) ", cond_index));
|
||||
string cond_str = cond.str(s);
|
||||
cond_log.debug("%s", cond_str.c_str());
|
||||
cond_log.debug_f("{}", cond_str);
|
||||
if (!this->card_ref_has_ability_trap(cond)) {
|
||||
if (cond.type == ConditionType::UNKNOWN_75) {
|
||||
if (ret == StatSwapType::A_H_SWAP) {
|
||||
log.debug("UNKNOWN_75: clearing");
|
||||
log.debug_f("UNKNOWN_75: clearing");
|
||||
ret = StatSwapType::NONE;
|
||||
} else {
|
||||
log.debug("UNKNOWN_75: setting A_H_SWAP");
|
||||
log.debug_f("UNKNOWN_75: setting A_H_SWAP");
|
||||
ret = StatSwapType::A_H_SWAP;
|
||||
}
|
||||
} else if (cond.type == ConditionType::A_T_SWAP) {
|
||||
if (ret == StatSwapType::A_T_SWAP) {
|
||||
log.debug("A_T_SWAP: clearing");
|
||||
log.debug_f("A_T_SWAP: clearing");
|
||||
ret = StatSwapType::NONE;
|
||||
} else {
|
||||
log.debug("A_T_SWAP: setting A_T_SWAP");
|
||||
log.debug_f("A_T_SWAP: setting A_T_SWAP");
|
||||
ret = StatSwapType::A_T_SWAP;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug("skipping due to ability trap");
|
||||
log.debug_f("skipping due to ability trap");
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug("ret = %zu", static_cast<size_t>(ret));
|
||||
log.debug_f("ret = {}", static_cast<size_t>(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1713,8 +1713,8 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
const char* expr,
|
||||
DiceRoll& dice_roll) const {
|
||||
auto log = this->server()->log_stack("evaluate_effect_expr: ");
|
||||
if (log.min_level == phosg::LogLevel::DEBUG) {
|
||||
log.debug("ast, expr=\"%s\", dice_roll=(client_id=%02hhX, a2=%02hhX, value=%02hhX, value_used_in_expr=%s, a5=%04hX)", expr, dice_roll.client_id, dice_roll.unknown_a2, dice_roll.value, dice_roll.value_used_in_expr ? "true" : "false", dice_roll.unknown_a5);
|
||||
if (log.min_level == phosg::LogLevel::L_DEBUG) {
|
||||
log.debug_f("ast, expr=\"{}\", dice_roll=(client_id={:02X}, a2={:02X}, value={:02X}, value_used_in_expr={}, a5={:04X})", expr, dice_roll.client_id, dice_roll.unknown_a2, dice_roll.value, dice_roll.value_used_in_expr ? "true" : "false", dice_roll.unknown_a5);
|
||||
ast.print(stderr);
|
||||
}
|
||||
|
||||
@@ -1747,7 +1747,7 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
// Operators are evaluated left-to-right - there are no operator precedence
|
||||
// rules
|
||||
int32_t value = 0;
|
||||
log.debug("value=%" PRId32 " (start)", value);
|
||||
log.debug_f("value={} (start)", value);
|
||||
for (size_t token_index = 0; token_index < tokens.size(); token_index++) {
|
||||
auto token_type = tokens[token_index].first;
|
||||
int32_t token_value = tokens[token_index].second;
|
||||
@@ -1756,7 +1756,7 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
}
|
||||
if (token_type == ExpressionTokenType::NUMBER) {
|
||||
value = token_value;
|
||||
log.debug("value=%" PRId32 " (token_type=NUMBER, token_value=%" PRId32 ")", value, token_value);
|
||||
log.debug_f("value={} (token_type=NUMBER, token_value={})", value, token_value);
|
||||
} else {
|
||||
if (token_index >= tokens.size() - 1) {
|
||||
throw runtime_error("no token on right side of binary operator");
|
||||
@@ -1770,23 +1770,23 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
switch (token_type) {
|
||||
case ExpressionTokenType::ROUND_DIVIDE:
|
||||
value = lround(static_cast<double>(value) / right_value);
|
||||
log.debug("value=%" PRId32 " (token_type=ROUND_DIVIDE, right_token_value=%" PRId32 ")", value, right_value);
|
||||
log.debug_f("value={} (token_type=ROUND_DIVIDE, right_token_value={})", value, right_value);
|
||||
break;
|
||||
case ExpressionTokenType::SUBTRACT:
|
||||
value -= right_value;
|
||||
log.debug("value=%" PRId32 " (token_type=SUBTRACT, right_token_value=%" PRId32 ")", value, right_value);
|
||||
log.debug_f("value={} (token_type=SUBTRACT, right_token_value={})", value, right_value);
|
||||
break;
|
||||
case ExpressionTokenType::ADD:
|
||||
value += right_value;
|
||||
log.debug("value=%" PRId32 " (token_type=ADD, right_token_value=%" PRId32 ")", value, right_value);
|
||||
log.debug_f("value={} (token_type=ADD, right_token_value={})", value, right_value);
|
||||
break;
|
||||
case ExpressionTokenType::MULTIPLY:
|
||||
value *= right_value;
|
||||
log.debug("value=%" PRId32 " (token_type=MULTIPLY, right_token_value=%" PRId32 ")", value, right_value);
|
||||
log.debug_f("value={} (token_type=MULTIPLY, right_token_value={})", value, right_value);
|
||||
break;
|
||||
case ExpressionTokenType::FLOOR_DIVIDE:
|
||||
value = floor(value / right_value);
|
||||
log.debug("value=%" PRId32 " (token_type=FLOOR_DIVIDE, right_token_value=%" PRId32 ")", value, right_value);
|
||||
log.debug_f("value={} (token_type=FLOOR_DIVIDE, right_token_value={})", value, right_value);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid binary operator");
|
||||
@@ -1794,7 +1794,7 @@ int32_t CardSpecial::evaluate_effect_expr(
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("value=%" PRId32 " (result)", value);
|
||||
log.debug_f("value={} (result)", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -1807,10 +1807,10 @@ bool CardSpecial::execute_effect(
|
||||
uint32_t unknown_p7,
|
||||
uint16_t attacker_card_ref) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("execute_effect(@%04hX #%04hX): ", card->get_card_ref(), card->get_card_id()));
|
||||
auto log = s->log_stack(std::format("execute_effect(@{:04X} #{:04X}): ", card->get_card_ref(), card->get_card_id()));
|
||||
{
|
||||
string cond_str = cond.str(s);
|
||||
log.debug("cond=%s, card=@%04hX, expr_value=%hd, unknown_p5=%hd, cond_type=%s, unknown_p7=%" PRIu32 ", attacker_card_ref=@%04hX", cond_str.c_str(), ref_for_card(card), expr_value, unknown_p5, phosg::name_for_enum(cond_type), unknown_p7, attacker_card_ref);
|
||||
log.debug_f("cond={}, card=@{:04X}, expr_value={}, unknown_p5={}, cond_type={}, unknown_p7={} attacker_card_ref=@{:04X}", cond_str, ref_for_card(card), expr_value, unknown_p5, phosg::name_for_enum(cond_type), unknown_p7, attacker_card_ref);
|
||||
}
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
@@ -1959,7 +1959,7 @@ bool CardSpecial::execute_effect(
|
||||
if (unknown_p7 & 4) {
|
||||
int16_t hp = is_nte ? card->get_current_hp() : clamp<int16_t>(card->get_current_hp(), -99, 99);
|
||||
int16_t new_hp = is_nte ? (hp + positive_expr_value) : clamp<int16_t>(hp + positive_expr_value, -99, 99);
|
||||
log.debug("HEAL: hp=%hd, positive_expr_value=%hd, new_hp=%hd", hp, positive_expr_value, new_hp);
|
||||
log.debug_f("HEAL: hp={}, positive_expr_value={}, new_hp={}", hp, positive_expr_value, new_hp);
|
||||
this->send_6xB4x06_for_stat_delta(card, attacker_card_ref, 0x20, new_hp - hp, true, true);
|
||||
if (new_hp != hp) {
|
||||
card->set_current_hp(new_hp);
|
||||
@@ -2842,8 +2842,8 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
int16_t p_target_type,
|
||||
bool apply_usability_filters) const {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("get_targeted_cards_for_condition(@%04hX, %hhu, @%04hX): ", card_ref, def_effect_index, setter_card_ref));
|
||||
log.debug("card_ref=@%04hX, def_effect_index=%02hhX, setter_card_ref=@%04hX, as, p_target_type=%hd, apply_usability_filters=%s", card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false");
|
||||
auto log = s->log_stack(std::format("get_targeted_cards_for_condition(@{:04X}, {}, @{:04X}): ", card_ref, def_effect_index, setter_card_ref));
|
||||
log.debug_f("card_ref=@{:04X}, def_effect_index={:02X}, setter_card_ref=@{:04X}, as, p_target_type={}, apply_usability_filters={}", card_ref, def_effect_index, setter_card_ref, p_target_type, apply_usability_filters ? "true" : "false");
|
||||
|
||||
vector<shared_ptr<const Card>> ret;
|
||||
|
||||
@@ -2852,12 +2852,12 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (!card1) {
|
||||
card1 = s->card_for_set_card_ref(setter_card_ref);
|
||||
}
|
||||
log.debug("card1=@%04hX", ref_for_card(card1));
|
||||
log.debug_f("card1=@{:04X}", ref_for_card(card1));
|
||||
|
||||
auto card2 = s->card_for_set_card_ref((as.attacker_card_ref == 0xFFFF)
|
||||
? as.original_attacker_card_ref
|
||||
: as.attacker_card_ref);
|
||||
log.debug("card2=@%04hX", ref_for_card(card2));
|
||||
log.debug_f("card2=@{:04X}", ref_for_card(card2));
|
||||
|
||||
Location card1_loc;
|
||||
if (!card1) {
|
||||
@@ -2868,13 +2868,13 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
this->get_card1_loc_with_card2_opposite_direction(&card1_loc, card1, card2);
|
||||
|
||||
string card1_loc_str = card1_loc.str();
|
||||
log.debug("card1_loc=%s", card1_loc_str.c_str());
|
||||
log.debug_f("card1_loc={}", card1_loc_str);
|
||||
}
|
||||
|
||||
AttackMedium attack_medium = card2
|
||||
? card2->action_chain.chain.attack_medium
|
||||
: AttackMedium::UNKNOWN;
|
||||
log.debug("attack_medium=%s", phosg::name_for_enum(attack_medium));
|
||||
log.debug_f("attack_medium={}", phosg::name_for_enum(attack_medium));
|
||||
|
||||
auto add_card_refs = [&](const vector<uint16_t>& result_card_refs) -> void {
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
@@ -2890,10 +2890,10 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
case 0x05: { // p05
|
||||
auto result_card = s->card_for_set_card_ref(setter_card_ref);
|
||||
if (result_card) {
|
||||
log.debug("(p01/p05) result_card=@%04hX", ref_for_card(result_card));
|
||||
log.debug_f("(p01/p05) result_card=@{:04X}", ref_for_card(result_card));
|
||||
ret.emplace_back(result_card);
|
||||
} else {
|
||||
log.debug("(p01/p05) result_card=null");
|
||||
log.debug_f("(p01/p05) result_card=null");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3073,28 +3073,28 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (def && ps) {
|
||||
// TODO: Again with the Gifoie hardcoding...
|
||||
uint16_t range_card_id = this->get_card_id_with_effective_range(card1, 0x00D9, card2);
|
||||
log23.debug("effective range card ID is #%04hX", range_card_id);
|
||||
log23.debug_f("effective range card ID is #{:04X}", range_card_id);
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, s->options.card_index, range_card_id, card1_loc, s->map_and_rules, &log23);
|
||||
auto result_card_refs = ps->get_all_cards_within_range(range, card1_loc, 0xFF);
|
||||
log23.debug("%zu result card refs", result_card_refs.size());
|
||||
log23.debug_f("{} result card refs", result_card_refs.size());
|
||||
for (uint16_t result_card_ref : result_card_refs) {
|
||||
auto result_log = log23.subf("(result @%04hX) ", result_card_ref);
|
||||
auto result_log = log23.sub(std::format("(result @{:04X}) ", result_card_ref));
|
||||
auto result_card = s->card_for_set_card_ref(result_card_ref);
|
||||
if (!result_card) {
|
||||
result_log.debug("result card not found");
|
||||
result_log.debug_f("result card not found");
|
||||
} else if (result_card->get_definition()->def.type == CardType::ITEM) {
|
||||
result_log.debug("result card is item");
|
||||
result_log.debug_f("result card is item");
|
||||
} else {
|
||||
result_log.debug("result card found and is not item");
|
||||
result_log.debug_f("result card found and is not item");
|
||||
ret.emplace_back(result_card);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log23.debug("def or ps is missing");
|
||||
log23.debug_f("def or ps is missing");
|
||||
}
|
||||
} else {
|
||||
log23.debug("card1 is missing");
|
||||
log23.debug_f("card1 is missing");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3194,28 +3194,28 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
};
|
||||
bool is_nte = s->options.is_nte();
|
||||
if (as.original_attacker_card_ref == 0xFFFF) {
|
||||
log36.debug("original_attacker_card_ref missing");
|
||||
log36.debug_f("original_attacker_card_ref missing");
|
||||
// debug_str_for_card_ref
|
||||
for (size_t z = 0; (z < 4 * 9) && (as.target_card_refs[z] != 0xFFFF); z++) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(as.target_card_refs[z]);
|
||||
log36.debug("examining %s", debug_ref_str.c_str());
|
||||
log36.debug_f("examining {}", debug_ref_str);
|
||||
auto result_card = s->card_for_set_card_ref(as.target_card_refs[z]);
|
||||
if (result_card && should_include(result_card->get_definition(), is_nte)) {
|
||||
log36.debug("adding %s", debug_ref_str.c_str());
|
||||
log36.debug_f("adding {}", debug_ref_str);
|
||||
ret.emplace_back(result_card);
|
||||
} else {
|
||||
log36.debug("skipping %s", debug_ref_str.c_str());
|
||||
log36.debug_f("skipping {}", debug_ref_str);
|
||||
}
|
||||
}
|
||||
} else if (card2 && should_include(card2->get_definition(), is_nte)) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(card2->get_card_ref());
|
||||
log36.debug("original_attacker_card_ref present; adding card2 = %s", debug_ref_str.c_str());
|
||||
log36.debug_f("original_attacker_card_ref present; adding card2 = {}", debug_ref_str);
|
||||
ret.emplace_back(card2);
|
||||
} else if (card2) {
|
||||
string debug_ref_str = s->debug_str_for_card_ref(card2->get_card_ref());
|
||||
log36.debug("original_attacker_card_ref present and card2 (%s) not eligible", debug_ref_str.c_str());
|
||||
log36.debug_f("original_attacker_card_ref present and card2 ({}) not eligible", debug_ref_str);
|
||||
} else {
|
||||
log36.debug("original_attacker_card_ref present and card2 missing");
|
||||
log36.debug_f("original_attacker_card_ref present and card2 missing");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3252,17 +3252,17 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
ret = this->find_all_set_cards_with_cost_in_range(
|
||||
(p_target_type == 0x27) ? 4 : 0,
|
||||
(p_target_type == 0x27) ? 99 : 3);
|
||||
if (log3940.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log3940.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
for (const auto& card : ret) {
|
||||
log3940.debug("found target @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
log3940.debug_f("found target @{:04X} #{:04X}", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
}
|
||||
if (!s->options.is_nte()) {
|
||||
log3940.debug("filtering targets");
|
||||
log3940.debug_f("filtering targets");
|
||||
ret = this->filter_cards_by_range(ret, card1, card1_loc, card2);
|
||||
if (log3940.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log3940.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
for (const auto& card : ret) {
|
||||
log3940.debug("retained target @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
log3940.debug_f("retained target @{:04X} #{:04X}", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3497,9 +3497,9 @@ vector<shared_ptr<const Card>> CardSpecial::get_targeted_cards_for_condition(
|
||||
if (s->ruler_server->check_usability_or_apply_condition_for_card_refs(
|
||||
card_ref, setter_card_ref, c->get_card_ref(), def_effect_index, attack_medium)) {
|
||||
filtered_ret.emplace_back(c);
|
||||
log.debug("usability filter: kept card @%04hX", ref_for_card(c));
|
||||
log.debug_f("usability filter: kept card @{:04X}", ref_for_card(c));
|
||||
} else {
|
||||
log.debug("usability filter: removed card @%04hX", ref_for_card(c));
|
||||
log.debug_f("usability filter: removed card @{:04X}", ref_for_card(c));
|
||||
}
|
||||
}
|
||||
return filtered_ret;
|
||||
@@ -3526,16 +3526,16 @@ bool CardSpecial::is_card_targeted_by_condition(
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack("is_card_targeted_by_condition: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug("card=(@%04hX #%04hX)", card->get_card_ref(), card->get_card_id());
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
log.debug_f("card=(@{:04X} #{:04X})", card->get_card_ref(), card->get_card_id());
|
||||
auto cond_str = cond.str(s);
|
||||
auto as_str = as.str(s);
|
||||
log.debug("cond = %s", cond_str.c_str());
|
||||
log.debug("as = %s", as_str.c_str());
|
||||
log.debug_f("cond = {}", cond_str);
|
||||
log.debug_f("as = {}", as_str);
|
||||
}
|
||||
|
||||
if (cond.type == ConditionType::NONE) {
|
||||
log.debug("condition is NONE (=> true)");
|
||||
log.debug_f("condition is NONE (=> true)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3546,12 +3546,12 @@ bool CardSpecial::is_card_targeted_by_condition(
|
||||
((ce->def.type == CardType::ITEM) || ce->def.is_sc()) &&
|
||||
(cond.remaining_turns != 100) &&
|
||||
(s->options.is_nte() || (client_id_for_card_ref(card->get_card_ref()) == client_id_for_card_ref(cond.card_ref)))) {
|
||||
log.debug("failed item or SC check (=> false)");
|
||||
log.debug_f("failed item or SC check (=> false)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cond.remaining_turns != 102) {
|
||||
log.debug("remaining_turns != 102 (=> true)");
|
||||
log.debug_f("remaining_turns != 102 (=> true)");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3569,15 +3569,15 @@ bool CardSpecial::is_card_targeted_by_condition(
|
||||
0);
|
||||
for (auto c : target_cards) {
|
||||
if (c == card) {
|
||||
log.debug("targeted by p condition (=> true)");
|
||||
log.debug_f("targeted by p condition (=> true)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
log.debug("not targeted by p condition (=> false)");
|
||||
log.debug_f("not targeted by p condition (=> false)");
|
||||
return false;
|
||||
} else {
|
||||
|
||||
log.debug("SC check does not apply");
|
||||
log.debug_f("SC check does not apply");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3806,7 +3806,7 @@ size_t CardSpecial::sum_last_attack_damage(
|
||||
size_t damage_count = 0;
|
||||
auto check_card = [&](shared_ptr<const Card> c) -> void {
|
||||
if (c && (c->last_attack_final_damage > 0)) {
|
||||
log.debug("check_card @%04hX #%04hX => %hd", c->get_card_ref(), c->get_card_id(), c->last_attack_final_damage);
|
||||
log.debug_f("check_card @{:04X} #{:04X} => {}", c->get_card_ref(), c->get_card_id(), c->last_attack_final_damage);
|
||||
if (out_damage_sum) {
|
||||
*out_damage_sum += c->last_attack_final_damage;
|
||||
}
|
||||
@@ -4013,13 +4013,13 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
bool apply_defense_condition_to_all_cards,
|
||||
uint16_t apply_defense_condition_to_card_ref) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("evaluate_and_apply_effects(%s, @%04hX, @%04hX): ", phosg::name_for_enum(when), set_card_ref, sc_card_ref));
|
||||
auto log = s->log_stack(std::format("evaluate_and_apply_effects({}, @{:04X}, @{:04X}): ", phosg::name_for_enum(when), set_card_ref, sc_card_ref));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
{
|
||||
string as_str = as.str(s);
|
||||
log.debug("when=%s, set_card_ref=@%04hX, as=%s, sc_card_ref=@%04hX, apply_defense_condition_to_all_cards=%s, apply_defense_condition_to_card_ref=@%04hX",
|
||||
phosg::name_for_enum(when), set_card_ref, as_str.c_str(), sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref);
|
||||
log.debug_f("when={}, set_card_ref=@{:04X}, as={}, sc_card_ref=@{:04X}, apply_defense_condition_to_all_cards={}, apply_defense_condition_to_card_ref=@{:04X}",
|
||||
phosg::name_for_enum(when), set_card_ref, as_str, sc_card_ref, apply_defense_condition_to_all_cards ? "true" : "false", apply_defense_condition_to_card_ref);
|
||||
}
|
||||
|
||||
if (!is_nte) {
|
||||
@@ -4028,7 +4028,7 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
|
||||
auto ce = this->server()->definition_for_card_ref(set_card_ref);
|
||||
if (!ce) {
|
||||
log.debug("ce missing");
|
||||
log.debug_f("ce missing");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4076,15 +4076,15 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
dice_roll.unknown_a2 = 3;
|
||||
dice_roll.value_used_in_expr = false;
|
||||
|
||||
log.debug("inputs: dice_roll=%02hhX, random_percent=%hhu, unknown_v1=%s", dice_roll.value, random_percent, unknown_v1 ? "true" : "false");
|
||||
log.debug_f("inputs: dice_roll={:02X}, random_percent={}, unknown_v1={}", dice_roll.value, random_percent, unknown_v1 ? "true" : "false");
|
||||
|
||||
for (size_t def_effect_index = 0; (def_effect_index < 3) && !unknown_v1 && (ce->def.effects[def_effect_index].type != ConditionType::NONE); def_effect_index++) {
|
||||
auto effect_log = log.sub(phosg::string_printf("(effect:%zu) ", def_effect_index));
|
||||
auto effect_log = log.sub(std::format("(effect:{}) ", def_effect_index));
|
||||
const auto& card_effect = ce->def.effects[def_effect_index];
|
||||
string card_effect_str = card_effect.str();
|
||||
effect_log.debug("effect: %s", card_effect_str.c_str());
|
||||
effect_log.debug_f("effect: {}", card_effect_str);
|
||||
if (card_effect.when != when) {
|
||||
effect_log.debug("does not apply (effect.when=%s, when=%s)", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when));
|
||||
effect_log.debug_f("does not apply (effect.when={}, when={})", phosg::name_for_enum(card_effect.when), phosg::name_for_enum(when));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -4093,18 +4093,18 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
throw runtime_error("card effect arg3 is missing");
|
||||
}
|
||||
int16_t arg3_value = atoi(arg3_s.c_str() + 1);
|
||||
effect_log.debug("arg3_value=%hd", arg3_value);
|
||||
effect_log.debug_f("arg3_value={}", arg3_value);
|
||||
auto targeted_cards = this->get_targeted_cards_for_condition(
|
||||
set_card_ref, def_effect_index, sc_card_ref, as, arg3_value, 1);
|
||||
string refs_str = refs_str_for_cards_vector(targeted_cards);
|
||||
effect_log.debug("targeted_cards=[%s]", refs_str.c_str());
|
||||
effect_log.debug_f("targeted_cards=[{}]", refs_str);
|
||||
bool all_targets_matched = false;
|
||||
if (!is_nte &&
|
||||
!targeted_cards.empty() &&
|
||||
((card_effect.type == ConditionType::UNKNOWN_64) ||
|
||||
(card_effect.type == ConditionType::MISC_DEFENSE_BONUSES) ||
|
||||
(card_effect.type == ConditionType::MOSTLY_HALFGUARDS))) {
|
||||
effect_log.debug("special targeting applies");
|
||||
effect_log.debug_f("special targeting applies");
|
||||
size_t count = 0;
|
||||
for (size_t z = 0; z < targeted_cards.size(); z++) {
|
||||
dice_roll.value_used_in_expr = false;
|
||||
@@ -4132,22 +4132,22 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
targeted_cards.clear();
|
||||
}
|
||||
} else {
|
||||
effect_log.debug("special targeting does not apply");
|
||||
effect_log.debug_f("special targeting does not apply");
|
||||
}
|
||||
|
||||
for (size_t z = 0; z < targeted_cards.size(); z++) {
|
||||
auto target_log = effect_log.sub(phosg::string_printf("(target:@%04hX) ", targeted_cards[z]->get_card_ref()));
|
||||
auto target_log = effect_log.sub(std::format("(target:@{:04X}) ", targeted_cards[z]->get_card_ref()));
|
||||
dice_roll.value_used_in_expr = false;
|
||||
string arg2_str = card_effect.arg2.decode();
|
||||
target_log.debug("arg2_str = %s", arg2_str.c_str());
|
||||
target_log.debug_f("arg2_str = {}", arg2_str);
|
||||
if (all_targets_matched ||
|
||||
this->evaluate_effect_arg2_condition(
|
||||
as, targeted_cards[z], arg2_str.c_str(), dice_roll, set_card_ref, sc_card_ref, random_percent, when)) {
|
||||
target_log.debug("arg2 condition passed");
|
||||
target_log.debug_f("arg2 condition passed");
|
||||
auto env_stats = this->compute_attack_env_stats(as, targeted_cards[z], dice_roll, set_card_ref, sc_card_ref);
|
||||
string expr_str = card_effect.expr.decode();
|
||||
int16_t value = this->evaluate_effect_expr(env_stats, expr_str.c_str(), dice_roll);
|
||||
target_log.debug("expr = %s, value = %hd", expr_str.c_str(), value);
|
||||
target_log.debug_f("expr = {}, value = {}", expr_str, value);
|
||||
|
||||
uint32_t unknown_v1 = 0;
|
||||
auto target_card = this->compute_replaced_target_based_on_conditions(
|
||||
@@ -4163,16 +4163,16 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
sc_card_ref);
|
||||
if (!target_card) {
|
||||
target_card = targeted_cards[z];
|
||||
target_log.debug("target card (not replaced) = @%04hX", target_card->get_card_ref());
|
||||
target_log.debug_f("target card (not replaced) = @{:04X}", target_card->get_card_ref());
|
||||
} else {
|
||||
target_log.debug("target card (replaced) = @%04hX", target_card->get_card_ref());
|
||||
target_log.debug_f("target card (replaced) = @{:04X}", target_card->get_card_ref());
|
||||
}
|
||||
|
||||
ssize_t applied_cond_index = -1;
|
||||
if ((unknown_v1 == 0) && !this->should_cancel_condition_due_to_anti_abnormality(card_effect, target_card, dice_cmd.effect.target_card_ref, sc_card_ref)) {
|
||||
applied_cond_index = target_card->apply_abnormal_condition(
|
||||
card_effect, def_effect_index, dice_cmd.effect.target_card_ref, sc_card_ref, value, dice_roll.value, random_percent);
|
||||
target_log.debug("applied abnormal condition");
|
||||
target_log.debug_f("applied abnormal condition");
|
||||
// This debug_print call is in the original code.
|
||||
// this->debug_print(when, 4, &env_stats, "!set_abnormal..", target_card, card_effect.type);
|
||||
}
|
||||
@@ -4202,12 +4202,12 @@ void CardSpecial::evaluate_and_apply_effects(
|
||||
if (apply_defense_condition_to_all_cards || (apply_defense_condition_to_card_ref == targeted_cards[z]->get_card_ref())) {
|
||||
this->apply_defense_condition(
|
||||
when, &target_card->action_chain.conditions[applied_cond_index], applied_cond_index, as, target_card, 4, 1);
|
||||
target_log.debug("applied defense condition");
|
||||
target_log.debug_f("applied defense condition");
|
||||
}
|
||||
}
|
||||
target_card->send_6xB4x4E_4C_4D_if_needed(0);
|
||||
} else {
|
||||
target_log.debug("arg2 condition failed");
|
||||
target_log.debug_f("arg2 condition failed");
|
||||
}
|
||||
if (dice_roll.value_used_in_expr) {
|
||||
any_expr_used_dice_roll = true;
|
||||
@@ -4712,73 +4712,73 @@ vector<shared_ptr<const Card>> CardSpecial::filter_cards_by_range(
|
||||
shared_ptr<const Card> card2) const {
|
||||
auto log = this->server()->log_stack("filter_cards_by_range: ");
|
||||
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
auto card1_str = card1 ? phosg::string_printf("@%04hX #%04hX", card1->get_card_ref(), card1->get_card_id()) : "null";
|
||||
auto card2_str = card2 ? phosg::string_printf("@%04hX #%04hX", card2->get_card_ref(), card2->get_card_id()) : "null";
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
auto card1_str = card1 ? std::format("@{:04X} #{:04X}", card1->get_card_ref(), card1->get_card_id()) : "null";
|
||||
auto card2_str = card2 ? std::format("@{:04X} #{:04X}", card2->get_card_ref(), card2->get_card_id()) : "null";
|
||||
auto loc_str = card1_loc.str();
|
||||
log.debug("card1=(%s), card2=(%s), loc=%s", card1_str.c_str(), card2_str.c_str(), loc_str.c_str());
|
||||
log.debug_f("card1=({}), card2=({}), loc={}", card1_str, card2_str, loc_str);
|
||||
|
||||
for (const auto& card : cards) {
|
||||
if (card) {
|
||||
log.debug("input card: @%04hX #%04hX", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("input card: @{:04X} #{:04X}", card->get_card_ref(), card->get_card_id());
|
||||
} else {
|
||||
log.debug("input card: null");
|
||||
log.debug_f("input card: null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vector<shared_ptr<const Card>> ret;
|
||||
if (!card1 || cards.empty()) {
|
||||
log.debug("card1 missing or input list is blank");
|
||||
log.debug_f("card1 missing or input list is blank");
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto ps = card1->player_state();
|
||||
if (!ps) {
|
||||
log.debug("ps is missing");
|
||||
log.debug_f("ps is missing");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO: Remove hardcoded card ID here (Earthquake)
|
||||
uint16_t card_id = this->get_card_id_with_effective_range(card1, 0x00ED, card2);
|
||||
log.debug("card_id = #%04hX", card_id);
|
||||
log.debug_f("card_id = #{:04X}", card_id);
|
||||
|
||||
parray<uint8_t, 9 * 9> range;
|
||||
compute_effective_range(range, this->server()->options.card_index, card_id, card1_loc, this->server()->map_and_rules);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
auto loc_str = card1_loc.str();
|
||||
log.debug("compute_effective_range(range, ci, #%04hX, %s, map) =>", card_id, loc_str.c_str());
|
||||
log.debug_f("compute_effective_range(range, ci, #{:04X}, {}, map) =>", card_id, loc_str);
|
||||
for (size_t y = 0; y < 9; y++) {
|
||||
const uint8_t* row = &range[y * 9];
|
||||
log.debug(" range[%zu] = %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX",
|
||||
log.debug_f(" range[{}] = {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
|
||||
y, row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8]);
|
||||
}
|
||||
}
|
||||
|
||||
auto card_refs_in_range = ps->get_card_refs_within_range_from_all_players(range, card1_loc, CardType::ITEM);
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
for (uint16_t card_ref : card_refs_in_range) {
|
||||
log.debug("ref in range: @%04hX", card_ref);
|
||||
log.debug_f("ref in range: @{:04X}", card_ref);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto card : cards) {
|
||||
if (!card || (card->get_card_ref() == 0xFFFF)) {
|
||||
if (card) {
|
||||
log.debug("(@%04hX #%04hX) out of range", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("(@{:04X} #{:04X}) out of range", card->get_card_ref(), card->get_card_id());
|
||||
} else {
|
||||
log.debug("(null) card missing");
|
||||
log.debug_f("(null) card missing");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (uint16_t card_ref_in_range : card_refs_in_range) {
|
||||
if (card_ref_in_range == card->get_card_ref()) {
|
||||
log.debug("(@%04hX #%04hX) in range", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("(@{:04X} #{:04X}) in range", card->get_card_ref(), card->get_card_id());
|
||||
ret.emplace_back(card);
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.debug("(@%04hX #%04hX) out of range", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("(@{:04X} #{:04X}) out of range", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -4787,7 +4787,7 @@ void CardSpecial::apply_effects_after_attack_target_resolution(const ActionState
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack("apply_effects_after_attack_target_resolution: ");
|
||||
string as_str = as.str(s);
|
||||
log.debug("as=%s", as_str.c_str());
|
||||
log.debug_f("as={}", as_str);
|
||||
|
||||
for (size_t z = 0; (z < 8) && (as.action_card_refs[z] != 0xFFFF); z++) {
|
||||
uint16_t card_ref = this->send_6xB4x06_if_card_ref_invalid(as.action_card_refs[z], 0x1E);
|
||||
@@ -4878,7 +4878,7 @@ void CardSpecial::dice_phase_before_for_card(shared_ptr<Card> card) {
|
||||
template <EffectWhen When1, EffectWhen When2>
|
||||
void CardSpecial::apply_effects_on_phase_change_t(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("apply_effects_on_phase_change_t<%s, %s>(@%04hX #%04hX): ", phosg::name_for_enum(When1), phosg::name_for_enum(When2), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
auto log = s->log_stack(std::format("apply_effects_on_phase_change_t<{}, {}>(@{:04X} #{:04X}): ", phosg::name_for_enum(When1), phosg::name_for_enum(When2), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
bool is_nte = s->options.is_nte();
|
||||
|
||||
ActionState as;
|
||||
@@ -4929,7 +4929,7 @@ void CardSpecial::unknown_8024945C(shared_ptr<Card> unknown_p2, const ActionStat
|
||||
}
|
||||
|
||||
void CardSpecial::unknown_8024966C(shared_ptr<Card> unknown_p2, const ActionState* existing_as) {
|
||||
auto log = this->server()->log_stack(phosg::string_printf("unknown_8024966C(@%04hX #%04hX): ", unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
auto log = this->server()->log_stack(std::format("unknown_8024966C(@{:04X} #{:04X}): ", unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
|
||||
ActionState as;
|
||||
if (!existing_as) {
|
||||
@@ -5080,7 +5080,7 @@ template <
|
||||
EffectWhen WhenTargetsAndActionCards>
|
||||
void CardSpecial::apply_effects_before_or_after_attack(shared_ptr<Card> unknown_p2) {
|
||||
auto s = this->server();
|
||||
auto log = s->log_stack(phosg::string_printf("apply_effects_before_or_after_attack<%s, %s, %s, %s>(@%04hX #%04hX): ",
|
||||
auto log = s->log_stack(std::format("apply_effects_before_or_after_attack<{}, {}, {}, {}>(@{:04X} #{:04X}): ",
|
||||
phosg::name_for_enum(WhenAllCards), phosg::name_for_enum(WhenAttackerAndActionCards), phosg::name_for_enum(WhenAttackerOrHunterSCCard), phosg::name_for_enum(WhenTargetsAndActionCards), unknown_p2->get_card_ref(), unknown_p2->get_card_id()));
|
||||
|
||||
ActionState as = this->create_attack_state_from_card_action_chain(unknown_p2);
|
||||
|
||||
+295
-292
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
@@ -1566,8 +1567,8 @@ public:
|
||||
std::shared_ptr<const CardEntry> definition_for_name(const std::string& name) const;
|
||||
std::shared_ptr<const CardEntry> definition_for_name_normalized(const std::string& name) const;
|
||||
std::set<uint32_t> all_ids() const;
|
||||
uint64_t definitions_mtime() const;
|
||||
phosg::JSON definitions_json() const;
|
||||
uint64_t definitions_hash() const;
|
||||
|
||||
private:
|
||||
static std::string normalize_card_name(const std::string& name);
|
||||
@@ -1576,7 +1577,7 @@ private:
|
||||
std::unordered_map<uint32_t, std::shared_ptr<CardEntry>> card_definitions;
|
||||
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name;
|
||||
std::unordered_map<std::string, std::shared_ptr<CardEntry>> card_definitions_by_name_normalized;
|
||||
uint64_t mtime_for_card_definitions;
|
||||
uint64_t defs_hash;
|
||||
};
|
||||
|
||||
class MapIndex {
|
||||
|
||||
@@ -187,7 +187,7 @@ void DeckState::restart() {
|
||||
this->shuffle();
|
||||
}
|
||||
|
||||
void DeckState::do_mulligan(bool is_nte) {
|
||||
void DeckState::redraw_initial_hand(bool is_nte) {
|
||||
for (size_t z = 0; z < this->entries.size(); z++) {
|
||||
if (this->entries[z].state == CardState::DISCARDED) {
|
||||
this->entries[z].state = CardState::DRAWABLE;
|
||||
@@ -308,7 +308,7 @@ static const char* name_for_card_state(DeckState::CardState st) {
|
||||
}
|
||||
|
||||
void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index) const {
|
||||
fprintf(stream, "DeckState: client_id=%hhu draw_index=%hhu card_ref_base=@%04hX shuffle=%s loop=%s\n",
|
||||
phosg::fwrite_fmt(stream, "DeckState: client_id={} draw_index={} card_ref_base=@{:04X} shuffle={} loop={}\n",
|
||||
this->client_id, this->draw_index, this->card_ref_base, this->shuffle_enabled ? "true" : "false", this->loop_enabled ? "true" : "false");
|
||||
for (size_t z = 0; z < 31; z++) {
|
||||
const auto& e = this->entries[z];
|
||||
@@ -321,10 +321,10 @@ void DeckState::print(FILE* stream, std::shared_ptr<const CardIndex> card_index)
|
||||
}
|
||||
if (ce) {
|
||||
string name = ce->def.en_name.decode(1);
|
||||
fprintf(stream, " (%02zu) index=%02hhX ref=@%04hX card_id=#%04hX \"%s\" %s\n",
|
||||
z, e.deck_index, this->card_refs[z], e.card_id, name.c_str(), name_for_card_state(e.state));
|
||||
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));
|
||||
} else {
|
||||
fprintf(stream, " (%02zu) index=%02hhX ref=@%04hX card_id=#%04hX %s\n",
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
|
||||
void restart();
|
||||
void shuffle();
|
||||
void do_mulligan(bool is_nte);
|
||||
void redraw_initial_hand(bool is_nte);
|
||||
|
||||
void print(FILE* stream, std::shared_ptr<const CardIndex> card_index = nullptr) const;
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ void MapState::clear() {
|
||||
}
|
||||
|
||||
void MapState::print(FILE* stream) const {
|
||||
fprintf(stream, "[Map: w=%hu h=%hu]\n", this->width.load(), this->height.load());
|
||||
phosg::fwrite_fmt(stream, "[Map: w={} h={}]\n", this->width, this->height);
|
||||
for (size_t y = 0; y < this->height; y++) {
|
||||
fputc(' ', stream);
|
||||
for (size_t x = 0; x < this->width; x++) {
|
||||
fprintf(stream, " %02hhX", this->tiles[y][x]);
|
||||
phosg::fwrite_fmt(stream, " {:02X}", this->tiles[y][x]);
|
||||
}
|
||||
fputc('\n', stream);
|
||||
}
|
||||
|
||||
+21
-21
@@ -9,7 +9,7 @@ namespace Episode3 {
|
||||
PlayerState::PlayerState(uint8_t client_id, shared_ptr<Server> server)
|
||||
: w_server(server),
|
||||
client_id(client_id),
|
||||
num_mulligans_allowed(1),
|
||||
num_hand_redraws_allowed(1),
|
||||
sc_card_type(CardType::HUNTERS_SC),
|
||||
team_id(0xFF),
|
||||
atk_points(0),
|
||||
@@ -705,14 +705,14 @@ void PlayerState::discard_set_assist_card() {
|
||||
s->destroy_cards_with_zero_hp();
|
||||
}
|
||||
|
||||
bool PlayerState::do_mulligan() {
|
||||
if (!this->is_mulligan_allowed()) {
|
||||
bool PlayerState::redraw_initial_hand() {
|
||||
if (!this->is_hand_redraw_allowed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto s = this->server();
|
||||
|
||||
this->num_mulligans_allowed--;
|
||||
this->num_hand_redraws_allowed--;
|
||||
while (this->card_refs[0] != 0xFFFF) {
|
||||
this->discard_ref_from_hand(this->card_refs[0]);
|
||||
}
|
||||
@@ -727,7 +727,7 @@ bool PlayerState::do_mulligan() {
|
||||
s->send(cmd);
|
||||
}
|
||||
|
||||
this->deck_state->do_mulligan(s->options.is_nte());
|
||||
this->deck_state->redraw_initial_hand(s->options.is_nte());
|
||||
this->draw_hand(5);
|
||||
|
||||
if (!s->options.is_nte()) {
|
||||
@@ -841,7 +841,7 @@ vector<uint16_t> PlayerState::get_all_cards_within_range(
|
||||
|
||||
auto log = s->log_stack("get_all_cards_within_range: ");
|
||||
string loc_str = loc.str();
|
||||
log.debug("loc=%s, target_team_id=%02hhX", loc_str.c_str(), target_team_id);
|
||||
log.debug_f("loc={}, target_team_id={:02X}", loc_str, target_team_id);
|
||||
|
||||
vector<uint16_t> ret;
|
||||
for (size_t client_id = 0; client_id < 4; client_id++) {
|
||||
@@ -939,8 +939,8 @@ size_t PlayerState::set_index_for_card_ref(uint16_t card_ref) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool PlayerState::is_mulligan_allowed() const {
|
||||
return (this->num_mulligans_allowed > 0);
|
||||
bool PlayerState::is_hand_redraw_allowed() const {
|
||||
return (this->num_hand_redraws_allowed > 0);
|
||||
}
|
||||
|
||||
bool PlayerState::is_team_turn() const {
|
||||
@@ -1766,20 +1766,20 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
|
||||
auto attacker_card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (attacker_card) {
|
||||
log.debug("attacker card present");
|
||||
log.debug_f("attacker card present");
|
||||
attacker_card->card_flags |= 0x100;
|
||||
}
|
||||
|
||||
auto action_type = s->ruler_server->get_pending_action_type(pa);
|
||||
if (action_type == ActionType::DEFENSE) {
|
||||
log.debug("action type is DEFENSE");
|
||||
log.debug_f("action type is DEFENSE");
|
||||
} else if (action_type == ActionType::ATTACK) {
|
||||
log.debug("action type is ATTACK");
|
||||
log.debug_f("action type is ATTACK");
|
||||
} else {
|
||||
log.debug("action type is UNKNOWN");
|
||||
log.debug_f("action type is UNKNOWN");
|
||||
}
|
||||
if (!is_nte) {
|
||||
log.debug("(non-nte) subtracting action points");
|
||||
log.debug_f("(non-nte) subtracting action points");
|
||||
this->subtract_or_check_atk_or_def_points_for_action(pa, true);
|
||||
}
|
||||
|
||||
@@ -1787,7 +1787,7 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
auto card = s->card_for_set_card_ref(pa.attacker_card_ref);
|
||||
if (card) {
|
||||
card->loc.direction = pa.facing_direction;
|
||||
log.debug("set facing direction to %s", phosg::name_for_enum(card->loc.direction));
|
||||
log.debug_f("set facing direction to {}", phosg::name_for_enum(card->loc.direction));
|
||||
|
||||
G_AddToSetCardLog_Ep3_6xB4x4A cmd;
|
||||
cmd.card_refs.clear(0xFFFF);
|
||||
@@ -1796,9 +1796,9 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
cmd.entry_count = 0;
|
||||
size_t z = 0;
|
||||
do {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug("on action card ref %s", ref_str.c_str());
|
||||
log.debug_f("on action card ref {}", ref_str);
|
||||
}
|
||||
card->unknown_80237A90(pa, pa.action_card_refs[z]);
|
||||
card->unknown_802379BC(pa.action_card_refs[z]);
|
||||
@@ -1833,9 +1833,9 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
for (size_t z = 0; (z < 4 * 9) && (pa.target_card_refs[z] != 0xFFFF); z++) {
|
||||
auto target_card = s->card_for_set_card_ref(pa.target_card_refs[z]);
|
||||
if (target_card) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.target_card_refs[z]);
|
||||
log.debug("on target card ref %s", ref_str.c_str());
|
||||
log.debug_f("on target card ref {}", ref_str);
|
||||
}
|
||||
target_card->unknown_802379DC(pa);
|
||||
if (!is_nte) {
|
||||
@@ -1859,13 +1859,13 @@ bool PlayerState::set_action_cards_for_action_state(const ActionState& pa) {
|
||||
}
|
||||
}
|
||||
if (is_nte) {
|
||||
log.debug("(nte) subtracting action points");
|
||||
log.debug_f("(nte) subtracting action points");
|
||||
this->subtract_or_check_atk_or_def_points_for_action(pa, 1);
|
||||
}
|
||||
for (size_t z = 0; (z < pa.action_card_refs.size()) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string ref_str = s->debug_str_for_card_ref(pa.action_card_refs[z]);
|
||||
log.debug("discarding %s from hand", ref_str.c_str());
|
||||
log.debug_f("discarding {} from hand", ref_str);
|
||||
}
|
||||
this->discard_ref_from_hand(pa.action_card_refs[z]);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
void discard_random_hand_card();
|
||||
bool discard_ref_from_hand(uint16_t card_ref);
|
||||
void discard_set_assist_card();
|
||||
bool do_mulligan();
|
||||
bool redraw_initial_hand();
|
||||
void draw_hand(ssize_t override_count = 0);
|
||||
void draw_initial_hand();
|
||||
int32_t error_code_for_client_setting_card(
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
uint8_t get_team_id() const;
|
||||
ssize_t hand_index_for_card_ref(uint16_t card_ref) const;
|
||||
size_t set_index_for_card_ref(uint16_t card_ref) const;
|
||||
bool is_mulligan_allowed() const;
|
||||
bool is_hand_redraw_allowed() const;
|
||||
bool is_team_turn() const;
|
||||
void log_discard(uint16_t card_ref, uint16_t reason);
|
||||
uint16_t pop_from_discard_log(uint16_t reason);
|
||||
@@ -152,7 +152,7 @@ public:
|
||||
std::shared_ptr<Card> sc_card;
|
||||
bcarray<std::shared_ptr<Card>, 8> set_cards;
|
||||
uint8_t client_id;
|
||||
uint16_t num_mulligans_allowed;
|
||||
uint16_t num_hand_redraws_allowed;
|
||||
CardType sc_card_type;
|
||||
uint8_t team_id;
|
||||
uint8_t atk_points;
|
||||
|
||||
@@ -64,19 +64,19 @@ void Condition::clear_FF() {
|
||||
std::string Condition::str(shared_ptr<const Server> s) const {
|
||||
auto card_ref_str = s->debug_str_for_card_ref(this->card_ref);
|
||||
auto giver_ref_str = s->debug_str_for_card_ref(this->condition_giver_card_ref);
|
||||
return phosg::string_printf(
|
||||
"Condition[type=%s, turns=%hhu, a_arg=%hhd, dice=%hhu, flags=%02hhX, "
|
||||
"def_eff_index=%hhu, ref=%s, value=%hd, giver_ref=%s "
|
||||
"percent=%hhu value8=%hd order=%hu a8=%hu]",
|
||||
return std::format(
|
||||
"Condition[type={}, turns={}, a_arg={}, dice={}, flags={:02X}, "
|
||||
"def_eff_index={}, ref={}, value={}, giver_ref={} "
|
||||
"percent={} value8={} order={} a8={}]",
|
||||
phosg::name_for_enum(this->type),
|
||||
this->remaining_turns,
|
||||
this->a_arg_value,
|
||||
this->dice_roll_value,
|
||||
this->flags,
|
||||
this->card_definition_effect_index,
|
||||
card_ref_str.c_str(),
|
||||
this->value.load(),
|
||||
giver_ref_str.c_str(),
|
||||
card_ref_str,
|
||||
this->value,
|
||||
giver_ref_str,
|
||||
this->random_percent,
|
||||
this->value8,
|
||||
this->order,
|
||||
@@ -103,12 +103,12 @@ void EffectResult::clear() {
|
||||
std::string EffectResult::str(shared_ptr<const Server> s) const {
|
||||
string attacker_ref_str = s->debug_str_for_card_ref(this->attacker_card_ref);
|
||||
string target_ref_str = s->debug_str_for_card_ref(this->target_card_ref);
|
||||
return phosg::string_printf(
|
||||
"EffectResult[att_ref=%s, target_ref=%s, value=%hhd, "
|
||||
"cur_hp=%hhd, ap=%hhd, tp=%hhd, flags=%02hhX, op=%hhd, "
|
||||
"cond_index=%hhu, dice=%hhu]",
|
||||
attacker_ref_str.c_str(),
|
||||
target_ref_str.c_str(),
|
||||
return std::format(
|
||||
"EffectResult[att_ref={}, target_ref={}, value={}, "
|
||||
"cur_hp={}, ap={}, tp={}, flags={:02X}, op={}, "
|
||||
"cond_index={}, dice={}]",
|
||||
attacker_ref_str,
|
||||
target_ref_str,
|
||||
this->value,
|
||||
this->current_hp,
|
||||
this->ap,
|
||||
@@ -139,14 +139,14 @@ bool CardShortStatus::operator!=(const CardShortStatus& other) const {
|
||||
std::string CardShortStatus::str(shared_ptr<const Server> s) const {
|
||||
string loc_s = this->loc.str();
|
||||
string ref_str = s->debug_str_for_card_ref(this->card_ref);
|
||||
return phosg::string_printf(
|
||||
"CardShortStatus[ref=%s, cur_hp=%hd, flags=%08" PRIX32 ", loc=%s, "
|
||||
"u1=%04hX, max_hp=%hhd, u2=%hhu]",
|
||||
ref_str.c_str(),
|
||||
this->current_hp.load(),
|
||||
this->card_flags.load(),
|
||||
loc_s.c_str(),
|
||||
this->unused1.load(),
|
||||
return std::format(
|
||||
"CardShortStatus[ref={}, cur_hp={}, flags={:08X}, loc={}, "
|
||||
"u1={:04X}, max_hp={}, u2={}]",
|
||||
ref_str,
|
||||
this->current_hp,
|
||||
this->card_flags,
|
||||
loc_s,
|
||||
this->unused1,
|
||||
this->max_hp,
|
||||
this->unused2);
|
||||
}
|
||||
@@ -193,18 +193,18 @@ std::string ActionState::str(shared_ptr<const Server> s) const {
|
||||
string original_attacker_ref_s = s->debug_str_for_card_ref(this->original_attacker_card_ref);
|
||||
string target_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
string action_refs_s = s->debug_str_for_card_refs(this->action_card_refs);
|
||||
return phosg::string_printf(
|
||||
"ActionState[client=%hX, u=%hhu, facing=%s, attacker_ref=%s, "
|
||||
"def_ref=%s, target_refs=%s, action_refs=%s, "
|
||||
"orig_attacker_ref=%s]",
|
||||
this->client_id.load(),
|
||||
return std::format(
|
||||
"ActionState[client={:X}, u={}, facing={}, attacker_ref={}, "
|
||||
"def_ref={}, target_refs={}, action_refs={}, "
|
||||
"orig_attacker_ref={}]",
|
||||
this->client_id,
|
||||
this->unused,
|
||||
phosg::name_for_enum(this->facing_direction),
|
||||
attacker_ref_s.c_str(),
|
||||
defense_ref_s.c_str(),
|
||||
target_refs_s.c_str(),
|
||||
action_refs_s.c_str(),
|
||||
original_attacker_ref_s.c_str());
|
||||
attacker_ref_s,
|
||||
defense_ref_s,
|
||||
target_refs_s,
|
||||
action_refs_s,
|
||||
original_attacker_ref_s);
|
||||
}
|
||||
|
||||
ActionChain::ActionChain() {
|
||||
@@ -243,20 +243,20 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
string unknown_card_ref_a3_s = s->debug_str_for_card_ref(this->unknown_card_ref_a3);
|
||||
string attack_action_card_refs_s = s->debug_str_for_card_refs(this->attack_action_card_refs);
|
||||
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
return phosg::string_printf(
|
||||
"ActionChain[eff_ap=%hhd, eff_tp=%hhd, ap_bonus=%hhd, damage=%hhd, "
|
||||
"acting_ref=%s, unknown_ref_a3=%s, attack_action_refs=%s, "
|
||||
"attack_action_ref_count=%hhu, medium=%s, target_ref_count=%hhu, "
|
||||
"subphase=%s, strikes=%hhu, damage_mult=%hhd, attack_num=%hhu, "
|
||||
"tp_bonus=%hhd, phys_bonus_nte=%hhu, tech_bonus_nte=%hhu, card_ap=%hhd, "
|
||||
"card_tp=%hhd, flags=%08" PRIX32 ", target_refs=%s]",
|
||||
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={}, "
|
||||
"tp_bonus={}, phys_bonus_nte={}, tech_bonus_nte={}, card_ap={}, "
|
||||
"card_tp={}, flags={:08X}, target_refs={}]",
|
||||
this->effective_ap,
|
||||
this->effective_tp,
|
||||
this->ap_effect_bonus,
|
||||
this->damage,
|
||||
acting_card_ref_s.c_str(),
|
||||
unknown_card_ref_a3_s.c_str(),
|
||||
attack_action_card_refs_s.c_str(),
|
||||
acting_card_ref_s,
|
||||
unknown_card_ref_a3_s,
|
||||
attack_action_card_refs_s,
|
||||
this->attack_action_card_ref_count,
|
||||
phosg::name_for_enum(this->attack_medium),
|
||||
this->target_card_ref_count,
|
||||
@@ -269,8 +269,8 @@ std::string ActionChain::str(shared_ptr<const Server> s) const {
|
||||
this->tech_attack_bonus_nte,
|
||||
this->card_ap,
|
||||
this->card_tp,
|
||||
this->flags.load(),
|
||||
target_card_refs_s.c_str());
|
||||
this->flags,
|
||||
target_card_refs_s);
|
||||
}
|
||||
|
||||
void ActionChain::clear() {
|
||||
@@ -341,7 +341,7 @@ std::string ActionChainWithConds::str(shared_ptr<const Server> s) const {
|
||||
if (ret.back() != '[') {
|
||||
ret += ", ";
|
||||
}
|
||||
ret += phosg::string_printf("%zu:", z);
|
||||
ret += std::format("{}:", z);
|
||||
ret += this->conditions[z].str(s);
|
||||
}
|
||||
}
|
||||
@@ -580,22 +580,22 @@ std::string ActionMetadata::str(shared_ptr<const Server> s) const {
|
||||
string target_card_refs_s = s->debug_str_for_card_refs(this->target_card_refs);
|
||||
string defense_card_refs_s = s->debug_str_for_card_refs(this->defense_card_refs);
|
||||
string original_attacker_card_refs_s = s->debug_str_for_card_refs(this->original_attacker_card_refs);
|
||||
return phosg::string_printf(
|
||||
"ActionMetadata[ref=%s, target_ref_count=%hhu, def_ref_count=%hhu, "
|
||||
"subphase=%s, def_power=%hhd, def_bonus=%hhd, "
|
||||
"att_bonus=%hhd, flags=%08" PRIX32 ", target_refs=%s, "
|
||||
"defense_refs=%s, original_attacker_refs=%s]",
|
||||
card_ref_s.c_str(),
|
||||
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={}]",
|
||||
card_ref_s,
|
||||
this->target_card_ref_count,
|
||||
this->defense_card_ref_count,
|
||||
phosg::name_for_enum(this->action_subphase),
|
||||
this->defense_power,
|
||||
this->defense_bonus,
|
||||
this->attack_bonus,
|
||||
this->flags.load(),
|
||||
target_card_refs_s.c_str(),
|
||||
defense_card_refs_s.c_str(),
|
||||
original_attacker_card_refs_s.c_str());
|
||||
this->flags,
|
||||
target_card_refs_s,
|
||||
defense_card_refs_s,
|
||||
original_attacker_card_refs_s);
|
||||
}
|
||||
|
||||
void ActionMetadata::clear() {
|
||||
@@ -683,13 +683,13 @@ std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
|
||||
string set_card_refs_s = s->debug_str_for_card_refs(this->set_card_refs);
|
||||
string hand_card_refs2_s = s->debug_str_for_card_refs(this->hand_card_refs2);
|
||||
string set_card_refs2_s = s->debug_str_for_card_refs(this->set_card_refs2);
|
||||
return phosg::string_printf(
|
||||
"HandAndEquipState[dice=[%hhu, %hhu], atk=%hhu, def=%hhu, atk2=%hhu, "
|
||||
"a1=%hhu, total_set_cost=%hhu, is_cpu=%hhu, assist_flags=%08" PRIX32 ", "
|
||||
"hand_refs=%s, assist_ref=%s, set_refs=%s, sc_ref=%s, hand_refs2=%s, "
|
||||
"set_refs2=%s, assist_ref2=%s, assist_set_num=%hu, assist_card_id=%s, "
|
||||
"assist_turns=%hhu, assist_delay=%hhu, atk_bonus=%hhu, def_bonus=%hhu, "
|
||||
"u2=[%hhu, %hhu]]",
|
||||
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={}, "
|
||||
"assist_turns={}, assist_delay={}, atk_bonus={}, def_bonus={}, "
|
||||
"u2=[{}, {}]]",
|
||||
this->dice_results[0],
|
||||
this->dice_results[1],
|
||||
this->atk_points,
|
||||
@@ -698,16 +698,16 @@ std::string HandAndEquipState::str(shared_ptr<const Server> s) const {
|
||||
this->unknown_a1,
|
||||
this->total_set_cards_cost,
|
||||
this->is_cpu_player,
|
||||
this->assist_flags.load(),
|
||||
hand_card_refs_s.c_str(),
|
||||
assist_card_ref_s.c_str(),
|
||||
set_card_refs_s.c_str(),
|
||||
sc_card_ref_s.c_str(),
|
||||
hand_card_refs2_s.c_str(),
|
||||
set_card_refs2_s.c_str(),
|
||||
assist_card_ref2_s.c_str(),
|
||||
this->assist_card_set_number.load(),
|
||||
assist_card_id_s.c_str(),
|
||||
this->assist_flags,
|
||||
hand_card_refs_s,
|
||||
assist_card_ref_s,
|
||||
set_card_refs_s,
|
||||
sc_card_ref_s,
|
||||
hand_card_refs2_s,
|
||||
set_card_refs2_s,
|
||||
assist_card_ref2_s,
|
||||
this->assist_card_set_number,
|
||||
assist_card_id_s,
|
||||
this->assist_remaining_turns,
|
||||
this->assist_delay_turns,
|
||||
this->atk_bonuses,
|
||||
@@ -838,19 +838,19 @@ const char* PlayerBattleStats::name_for_rank(uint8_t rank) {
|
||||
}
|
||||
|
||||
PlayerBattleStatsTrial::PlayerBattleStatsTrial(const PlayerBattleStats& data)
|
||||
: damage_given(data.damage_given.load()),
|
||||
damage_taken(data.damage_taken.load()),
|
||||
num_opponent_cards_destroyed(data.num_opponent_cards_destroyed.load()),
|
||||
num_owned_cards_destroyed(data.num_owned_cards_destroyed.load()),
|
||||
total_move_distance(data.total_move_distance.load()) {}
|
||||
: damage_given(data.damage_given),
|
||||
damage_taken(data.damage_taken),
|
||||
num_opponent_cards_destroyed(data.num_opponent_cards_destroyed),
|
||||
num_owned_cards_destroyed(data.num_owned_cards_destroyed),
|
||||
total_move_distance(data.total_move_distance) {}
|
||||
|
||||
PlayerBattleStatsTrial::operator PlayerBattleStats() const {
|
||||
PlayerBattleStats ret;
|
||||
ret.damage_given = this->damage_given.load();
|
||||
ret.damage_taken = this->damage_taken.load();
|
||||
ret.num_opponent_cards_destroyed = this->num_opponent_cards_destroyed.load();
|
||||
ret.num_owned_cards_destroyed = this->num_owned_cards_destroyed.load();
|
||||
ret.total_move_distance = this->total_move_distance.load();
|
||||
ret.damage_given = this->damage_given;
|
||||
ret.damage_taken = this->damage_taken;
|
||||
ret.num_opponent_cards_destroyed = this->num_opponent_cards_destroyed;
|
||||
ret.num_owned_cards_destroyed = this->num_owned_cards_destroyed;
|
||||
ret.total_move_distance = this->total_move_distance;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -861,26 +861,26 @@ static bool is_card_within_range(
|
||||
phosg::PrefixedLogger* log) {
|
||||
if (ss.card_ref == 0xFFFF) {
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (false) ss.card_ref missing");
|
||||
log->debug_f("is_card_within_range: (false) ss.card_ref missing");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (range[0] == 2) {
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (true) range is entire field");
|
||||
log->debug_f("is_card_within_range: (true) range is entire field");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((ss.loc.x < anchor_loc.x - 4) || (ss.loc.x > anchor_loc.x + 4)) {
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (false) outside x range (ss.loc.x=%hhu, anchor_loc.x=%hhu)", ss.loc.x, anchor_loc.x);
|
||||
log->debug_f("is_card_within_range: (false) outside x range (ss.loc.x={}, anchor_loc.x={})", ss.loc.x, anchor_loc.x);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if ((ss.loc.y < anchor_loc.y - 4) || (ss.loc.y > anchor_loc.y + 4)) {
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (false) outside y range (ss.loc.y=%hhu, anchor_loc.y=%hhu)", ss.loc.y, anchor_loc.y);
|
||||
log->debug_f("is_card_within_range: (false) outside y range (ss.loc.y={}, anchor_loc.y={})", ss.loc.y, anchor_loc.y);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -889,7 +889,7 @@ static bool is_card_within_range(
|
||||
uint8_t x_index = (ss.loc.x - anchor_loc.x) + 4;
|
||||
bool ret = (range[y_index * 9 + x_index] != 0);
|
||||
if (log) {
|
||||
log->debug("is_card_within_range: (%s) (ss.loc=(%hhu,%hhu), anchor_loc=(%hhu,%hhu), indexes=(%hhu,%hhu))",
|
||||
log->debug_f("is_card_within_range: ({}) (ss.loc=({},{}), anchor_loc=({},{}), indexes=({},{}))",
|
||||
ret ? "true" : "false", ss.loc.x, ss.loc.y, anchor_loc.x, anchor_loc.y, x_index, y_index);
|
||||
}
|
||||
return ret;
|
||||
@@ -903,24 +903,24 @@ vector<uint16_t> get_card_refs_within_range(
|
||||
vector<uint16_t> ret;
|
||||
if (is_card_within_range(range, loc, short_statuses[0], log)) {
|
||||
if (log) {
|
||||
log->debug("get_card_refs_within_range: sc card @%04hX within range", short_statuses[0].card_ref.load());
|
||||
log->debug_f("get_card_refs_within_range: sc card @{:04X} within range", short_statuses[0].card_ref);
|
||||
}
|
||||
ret.emplace_back(short_statuses[0].card_ref);
|
||||
} else {
|
||||
if (log) {
|
||||
log->debug("get_card_refs_within_range: sc card @%04hX not within range", short_statuses[0].card_ref.load());
|
||||
log->debug_f("get_card_refs_within_range: sc card @{:04X} not within range", short_statuses[0].card_ref);
|
||||
}
|
||||
}
|
||||
for (size_t card_index = 7; card_index < 15; card_index++) {
|
||||
const auto& ss = short_statuses[card_index];
|
||||
if (is_card_within_range(range, loc, ss, log)) {
|
||||
if (log) {
|
||||
log->debug("get_card_refs_within_range: card @%04hX within range", ss.card_ref.load());
|
||||
log->debug_f("get_card_refs_within_range: card @{:04X} within range", ss.card_ref);
|
||||
}
|
||||
ret.emplace_back(ss.card_ref);
|
||||
} else {
|
||||
if (log) {
|
||||
log->debug("get_card_refs_within_range: card @%04hX not within range", ss.card_ref.load());
|
||||
log->debug_f("get_card_refs_within_range: card @{:04X} not within range", ss.card_ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-34
@@ -16,10 +16,10 @@ void compute_effective_range(
|
||||
const Location& loc,
|
||||
shared_ptr<const MapAndRulesState> map_and_rules,
|
||||
phosg::PrefixedLogger* log) {
|
||||
if (log && log->should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log && log->should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string loc_str = loc.str();
|
||||
log->debug("compute_effective_range: card_id=#%04hX, loc=%s", card_id, loc_str.c_str());
|
||||
log->debug("compute_effective_range: map_and_rules->map:");
|
||||
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);
|
||||
}
|
||||
ret.clear(0);
|
||||
@@ -40,14 +40,14 @@ void compute_effective_range(
|
||||
}
|
||||
}
|
||||
if (log) {
|
||||
log->debug("compute_effective_range: range_def: %05" PRIX32 " %05" PRIX32 " %05" PRIX32 " %05" PRIX32 " %05" PRIX32 " %05" PRIX32, range_def[0], range_def[1], range_def[2], range_def[3], range_def[4], range_def[5]);
|
||||
log->debug_f("compute_effective_range: range_def: {:05X} {:05X} {:05X} {:05X} {:05X} {:05X}", range_def[0], range_def[1], range_def[2], range_def[3], range_def[4], range_def[5]);
|
||||
}
|
||||
|
||||
if (range_def[0] == 0x000FFFFF) {
|
||||
// Entire field
|
||||
ret.clear(2);
|
||||
if (log) {
|
||||
log->debug("compute_effective_range: entire field (2)");
|
||||
log->debug_f("compute_effective_range: entire field (2)");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -64,7 +64,7 @@ void compute_effective_range(
|
||||
}
|
||||
if (log) {
|
||||
for (size_t y = 0; y < 9; y++) {
|
||||
log->debug("compute_effective_range: decoded_range: %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX",
|
||||
log->debug_f("compute_effective_range: decoded_range: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}",
|
||||
decoded_range[y * 9 + 0], decoded_range[y * 9 + 1], decoded_range[y * 9 + 2], decoded_range[y * 9 + 3], decoded_range[y * 9 + 4], decoded_range[y * 9 + 5], decoded_range[y * 9 + 6], decoded_range[y * 9 + 7], decoded_range[y * 9 + 8]);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ void compute_effective_range(
|
||||
}
|
||||
ret[y * 9 + x] = decoded_range[up_y * 9 + up_x];
|
||||
if (log) {
|
||||
log->debug("compute_effective_range: x=%hd y=%hd up_x=%hd up_y=%hd v=%hhX", x, y, up_x, up_y, ret[y * 9 + x]);
|
||||
log->debug_f("compute_effective_range: x={} y={} up_x={} up_y={} v={:X}", x, y, up_x, up_y, ret[y * 9 + x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +107,7 @@ void compute_effective_range(
|
||||
|
||||
if (log) {
|
||||
for (size_t y = 0; y < 9; y++) {
|
||||
log->debug("compute_effective_range: ret: %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX %hhX",
|
||||
log->debug_f("compute_effective_range: ret: {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X} {:X}",
|
||||
ret[y * 9 + 0], ret[y * 9 + 1], ret[y * 9 + 2], ret[y * 9 + 3], ret[y * 9 + 4], ret[y * 9 + 5], ret[y * 9 + 6], ret[y * 9 + 7], ret[y * 9 + 8]);
|
||||
}
|
||||
}
|
||||
@@ -941,7 +941,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
AttackMedium attack_medium) const {
|
||||
auto s = this->server();
|
||||
bool is_nte = s->options.is_nte();
|
||||
auto log = s->log_stack(phosg::string_printf("check_usability_or_condition_apply(%02hhX, #%04hX, %02hhX, #%04hX, #%04hX, %02hhX, %s, %s): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", phosg::name_for_enum(attack_medium)));
|
||||
auto log = s->log_stack(std::format("check_usability_or_condition_apply({:02X}, #{:04X}, {:02X}, #{:04X}, #{:04X}, {:02X}, {}, {}): ", client_id1, card_id1, client_id2, card_id2, card_id3, def_effect_index, is_item_usability_check ? "true" : "false", phosg::name_for_enum(attack_medium)));
|
||||
|
||||
if (static_cast<uint8_t>(attack_medium) & 0x80) {
|
||||
attack_medium = AttackMedium::UNKNOWN;
|
||||
@@ -951,11 +951,11 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
auto ce2 = this->definition_for_card_id(card_id2);
|
||||
auto ce3 = this->definition_for_card_id(card_id3);
|
||||
if (!ce1) {
|
||||
log.debug("ce1 missing");
|
||||
log.debug_f("ce1 missing");
|
||||
return false;
|
||||
}
|
||||
if (!is_nte && (ce1->def.type == CardType::ITEM) && this->card_id_is_boss_sc(card_id2)) {
|
||||
log.debug("ce1 is item and card_id2 is boss sc");
|
||||
log.debug_f("ce1 is item and card_id2 is boss sc");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -964,12 +964,12 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
criterion_code = ce1->def.usable_criterion;
|
||||
} else {
|
||||
if (def_effect_index > 2) {
|
||||
log.debug("invalid def_effect_index");
|
||||
log.debug_f("invalid def_effect_index");
|
||||
return false;
|
||||
}
|
||||
criterion_code = ce1->def.effects[def_effect_index].apply_criterion;
|
||||
}
|
||||
log.debug("criterion_code=%s", phosg::name_for_enum(criterion_code));
|
||||
log.debug_f("criterion_code={}", phosg::name_for_enum(criterion_code));
|
||||
|
||||
// For item usability checks, prevent criteria that depend on player
|
||||
// positioning/team setup
|
||||
@@ -980,7 +980,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
(criterion_code == CriterionCode::FC) ||
|
||||
(criterion_code == CriterionCode::NOT_SC) ||
|
||||
(criterion_code == CriterionCode::SC))) {
|
||||
log.debug("criterion is forbidden");
|
||||
log.debug_f("criterion is forbidden");
|
||||
criterion_code = CriterionCode::NONE;
|
||||
}
|
||||
|
||||
@@ -1354,7 +1354,7 @@ bool RulerServer::check_usability_or_condition_apply(
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("default return (false)");
|
||||
log.debug_f("default return (false)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1478,43 +1478,43 @@ bool RulerServer::compute_effective_range_and_target_mode_for_attack(
|
||||
for (z = 0; (z < 8) && (pa.action_card_refs[z] != 0xFFFF); z++) {
|
||||
}
|
||||
if (z >= 8) {
|
||||
log.debug("too many action card refs");
|
||||
log.debug_f("too many action card refs");
|
||||
return false;
|
||||
}
|
||||
log.debug("%zu action card refs", z);
|
||||
log.debug_f("{} action card refs", z);
|
||||
uint16_t card_ref = (z == 0) ? pa.attacker_card_ref : pa.action_card_refs[z - 1];
|
||||
log.debug("base card ref = @%04hX", card_ref);
|
||||
log.debug_f("base card ref = @{:04X}", card_ref);
|
||||
|
||||
uint16_t card_id = this->card_id_for_card_ref(card_ref);
|
||||
if (card_id == 0xFFFF) {
|
||||
log.debug("card ref is broken");
|
||||
log.debug_f("card ref is broken");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ce = this->definition_for_card_id(card_id);
|
||||
uint8_t client_id = client_id_for_card_ref(pa.attacker_card_ref);
|
||||
if ((client_id == 0xFF) || !ce) {
|
||||
log.debug("card ref is broken or definition is missing");
|
||||
log.debug_f("card ref is broken or definition is missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_orig_card_ref) {
|
||||
log.debug("orig_card_ref = @%04hX", card_ref);
|
||||
log.debug_f("orig_card_ref = @{:04X}", card_ref);
|
||||
*out_orig_card_ref = card_ref;
|
||||
}
|
||||
|
||||
auto target_mode = ce->def.target_mode;
|
||||
if (this->card_ref_or_sc_has_fixed_range(pa.attacker_card_ref)) {
|
||||
const char* target_mode_name = name_for_target_mode(target_mode);
|
||||
log.debug("attacker card ref @%04hX has fixed range; target mode is %s (%hhu)",
|
||||
pa.attacker_card_ref.load(), target_mode_name, static_cast<uint8_t>(target_mode));
|
||||
log.debug_f("attacker card ref @{:04X} has fixed range; target mode is {} ({})",
|
||||
pa.attacker_card_ref, target_mode_name, static_cast<uint8_t>(target_mode));
|
||||
card_id = this->card_id_for_card_ref(pa.attacker_card_ref);
|
||||
if (!is_nte) {
|
||||
auto sc_ce = this->definition_for_card_id(card_id);
|
||||
if (sc_ce && (static_cast<uint8_t>(target_mode) < 6)) {
|
||||
target_mode = sc_ce->def.target_mode;
|
||||
const char* target_mode_name = name_for_target_mode(target_mode);
|
||||
log.debug("sc_ce overrides target mode with %s (%hhu)",
|
||||
log.debug_f("sc_ce overrides target mode with {} ({})",
|
||||
target_mode_name, static_cast<uint8_t>(target_mode));
|
||||
}
|
||||
}
|
||||
@@ -1525,10 +1525,10 @@ bool RulerServer::compute_effective_range_and_target_mode_for_attack(
|
||||
auto assist_effect = this->assist_server->get_active_assist_by_index(z);
|
||||
if (assist_effect == AssistEffect::SIMPLE) {
|
||||
card_id = this->card_id_for_card_ref(pa.attacker_card_ref);
|
||||
log.debug("SIMPLE assist overrides card id with #%04hX", card_id);
|
||||
log.debug_f("SIMPLE assist overrides card id with #{:04X}", card_id);
|
||||
} else if (assist_effect == AssistEffect::HEAVY_FOG) {
|
||||
card_id = 0xFFFE;
|
||||
log.debug("HEAVY_FOG assist overrides card id with #%04hX", card_id);
|
||||
log.debug_f("HEAVY_FOG assist overrides card id with #{:04X}", card_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2059,27 +2059,27 @@ shared_ptr<const CardIndex::CardEntry> RulerServer::definition_for_card_id(uint3
|
||||
|
||||
uint32_t RulerServer::get_card_id_with_effective_range(
|
||||
uint16_t card_ref, uint16_t card_id_override, TargetMode* out_target_mode) const {
|
||||
auto log = this->server()->log_stack(phosg::string_printf("get_card_id_with_effective_range(@%04hX, #%04hX): ", card_ref, card_id_override));
|
||||
auto log = this->server()->log_stack(std::format("get_card_id_with_effective_range(@{:04X}, #{:04X}): ", card_ref, card_id_override));
|
||||
|
||||
uint16_t card_id = (card_id_override == 0xFFFF)
|
||||
? this->card_id_for_card_ref(card_ref)
|
||||
: card_id_override;
|
||||
log.debug("card_id=#%04hX", card_id);
|
||||
log.debug_f("card_id=#{:04X}", card_id);
|
||||
|
||||
if (card_id != 0xFFFF) {
|
||||
auto ce = this->definition_for_card_id(card_id);
|
||||
uint8_t client_id = client_id_for_card_ref(card_ref);
|
||||
if ((client_id != 0xFF) && ce) {
|
||||
TargetMode effective_target_mode = ce->def.target_mode;
|
||||
log.debug("ce valid for #%04hX with effective target mode %s", card_id, name_for_target_mode(effective_target_mode));
|
||||
log.debug_f("ce valid for #{:04X} with effective target mode {}", card_id, name_for_target_mode(effective_target_mode));
|
||||
|
||||
if (this->card_ref_or_sc_has_fixed_range(card_ref)) {
|
||||
// Undo the override that may have been passed in
|
||||
log.debug("@%04hX has FIXED_RANGE", card_ref);
|
||||
log.debug_f("@{:04X} has FIXED_RANGE", card_ref);
|
||||
card_id = this->card_id_for_card_ref(card_ref);
|
||||
auto orig_ce = this->definition_for_card_id(card_id);
|
||||
if (orig_ce && (static_cast<uint8_t>(effective_target_mode) < 6)) {
|
||||
log.debug("ce valid for #%04hX with effective target mode %s; overriding to %s", card_id, name_for_target_mode(effective_target_mode), name_for_target_mode(orig_ce->def.target_mode));
|
||||
log.debug_f("ce valid for #{:04X} with effective target mode {}; overriding to {}", card_id, name_for_target_mode(effective_target_mode), name_for_target_mode(orig_ce->def.target_mode));
|
||||
effective_target_mode = orig_ce->def.target_mode;
|
||||
}
|
||||
}
|
||||
@@ -2089,17 +2089,17 @@ uint32_t RulerServer::get_card_id_with_effective_range(
|
||||
auto eff = this->assist_server->get_active_assist_by_index(z);
|
||||
if (eff == AssistEffect::SIMPLE) {
|
||||
card_id = this->card_id_for_card_ref(card_ref);
|
||||
log.debug("SIMPLE assist effect is active; using #%04hX for range", card_id);
|
||||
log.debug_f("SIMPLE assist effect is active; using #{:04X} for range", card_id);
|
||||
} else if (eff == AssistEffect::HEAVY_FOG) {
|
||||
card_id = 0xFFFE;
|
||||
log.debug("HEAVY_FOG assist effect is active; limiting range to one tile in front");
|
||||
log.debug_f("HEAVY_FOG assist effect is active; limiting range to one tile in front");
|
||||
}
|
||||
}
|
||||
|
||||
if (out_target_mode) {
|
||||
*out_target_mode = effective_target_mode;
|
||||
}
|
||||
log.debug("results: card_id=#%04hX, target_mode=%s", card_id, name_for_target_mode(effective_target_mode));
|
||||
log.debug_f("results: card_id=#{:04X}, target_mode={}", card_id, name_for_target_mode(effective_target_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+73
-93
@@ -58,7 +58,7 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
battle_start_usecs(0),
|
||||
should_copy_prev_states_to_current_states(0),
|
||||
card_special(nullptr),
|
||||
clients_done_in_mulligan_phase(false),
|
||||
clients_done_in_redraw_initial_hand_phase(false),
|
||||
num_pending_attacks_with_cards(0),
|
||||
unknown_a14(0),
|
||||
unknown_a15(0),
|
||||
@@ -83,12 +83,14 @@ Server::Server(shared_ptr<Lobby> lobby, Options&& options)
|
||||
|
||||
Server::~Server() noexcept(false) {
|
||||
if (this->logger_stack.size() != 1) {
|
||||
throw logic_error(phosg::string_printf("incorrect logger stack size: expected 1, received %zu", this->logger_stack.size()));
|
||||
throw logic_error(std::format("incorrect logger stack size: expected 1, received {}", this->logger_stack.size()));
|
||||
}
|
||||
delete this->logger_stack.back();
|
||||
}
|
||||
|
||||
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->num_clients_present = 0;
|
||||
this->overlay_state.clear();
|
||||
@@ -173,9 +175,9 @@ 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 phosg::string_printf("@%04hX (#%04" PRIX32 " %s)", card_ref, ce->def.card_id.load(), name.c_str());
|
||||
return std::format("@{:04X} (#{:04X} {})", card_ref, ce->def.card_id, name);
|
||||
} else {
|
||||
return phosg::string_printf("@%04hX (missing)", card_ref);
|
||||
return std::format("@{:04X} (missing)", card_ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,9 +188,9 @@ 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 phosg::string_printf("#%04hX (%s)", card_id, name.c_str());
|
||||
return std::format("#{:04X} ({})", card_id, name);
|
||||
} else {
|
||||
return phosg::string_printf("#%04hX (missing)", card_id);
|
||||
return std::format("#{:04X} (missing)", card_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +262,7 @@ void Server::send(const void* data, size_t size, uint8_t command, bool enable_ma
|
||||
}
|
||||
|
||||
} else if ((this->options.behavior_flags & BehaviorFlag::LOG_COMMANDS_IF_LOBBY_MISSING) &&
|
||||
this->log().info("Generated command")) {
|
||||
this->log().info_f("Generated command")) {
|
||||
phosg::print_data(stderr, data, size, 0, nullptr, phosg::PrintDataFlags::PRINT_ASCII | phosg::PrintDataFlags::DISABLE_COLOR | phosg::PrintDataFlags::OFFSET_16_BITS);
|
||||
}
|
||||
}
|
||||
@@ -273,9 +275,9 @@ void Server::send_6xB4x46() const {
|
||||
// debugging easier.
|
||||
G_ServerVersionStrings_Ep3_6xB4x46 cmd;
|
||||
cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, 1);
|
||||
cmd.date_str1.encode(phosg::format_time(this->options.card_index->definitions_mtime() * 1000000), 1);
|
||||
cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), 1);
|
||||
string build_date = phosg::format_time(BUILD_TIMESTAMP);
|
||||
cmd.date_str2.encode(phosg::string_printf("newserv %s compiled at %s", GIT_REVISION_HASH, build_date.c_str()), 1);
|
||||
cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), 1);
|
||||
this->send(cmd);
|
||||
}
|
||||
|
||||
@@ -291,7 +293,7 @@ string Server::prepare_6xB6x41_map_definition(shared_ptr<const MapIndex::Map> ma
|
||||
return std::move(w.str());
|
||||
}
|
||||
|
||||
void Server::send_commands_for_joining_spectator(Channel& ch) const {
|
||||
void Server::send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) const {
|
||||
bool should_send_state = true;
|
||||
if (this->setup_phase == SetupPhase::REGISTRATION) {
|
||||
// If registration is still in progress, we only need to send the map data
|
||||
@@ -303,84 +305,62 @@ void Server::send_commands_for_joining_spectator(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());
|
||||
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(ch.language), this->last_chosen_map->map_number);
|
||||
ch.send(0x6C, 0x00, data);
|
||||
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}", char_for_language_code(ch->language), this->last_chosen_map->map_number);
|
||||
ch->send(0x6C, 0x00, data);
|
||||
}
|
||||
|
||||
if (should_send_state) {
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x03());
|
||||
ch->send(0xC9, 0x00, this->prepare_6xB4x03());
|
||||
for (uint8_t client_id = 0; client_id < 4; client_id++) {
|
||||
auto ps = this->player_states[client_id];
|
||||
if (ps) {
|
||||
ch.send(0xC9, 0x00, ps->prepare_6xB4x02());
|
||||
ch.send(0xC9, 0x00, ps->prepare_6xB4x04());
|
||||
ch->send(0xC9, 0x00, ps->prepare_6xB4x02());
|
||||
ch->send(0xC9, 0x00, ps->prepare_6xB4x04());
|
||||
}
|
||||
}
|
||||
if (ch.version == Version::GC_EP3_NTE) {
|
||||
if (ch->version == Version::GC_EP3_NTE) {
|
||||
G_UpdateMap_Ep3NTE_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
ch.send(0xC9, 0x00, cmd);
|
||||
ch->send(0xC9, 0x00, cmd);
|
||||
} else {
|
||||
G_UpdateMap_Ep3_6xB4x05 cmd;
|
||||
cmd.state = *this->map_and_rules;
|
||||
ch.send(0xC9, 0x00, cmd);
|
||||
ch->send(0xC9, 0x00, cmd);
|
||||
}
|
||||
// TODO: Sega does something like this; do we have to do this too?
|
||||
// for (uint8_t client_id = 0; client_id < 4; client_id++) {
|
||||
// (send 6xB4x4E, 6xB4x4C, 6xB4x4D for each set card)
|
||||
// (send 6xB4x4F for client_id)
|
||||
// }
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
|
||||
ch->send(0xC9, 0x00, this->prepare_6xB4x07_decks_update());
|
||||
// TODO: Sega sends 6xB4x05 here again; why? Is that necessary? They also
|
||||
// send 6xB4x02 again for each player after that (but not 6xB4x04)
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
|
||||
ch.send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
|
||||
ch->send(0xC9, 0x00, this->prepare_6xB4x1C_names_update());
|
||||
ch->send(0xC9, 0x00, this->prepare_6xB4x50_trap_tile_locations());
|
||||
{
|
||||
G_LoadCurrentEnvironment_Ep3_6xB4x3B cmd_3B;
|
||||
ch.send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B));
|
||||
ch->send(0xC9, 0x00, &cmd_3B, sizeof(cmd_3B));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void Server::send_debug_message_printf(const char* fmt, ...) const {
|
||||
auto l = this->lobby.lock();
|
||||
if (l && (this->options.behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
std::string buf = phosg::string_vprintf(fmt, va);
|
||||
va_end(va);
|
||||
send_text_message(l, buf);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void Server::send_info_message_printf(const char* fmt, ...) const {
|
||||
auto l = this->lobby.lock();
|
||||
if (l) {
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
std::string buf = phosg::string_vprintf(fmt, va);
|
||||
va_end(va);
|
||||
send_text_message(l, buf);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::send_debug_command_received_message(
|
||||
uint8_t client_id, uint8_t subsubcommand, const char* description) const {
|
||||
this->log().debug("%hhu/CAx%02hhX %s", client_id, subsubcommand, description);
|
||||
this->send_debug_message_printf("$C5%hhu/CAx%02hhX %s", client_id, subsubcommand, description);
|
||||
this->log().debug_f("{}/CAx{:02X} {}", client_id, subsubcommand, description);
|
||||
this->send_debug_message("$C5{}/CAx{:02X} {}", client_id, subsubcommand, description);
|
||||
}
|
||||
|
||||
void Server::send_debug_command_received_message(uint8_t subsubcommand, const char* description) const {
|
||||
this->log().debug("*/CAx%02hhX %s", subsubcommand, description);
|
||||
this->send_debug_message_printf("$C5*/CAx%02hhX %s", subsubcommand, description);
|
||||
this->log().debug_f("*/CAx{:02X} {}", subsubcommand, description);
|
||||
this->send_debug_message("$C5*/CAx{:02X} {}", subsubcommand, description);
|
||||
}
|
||||
|
||||
void Server::send_debug_message_if_error_code_nonzero(uint8_t client_id, int32_t error_code) const {
|
||||
if (error_code < 0) {
|
||||
this->send_debug_message_printf("$C4%hhu/ERROR -0x%zX", client_id, static_cast<ssize_t>(-error_code));
|
||||
this->send_debug_message("$C4{}/ERROR -0x{:X}", client_id, static_cast<ssize_t>(-error_code));
|
||||
} else if (error_code > 0) {
|
||||
this->send_debug_message_printf("$C4%hhu/ERROR 0x%zX", client_id, static_cast<ssize_t>(error_code));
|
||||
this->send_debug_message("$C4{}/ERROR 0x{:X}", client_id, static_cast<ssize_t>(error_code));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,62 +929,62 @@ 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::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string s = pa->str(this->shared_from_this());
|
||||
log.debug("input: %s", s.c_str());
|
||||
log.debug_f("input: {}", s);
|
||||
}
|
||||
|
||||
if (client_id >= 4) {
|
||||
this->ruler_server->error_code3 = -0x78;
|
||||
log.debug("failed: invalid client ID");
|
||||
log.debug_f("failed: invalid client ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ps = this->player_states[client_id];
|
||||
if (!ps) {
|
||||
this->ruler_server->error_code3 = -0x72;
|
||||
log.debug("failed: player not present");
|
||||
log.debug_f("failed: player not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pa->action_card_refs[0] == 0xFFFF) {
|
||||
if (pa->defense_card_ref != 0xFFFF) {
|
||||
pa->action_card_refs[0] = pa->defense_card_ref;
|
||||
log.debug("moved defense card ref to action card ref 0");
|
||||
log.debug_f("moved defense card ref to action card ref 0");
|
||||
}
|
||||
} else {
|
||||
pa->defense_card_ref = pa->action_card_refs[0];
|
||||
log.debug("moved action card ref 0 to defense card ref");
|
||||
log.debug_f("moved action card ref 0 to defense card ref");
|
||||
}
|
||||
|
||||
if (!this->ruler_server->is_attack_or_defense_valid(*pa)) {
|
||||
log.debug("failed: attack or defense not valid");
|
||||
log.debug_f("failed: attack or defense not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
int16_t ally_atk_result = this->send_6xB4x33_remove_ally_atk_if_needed(*pa);
|
||||
if (ally_atk_result == 1) {
|
||||
log.debug("pending: need ally approval");
|
||||
log.debug_f("pending: need ally approval");
|
||||
return true;
|
||||
} else if (ally_atk_result == -1) {
|
||||
log.debug("failed: ally declined");
|
||||
log.debug_f("failed: ally declined");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->num_pending_attacks >= 0x20) {
|
||||
this->ruler_server->error_code3 = -0x71;
|
||||
log.debug("failed: too many pending attacks");
|
||||
log.debug_f("failed: too many pending attacks");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t attack_index = this->num_pending_attacks++;
|
||||
this->pending_attacks[attack_index] = *pa;
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string pa_str = this->pending_attacks[attack_index].str(this->shared_from_this());
|
||||
log.debug("set pending attack %zu: %s", attack_index, pa_str.c_str());
|
||||
log.debug_f("set pending attack {}: {}", attack_index, pa_str);
|
||||
}
|
||||
ps->set_action_cards_for_action_state(*pa);
|
||||
log.debug("set action cards");
|
||||
log.debug_f("set action cards");
|
||||
auto card = this->card_for_set_card_ref(this->send_6xB4x06_if_card_ref_invalid(pa->attacker_card_ref, 1));
|
||||
if (card) {
|
||||
card->card_flags |= 0x400;
|
||||
@@ -1056,7 +1036,7 @@ uint32_t Server::get_random_raw() {
|
||||
if (this->options.opt_rand_stream) {
|
||||
this->options.opt_rand_stream->readx(&ret, sizeof(ret));
|
||||
} else {
|
||||
ret = random_from_optional_crypt(this->options.opt_rand_crypt);
|
||||
ret = this->options.rand_crypt->next();
|
||||
}
|
||||
|
||||
if (this->battle_record && this->battle_record->writable()) {
|
||||
@@ -1740,20 +1720,20 @@ bool Server::update_registration_phase() {
|
||||
auto log = this->log_stack("update_registration_phase: ");
|
||||
|
||||
if (this->setup_phase != SetupPhase::REGISTRATION) {
|
||||
log.debug("setup_phase is not REGISTRATION");
|
||||
log.debug_f("setup_phase is not REGISTRATION");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->map_and_rules->num_players == 0) {
|
||||
this->registration_phase = RegistrationPhase::AWAITING_NUM_PLAYERS;
|
||||
log.debug("registration_phase set to AWAITING_NUM_PLAYERS");
|
||||
log.debug_f("registration_phase set to AWAITING_NUM_PLAYERS");
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->map_and_rules->num_players != this->num_clients_present) {
|
||||
this->registration_phase = RegistrationPhase::AWAITING_PLAYERS;
|
||||
log.debug("registration_phase set to AWAITING_PLAYERS");
|
||||
log.debug_f("registration_phase set to AWAITING_PLAYERS");
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||
return false;
|
||||
}
|
||||
@@ -1767,20 +1747,20 @@ bool Server::update_registration_phase() {
|
||||
|
||||
if (num_team0_registered_players != this->map_and_rules->num_team0_players) {
|
||||
this->registration_phase = RegistrationPhase::AWAITING_DECKS;
|
||||
log.debug("registration_phase set to AWAITING_DECKS");
|
||||
log.debug_f("registration_phase set to AWAITING_DECKS");
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||
return false;
|
||||
}
|
||||
|
||||
this->registration_phase = RegistrationPhase::REGISTERED;
|
||||
this->update_battle_state_flags_and_send_6xB4x03_if_needed();
|
||||
log.debug("battle can begin");
|
||||
log.debug_f("battle can begin");
|
||||
return true;
|
||||
}
|
||||
|
||||
const unordered_map<uint8_t, Server::handler_t> Server::subcommand_handlers({
|
||||
{0x0B, &Server::handle_CAx0B_mulligan_hand},
|
||||
{0x0C, &Server::handle_CAx0C_end_mulligan_phase},
|
||||
{0x0B, &Server::handle_CAx0B_redraw_initial_hand},
|
||||
{0x0C, &Server::handle_CAx0C_end_redraw_initial_hand_phase},
|
||||
{0x0D, &Server::handle_CAx0D_end_non_action_phase},
|
||||
{0x0E, &Server::handle_CAx0E_discard_card_from_hand},
|
||||
{0x0F, &Server::handle_CAx0F_set_card_from_hand},
|
||||
@@ -1809,7 +1789,7 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
size_t expected_size = header.size * 4;
|
||||
if (expected_size < data.size()) {
|
||||
phosg::print_data(stderr, data);
|
||||
throw runtime_error(phosg::string_printf("command is incomplete: expected %zX bytes, received %zX bytes", expected_size, data.size()));
|
||||
throw 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");
|
||||
@@ -1835,7 +1815,7 @@ void Server::on_server_data_input(shared_ptr<Client> sender_c, const string& dat
|
||||
}
|
||||
}
|
||||
|
||||
void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0B_redraw_initial_hand(shared_ptr<Client>, const 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) {
|
||||
@@ -1854,20 +1834,20 @@ void Server::handle_CAx0B_mulligan_hand(shared_ptr<Client>, const string& data)
|
||||
if (!ps) {
|
||||
error_code = -0x72;
|
||||
} else {
|
||||
ps->do_mulligan();
|
||||
ps->redraw_initial_hand();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->options.is_nte() || (error_code == 0)) {
|
||||
G_ActionResult_Ep3_6xB4x1E out_cmd;
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num.load();
|
||||
out_cmd.sequence_num = in_cmd.header.sequence_num;
|
||||
out_cmd.error_code = error_code;
|
||||
this->send(out_cmd);
|
||||
}
|
||||
this->send_debug_message_if_error_code_nonzero(in_cmd.client_id, error_code);
|
||||
}
|
||||
|
||||
void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& data) {
|
||||
void Server::handle_CAx0C_end_redraw_initial_hand_phase(shared_ptr<Client>, const 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) {
|
||||
@@ -1898,13 +1878,13 @@ void Server::handle_CAx0C_end_mulligan_phase(shared_ptr<Client>, const string& d
|
||||
if (!ps) {
|
||||
error_code = -0x72;
|
||||
} else {
|
||||
this->clients_done_in_mulligan_phase[in_cmd.client_id] = true;
|
||||
this->clients_done_in_redraw_initial_hand_phase[in_cmd.client_id] = true;
|
||||
ps->assist_flags |= AssistFlag::READY_TO_END_PHASE;
|
||||
ps->update_hand_and_equip_state_and_send_6xB4x02_if_needed();
|
||||
|
||||
bool all_clients_ready = true;
|
||||
for (size_t z = 0; z < 4; z++) {
|
||||
if (this->player_states[z] && !this->clients_done_in_mulligan_phase[z]) {
|
||||
if (this->player_states[z] && !this->clients_done_in_redraw_initial_hand_phase[z]) {
|
||||
all_clients_ready = false;
|
||||
break;
|
||||
}
|
||||
@@ -2229,7 +2209,7 @@ void Server::handle_CAx14_update_deck_during_setup(shared_ptr<Client>, const str
|
||||
}
|
||||
}
|
||||
if (verify_error) {
|
||||
throw runtime_error(phosg::string_printf("invalid deck: -0x%" PRIX32, verify_error));
|
||||
throw 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);
|
||||
@@ -2573,7 +2553,7 @@ void Server::send_6xB6x41_to_all_clients() const {
|
||||
map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition(
|
||||
this->last_chosen_map, c->language(), this->options.is_nte());
|
||||
}
|
||||
this->log().info("Sending %c version of map %08" PRIX32, char_for_language_code(c->language()), this->last_chosen_map->map_number);
|
||||
this->log().info_f("Sending {} version of map {:08X}", char_for_language_code(c->language()), this->last_chosen_map->map_number);
|
||||
send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]);
|
||||
};
|
||||
for (const auto& c : l->clients) {
|
||||
@@ -2782,7 +2762,7 @@ void Server::unknown_8023EEF4() {
|
||||
auto log = this->log_stack("unknown_8023EEF4: ");
|
||||
|
||||
if (this->unknown_a14 >= 0x20) {
|
||||
log.debug("unknown_a14 too large (0x%" PRIX32 ")", this->unknown_a14);
|
||||
log.debug_f("unknown_a14 too large (0x{:X})", this->unknown_a14);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2791,34 +2771,34 @@ void Server::unknown_8023EEF4() {
|
||||
auto card = this->attack_cards[this->unknown_a14];
|
||||
if (this->get_current_team_turn() == card->get_team_id()) {
|
||||
ActionState as = this->pending_attacks_with_cards[this->unknown_a14];
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
log.debug("card @%04hX #%04hX can attack", card->get_card_ref(), card->get_card_id());
|
||||
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("as: %s", as_str.c_str());
|
||||
log.debug_f("as: {}", as_str);
|
||||
}
|
||||
if (is_nte) {
|
||||
this->replace_targets_due_to_destruction_nte(&as);
|
||||
} else {
|
||||
this->replace_targets_due_to_destruction_or_conditions(&as);
|
||||
}
|
||||
if (log.should_log(phosg::LogLevel::DEBUG)) {
|
||||
if (log.should_log(phosg::LogLevel::L_DEBUG)) {
|
||||
string as_str = as.str(this->shared_from_this());
|
||||
log.debug("as after target replacement: %s", as_str.c_str());
|
||||
log.debug_f("as after target replacement: {}", as_str);
|
||||
}
|
||||
if (this->any_target_exists_for_attack(as)) {
|
||||
log.debug("as is valid");
|
||||
log.debug_f("as is valid");
|
||||
break;
|
||||
} else {
|
||||
log.debug("as is not valid");
|
||||
log.debug_f("as is not valid");
|
||||
}
|
||||
} else {
|
||||
log.debug("card @%04hX #%04hX cannot attack (wrong turn)", card->get_card_ref(), card->get_card_id());
|
||||
log.debug_f("card @{:04X} #{:04X} cannot attack (wrong turn)", card->get_card_ref(), card->get_card_id());
|
||||
}
|
||||
this->unknown_a14++;
|
||||
}
|
||||
|
||||
if (this->unknown_a14 < this->num_pending_attacks_with_cards) {
|
||||
log.debug("a14 (%" PRIu32 ") < num_pending_attacks_with_cards (%" PRIu32 ")", this->unknown_a14, this->num_pending_attacks_with_cards);
|
||||
log.debug_f("a14 ({}) < num_pending_attacks_with_cards ({})", this->unknown_a14, this->num_pending_attacks_with_cards);
|
||||
this->defense_list_ended_for_client.clear(false);
|
||||
|
||||
G_UpdateAttackTargets_Ep3_6xB4x29 cmd;
|
||||
@@ -3142,13 +3122,13 @@ void Server::unknown_802402F4() {
|
||||
if (ps && (this->current_team_turn2 == ps->get_team_id())) {
|
||||
auto card = ps->get_sc_card();
|
||||
if (card) {
|
||||
log.debug("SC card has action chain");
|
||||
log.debug_f("SC card has action chain");
|
||||
card->compute_action_chain_results(true, false);
|
||||
}
|
||||
for (size_t set_index = 0; set_index < 8; set_index++) {
|
||||
card = ps->get_set_card(set_index);
|
||||
if (card) {
|
||||
log.debug("set card %zu has action chain", set_index);
|
||||
log.debug_f("set card {} has action chain", set_index);
|
||||
card->compute_action_chain_results(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-14
@@ -73,7 +73,7 @@ public:
|
||||
std::shared_ptr<const MapIndex> map_index;
|
||||
uint32_t behavior_flags;
|
||||
std::shared_ptr<phosg::StringReader> opt_rand_stream;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||
std::shared_ptr<const Tournament> tournament;
|
||||
std::array<std::vector<uint16_t>, 5> trap_card_ids;
|
||||
|
||||
@@ -109,7 +109,7 @@ public:
|
||||
for (size_t z = 0; z < count; z++) {
|
||||
if (refs[z] != 0xFFFF) {
|
||||
std::string ref_str = this->debug_str_for_card_ref(refs[z]);
|
||||
ret += phosg::string_printf("%zu:%s ", z, ref_str.c_str());
|
||||
ret += std::format("{}:{} ", z, ref_str);
|
||||
}
|
||||
}
|
||||
if (ret.size() > 1) {
|
||||
@@ -145,20 +145,23 @@ public:
|
||||
this->send(&cmd, cmd.header.size * 4, command, enable_masking);
|
||||
}
|
||||
void send(const void* data, size_t size, uint8_t command = 0xC9, bool enable_masking = true) const;
|
||||
void send_commands_for_joining_spectator(Channel& ch) const;
|
||||
void send_commands_for_joining_spectator(std::shared_ptr<Channel> ch) const;
|
||||
|
||||
void force_battle_result(uint8_t surrendered_client_id, bool set_winner);
|
||||
void force_replace_assist_card(uint8_t client_id, uint16_t card_id);
|
||||
void force_destroy_field_character(uint8_t client_id, size_t set_index);
|
||||
|
||||
__attribute__((format(printf, 2, 3))) void send_debug_message_printf(const char* fmt, ...) const;
|
||||
__attribute__((format(printf, 2, 3))) void send_info_message_printf(const char* fmt, ...) const;
|
||||
void send_debug_command_received_message(
|
||||
uint8_t client_id, uint8_t subsubcommand, const char* description) const;
|
||||
void send_debug_command_received_message(
|
||||
uint8_t subsubcommand, const char* description) const;
|
||||
void send_debug_message_if_error_code_nonzero(
|
||||
uint8_t client_id, int32_t error_code) const;
|
||||
template <typename... ArgTs>
|
||||
void send_debug_message(std::format_string<ArgTs...> fmt, ArgTs&&... args) const {
|
||||
auto l = this->lobby.lock();
|
||||
if (l && (this->options.behavior_flags & Episode3::BehaviorFlag::ENABLE_STATUS_MESSAGES)) {
|
||||
send_text_message(l, std::format(std::forward<std::format_string<ArgTs...>>(fmt), std::forward<ArgTs>(args)...));
|
||||
}
|
||||
}
|
||||
|
||||
void send_debug_command_received_message(uint8_t client_id, uint8_t subsubcommand, const char* description) const;
|
||||
void send_debug_command_received_message(uint8_t subsubcommand, const char* description) const;
|
||||
void send_debug_message_if_error_code_nonzero(uint8_t client_id, int32_t error_code) const;
|
||||
|
||||
void send_6xB4x46() const;
|
||||
|
||||
@@ -218,8 +221,8 @@ public:
|
||||
void update_battle_state_flags_and_send_6xB4x03_if_needed(bool always_send = false);
|
||||
bool update_registration_phase();
|
||||
void on_server_data_input(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0B_mulligan_hand(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0C_end_mulligan_phase(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0B_redraw_initial_hand(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0C_end_redraw_initial_hand_phase(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0D_end_non_action_phase(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0E_discard_card_from_hand(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
void handle_CAx0F_set_card_from_hand(std::shared_ptr<Client> sender_c, const std::string& data);
|
||||
@@ -330,7 +333,7 @@ public:
|
||||
std::shared_ptr<CardSpecial> card_special;
|
||||
std::shared_ptr<StateFlags> state_flags;
|
||||
std::array<std::shared_ptr<PlayerState>, 4> player_states;
|
||||
parray<uint32_t, 4> clients_done_in_mulligan_phase;
|
||||
parray<uint32_t, 4> clients_done_in_redraw_initial_hand_phase;
|
||||
uint32_t num_pending_attacks_with_cards;
|
||||
bcarray<std::shared_ptr<Card>, 0x20> attack_cards;
|
||||
bcarray<ActionState, 0x20> pending_attacks_with_cards;
|
||||
|
||||
+27
-21
@@ -3,7 +3,9 @@
|
||||
#include <phosg/Random.hh>
|
||||
|
||||
#include "../CommandFormats.hh"
|
||||
#include "../GameServer.hh"
|
||||
#include "../SendCommands.hh"
|
||||
#include "../ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -49,16 +51,16 @@ string Tournament::Team::str() const {
|
||||
num_com_players += player.is_com();
|
||||
}
|
||||
|
||||
string ret = phosg::string_printf("[Team/%zu %s %zuH/%zuC/%zuP name=%s pass=%s rounds=%zu",
|
||||
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.c_str(),
|
||||
this->password.c_str(), this->num_rounds_cleared);
|
||||
num_human_players, num_com_players, this->max_players, this->name,
|
||||
this->password, this->num_rounds_cleared);
|
||||
for (const auto& player : this->players) {
|
||||
if (player.is_human()) {
|
||||
if (player.player_name.empty()) {
|
||||
ret += phosg::string_printf(" %08" PRIX32, player.account_id);
|
||||
ret += std::format(" {:08X}", player.account_id);
|
||||
} else {
|
||||
ret += phosg::string_printf(" %08" PRIX32 " (%s)", player.account_id, player.player_name.c_str());
|
||||
ret += std::format(" {:08X} ({})", player.account_id, player.player_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +208,7 @@ Tournament::Match::Match(
|
||||
|
||||
string Tournament::Match::str() const {
|
||||
string winner_str = this->winner_team ? this->winner_team->str() : "(none)";
|
||||
return phosg::string_printf("[Match round=%zu winner=%s]", this->round_num, winner_str.c_str());
|
||||
return std::format("[Match round={} winner={}]", this->round_num, winner_str);
|
||||
}
|
||||
|
||||
bool Tournament::Match::resolve_if_skippable() {
|
||||
@@ -318,7 +320,7 @@ Tournament::Tournament(
|
||||
const Rules& rules,
|
||||
size_t num_teams,
|
||||
uint8_t flags)
|
||||
: log(phosg::string_printf("[Tournament:%s] ", name.c_str())),
|
||||
: log(std::format("[Tournament:{}] ", name)),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
name(name),
|
||||
@@ -343,7 +345,7 @@ Tournament::Tournament(
|
||||
shared_ptr<const MapIndex> map_index,
|
||||
shared_ptr<const COMDeckIndex> com_deck_index,
|
||||
const phosg::JSON& json)
|
||||
: log(phosg::string_printf("[Tournament:%s] ", json.get_string("name").c_str())),
|
||||
: log(std::format("[Tournament:{}] ", json.get_string("name"))),
|
||||
map_index(map_index),
|
||||
com_deck_index(com_deck_index),
|
||||
source_json(json),
|
||||
@@ -665,7 +667,7 @@ void Tournament::start() {
|
||||
auto m = this->zero_round_matches[z];
|
||||
auto t = m->winner_team;
|
||||
if (t->name.empty()) {
|
||||
t->name = has_com_teams ? phosg::string_printf("COM:%zu", z) : "(no entrant)";
|
||||
t->name = has_com_teams ? std::format("COM:{}", z) : "(no entrant)";
|
||||
}
|
||||
for (const auto& player : t->players) {
|
||||
if (player.is_com()) {
|
||||
@@ -718,7 +720,7 @@ void Tournament::send_all_state_updates_on_deletion() const {
|
||||
}
|
||||
|
||||
string Tournament::bracket_str() const {
|
||||
string ret = phosg::string_printf("Tournament \"%s\"\n", this->name.c_str());
|
||||
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 {
|
||||
ret.append(2 * indent_level, ' ');
|
||||
@@ -738,16 +740,16 @@ string Tournament::bracket_str() const {
|
||||
auto en_vm = this->map->version(1);
|
||||
if (en_vm) {
|
||||
string map_name = en_vm->map->name.decode(en_vm->language);
|
||||
ret += phosg::string_printf(" Map: %08" PRIX32 " (%s)\n", this->map->map_number, map_name.c_str());
|
||||
ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name);
|
||||
} else {
|
||||
ret += phosg::string_printf(" Map: %08" PRIX32 "\n", this->map->map_number);
|
||||
ret += std::format(" Map: {:08X}\n", this->map->map_number);
|
||||
}
|
||||
string rules_str = this->rules.str();
|
||||
ret += phosg::string_printf(" Rules: %s\n", rules_str.c_str());
|
||||
ret += phosg::string_printf(" Structure: %s, %zu entries\n", (this->flags & Flag::IS_2V2) ? "2v2" : "1v1", this->num_teams);
|
||||
ret += phosg::string_printf(" COM teams: %s\n", (this->flags & Flag::HAS_COM_TEAMS) ? "allowed" : "forbidden");
|
||||
ret += phosg::string_printf(" Shuffle entries: %s\n", (this->flags & Flag::SHUFFLE_ENTRIES) ? "yes" : "no");
|
||||
ret += phosg::string_printf(" Resize on start: %s\n", (this->flags & Flag::RESIZE_ON_START) ? "yes" : "no");
|
||||
ret += std::format(" Rules: {}\n", 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");
|
||||
ret += std::format(" Resize on start: {}\n", (this->flags & Flag::RESIZE_ON_START) ? "yes" : "no");
|
||||
switch (this->current_state) {
|
||||
case State::REGISTRATION:
|
||||
ret += " State: REGISTRATION\n";
|
||||
@@ -770,13 +772,13 @@ string Tournament::bracket_str() const {
|
||||
ret += " Teams:\n";
|
||||
for (const auto& team : this->teams) {
|
||||
string team_str = team->str();
|
||||
ret += phosg::string_printf(" %s\n", team_str.c_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 += phosg::string_printf(" %s\n", match_str.c_str());
|
||||
ret += std::format(" {}\n", match_str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,8 +942,12 @@ void TournamentIndex::link_client(shared_ptr<Client> c) {
|
||||
}
|
||||
|
||||
void TournamentIndex::link_all_clients(std::shared_ptr<ServerState> s) {
|
||||
for (const auto& c_it : s->channel_to_client) {
|
||||
this->link_client(c_it.second);
|
||||
// This can be called before the game server exists, so do nothing in that
|
||||
// case
|
||||
if (s->game_server) {
|
||||
for (const auto& c : s->game_server->all_clients()) {
|
||||
this->link_client(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
#include "EventUtils.hh"
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void dispatch_forward_to_event_thread(evutil_socket_t, short, void* ctx) {
|
||||
auto* fn = reinterpret_cast<function<void()>*>(ctx);
|
||||
(*fn)();
|
||||
delete fn;
|
||||
}
|
||||
|
||||
void forward_to_event_thread(shared_ptr<struct event_base> base, function<void()>&& fn) {
|
||||
struct timeval tv = {0, 0};
|
||||
function<void()>* new_fn = new function<void()>(std::move(fn));
|
||||
event_base_once(base.get(), -1, EV_TIMEOUT, dispatch_forward_to_event_thread, new_fn, &tv);
|
||||
}
|
||||
|
||||
template <>
|
||||
void call_on_event_thread<void>(shared_ptr<struct event_base> base, function<void()>&& compute) {
|
||||
bool succeeded = false;
|
||||
string exc_what;
|
||||
mutex ret_lock;
|
||||
condition_variable ret_cv;
|
||||
unique_lock<mutex> g(ret_lock);
|
||||
forward_to_event_thread(base, [&]() -> void {
|
||||
lock_guard<mutex> g(ret_lock);
|
||||
try {
|
||||
compute();
|
||||
succeeded = true;
|
||||
} catch (const exception& e) {
|
||||
exc_what = e.what();
|
||||
}
|
||||
ret_cv.notify_one();
|
||||
});
|
||||
ret_cv.wait(g);
|
||||
if (!succeeded) {
|
||||
throw runtime_error(exc_what);
|
||||
}
|
||||
}
|
||||
|
||||
string evbuffer_remove_str(struct evbuffer* buf, ssize_t size) {
|
||||
if (!buf) {
|
||||
return "";
|
||||
}
|
||||
if (size < 0) {
|
||||
size = static_cast<size_t>(evbuffer_get_length(buf));
|
||||
}
|
||||
string ret(size, '\0');
|
||||
ssize_t bytes_removed = evbuffer_remove(buf, ret.data(), ret.size());
|
||||
if (bytes_removed < 0) {
|
||||
throw std::runtime_error("can\'t remove data from buffer");
|
||||
}
|
||||
ret.resize(bytes_removed);
|
||||
return ret;
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
// Calls a function on the given base's event thread. This function returns
|
||||
// when the call has been enqueued, not necessarily after it returns.
|
||||
void forward_to_event_thread(std::shared_ptr<struct event_base> base, std::function<void()>&& fn);
|
||||
|
||||
// Calls a function on the given base's event thread and waits for it to
|
||||
// return. Returns the value returned on that thread.
|
||||
template <typename T>
|
||||
T call_on_event_thread(std::shared_ptr<struct event_base> base, std::function<T()>&& compute) {
|
||||
std::optional<T> ret;
|
||||
std::string exc_what;
|
||||
std::mutex ret_lock;
|
||||
std::condition_variable ret_cv;
|
||||
std::unique_lock<std::mutex> g(ret_lock);
|
||||
forward_to_event_thread(base, [&]() -> void {
|
||||
std::lock_guard<std::mutex> g(ret_lock);
|
||||
try {
|
||||
ret = compute();
|
||||
} catch (const std::exception& e) {
|
||||
exc_what = e.what();
|
||||
}
|
||||
ret_cv.notify_one();
|
||||
});
|
||||
ret_cv.wait(g);
|
||||
if (!ret.has_value()) {
|
||||
throw std::runtime_error(exc_what);
|
||||
}
|
||||
return ret.value();
|
||||
}
|
||||
|
||||
template <>
|
||||
void call_on_event_thread<void>(std::shared_ptr<struct event_base> base, std::function<void()>&& compute);
|
||||
|
||||
std::string evbuffer_remove_str(struct evbuffer* buf, ssize_t size = -1);
|
||||
+46
-61
@@ -3,6 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
@@ -19,16 +20,6 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
static bool is_function_compiler_available = true;
|
||||
|
||||
bool function_compiler_available() {
|
||||
return is_function_compiler_available;
|
||||
}
|
||||
|
||||
void set_function_compiler_available(bool is_available) {
|
||||
is_function_compiler_available = is_available;
|
||||
}
|
||||
|
||||
const char* name_for_architecture(CompiledFunctionCode::Architecture arch) {
|
||||
switch (arch) {
|
||||
case CompiledFunctionCode::Architecture::POWERPC:
|
||||
@@ -149,11 +140,11 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
}
|
||||
|
||||
// Look in the function directory first, then the system directory
|
||||
string asm_filename = phosg::string_printf("%s/%s.%s.inc.s", function_directory.c_str(), name.c_str(), arch_name_token);
|
||||
if (!phosg::isfile(asm_filename)) {
|
||||
asm_filename = phosg::string_printf("%s/%s.%s.inc.s", system_directory.c_str(), name.c_str(), arch_name_token);
|
||||
string asm_filename = std::format("{}/{}.{}.inc.s", function_directory, name, arch_name_token);
|
||||
if (!std::filesystem::is_regular_file(asm_filename)) {
|
||||
asm_filename = std::format("{}/{}.{}.inc.s", system_directory, name, arch_name_token);
|
||||
}
|
||||
if (phosg::isfile(asm_filename)) {
|
||||
if (std::filesystem::is_regular_file(asm_filename)) {
|
||||
if (!get_include_stack.emplace(name).second) {
|
||||
throw runtime_error("mutual recursion between includes: " + name);
|
||||
}
|
||||
@@ -176,11 +167,11 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
}
|
||||
|
||||
string bin_filename = function_directory + "/" + name + ".inc.bin";
|
||||
if (phosg::isfile(bin_filename)) {
|
||||
if (std::filesystem::is_regular_file(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
bin_filename = system_directory + "/" + name + ".inc.bin";
|
||||
if (phosg::isfile(bin_filename)) {
|
||||
if (std::filesystem::is_regular_file(bin_filename)) {
|
||||
return phosg::load_file(bin_filename);
|
||||
}
|
||||
throw runtime_error("data not found for include: " + name + " (from " + asm_filename + " or " + bin_filename + ")");
|
||||
@@ -217,7 +208,7 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
|
||||
set<uint32_t> reloc_indexes;
|
||||
for (const auto& it : ret->label_offsets) {
|
||||
if (phosg::starts_with(it.first, "reloc")) {
|
||||
if (it.first.starts_with("reloc")) {
|
||||
reloc_indexes.emplace(it.second / 4);
|
||||
}
|
||||
}
|
||||
@@ -242,33 +233,30 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
|
||||
}
|
||||
|
||||
FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
if (!function_compiler_available()) {
|
||||
function_compiler_log.info("Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
|
||||
string system_dir_path = phosg::ends_with(directory, "/") ? (directory + "System") : (directory + "/System");
|
||||
string system_dir_path = directory.ends_with("/") ? (directory + "System") : (directory + "/System");
|
||||
|
||||
uint32_t next_menu_item_id = 1;
|
||||
for (const auto& subdir_name : phosg::list_directory_sorted(directory)) {
|
||||
string subdir_path = phosg::ends_with(directory, "/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
if (!phosg::isdir(subdir_path)) {
|
||||
function_compiler_log.warning("Skipping %s (not a directory)", subdir_name.c_str());
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
string subdir_name = item.path().filename().string();
|
||||
string subdir_path = directory.ends_with("/") ? (directory + subdir_name) : (directory + "/" + subdir_name);
|
||||
if (!std::filesystem::is_directory(subdir_path)) {
|
||||
function_compiler_log.warning_f("Skipping {} (not a directory)", subdir_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& filename : phosg::list_directory_sorted(subdir_path)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(subdir_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
try {
|
||||
if (!phosg::ends_with(filename, ".s")) {
|
||||
if (!filename.ends_with(".s")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string name = filename.substr(0, filename.size() - 2);
|
||||
if (phosg::ends_with(name, ".inc")) {
|
||||
if (name.ends_with(".inc")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_patch = phosg::ends_with(name, ".patch");
|
||||
bool is_patch = name.ends_with(".patch");
|
||||
if (is_patch) {
|
||||
name.resize(name.size() - 6);
|
||||
}
|
||||
@@ -277,15 +265,15 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
|
||||
uint32_t specific_version = 0;
|
||||
string short_name = name;
|
||||
if (phosg::ends_with(name, ".ppc")) {
|
||||
if (name.ends_with(".ppc")) {
|
||||
arch = CompiledFunctionCode::Architecture::POWERPC;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (phosg::ends_with(name, ".x86")) {
|
||||
} else if (name.ends_with(".x86")) {
|
||||
arch = CompiledFunctionCode::Architecture::X86;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
} else if (phosg::ends_with(name, ".sh4")) {
|
||||
} else if (name.ends_with(".sh4")) {
|
||||
arch = CompiledFunctionCode::Architecture::SH4;
|
||||
name.resize(name.size() - 4);
|
||||
short_name = name;
|
||||
@@ -314,8 +302,8 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
auto code = compile_function_code(arch, subdir_path, system_dir_path, name, text);
|
||||
if (code->index != 0) {
|
||||
if (!this->index_to_function.emplace(code->index, code).second) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"duplicate function index: %08" PRIX32, code->index));
|
||||
throw runtime_error(std::format(
|
||||
"duplicate function index: {:08X}", code->index));
|
||||
}
|
||||
}
|
||||
code->specific_version = specific_version;
|
||||
@@ -327,16 +315,16 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
this->menu_item_id_and_specific_version_to_patch_function.emplace(
|
||||
static_cast<uint64_t>(code->menu_item_id) << 32 | specific_version, code);
|
||||
this->name_and_specific_version_to_patch_function.emplace(
|
||||
phosg::string_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code);
|
||||
std::format("{}-{:08X}", short_name, specific_version), code);
|
||||
}
|
||||
|
||||
string index_prefix = code->index ? phosg::string_printf("%02X => ", code->index) : "";
|
||||
string patch_prefix = is_patch ? phosg::string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : "";
|
||||
function_compiler_log.info("Compiled function %s%s%s (%s)",
|
||||
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
|
||||
string index_prefix = code->index ? std::format("{:02X} => ", code->index) : "";
|
||||
string patch_prefix = is_patch ? std::format("[{:08X}/{:08X}] ", code->menu_item_id, code->specific_version) : "";
|
||||
function_compiler_log.info_f("Compiled function {}{}{} ({})",
|
||||
index_prefix, patch_prefix, name, name_for_architecture(code->arch));
|
||||
|
||||
} catch (const exception& e) {
|
||||
function_compiler_log.warning("Failed to compile function %s: %s", filename.c_str(), e.what());
|
||||
function_compiler_log.warning_f("Failed to compile function {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -344,13 +332,13 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
|
||||
|
||||
shared_ptr<const Menu> FunctionCodeIndex::patch_switches_menu(
|
||||
uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const {
|
||||
auto suffix = phosg::string_printf("-%08" PRIX32, specific_version);
|
||||
auto suffix = std::format("-{:08X}", specific_version);
|
||||
|
||||
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patches");
|
||||
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
for (const auto& it : this->name_and_specific_version_to_patch_function) {
|
||||
const auto& fn = it.second;
|
||||
if (fn->hide_from_patches_menu || !phosg::ends_with(it.first, suffix)) {
|
||||
if (fn->hide_from_patches_menu || !it.first.ends_with(suffix)) {
|
||||
continue;
|
||||
}
|
||||
string name;
|
||||
@@ -374,16 +362,12 @@ bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const {
|
||||
std::shared_ptr<const CompiledFunctionCode> FunctionCodeIndex::get_patch(
|
||||
const std::string& name, uint32_t specific_version) const {
|
||||
return this->name_and_specific_version_to_patch_function.at(
|
||||
phosg::string_printf("%s-%08" PRIX32, name.c_str(), specific_version));
|
||||
std::format("{}-{:08X}", name, specific_version));
|
||||
}
|
||||
|
||||
DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
if (!function_compiler_available()) {
|
||||
function_compiler_log.info("Function compiler is not available");
|
||||
return;
|
||||
}
|
||||
if (!phosg::isdir(directory)) {
|
||||
function_compiler_log.info("DOL file directory is missing");
|
||||
if (!std::filesystem::is_directory(directory)) {
|
||||
function_compiler_log.info_f("DOL file directory is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -392,9 +376,10 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
|
||||
|
||||
uint32_t next_menu_item_id = 0;
|
||||
for (const auto& filename : phosg::list_directory_sorted(directory)) {
|
||||
bool is_dol = phosg::ends_with(filename, ".dol");
|
||||
bool is_compressed_dol = phosg::ends_with(filename, ".dol.prs");
|
||||
for (const auto& item : std::filesystem::directory_iterator(directory)) {
|
||||
string filename = item.path().filename().string();
|
||||
bool is_dol = filename.ends_with(".dol");
|
||||
bool is_compressed_dol = filename.ends_with(".dol.prs");
|
||||
if (!is_dol && !is_compressed_dol) {
|
||||
continue;
|
||||
}
|
||||
@@ -423,10 +408,10 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
|
||||
string compressed_size_str = phosg::format_size(file_data.size());
|
||||
string decompressed_size_str = phosg::format_size(decompressed_size);
|
||||
function_compiler_log.info("Loaded compressed DOL file %s (%s -> %s)",
|
||||
dol->name.c_str(), compressed_size_str.c_str(), decompressed_size_str.c_str());
|
||||
description = phosg::string_printf("$C6%s$C7\n%s\n%s (orig)",
|
||||
dol->name.c_str(), compressed_size_str.c_str(), decompressed_size_str.c_str());
|
||||
function_compiler_log.info_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);
|
||||
|
||||
} else {
|
||||
phosg::StringWriter w;
|
||||
@@ -439,8 +424,8 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
dol->data = std::move(w.str());
|
||||
|
||||
string size_str = phosg::format_size(dol->data.size());
|
||||
function_compiler_log.info("Loaded DOL file %s (%s)", filename.c_str(), size_str.c_str());
|
||||
description = phosg::string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str());
|
||||
function_compiler_log.info_f("Loaded DOL file {} ({})", filename, size_str);
|
||||
description = std::format("$C6{}$C7\n{}", dol->name, size_str);
|
||||
}
|
||||
|
||||
this->name_to_file.emplace(dol->name, dol);
|
||||
@@ -449,7 +434,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) {
|
||||
menu->items.emplace_back(dol->menu_item_id, dol->name, description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE);
|
||||
|
||||
} catch (const exception& e) {
|
||||
function_compiler_log.warning("Failed to load DOL file %s: %s", filename.c_str(), e.what());
|
||||
function_compiler_log.warning_f("Failed to load DOL file {}: {}", filename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
|
||||
#include "Menu.hh"
|
||||
|
||||
bool function_compiler_available();
|
||||
void set_function_compiler_available(bool is_available);
|
||||
|
||||
// TODO: Support x86 and SH4 function calls in the future. Currently we only
|
||||
// support PPC32 because I haven't written an appropriate x86 assembler yet.
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "GameServer.hh"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Loggers.hh"
|
||||
#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) {}
|
||||
|
||||
void GameServer::listen(
|
||||
const std::string& name,
|
||||
const string& addr,
|
||||
uint16_t port,
|
||||
Version version,
|
||||
ServerBehavior behavior) {
|
||||
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<GameServerSocket>();
|
||||
sock->name = name;
|
||||
sock->endpoint = asio::ip::tcp::endpoint(asio_addr, port);
|
||||
sock->version = version;
|
||||
sock->behavior = behavior;
|
||||
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);
|
||||
|
||||
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));
|
||||
|
||||
asio::co_spawn(*this->io_context, this->handle_connected_client(c), asio::detached);
|
||||
return c;
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::get_client() const {
|
||||
if (this->clients.empty()) {
|
||||
throw runtime_error("no clients on game server");
|
||||
}
|
||||
if (this->clients.size() > 1) {
|
||||
throw runtime_error("multiple clients on game server");
|
||||
}
|
||||
return *this->clients.begin();
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> GameServer::get_clients_by_identifier(const 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&) {
|
||||
}
|
||||
try {
|
||||
account_id_hex = stoul(ident, nullptr, 16);
|
||||
} catch (const 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;
|
||||
for (const auto& c : this->clients) {
|
||||
if (c->login && c->login->account->account_id == account_id_hex) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->account->account_id == account_id_dec) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->login && c->login->bb_license && c->login->bb_license->username == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto p = c->character(false, false);
|
||||
if (p && p->disp.name.eq(ident, p->inventory.language)) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c->channel->name == ident) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
if (c->channel->name.starts_with(ident + " ")) {
|
||||
results.emplace_back(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
shared_ptr<Client> GameServer::create_client(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)) {
|
||||
client_sock.close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto channel = SocketChannel::create(
|
||||
this->io_context,
|
||||
make_unique<asio::ip::tcp::socket>(std::move(client_sock)),
|
||||
listen_sock->version,
|
||||
1,
|
||||
"",
|
||||
phosg::TerminalFormat::FG_YELLOW,
|
||||
phosg::TerminalFormat::FG_GREEN);
|
||||
auto c = 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) {
|
||||
try {
|
||||
co_await on_command(c, std::move(msg));
|
||||
} catch (const 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) {
|
||||
auto g = phosg::on_close_scope(std::bind(&Client::cancel_pending_promises, c.get()));
|
||||
|
||||
try {
|
||||
co_await on_connect(c);
|
||||
} catch (const exception& e) {
|
||||
this->log.warning_f("Error in client initialization: {}", e.what());
|
||||
c->channel->disconnect();
|
||||
}
|
||||
|
||||
while (c->channel->connected()) {
|
||||
auto msg = std::make_unique<Channel::Message>(co_await c->channel->recv());
|
||||
asio::co_spawn(co_await asio::this_coro::executor, this->handle_client_command(c, std::move(msg)), asio::detached);
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> c) {
|
||||
this->log.info_f("Running cleanup tasks for {}", c->channel->name);
|
||||
|
||||
// The client may not actually be disconnected yet if an uncaught exception
|
||||
// occurred in a handler task
|
||||
c->channel->disconnect();
|
||||
|
||||
// Close the proxy session, if any
|
||||
if (c->proxy_session) {
|
||||
if (c->proxy_session->server_channel) {
|
||||
c->proxy_session->server_channel->disconnect();
|
||||
}
|
||||
c->proxy_session.reset();
|
||||
}
|
||||
|
||||
try {
|
||||
co_await on_disconnect(c);
|
||||
} catch (const 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);
|
||||
for (auto h_it : hooks) {
|
||||
try {
|
||||
h_it.second();
|
||||
} catch (const exception& e) {
|
||||
c->log.warning_f("Disconnect hook {} failed: {}", h_it.first, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Client.hh"
|
||||
#include "Server.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
struct GameServerSocket : ServerSocket {
|
||||
Version version;
|
||||
ServerBehavior behavior;
|
||||
};
|
||||
|
||||
class GameServer
|
||||
: public Server<Client, GameServerSocket>,
|
||||
public std::enable_shared_from_this<GameServer> {
|
||||
public:
|
||||
GameServer() = delete;
|
||||
GameServer(const GameServer&) = delete;
|
||||
GameServer(GameServer&&) = delete;
|
||||
explicit GameServer(std::shared_ptr<ServerState> state);
|
||||
virtual ~GameServer() = default;
|
||||
|
||||
void listen(const std::string& name, const std::string& addr, uint16_t port, Version version, ServerBehavior initial_state);
|
||||
|
||||
std::shared_ptr<Client> connect_channel(std::shared_ptr<Channel> ch, uint16_t port, ServerBehavior initial_state);
|
||||
|
||||
std::shared_ptr<Client> get_client() const;
|
||||
std::vector<std::shared_ptr<Client>> get_clients_by_identifier(const std::string& ident) const;
|
||||
|
||||
inline std::shared_ptr<ServerState> get_state() const {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<ServerState> state;
|
||||
|
||||
asio::awaitable<void> handle_client_command(std::shared_ptr<Client> c, std::unique_ptr<Channel::Message> msg);
|
||||
|
||||
[[nodiscard]] virtual std::shared_ptr<Client> create_client(
|
||||
std::shared_ptr<GameServerSocket> listen_sock, asio::ip::tcp::socket&& client_sock);
|
||||
virtual asio::awaitable<void> handle_client(std::shared_ptr<Client> c);
|
||||
virtual asio::awaitable<void> destroy_client(std::shared_ptr<Client> c);
|
||||
};
|
||||
+473
-953
File diff suppressed because it is too large
Load Diff
+26
-98
@@ -1,122 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/http.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "AsyncHTTPServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class HTTPServer {
|
||||
class HTTPServer : public AsyncHTTPServer<> {
|
||||
public:
|
||||
// shared_base should be null unless the HTTP server should run on the main
|
||||
// thread (on Windows).
|
||||
HTTPServer(std::shared_ptr<ServerState> state, std::shared_ptr<struct event_base> shared_base);
|
||||
|
||||
explicit HTTPServer(std::shared_ptr<ServerState> state);
|
||||
HTTPServer(const HTTPServer&) = delete;
|
||||
HTTPServer(HTTPServer&&) = delete;
|
||||
HTTPServer& operator=(const HTTPServer&) = delete;
|
||||
HTTPServer& operator=(HTTPServer&&) = delete;
|
||||
virtual ~HTTPServer() = default;
|
||||
|
||||
void listen(const std::string& socket_path);
|
||||
void listen(const std::string& addr, int port);
|
||||
void listen(int port);
|
||||
void add_socket(int fd);
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
void send_rare_drop_notification(std::shared_ptr<const phosg::JSON> message);
|
||||
asio::awaitable<void> send_rare_drop_notification(std::shared_ptr<const phosg::JSON> message);
|
||||
|
||||
protected:
|
||||
class http_error : public std::runtime_error {
|
||||
public:
|
||||
http_error(int code, const std::string& what);
|
||||
int code;
|
||||
};
|
||||
|
||||
struct WebsocketClient {
|
||||
struct evhttp_connection* conn;
|
||||
struct bufferevent* bev;
|
||||
|
||||
uint8_t pending_opcode;
|
||||
std::string pending_data;
|
||||
|
||||
uint64_t last_communication_time;
|
||||
|
||||
void* context;
|
||||
|
||||
WebsocketClient(struct evhttp_connection* conn);
|
||||
~WebsocketClient();
|
||||
|
||||
void reset_pending_frame();
|
||||
};
|
||||
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct evhttp> http;
|
||||
std::thread th; // Not used on Windows
|
||||
std::unordered_set<std::shared_ptr<HTTPClient>> rare_drop_subscribers;
|
||||
|
||||
std::unordered_set<std::shared_ptr<WebsocketClient>> rare_drop_subscribers;
|
||||
std::shared_ptr<phosg::JSON> generate_server_version() const;
|
||||
std::shared_ptr<phosg::JSON> generate_account_json(std::shared_ptr<const Account> a) const;
|
||||
std::shared_ptr<phosg::JSON> generate_client_json(
|
||||
std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index) const;
|
||||
std::shared_ptr<phosg::JSON> generate_lobby_json(
|
||||
std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index) const;
|
||||
std::shared_ptr<phosg::JSON> generate_accounts_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_clients_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_server_info_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_lobbies_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_summary_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_all_json() const;
|
||||
|
||||
std::unordered_map<struct bufferevent*, std::shared_ptr<WebsocketClient>> bev_to_websocket_client;
|
||||
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_ep3_cards_json(bool trial) const;
|
||||
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_common_tables_json() const;
|
||||
std::shared_ptr<phosg::JSON> generate_rare_table_list_json() const;
|
||||
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_rare_table_json(const std::string& table_name) const;
|
||||
asio::awaitable<std::shared_ptr<phosg::JSON>> generate_quest_list_json(std::shared_ptr<const QuestIndex> q);
|
||||
|
||||
static void require_GET(struct evhttp_request* req);
|
||||
static phosg::JSON require_POST(struct evhttp_request* req);
|
||||
void require_GET(const HTTPRequest& req);
|
||||
phosg::JSON require_POST(const HTTPRequest& req);
|
||||
|
||||
std::shared_ptr<WebsocketClient> enable_websockets(struct evhttp_request* req);
|
||||
|
||||
static void dispatch_on_websocket_read(struct bufferevent* bev, void* ctx);
|
||||
static void dispatch_on_websocket_error(struct bufferevent* bev, short events, void* ctx);
|
||||
|
||||
void on_websocket_read(struct bufferevent* bev);
|
||||
void on_websocket_error(struct bufferevent* bev, short events);
|
||||
|
||||
void disconnect_websocket_client(struct bufferevent* bev);
|
||||
void send_websocket_message(struct bufferevent* bev, const std::string& message, uint8_t opcode = 0x01);
|
||||
void send_websocket_message(std::shared_ptr<WebsocketClient> c, const std::string& message, uint8_t opcode = 0x01);
|
||||
|
||||
virtual void handle_websocket_message(std::shared_ptr<WebsocketClient> c, uint8_t opcode, const std::string& message);
|
||||
virtual void handle_websocket_disconnect(std::shared_ptr<WebsocketClient> c);
|
||||
|
||||
void thread_fn();
|
||||
|
||||
static void dispatch_handle_request(struct evhttp_request* req, void* ctx);
|
||||
void handle_request(struct evhttp_request* req);
|
||||
|
||||
static const std::unordered_map<int, const char*> explanation_for_response_code;
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, struct evbuffer* b);
|
||||
static void send_response(struct evhttp_request* req, int code, const char* content_type, const char* fmt, ...);
|
||||
|
||||
static std::unordered_multimap<std::string, std::string> parse_url_params(const std::string& query);
|
||||
static std::unordered_map<std::string, std::string> parse_url_params_unique(const std::string& query);
|
||||
static const std::string& get_url_param(
|
||||
const std::unordered_multimap<std::string, std::string>& params,
|
||||
const std::string& key,
|
||||
const std::string* _default = nullptr);
|
||||
|
||||
static phosg::JSON generate_server_version_st();
|
||||
static phosg::JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static phosg::JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static phosg::JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static phosg::JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static phosg::JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
phosg::JSON generate_accounts_json() const;
|
||||
phosg::JSON generate_game_server_clients_json() const;
|
||||
phosg::JSON generate_proxy_server_clients_json() const;
|
||||
phosg::JSON generate_server_info_json() const;
|
||||
phosg::JSON generate_lobbies_json() const;
|
||||
phosg::JSON generate_summary_json() const;
|
||||
phosg::JSON generate_all_json() const;
|
||||
|
||||
phosg::JSON generate_ep3_cards_json(bool trial) const;
|
||||
phosg::JSON generate_common_tables_json() const;
|
||||
phosg::JSON generate_rare_tables_json() const;
|
||||
phosg::JSON generate_rare_table_json(const std::string& table_name) const;
|
||||
phosg::JSON generate_quest_list_json(std::shared_ptr<const QuestIndex> q);
|
||||
virtual asio::awaitable<std::unique_ptr<HTTPResponse>> handle_request(std::shared_ptr<HTTPClient> c, HTTPRequest&& req);
|
||||
virtual asio::awaitable<void> destroy_client(std::shared_ptr<HTTPClient> c);
|
||||
};
|
||||
|
||||
+18
-21
@@ -13,9 +13,6 @@ static inline uint16_t collapse_checksum(uint32_t sum) {
|
||||
return (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
|
||||
FrameInfo::FrameInfo(LinkType link_type, const string& data)
|
||||
: FrameInfo(link_type, data.data(), data.size()) {}
|
||||
|
||||
FrameInfo::FrameInfo(LinkType link_type, const void* header_start, size_t size)
|
||||
: FrameInfo() {
|
||||
this->link_type = link_type;
|
||||
@@ -126,37 +123,37 @@ string FrameInfo::header_str() const {
|
||||
|
||||
string ret;
|
||||
if (this->ether) {
|
||||
ret = phosg::string_printf(
|
||||
"ETHER:%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX->%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
|
||||
ret = std::format(
|
||||
"ETHER:{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}->{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
|
||||
this->ether->src_mac[0], this->ether->src_mac[1], this->ether->src_mac[2],
|
||||
this->ether->src_mac[3], this->ether->src_mac[4], this->ether->src_mac[5],
|
||||
this->ether->dest_mac[0], this->ether->dest_mac[1], this->ether->dest_mac[2],
|
||||
this->ether->dest_mac[3], this->ether->dest_mac[4], this->ether->dest_mac[5]);
|
||||
} else if (this->hdlc) {
|
||||
ret = phosg::string_printf("HDLC:%02hhX/%02hhX", this->hdlc->address, this->hdlc->control);
|
||||
ret = std::format("HDLC:{:02X}/{:02X}", this->hdlc->address, this->hdlc->control);
|
||||
} else {
|
||||
return "<invalid-frame-info>";
|
||||
}
|
||||
|
||||
if (this->arp) {
|
||||
ret += phosg::string_printf(
|
||||
",ARP,hw_type=%04hX,proto_type=%04hX,hw_addr_len=%02hhX,proto_addr_len=%02hhX,op=%04hX",
|
||||
this->arp->hardware_type.load(), this->arp->protocol_type.load(), this->arp->hwaddr_len, this->arp->paddr_len, this->arp->operation.load());
|
||||
ret += std::format(
|
||||
",ARP,hw_type={:04X},proto_type={:04X},hw_addr_len={:02X},proto_addr_len={:02X},op={:04X}",
|
||||
this->arp->hardware_type, this->arp->protocol_type, this->arp->hwaddr_len, this->arp->paddr_len, this->arp->operation);
|
||||
|
||||
} else if (this->ipv4) {
|
||||
ret += phosg::string_printf(
|
||||
",IPv4,size=%04hX,src=%08" PRIX32 ",dest=%08" PRIX32,
|
||||
this->ipv4->size.load(), this->ipv4->src_addr.load(), this->ipv4->dest_addr.load());
|
||||
ret += std::format(
|
||||
",IPv4,size={:04X},src={:08X},dest={:08X}",
|
||||
this->ipv4->size, this->ipv4->src_addr, this->ipv4->dest_addr);
|
||||
|
||||
if (this->udp) {
|
||||
ret += phosg::string_printf(
|
||||
",UDP,src_port=%04hX,dest_port=%04hX,size=%04hX",
|
||||
this->udp->src_port.load(), this->udp->dest_port.load(), this->udp->size.load());
|
||||
ret += std::format(
|
||||
",UDP,src_port={:04X},dest_port={:04X},size={:04X}",
|
||||
this->udp->src_port, this->udp->dest_port, this->udp->size);
|
||||
|
||||
} else if (this->tcp) {
|
||||
ret += phosg::string_printf(
|
||||
",TCP,src_port=%04hX,dest_port=%04hX,seq=%08" PRIX32 ",ack=%08" PRIX32 ",flags=%04hX(",
|
||||
this->tcp->src_port.load(), this->tcp->dest_port.load(), this->tcp->seq_num.load(), this->tcp->ack_num.load(), this->tcp->flags.load());
|
||||
ret += std::format(
|
||||
",TCP,src_port={:04X},dest_port={:04X},seq={:08X},ack={:08X},flags={:04X}(",
|
||||
this->tcp->src_port, this->tcp->dest_port, this->tcp->seq_num, this->tcp->ack_num, this->tcp->flags);
|
||||
if (this->tcp->flags & TCPHeader::Flag::FIN) {
|
||||
ret += "FIN,";
|
||||
}
|
||||
@@ -175,14 +172,14 @@ string FrameInfo::header_str() const {
|
||||
ret += ')';
|
||||
|
||||
} else {
|
||||
ret += phosg::string_printf(",proto=%02hhX", this->ipv4->protocol);
|
||||
ret += std::format(",proto={:02X}", this->ipv4->protocol);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this->ether) {
|
||||
ret += phosg::string_printf(",proto=%04hX", this->ether->protocol.load());
|
||||
ret += std::format(",proto={:04X}", this->ether->protocol);
|
||||
} else if (this->hdlc) {
|
||||
ret += phosg::string_printf(",proto=%04hX", this->hdlc->protocol.load());
|
||||
ret += std::format(",proto={:04X}", this->hdlc->protocol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,6 @@ struct FrameInfo {
|
||||
size_t payload_size = 0;
|
||||
|
||||
FrameInfo() = default;
|
||||
FrameInfo(LinkType link_type, const std::string& data);
|
||||
FrameInfo(LinkType link_type, const void* data, size_t size);
|
||||
|
||||
std::string header_str() const;
|
||||
|
||||
+683
-683
File diff suppressed because it is too large
Load Diff
+165
-125
@@ -1,169 +1,209 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <deque>
|
||||
#include <asio.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Process.hh>
|
||||
#include <string>
|
||||
|
||||
#include "AsyncUtils.hh"
|
||||
#include "Channel.hh"
|
||||
#include "IPFrameInfo.hh"
|
||||
#include "ProxyServer.hh"
|
||||
#include "Server.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
class IPStackSimulator : public std::enable_shared_from_this<IPStackSimulator> {
|
||||
class IPStackSimulator;
|
||||
class IPSSChannel;
|
||||
|
||||
constexpr size_t DEFAULT_RESEND_PUSH_USECS = 200000; // 200ms
|
||||
|
||||
enum class VirtualNetworkProtocol {
|
||||
ETHERNET_TAPSERVER = 0,
|
||||
HDLC_TAPSERVER,
|
||||
HDLC_RAW,
|
||||
};
|
||||
|
||||
struct IPSSSocket : ServerSocket {
|
||||
VirtualNetworkProtocol protocol;
|
||||
};
|
||||
|
||||
struct IPSSClient : std::enable_shared_from_this<IPSSClient> {
|
||||
std::shared_ptr<asio::io_context> io_context;
|
||||
std::weak_ptr<IPStackSimulator> sim;
|
||||
uint64_t network_id;
|
||||
asio::ip::tcp::socket sock;
|
||||
VirtualNetworkProtocol protocol;
|
||||
uint32_t hdlc_escape_control_character_flags = 0xFFFFFFFF;
|
||||
uint32_t hdlc_remote_magic_number = 0;
|
||||
parray<uint8_t, 6> mac_addr; // Only used for LinkType::ETHERNET
|
||||
uint32_t ipv4_addr;
|
||||
asio::steady_timer idle_timeout_timer;
|
||||
|
||||
struct TCPConnection {
|
||||
std::weak_ptr<IPSSClient> client;
|
||||
std::shared_ptr<IPSSChannel> server_channel;
|
||||
bool awaiting_first_ack = true;
|
||||
bool awaiting_ack = false;
|
||||
uint32_t server_addr = 0;
|
||||
uint16_t server_port = 0;
|
||||
uint16_t client_port = 0;
|
||||
uint32_t next_client_seq = 0;
|
||||
uint32_t acked_server_seq = 0;
|
||||
size_t resend_push_usecs = DEFAULT_RESEND_PUSH_USECS;
|
||||
size_t next_push_max_frame_size = 1024;
|
||||
size_t max_frame_size = 1024;
|
||||
size_t bytes_received = 0;
|
||||
size_t bytes_sent = 0;
|
||||
size_t outbound_data_bytes = 0;
|
||||
std::list<std::string> outbound_data;
|
||||
asio::steady_timer resend_push_timer;
|
||||
|
||||
TCPConnection(std::shared_ptr<IPSSClient> client);
|
||||
|
||||
inline uint64_t key() const {
|
||||
return (static_cast<uint64_t>(this->server_addr) << 32) |
|
||||
(static_cast<uint64_t>(this->server_port) << 16) |
|
||||
static_cast<uint64_t>(this->client_port);
|
||||
}
|
||||
static inline uint64_t key(const IPv4Header& ipv4, const TCPHeader& tcp) {
|
||||
return (static_cast<uint64_t>(ipv4.dest_addr) << 32) |
|
||||
(static_cast<uint64_t>(tcp.dest_port) << 16) |
|
||||
static_cast<uint64_t>(tcp.src_port);
|
||||
}
|
||||
static inline uint64_t key(const FrameInfo& fi) {
|
||||
if (!fi.ipv4 || !fi.tcp) {
|
||||
throw std::logic_error("tcp_conn_key_for_frame called on non-TCP frame");
|
||||
}
|
||||
return key(*fi.ipv4, *fi.tcp);
|
||||
}
|
||||
|
||||
void drain_outbound_data(size_t bytes);
|
||||
void linearize_outbound_data(size_t bytes);
|
||||
};
|
||||
std::unordered_map<uint64_t, std::shared_ptr<TCPConnection>> tcp_connections;
|
||||
|
||||
IPSSClient(
|
||||
std::shared_ptr<IPStackSimulator> sim,
|
||||
uint64_t network_id,
|
||||
VirtualNetworkProtocol protocol,
|
||||
asio::ip::tcp::socket&& sock);
|
||||
void reschedule_idle_timeout();
|
||||
};
|
||||
|
||||
// IPSSChannel provides an "unwrapped" connection to the rest of the server. It
|
||||
// implements the Channel interface and can be used in place of an
|
||||
// SocketChannel, so the rest of the server doesn't have to know about
|
||||
// IPStackSimulator.
|
||||
class IPSSChannel : public Channel {
|
||||
public:
|
||||
enum class Protocol {
|
||||
ETHERNET_TAPSERVER = 0,
|
||||
HDLC_TAPSERVER,
|
||||
HDLC_RAW,
|
||||
};
|
||||
std::shared_ptr<IPStackSimulator> sim;
|
||||
std::weak_ptr<IPSSClient> ipss_client;
|
||||
std::weak_ptr<IPSSClient::TCPConnection> tcp_conn;
|
||||
|
||||
using unique_listener = std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)>;
|
||||
using unique_bufferevent = std::unique_ptr<struct bufferevent, void (*)(struct bufferevent*)>;
|
||||
using unique_evbuffer = std::unique_ptr<struct evbuffer, void (*)(struct evbuffer*)>;
|
||||
using unique_event = std::unique_ptr<struct event, void (*)(struct event*)>;
|
||||
IPSSChannel(
|
||||
std::shared_ptr<IPStackSimulator> sim,
|
||||
std::weak_ptr<IPSSClient> ipss_client,
|
||||
std::weak_ptr<IPSSClient::TCPConnection> tcp_conn,
|
||||
Version version,
|
||||
uint8_t language,
|
||||
const std::string& name = "",
|
||||
phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END,
|
||||
phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END);
|
||||
|
||||
struct IPClient : std::enable_shared_from_this<IPClient> {
|
||||
std::weak_ptr<IPStackSimulator> sim;
|
||||
uint64_t network_id;
|
||||
virtual std::string default_name() const;
|
||||
|
||||
unique_bufferevent bev;
|
||||
Protocol protocol;
|
||||
uint32_t hdlc_escape_control_character_flags = 0xFFFFFFFF;
|
||||
uint32_t hdlc_remote_magic_number = 0;
|
||||
parray<uint8_t, 6> mac_addr; // Only used for LinkType::ETHERNET
|
||||
uint32_t ipv4_addr;
|
||||
virtual bool connected() const;
|
||||
virtual void disconnect();
|
||||
|
||||
struct TCPConnection {
|
||||
std::weak_ptr<IPClient> client;
|
||||
// Adds inbound data, which will then be available via recv_raw(). This
|
||||
// function is called by IPStackSimulator to forward "unwrapped" data to
|
||||
// the game/proxy servers.
|
||||
void add_inbound_data(const void* data, size_t size);
|
||||
|
||||
// The PSO protocol begins with the server sending a command, but we
|
||||
// shouldn't send a PSH immediately after the SYN+ACK, so the connection
|
||||
// isn't handed to the Server object until after the 3-way handshake
|
||||
// (receive SYN, send SYN+ACK, receive ACK). This means server_bev is null
|
||||
// during the first part of the connection phase.
|
||||
unique_bufferevent server_bev;
|
||||
// TODO: Get rid of pending_data and just use server_bev's input buffer in
|
||||
// its place
|
||||
unique_evbuffer pending_data;
|
||||
unique_event resend_push_event;
|
||||
virtual void send_raw(std::string&& data);
|
||||
virtual asio::awaitable<void> recv_raw(void* data, size_t size);
|
||||
|
||||
bool awaiting_first_ack;
|
||||
private:
|
||||
AsyncEvent data_available_signal;
|
||||
std::deque<std::string> inbound_data;
|
||||
void* recv_buf = nullptr;
|
||||
size_t recv_buf_size = 0;
|
||||
};
|
||||
|
||||
uint32_t server_addr;
|
||||
uint16_t server_port;
|
||||
uint16_t client_port;
|
||||
uint32_t next_client_seq;
|
||||
uint32_t acked_server_seq;
|
||||
size_t resend_push_usecs;
|
||||
size_t next_push_max_frame_size;
|
||||
size_t max_frame_size;
|
||||
size_t bytes_received;
|
||||
size_t bytes_sent;
|
||||
class IPStackSimulator
|
||||
: public Server<IPSSClient, IPSSSocket>,
|
||||
public std::enable_shared_from_this<IPStackSimulator> {
|
||||
public:
|
||||
IPStackSimulator(std::shared_ptr<ServerState> state);
|
||||
~IPStackSimulator() = default;
|
||||
|
||||
TCPConnection();
|
||||
};
|
||||
std::unordered_map<uint64_t, TCPConnection> tcp_connections;
|
||||
|
||||
unique_event idle_timeout_event;
|
||||
|
||||
IPClient(std::shared_ptr<IPStackSimulator> sim, uint64_t network_id, Protocol protocol, struct bufferevent* bev);
|
||||
|
||||
static void dispatch_on_client_input(struct bufferevent* bev, void* ctx);
|
||||
void on_client_input(struct bufferevent* bev);
|
||||
static void dispatch_on_client_error(struct bufferevent* bev, short events, void* ctx);
|
||||
void on_client_error(struct bufferevent* bev, short events);
|
||||
|
||||
static void dispatch_on_idle_timeout(evutil_socket_t fd, short events, void* ctx);
|
||||
void on_idle_timeout();
|
||||
};
|
||||
|
||||
IPStackSimulator(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state);
|
||||
~IPStackSimulator();
|
||||
|
||||
void listen(const std::string& name, const std::string& socket_path, Protocol protocol);
|
||||
void listen(const std::string& name, const std::string& addr, int port, Protocol protocol);
|
||||
void listen(const std::string& name, int port, Protocol protocol);
|
||||
void add_socket(const std::string& name, int fd, Protocol protocol);
|
||||
void listen(const std::string& name, const std::string& addr, int port, VirtualNetworkProtocol protocol);
|
||||
|
||||
static uint32_t connect_address_for_remote_address(uint32_t remote_addr);
|
||||
|
||||
std::shared_ptr<IPClient> get_network(uint64_t network_id) const;
|
||||
inline const std::unordered_map<uint64_t, std::shared_ptr<IPClient>>& all_networks() const {
|
||||
return this->network_id_to_client;
|
||||
inline std::shared_ptr<ServerState> get_state() {
|
||||
return this->state;
|
||||
}
|
||||
|
||||
void disconnect_client(uint64_t network_id);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<ServerState> state;
|
||||
uint64_t next_network_id;
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string name;
|
||||
Protocol protocol;
|
||||
unique_listener listener;
|
||||
|
||||
ListeningSocket(const std::string& name, Protocol protocol, unique_listener&& l)
|
||||
: name(name),
|
||||
protocol(protocol),
|
||||
listener(std::move(l)) {}
|
||||
};
|
||||
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<IPClient>> network_id_to_client;
|
||||
uint64_t next_network_id = 1;
|
||||
|
||||
parray<uint8_t, 6> host_mac_address_bytes;
|
||||
parray<uint8_t, 6> broadcast_mac_address_bytes;
|
||||
|
||||
FILE* pcap_text_log_file;
|
||||
|
||||
static uint64_t tcp_conn_key_for_connection(const IPClient::TCPConnection& conn);
|
||||
static uint64_t tcp_conn_key_for_connection(std::shared_ptr<const IPSSClient::TCPConnection> conn);
|
||||
static uint64_t tcp_conn_key_for_client_frame(const IPv4Header& ipv4, const TCPHeader& tcp);
|
||||
static uint64_t tcp_conn_key_for_client_frame(const FrameInfo& fi);
|
||||
|
||||
static std::string str_for_ipv4_netloc(uint32_t addr, uint16_t port);
|
||||
static std::string str_for_tcp_connection(std::shared_ptr<const IPClient> c, const IPClient::TCPConnection& conn);
|
||||
static std::string str_for_tcp_connection(
|
||||
std::shared_ptr<const IPSSClient> c, std::shared_ptr<const IPSSClient::TCPConnection> conn);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
asio::awaitable<void> send_ethernet_tapserver_frame(
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const;
|
||||
asio::awaitable<void> send_hdlc_frame(
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size, bool is_raw) const;
|
||||
asio::awaitable<void> send_layer3_frame(
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const;
|
||||
[[nodiscard]] inline asio::awaitable<void> send_layer3_frame(
|
||||
std::shared_ptr<IPSSClient> c, FrameInfo::Protocol proto, const std::string& data) const {
|
||||
return this->send_layer3_frame(c, proto, data.data(), data.size());
|
||||
}
|
||||
|
||||
void send_layer3_frame(std::shared_ptr<IPClient> c, FrameInfo::Protocol proto, const std::string& data) const;
|
||||
void send_layer3_frame(std::shared_ptr<IPClient> c, FrameInfo::Protocol proto, const void* data, size_t size) const;
|
||||
asio::awaitable<void> on_client_frame(std::shared_ptr<IPSSClient> c, const void* data, size_t size);
|
||||
asio::awaitable<void> on_client_lcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
asio::awaitable<void> on_client_pap_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
asio::awaitable<void> on_client_ipcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
asio::awaitable<void> on_client_arp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
asio::awaitable<void> on_client_udp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
asio::awaitable<void> on_client_tcp_frame(std::shared_ptr<IPSSClient> c, const FrameInfo& fi);
|
||||
|
||||
void on_client_frame(std::shared_ptr<IPClient> c, const std::string& frame);
|
||||
void on_client_lcp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
void on_client_pap_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
void on_client_ipcp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
void on_client_arp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
void on_client_udp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
void on_client_tcp_frame(std::shared_ptr<IPClient> c, const FrameInfo& fi);
|
||||
|
||||
static void dispatch_on_server_input(struct bufferevent* bev, void* ctx);
|
||||
void on_server_input(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
|
||||
static void dispatch_on_server_error(struct bufferevent* bev, short events, void* ctx);
|
||||
void on_server_error(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn, short events);
|
||||
|
||||
static void dispatch_on_resend_push(evutil_socket_t, short, void* ctx);
|
||||
void send_pending_push_frame(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn, bool always_send);
|
||||
void send_tcp_frame(
|
||||
std::shared_ptr<IPClient> c,
|
||||
IPClient::TCPConnection& conn,
|
||||
void schedule_send_pending_push_frame(std::shared_ptr<IPSSClient::TCPConnection> conn, uint64_t delay_usecs);
|
||||
asio::awaitable<void> send_pending_push_frame(
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn);
|
||||
asio::awaitable<void> send_tcp_frame(
|
||||
std::shared_ptr<IPSSClient> c,
|
||||
std::shared_ptr<IPSSClient::TCPConnection> conn,
|
||||
uint16_t flags = 0,
|
||||
struct evbuffer* src_buf = nullptr,
|
||||
size_t src_bytes = 0);
|
||||
const void* payload_data = nullptr,
|
||||
size_t payload_bytes = 0);
|
||||
|
||||
void open_server_connection(std::shared_ptr<IPClient> c, IPClient::TCPConnection& conn);
|
||||
asio::awaitable<void> open_server_connection(
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn);
|
||||
asio::awaitable<void> close_tcp_connection(
|
||||
std::shared_ptr<IPSSClient> c, std::shared_ptr<IPSSClient::TCPConnection> conn);
|
||||
|
||||
void log_frame(const std::string& data) const;
|
||||
[[nodiscard]] virtual std::shared_ptr<IPSSClient> create_client(
|
||||
std::shared_ptr<IPSSSocket> listen_sock, asio::ip::tcp::socket&& client_sock);
|
||||
asio::awaitable<void> handle_tapserver_client(std::shared_ptr<IPSSClient> c);
|
||||
asio::awaitable<void> handle_hdlc_raw_client(std::shared_ptr<IPSSClient> c);
|
||||
virtual asio::awaitable<void> handle_client(std::shared_ptr<IPSSClient> c);
|
||||
virtual asio::awaitable<void> destroy_client(std::shared_ptr<IPSSClient> c);
|
||||
|
||||
friend class IPSSChannel;
|
||||
};
|
||||
|
||||
+1
-11
@@ -1,7 +1,5 @@
|
||||
#include "IPV4RangeSet.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
IPV4RangeSet::IPV4RangeSet(const phosg::JSON& json) {
|
||||
@@ -45,7 +43,7 @@ phosg::JSON IPV4RangeSet::json() const {
|
||||
for (const auto& it : this->ranges) {
|
||||
uint32_t addr = it.first;
|
||||
uint8_t mask_bits = it.second;
|
||||
ret.emplace_back(phosg::string_printf("%hhu.%hhu.%hhu.%hhu/%hhu",
|
||||
ret.emplace_back(std::format("{}.{}.{}.{}/{}",
|
||||
static_cast<uint8_t>((addr >> 24) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 16) & 0xFF),
|
||||
static_cast<uint8_t>((addr >> 8) & 0xFF),
|
||||
@@ -63,11 +61,3 @@ bool IPV4RangeSet::check(uint32_t addr) const {
|
||||
const auto& range = *(--it);
|
||||
return (((range.first ^ addr) & (0xFFFFFFFF << (32 - range.second))) == 0);
|
||||
}
|
||||
|
||||
bool IPV4RangeSet::check(const struct sockaddr_storage& ss) const {
|
||||
if (ss.ss_family != AF_INET) {
|
||||
return false;
|
||||
}
|
||||
const sockaddr_in* sin = reinterpret_cast<const sockaddr_in*>(&ss);
|
||||
return this->check(ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <set>
|
||||
|
||||
@@ -11,7 +12,6 @@ public:
|
||||
phosg::JSON json() const;
|
||||
|
||||
bool check(uint32_t addr) const;
|
||||
bool check(const struct sockaddr_storage& ss) const;
|
||||
|
||||
protected:
|
||||
std::map<uint32_t, uint8_t> ranges; // {addr: mask_bits}
|
||||
|
||||
@@ -184,7 +184,7 @@ int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
}
|
||||
|
||||
string IntegralExpression::FlagLookupNode::str() const {
|
||||
return phosg::string_printf("F_%04hX", this->flag_index);
|
||||
return std::format("F_{:04X}", this->flag_index);
|
||||
}
|
||||
|
||||
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
|
||||
@@ -214,7 +214,7 @@ int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& e
|
||||
}
|
||||
|
||||
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
|
||||
return phosg::string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
return std::format("CC_{}_{}", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
|
||||
}
|
||||
|
||||
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||
@@ -296,7 +296,7 @@ int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
|
||||
}
|
||||
|
||||
string IntegralExpression::ConstantNode::str() const {
|
||||
return phosg::string_printf("%" PRId64, this->value);
|
||||
return std::format("{}", this->value);
|
||||
}
|
||||
|
||||
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
|
||||
|
||||
+81
-93
@@ -34,9 +34,9 @@ ItemCreator::ItemCreator(
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||
shared_ptr<const BattleRules> restrictions)
|
||||
: log(phosg::string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||
: log(std::format("[ItemCreator:{}/{}/{}/{}/{}] ", phosg::name_for_enum(stack_limits->version), abbreviation_for_episode(episode), abbreviation_for_mode(mode), abbreviation_for_difficulty(difficulty), section_id), lobby_log.min_level),
|
||||
logic_version(stack_limits->version),
|
||||
stack_limits(stack_limits),
|
||||
episode(episode),
|
||||
@@ -52,18 +52,14 @@ ItemCreator::ItemCreator(
|
||||
common_item_set(common_item_set),
|
||||
pt(common_item_set->get_table(this->episode, this->mode, this->difficulty, this->section_id)),
|
||||
restrictions(restrictions),
|
||||
opt_rand_crypt(opt_rand_crypt ? make_shared<PSOV2Encryption>(opt_rand_crypt->seed()) : nullptr) {
|
||||
rand_crypt(rand_crypt) {
|
||||
this->generate_unit_stars_tables();
|
||||
}
|
||||
|
||||
void ItemCreator::set_random_crypt(shared_ptr<PSOLFGEncryption> new_random_crypt) {
|
||||
this->opt_rand_crypt = new_random_crypt;
|
||||
}
|
||||
|
||||
void ItemCreator::set_section_id(uint8_t new_section_id) {
|
||||
if (this->section_id != new_section_id) {
|
||||
this->section_id = new_section_id;
|
||||
this->log.prefix = phosg::string_printf("[ItemCreator:%s/%s/%s/%c/%hhu] ",
|
||||
this->log.prefix = std::format("[ItemCreator:{}/{}/{}/{}/{}] ",
|
||||
phosg::name_for_enum(stack_limits->version),
|
||||
abbreviation_for_episode(episode),
|
||||
abbreviation_for_mode(mode),
|
||||
@@ -150,7 +146,7 @@ ItemCreator::DropResult ItemCreator::on_box_item_drop(uint8_t area) {
|
||||
try {
|
||||
return this->on_box_item_drop_with_area_norm(this->normalize_area_number(area));
|
||||
} catch (const exception& e) {
|
||||
this->log.error("Exception in item creation: %s", e.what());
|
||||
this->log.error_f("Exception in item creation: {}", e.what());
|
||||
return DropResult();
|
||||
}
|
||||
}
|
||||
@@ -159,24 +155,20 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop(uint32_t enemy_type, u
|
||||
try {
|
||||
return this->on_monster_item_drop_with_area_norm(enemy_type, this->normalize_area_number(area));
|
||||
} catch (const exception& e) {
|
||||
this->log.error("Exception in item creation: %s", e.what());
|
||||
this->log.error_f("Exception in item creation: {}", e.what());
|
||||
return DropResult();
|
||||
}
|
||||
}
|
||||
|
||||
ItemCreator::DropResult ItemCreator::on_box_item_drop_with_area_norm(uint8_t area_norm) {
|
||||
this->log.info("Box drop checks for area_norm %02hhX", area_norm);
|
||||
if (this->opt_rand_crypt) {
|
||||
this->log.info("Random state: %08" PRIX32 " %08" PRIX32,
|
||||
this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset());
|
||||
}
|
||||
this->log.info_f("Box drop checks for area_norm {:02X}", area_norm);
|
||||
DropResult res;
|
||||
res.item = this->check_rare_specs_and_create_rare_box_item(area_norm);
|
||||
if (!res.item.empty()) {
|
||||
res.is_from_rare_table = true;
|
||||
} else {
|
||||
uint8_t item_class = this->get_rand_from_weighted_tables_2d_vertical(this->pt->box_item_class_prob_table, area_norm);
|
||||
this->log.info("Item class is %02hhX", item_class);
|
||||
this->log.info_f("Item class is {:02X}", item_class);
|
||||
switch (item_class) {
|
||||
case 0: // Weapon
|
||||
res.item.data1[0] = 0;
|
||||
@@ -215,22 +207,18 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop_with_area_norm(uint32_
|
||||
// Note: The original GC implementation uses (enemy_type > 0x58) here; we
|
||||
// extend it to the full array size for BB
|
||||
if (enemy_type >= 0x64) {
|
||||
this->log.warning("Invalid enemy type: %" PRIX32, enemy_type);
|
||||
this->log.warning_f("Invalid enemy type: {:X}", enemy_type);
|
||||
return DropResult();
|
||||
}
|
||||
this->log.info("Enemy type: %" PRIX32 "", enemy_type);
|
||||
if (this->opt_rand_crypt) {
|
||||
this->log.info("Random state: %08" PRIX32 " %08" PRIX32,
|
||||
this->opt_rand_crypt->seed(), this->opt_rand_crypt->absolute_offset());
|
||||
}
|
||||
this->log.info_f("Enemy type: {:X}", enemy_type);
|
||||
|
||||
uint8_t type_drop_prob = this->pt->enemy_type_drop_probs.at(enemy_type);
|
||||
uint8_t drop_sample = this->rand_int(100);
|
||||
if (drop_sample >= type_drop_prob) {
|
||||
this->log.info("Drop not chosen (%hhu >= %hhu)", drop_sample, type_drop_prob);
|
||||
this->log.info_f("Drop not chosen ({} >= {})", drop_sample, type_drop_prob);
|
||||
return DropResult();
|
||||
} else {
|
||||
this->log.info("Drop chosen (%hhu < %hhu)", drop_sample, type_drop_prob);
|
||||
this->log.info_f("Drop chosen ({} < {})", drop_sample, type_drop_prob);
|
||||
}
|
||||
|
||||
DropResult res;
|
||||
@@ -258,7 +246,7 @@ ItemCreator::DropResult ItemCreator::on_monster_item_drop_with_area_norm(uint32_
|
||||
throw logic_error("invalid item class determinant");
|
||||
}
|
||||
|
||||
this->log.info("Rare drop not chosen; item class determinant is %" PRIu32 "; item class is %" PRIu32, item_class_determinant, item_class);
|
||||
this->log.info_f("Rare drop not chosen; item class determinant is {}; item class is {}", item_class_determinant, item_class);
|
||||
|
||||
switch (item_class) {
|
||||
case 0: // Weapon
|
||||
@@ -303,27 +291,27 @@ ItemData ItemCreator::check_rare_specs_and_create_rare_box_item(uint8_t area_nor
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area_norm);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Box spec %08" PRIX32 " produced item %s", spec.probability, hex.c_str());
|
||||
this->log.info_f("Box spec {:08X} produced item {}", spec.probability, hex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Box spec %08" PRIX32 " did not produce item %s", spec.probability, hex.c_str());
|
||||
this->log.info_f("Box spec {:08X} did not produce item {}", spec.probability, hex);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
uint32_t ItemCreator::rand_int(uint64_t max) {
|
||||
return random_from_optional_crypt(this->opt_rand_crypt) % max;
|
||||
return this->rand_crypt->next() % max;
|
||||
}
|
||||
|
||||
float ItemCreator::rand_float_0_1_from_crypt() {
|
||||
// This lacks some precision, but matches the original implementation.
|
||||
return (static_cast<double>(random_from_optional_crypt(this->opt_rand_crypt) >> 16) / 65536.0);
|
||||
return (static_cast<double>(this->rand_crypt->next() >> 16) / 65536.0);
|
||||
}
|
||||
|
||||
template <size_t NumRanges>
|
||||
@@ -344,7 +332,7 @@ uint32_t ItemCreator::choose_meseta_amount(
|
||||
ret = this->rand_int((max - min) + 1) + min;
|
||||
}
|
||||
|
||||
this->log.info("Chose %" PRIu32 " Meseta from range [%hu, %hu]", ret, min, max);
|
||||
this->log.info_f("Chose {} Meseta from range [{}, {}]", ret, min, max);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -364,15 +352,15 @@ ItemData ItemCreator::check_rare_spec_and_create_rare_enemy_item(uint32_t enemy_
|
||||
for (const auto& spec : rare_specs) {
|
||||
item = this->check_rate_and_create_rare_item(spec, area_norm);
|
||||
if (!item.empty()) {
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Enemy spec %08" PRIX32 " produced item %s", spec.probability, hex.c_str());
|
||||
this->log.info_f("Enemy spec {:08X} produced item {}", spec.probability, hex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (this->log.should_log(phosg::LogLevel::INFO)) {
|
||||
if (this->log.should_log(phosg::LogLevel::L_INFO)) {
|
||||
auto hex = spec.data.hex();
|
||||
this->log.info("Enemy spec %08" PRIX32 " did not produce item %s", spec.probability, hex.c_str());
|
||||
this->log.info_f("Enemy spec {:08X} did not produce item {}", spec.probability, hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -454,12 +442,12 @@ void ItemCreator::generate_common_weapon_bonuses(ItemData& item, uint8_t area_no
|
||||
for (size_t row = 0; row < 3; row++) {
|
||||
uint8_t spec = this->pt->nonrare_bonus_prob_spec.at(row).at(area_norm);
|
||||
if (spec == 0xFF) {
|
||||
this->log.info("Bonus %zu is forbidden", row);
|
||||
this->log.info_f("Bonus {} is forbidden", row);
|
||||
} else {
|
||||
item.data1[(row * 2) + 6] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_type_prob_table, area_norm);
|
||||
int16_t amount = this->get_rand_from_weighted_tables_2d_vertical(this->pt->bonus_value_prob_table, spec);
|
||||
item.data1[(row * 2) + 7] = amount * 5 - 10;
|
||||
this->log.info("Bonus %zu generated as %02hhX %02hhX from area_norm %02hhX and spec %02hhX", row, item.data1[(row * 2) + 6], item.data1[(row * 2) + 7], area_norm, spec);
|
||||
this->log.info_f("Bonus {} generated as {:02X} {:02X} from area_norm {:02X} and spec {:02X}", row, item.data1[(row * 2) + 6], item.data1[(row * 2) + 7], area_norm, spec);
|
||||
}
|
||||
// Note: The original code has a special case here, which divides
|
||||
// item.data1[z + 7] by 5 and multiplies it by 5 again if bonus_type is 5
|
||||
@@ -487,7 +475,7 @@ void ItemCreator::deduplicate_weapon_bonuses(ItemData& item) const {
|
||||
|
||||
void ItemCreator::set_item_kill_count_if_unsealable(ItemData& item) const {
|
||||
if (this->item_parameter_table->is_unsealable_item(item)) {
|
||||
this->log.info("Item is unsealable; setting kill count to zero");
|
||||
this->log.info_f("Item is unsealable; setting kill count to zero");
|
||||
item.set_kill_count(0);
|
||||
}
|
||||
}
|
||||
@@ -526,7 +514,7 @@ void ItemCreator::clear_tool_item_if_invalid(ItemData& item) {
|
||||
|
||||
void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
if (this->item_parameter_table->is_item_rare(item) && !this->are_rare_drops_allowed()) {
|
||||
this->log.info("Restricted: item is rare, but rares not allowed");
|
||||
this->log.info_f("Restricted: item is rare, but rares not allowed");
|
||||
item.clear();
|
||||
return;
|
||||
}
|
||||
@@ -537,12 +525,12 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
// (HP/Resurrection and TP/Resurrection) only exist on BB.
|
||||
if (item.data1[0] == 1) {
|
||||
if ((item.data1[1] == 3) && (((item.data1[2] >= 0x33) && (item.data1[2] <= 0x38)) || (item.data1[2] == 0x61) || (item.data1[2] == 0x62))) {
|
||||
this->log.info("Restricted: restore units not allowed in Challenge mode");
|
||||
this->log.info_f("Restricted: restore units not allowed in Challenge mode");
|
||||
item.clear();
|
||||
return;
|
||||
}
|
||||
} else if (item.data1[0] == 4) {
|
||||
this->log.info("Restricted: meseta not allowed in Challenge mode");
|
||||
this->log.info_f("Restricted: meseta not allowed in Challenge mode");
|
||||
item.clear();
|
||||
return;
|
||||
}
|
||||
@@ -558,12 +546,12 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
break;
|
||||
case BattleRules::WeaponAndArmorMode::FORBID_RARES:
|
||||
if (this->item_parameter_table->is_item_rare(item)) {
|
||||
this->log.info("Restricted: rare weapons and armors not allowed");
|
||||
this->log.info_f("Restricted: rare weapons and armors not allowed");
|
||||
item.clear();
|
||||
}
|
||||
break;
|
||||
case BattleRules::WeaponAndArmorMode::FORBID_ALL:
|
||||
this->log.info("Restricted: weapons and armors not allowed");
|
||||
this->log.info_f("Restricted: weapons and armors not allowed");
|
||||
item.clear();
|
||||
break;
|
||||
default:
|
||||
@@ -572,24 +560,24 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
break;
|
||||
case 2:
|
||||
if (this->restrictions->mag_mode == BattleRules::MagMode::FORBID_ALL) {
|
||||
this->log.info("Restricted: mags not allowed");
|
||||
this->log.info_f("Restricted: mags not allowed");
|
||||
item.clear();
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (this->restrictions->tool_mode == BattleRules::ToolMode::FORBID_ALL) {
|
||||
this->log.info("Restricted: tools not allowed");
|
||||
this->log.info_f("Restricted: tools not allowed");
|
||||
item.clear();
|
||||
} else if (item.data1[1] == 2) {
|
||||
switch (this->restrictions->tech_disk_mode) {
|
||||
case BattleRules::TechDiskMode::ALLOW:
|
||||
break;
|
||||
case BattleRules::TechDiskMode::FORBID_ALL:
|
||||
this->log.info("Restricted: tech disks not allowed");
|
||||
this->log.info_f("Restricted: tech disks not allowed");
|
||||
item.clear();
|
||||
break;
|
||||
case BattleRules::TechDiskMode::LIMIT_LEVEL:
|
||||
this->log.info("Restricted: tech disk level limited to %hhu",
|
||||
this->log.info_f("Restricted: tech disk level limited to {}",
|
||||
static_cast<uint8_t>(this->restrictions->max_tech_level + 1));
|
||||
if (this->restrictions->max_tech_level == 0) {
|
||||
item.data1[2] = 0;
|
||||
@@ -601,13 +589,13 @@ void ItemCreator::clear_item_if_restricted(ItemData& item) const {
|
||||
throw logic_error("invalid tech disk mode");
|
||||
}
|
||||
} else if ((item.data1[1] == 9) && this->restrictions->forbid_scape_dolls) {
|
||||
this->log.info("Restricted: scape dolls not allowed");
|
||||
this->log.info_f("Restricted: scape dolls not allowed");
|
||||
item.clear();
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (this->restrictions->meseta_mode == BattleRules::MesetaMode::FORBID_ALL) {
|
||||
this->log.info("Restricted: meseta not allowed");
|
||||
this->log.info_f("Restricted: meseta not allowed");
|
||||
item.clear();
|
||||
}
|
||||
break;
|
||||
@@ -627,10 +615,10 @@ void ItemCreator::generate_common_item_variances(uint32_t area_norm, ItemData& i
|
||||
float f1 = 1.0 + this->pt->unit_max_stars_table.at(area_norm);
|
||||
float f2 = this->rand_float_0_1_from_crypt();
|
||||
uint8_t stars = static_cast<uint32_t>(f1 * f2) & 0xFF;
|
||||
this->log.info("Unit stars: %g * %g = %" PRIu32, f1, f2, stars);
|
||||
this->log.info_f("Unit stars: {:g} * {:g} = {}", f1, f2, stars);
|
||||
this->generate_common_unit_variances(stars, item);
|
||||
if (item.data1[2] == 0xFF) {
|
||||
this->log.info("Unit subtype not valid; clearing item");
|
||||
this->log.info_f("Unit subtype not valid; clearing item");
|
||||
item.clear();
|
||||
}
|
||||
} else {
|
||||
@@ -667,7 +655,7 @@ void ItemCreator::generate_common_armor_or_shield_type_and_variances(char area_n
|
||||
} else {
|
||||
item.data1[2] -= 3;
|
||||
}
|
||||
this->log.info("Armor/shield type: max(%02hhX + %02hhX + %02hhX - 3, 0) = %02hhX",
|
||||
this->log.info_f("Armor/shield type: max({:02X} + {:02X} + {:02X} - 3, 0) = {:02X}",
|
||||
area_norm, type, this->pt->armor_or_shield_type_bias, item.data1[2]);
|
||||
}
|
||||
|
||||
@@ -696,7 +684,7 @@ void ItemCreator::generate_common_tool_variances(uint32_t area_norm, ItemData& i
|
||||
if ((!is_v1_or_v2(this->logic_version) || (this->logic_version == Version::GC_NTE)) && (tool_class == 0x1A)) {
|
||||
tool_class = 0x73;
|
||||
}
|
||||
this->log.info("Generating tool with class %02hhX", tool_class);
|
||||
this->log.info_f("Generating tool with class {:02X}", tool_class);
|
||||
|
||||
// Note: This block was originally a separate function called
|
||||
// generate_common_tool_type
|
||||
@@ -711,7 +699,7 @@ void ItemCreator::generate_common_tool_variances(uint32_t area_norm, ItemData& i
|
||||
item.data1[1] = data.first;
|
||||
item.data1[2] = data.second;
|
||||
} catch (const out_of_range&) {
|
||||
this->log.info("Tool class is missing; skipping item generation");
|
||||
this->log.info_f("Tool class is missing; skipping item generation");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -744,9 +732,9 @@ void ItemCreator::generate_common_mag_variances(ItemData& item) {
|
||||
if (is_pre_v1(this->logic_version)) {
|
||||
item.data2[3] = 0x00;
|
||||
} else if (is_v1_or_v2(this->logic_version)) {
|
||||
item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x0E;
|
||||
item.data2[3] = this->rand_crypt->next() % 0x0E;
|
||||
} else {
|
||||
item.data2[3] = random_from_optional_crypt(this->opt_rand_crypt) % 0x12;
|
||||
item.data2[3] = this->rand_crypt->next() % 0x12;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -769,7 +757,7 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData&
|
||||
}
|
||||
}
|
||||
|
||||
this->log.info("Subtype table: %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX",
|
||||
this->log.info_f("Subtype table: {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X}",
|
||||
weapon_type_prob_table[0], weapon_type_prob_table[1], weapon_type_prob_table[2], weapon_type_prob_table[3],
|
||||
weapon_type_prob_table[4], weapon_type_prob_table[5], weapon_type_prob_table[6], weapon_type_prob_table[7],
|
||||
weapon_type_prob_table[8], weapon_type_prob_table[9], weapon_type_prob_table[10], weapon_type_prob_table[11],
|
||||
@@ -777,19 +765,19 @@ void ItemCreator::generate_common_weapon_variances(uint8_t area_norm, ItemData&
|
||||
|
||||
item.data1[1] = this->get_rand_from_weighted_tables_1d(weapon_type_prob_table);
|
||||
if (item.data1[1] == 0) {
|
||||
this->log.info("00 chosen from subtype table; skipping item");
|
||||
this->log.info_f("00 chosen from subtype table; skipping item");
|
||||
item.clear();
|
||||
} else {
|
||||
int8_t subtype_base = this->pt->subtype_base_table.at(item.data1[1] - 1);
|
||||
uint8_t area_length = this->pt->subtype_area_length_table.at(item.data1[1] - 1);
|
||||
this->log.info("Subtype table yielded %02hhX; subtype base is %hhd with area length %hhu", item.data1[1], subtype_base, area_length);
|
||||
this->log.info_f("Subtype table yielded {:02X}; subtype base is {} with area length {}", item.data1[1], subtype_base, area_length);
|
||||
if (subtype_base < 0) {
|
||||
item.data1[2] = (area_norm + subtype_base) / area_length;
|
||||
this->log.info("Resulting subtype: (%02hhX + %02hhX) / %02hhX = %02hhX", area_norm, subtype_base, area_length, item.data1[2]);
|
||||
this->log.info_f("Resulting subtype: ({:02X} + {:02X}) / {:02X} = {:02X}", area_norm, subtype_base, area_length, item.data1[2]);
|
||||
this->generate_common_weapon_grind(item, (area_norm + subtype_base) - (item.data1[2] * area_length));
|
||||
} else {
|
||||
item.data1[2] = subtype_base + (area_norm / area_length);
|
||||
this->log.info("Resulting subtype: %02hhX + (%02hhX / %02hhX) = %02hhX", subtype_base, area_norm, area_length, item.data1[2]);
|
||||
this->log.info_f("Resulting subtype: {:02X} + ({:02X} / {:02X}) = {:02X}", subtype_base, area_norm, area_length, item.data1[2]);
|
||||
this->generate_common_weapon_grind(item, area_norm - (area_norm / area_length) * area_length);
|
||||
}
|
||||
this->generate_common_weapon_bonuses(item, area_norm);
|
||||
@@ -802,7 +790,7 @@ void ItemCreator::generate_common_weapon_grind(ItemData& item, uint8_t offset_wi
|
||||
if (item.data1[0] == 0) {
|
||||
uint8_t offset = clamp<uint8_t>(offset_within_subtype_range, 0, 3);
|
||||
item.data1[3] = this->get_rand_from_weighted_tables_2d_vertical(this->pt->grind_prob_table, offset);
|
||||
this->log.info("Generated grind %02hhX from offset within subtype range %02hhX", item.data1[3], offset_within_subtype_range);
|
||||
this->log.info_f("Generated grind {:02X} from offset within subtype range {:02X}", item.data1[3], offset_within_subtype_range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -811,18 +799,18 @@ void ItemCreator::generate_common_weapon_special(ItemData& item, uint8_t area_no
|
||||
return;
|
||||
}
|
||||
if (this->item_parameter_table->is_item_rare(item)) {
|
||||
this->log.info("Item is rare; skipping special generation");
|
||||
this->log.info_f("Item is rare; skipping special generation");
|
||||
return;
|
||||
}
|
||||
uint8_t special_mult = this->pt->special_mult.at(area_norm);
|
||||
if (special_mult == 0) {
|
||||
this->log.info("Special multiplier is zero for area_norm %02hhX; skipping special generation", area_norm);
|
||||
this->log.info_f("Special multiplier is zero for area_norm {:02X}; skipping special generation", area_norm);
|
||||
return;
|
||||
}
|
||||
uint8_t det = this->rand_int(100);
|
||||
uint8_t prob = this->pt->special_percent.at(area_norm);
|
||||
if (det >= prob) {
|
||||
this->log.info("Special not chosen (%02hhX > %02hhX)", det, prob);
|
||||
this->log.info_f("Special not chosen ({:02X} > {:02X})", det, prob);
|
||||
return;
|
||||
}
|
||||
item.data1[4] = this->choose_weapon_special(special_mult * this->rand_float_0_1_from_crypt());
|
||||
@@ -830,25 +818,25 @@ void ItemCreator::generate_common_weapon_special(ItemData& item, uint8_t area_no
|
||||
|
||||
uint8_t ItemCreator::choose_weapon_special(uint8_t det) {
|
||||
if (det >= 4) {
|
||||
this->log.info("Special not chosen (det %02hhX >= 4)", det);
|
||||
this->log.info_f("Special not chosen (det {:02X} >= 4)", det);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const uint8_t maxes[4] = {8, 10, 11, 11};
|
||||
uint8_t det2 = this->rand_int(maxes[det]);
|
||||
this->log.info("Choosing special with det %02hhX and det2 %02hhX", det, det2);
|
||||
this->log.info_f("Choosing special with det {:02X} and det2 {:02X}", det, det2);
|
||||
size_t index = 0;
|
||||
for (size_t z = 1; z < this->item_parameter_table->num_specials; z++) {
|
||||
if (det + 1 == this->item_parameter_table->get_special_stars(z)) {
|
||||
if (index == det2) {
|
||||
this->log.info("Chose special %02zX", z);
|
||||
this->log.info_f("Chose special {:02X}", z);
|
||||
return z;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this->log.info("No special was eligible");
|
||||
this->log.info_f("No special was eligible");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -922,7 +910,7 @@ void ItemCreator::generate_common_unit_variances(uint8_t stars, ItemData& item)
|
||||
|
||||
const auto& results = this->unit_results_by_star_count.at(stars);
|
||||
if (results.empty()) {
|
||||
this->log.info("There are no available units with %hhu stars", stars);
|
||||
this->log.info_f("There are no available units with {} stars", stars);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -934,7 +922,7 @@ void ItemCreator::generate_common_unit_variances(uint8_t stars, ItemData& item)
|
||||
const auto& def = this->item_parameter_table->get_unit(result.unit);
|
||||
item.set_unit_bonus(def.modifier_amount * result.modifier);
|
||||
}
|
||||
this->log.info("Generated unit %02hhX with modifier %hhd, from %zu choices with %hhu stars",
|
||||
this->log.info_f("Generated unit {:02X} with modifier {}, from {} choices with {} stars",
|
||||
result.unit, result.modifier, results.size(), stars);
|
||||
}
|
||||
|
||||
@@ -1076,7 +1064,7 @@ void ItemCreator::generate_armor_shop_armors(vector<ItemData>& shop, size_t play
|
||||
pt.push(src_table.first[z].value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||
ItemData item;
|
||||
@@ -1120,7 +1108,7 @@ void ItemCreator::generate_armor_shop_shields(vector<ItemData>& shop, size_t pla
|
||||
pt.push(src_table.first[z].value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||
ItemData item;
|
||||
@@ -1163,7 +1151,7 @@ void ItemCreator::generate_armor_shop_units(vector<ItemData>& shop, size_t playe
|
||||
pt.push(src_table.first[z].value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
for (size_t items_generated = 0; items_generated < num_items;) {
|
||||
ItemData item;
|
||||
@@ -1266,7 +1254,7 @@ void ItemCreator::generate_rare_tool_shop_recovery_items(
|
||||
pt.push(e.value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
size_t effective_num_items = num_items;
|
||||
size_t items_generated = 0;
|
||||
@@ -1309,7 +1297,7 @@ void ItemCreator::generate_tool_shop_tech_disks(vector<ItemData>& shop, size_t p
|
||||
pt.push(e.value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
static const array<uint8_t, 0x13> tech_num_map = {
|
||||
0x00, 0x03, 0x06, 0x0F, 0x10, 0x0D, 0x0A, 0x0B, 0x0C, 0x01, 0x04, 0x07,
|
||||
@@ -1410,7 +1398,7 @@ vector<ItemData> ItemCreator::generate_weapon_shop_contents(size_t player_level)
|
||||
pt.push(e.value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
vector<ItemData> shop;
|
||||
while (shop.size() < num_items) {
|
||||
@@ -1608,7 +1596,7 @@ void ItemCreator::generate_weapon_shop_item_special(ItemData& item, size_t playe
|
||||
|
||||
// Note: The original code shuffles pt and then pops a single value from it.
|
||||
// For simplicity, we just sample a single value (and don't pop it) instead.
|
||||
switch (pt.sample(this->opt_rand_crypt)) {
|
||||
switch (pt.sample(this->rand_crypt)) {
|
||||
case 0:
|
||||
item.data1[4] = 0;
|
||||
break;
|
||||
@@ -1660,7 +1648,7 @@ void ItemCreator::generate_weapon_shop_item_bonus1(
|
||||
|
||||
// Note: The original code shuffles pt and then pops a single value from it.
|
||||
// For simplicity, we just sample a single value (and don't pop it) instead.
|
||||
item.data1[6] = pt.sample(this->opt_rand_crypt);
|
||||
item.data1[6] = pt.sample(this->rand_crypt);
|
||||
if (item.data1[6] == 0) {
|
||||
item.data1[7] = 0;
|
||||
|
||||
@@ -1701,7 +1689,7 @@ void ItemCreator::generate_weapon_shop_item_bonus2(ItemData& item, size_t player
|
||||
pt.push(e.value);
|
||||
}
|
||||
}
|
||||
pt.shuffle(this->opt_rand_crypt);
|
||||
pt.shuffle(this->rand_crypt);
|
||||
|
||||
do {
|
||||
item.data1[8] = pt.pop();
|
||||
@@ -1782,14 +1770,14 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
bool favored = item.data1[1] == favored_weapon_by_section_id[section_id];
|
||||
ssize_t luck = 0;
|
||||
|
||||
this->log.info("Applying tekker deltas for %s weapon", favored ? "favored" : "non-favored");
|
||||
this->log.info_f("Applying tekker deltas for {} weapon", favored ? "favored" : "non-favored");
|
||||
|
||||
// Adjust the weapon's special
|
||||
{
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_special_upgrade_prob_table(section_id, favored);
|
||||
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info("(Special) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||
this->log.info_f("(Special) Delta index {}, delta {}", delta_index, delta);
|
||||
// Note: The original code checks specifically for -1 and +1 here, but the
|
||||
// data files only include delta_indexes 4, 5, and 6 (which correspond to -1,
|
||||
// 0, and 1) anyway, so we just check for positive and negative numbers
|
||||
@@ -1809,29 +1797,29 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
this->item_parameter_table->get_special(new_special).type) {
|
||||
item.data1[4] = new_special;
|
||||
} else {
|
||||
this->log.info("(Special) Delta canceled because it would change special category");
|
||||
this->log.info_f("(Special) Delta canceled because it would change special category");
|
||||
}
|
||||
}
|
||||
} catch (const 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);
|
||||
this->log.info("(Special) Luck is now %zd", luck);
|
||||
this->log.info_f("(Special) Luck is now {}", luck);
|
||||
}
|
||||
|
||||
// Adjust the weapon's grind if it's not rare
|
||||
if (!this->item_parameter_table->is_item_rare(item)) {
|
||||
const auto& weapon_def = this->item_parameter_table->get_weapon(item.data1[1], item.data1[2]);
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_grind_delta_prob_table(section_id, favored);
|
||||
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info("(Grind) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||
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);
|
||||
luck += this->tekker_adjustment_set->get_luck_for_grind_delta(delta_index);
|
||||
this->log.info("(Grind) Luck is now %zd", luck);
|
||||
this->log.info_f("(Grind) Luck is now {}", luck);
|
||||
} else {
|
||||
this->log.info("(Grind) Item is rare; skipping grind adjustment");
|
||||
this->log.info_f("(Grind) Item is rare; skipping grind adjustment");
|
||||
}
|
||||
|
||||
// Adjust the weapon's bonuses
|
||||
@@ -1839,9 +1827,9 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
const auto& prob_table = this->tekker_adjustment_set->get_bonus_delta_prob_table(section_id, favored);
|
||||
// Note: The original code really does use the same delta for all three
|
||||
// bonuses.
|
||||
uint8_t delta_index = prob_table.sample(this->opt_rand_crypt);
|
||||
uint8_t delta_index = prob_table.sample(this->rand_crypt);
|
||||
int8_t delta = delta_table.at(delta_index);
|
||||
this->log.info("(Bonuses) Delta index %hhu, delta %hhd", delta_index, delta);
|
||||
this->log.info_f("(Bonuses) Delta index {}, delta {}", delta_index, delta);
|
||||
// Note: The original code doesn't check if there's actually a bonus in each
|
||||
// slot before incrementing the values. Presumably there's a check later
|
||||
// that will clear any invalid bonuses, but we don't have such a check, so
|
||||
@@ -1852,7 +1840,7 @@ ssize_t ItemCreator::apply_tekker_deltas(ItemData& item, uint8_t section_id) {
|
||||
}
|
||||
}
|
||||
luck += this->tekker_adjustment_set->get_luck_for_bonus_delta(delta_index);
|
||||
this->log.info("(Bonuses) Luck is now %zd", luck);
|
||||
this->log.info_f("(Bonuses) Luck is now {}", luck);
|
||||
}
|
||||
|
||||
return luck;
|
||||
|
||||
+2
-4
@@ -24,12 +24,10 @@ public:
|
||||
GameMode mode,
|
||||
uint8_t difficulty,
|
||||
uint8_t section_id,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||
std::shared_ptr<const BattleRules> restrictions = nullptr);
|
||||
~ItemCreator() = default;
|
||||
|
||||
void set_random_crypt(std::shared_ptr<PSOLFGEncryption> new_random_crypt);
|
||||
|
||||
struct DropResult {
|
||||
ItemData item;
|
||||
bool is_from_rare_table = false;
|
||||
@@ -101,7 +99,7 @@ private:
|
||||
// [0x0E] - apparently unused
|
||||
// [0x0F] - which common weapon special to generate
|
||||
// [0x10] - apparently unused
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||
|
||||
bool are_rare_drops_allowed() const;
|
||||
uint8_t normalize_area_number(uint8_t area) const;
|
||||
|
||||
+4
-4
@@ -797,13 +797,13 @@ ItemData ItemData::from_primary_identifier(const StackLimits& limits, uint32_t p
|
||||
}
|
||||
|
||||
string ItemData::hex() const {
|
||||
return phosg::string_printf("%08" PRIX32 " %08" PRIX32 " %08" PRIX32 " (%08" PRIX32 ") %08" PRIX32,
|
||||
this->data1db[0].load(), this->data1db[1].load(), this->data1db[2].load(), this->id.load(), this->data2db.load());
|
||||
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 {
|
||||
auto ret = phosg::string_printf("%08" PRIX32 "%08" PRIX32 "%08" PRIX32 "%08" PRIX32,
|
||||
this->data1db[0].load(), this->data1db[1].load(), this->data1db[2].load(), this->data2db.load());
|
||||
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) {
|
||||
offset += (offset & 1) ? 1 : 2;
|
||||
|
||||
+133
-133
@@ -99,7 +99,7 @@ const array<const char*, 0x11> name_for_s_rank_special = {
|
||||
|
||||
std::string ItemNameIndex::describe_item(const ItemData& item, bool include_color_escapes, bool hide_mag_stats) const {
|
||||
if (item.data1[0] == 0x04) {
|
||||
return phosg::string_printf("%s%" PRIu32 " Meseta", include_color_escapes ? "$C7" : "", item.data2d.load());
|
||||
return std::format("{}{} Meseta", include_color_escapes ? "$C7" : "", item.data2d);
|
||||
}
|
||||
|
||||
vector<string> ret_tokens;
|
||||
@@ -120,7 +120,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
try {
|
||||
ret_tokens.emplace_back(name_for_weapon_special.at(special_id));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("!SP:%02hhX", special_id));
|
||||
ret_tokens.emplace_back(std::format("!SP:{:02X}", special_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +128,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
try {
|
||||
ret_tokens.emplace_back(name_for_s_rank_special.at(item.data1[2]));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("!SSP:%02hhX", item.data1[2]));
|
||||
ret_tokens.emplace_back(std::format("!SSP:{:02X}", item.data1[2]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,27 +149,27 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
technique_name = tech_id_to_name.at(item.data1[4]);
|
||||
technique_name[0] = toupper(technique_name[0]);
|
||||
} catch (const out_of_range&) {
|
||||
technique_name = phosg::string_printf("!TD:%02hhX", item.data1[4]);
|
||||
technique_name = std::format("!TD:{:02X}", item.data1[4]);
|
||||
}
|
||||
// Hide the level for Reverser and Ryuker, unless the level isn't 1
|
||||
if ((item.data1[2] == 0) && ((item.data1[4] == 0x0E) || (item.data1[4] == 0x11))) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("Disk:%s", technique_name.c_str()));
|
||||
ret_tokens.emplace_back(std::format("Disk:{}", technique_name));
|
||||
} else {
|
||||
ret_tokens.emplace_back(phosg::string_printf("Disk:%s Lv.%d", technique_name.c_str(), item.data1[2] + 1));
|
||||
ret_tokens.emplace_back(std::format("Disk:{} Lv.{}", technique_name, item.data1[2] + 1));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
auto meta = this->primary_identifier_index.at(primary_identifier);
|
||||
ret_tokens.emplace_back(meta->name);
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("!ID:%08" PRIX32, primary_identifier));
|
||||
ret_tokens.emplace_back(std::format("!ID:{:08X}", primary_identifier));
|
||||
}
|
||||
}
|
||||
|
||||
if (item.data1[0] == 0x00) {
|
||||
// For weapons, add the grind and bonuses, or S-rank name if applicable
|
||||
if (item.data1[3] > 0) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("+%hhu", item.data1[3]));
|
||||
ret_tokens.emplace_back(std::format("+{}", item.data1[3]));
|
||||
}
|
||||
|
||||
if (item.is_s_rank_weapon()) {
|
||||
@@ -220,9 +220,9 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
}
|
||||
if (which & 0x80) {
|
||||
uint16_t kill_count = ((which << 8) & 0x7F00) | (value & 0xFF);
|
||||
ret_tokens.emplace_back(phosg::string_printf("K:%hu", kill_count));
|
||||
ret_tokens.emplace_back(std::format("K:{}", kill_count));
|
||||
} else if (which > 5) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("!PC:%02hhX%02hhX", which, value));
|
||||
ret_tokens.emplace_back(std::format("!PC:{:02X}{:02X}", which, value));
|
||||
} else {
|
||||
bonuses[which - 1] = value;
|
||||
}
|
||||
@@ -232,11 +232,11 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
bool should_highlight_hit = include_color_escapes && (bonuses[4] > 0);
|
||||
const char* color_prefix = include_color_escapes ? "$C7" : "";
|
||||
if (should_include_hit) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("%s%hhd/%hhd/%hhd/%hhd/%s%hhd",
|
||||
ret_tokens.emplace_back(std::format("{}{}/{}/{}/{}/{}{}",
|
||||
color_prefix, bonuses[0], bonuses[1], bonuses[2], bonuses[3],
|
||||
(should_highlight_hit ? "$C6" : ""), bonuses[4]));
|
||||
} else {
|
||||
ret_tokens.emplace_back(phosg::string_printf("%s%hhd/%hhd/%hhd/%hhd",
|
||||
ret_tokens.emplace_back(std::format("{}{}/{}/{}/{}",
|
||||
color_prefix, bonuses[0], bonuses[1], bonuses[2], bonuses[3]));
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,7 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
} else if (modifier <= -3) {
|
||||
ret_tokens.back().append("--");
|
||||
} else if (modifier != 0) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("!MD:%04hX", modifier));
|
||||
ret_tokens.emplace_back(std::format("!MD:{:04X}", modifier));
|
||||
}
|
||||
|
||||
} else { // Armor/shields
|
||||
@@ -263,22 +263,22 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
if (item.data1[5] == 1) {
|
||||
ret_tokens.emplace_back("(1 slot)");
|
||||
} else {
|
||||
ret_tokens.emplace_back(phosg::string_printf("(%hhu slots)", item.data1[5]));
|
||||
ret_tokens.emplace_back(std::format("({} slots)", item.data1[5]));
|
||||
}
|
||||
}
|
||||
if (item.data1w[3] != 0) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("+%hdDEF",
|
||||
static_cast<int16_t>(item.data1w[3].load())));
|
||||
ret_tokens.emplace_back(std::format("+{}DEF",
|
||||
static_cast<int16_t>(item.data1w[3])));
|
||||
}
|
||||
if (item.data1w[4] != 0) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("+%hdEVP",
|
||||
static_cast<int16_t>(item.data1w[4].load())));
|
||||
ret_tokens.emplace_back(std::format("+{}EVP",
|
||||
static_cast<int16_t>(item.data1w[4])));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!hide_mag_stats && (item.data1[0] == 0x02)) {
|
||||
// For mags, add tons of info
|
||||
ret_tokens.emplace_back(phosg::string_printf("LV%hhu", item.data1[2]));
|
||||
ret_tokens.emplace_back(std::format("LV{}", item.data1[2]));
|
||||
|
||||
uint16_t def = item.data1w[2];
|
||||
uint16_t pow = item.data1w[3];
|
||||
@@ -288,16 +288,16 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
uint16_t level = stat / 100;
|
||||
uint8_t partial = stat % 100;
|
||||
if (partial == 0) {
|
||||
return phosg::string_printf("%hu", level);
|
||||
return std::format("{}", level);
|
||||
} else if (partial % 10 == 0) {
|
||||
return phosg::string_printf("%hu.%hhu", level, static_cast<uint8_t>(partial / 10));
|
||||
return std::format("{}.{}", level, static_cast<uint8_t>(partial / 10));
|
||||
} else {
|
||||
return phosg::string_printf("%hu.%02hhu", level, partial);
|
||||
return std::format("{}.{:02}", level, partial);
|
||||
}
|
||||
};
|
||||
ret_tokens.emplace_back(format_stat(def) + "/" + format_stat(pow) + "/" + format_stat(dex) + "/" + format_stat(mind));
|
||||
ret_tokens.emplace_back(phosg::string_printf("%hhu%%", item.data2[0]));
|
||||
ret_tokens.emplace_back(phosg::string_printf("%hhuIQ", item.data2[1]));
|
||||
ret_tokens.emplace_back(std::format("{}%", item.data2[0]));
|
||||
ret_tokens.emplace_back(std::format("{}IQ", item.data2[1]));
|
||||
|
||||
uint8_t flags = item.data2[2];
|
||||
if (flags & 7) {
|
||||
@@ -332,15 +332,15 @@ std::string ItemNameIndex::describe_item(const ItemData& item, bool include_colo
|
||||
}
|
||||
|
||||
try {
|
||||
ret_tokens.emplace_back(phosg::string_printf("(%s)", name_for_mag_color.at(item.data2[3])));
|
||||
ret_tokens.emplace_back(std::format("({})", name_for_mag_color.at(item.data2[3])));
|
||||
} catch (const out_of_range&) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("(!CL:%02hhX)", item.data2[3]));
|
||||
ret_tokens.emplace_back(std::format("(!CL:{:02X})", item.data2[3]));
|
||||
}
|
||||
|
||||
} else if (item.data1[0] == 0x03) {
|
||||
// For tools, add the amount (if applicable)
|
||||
if (item.max_stack_size(*this->limits) > 1) {
|
||||
ret_tokens.emplace_back(phosg::string_printf("x%hhu", item.data1[5]));
|
||||
ret_tokens.emplace_back(std::format("x{}", item.data1[5]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,11 +374,11 @@ ItemData ItemNameIndex::parse_item_description(const std::string& desc) const {
|
||||
ret = ItemData::from_data(phosg::parse_data_string(desc));
|
||||
} catch (const exception& ed) {
|
||||
if (strcmp(e1.what(), e2.what())) {
|
||||
throw runtime_error(phosg::string_printf("cannot parse item description \"%s\" (as text 1: %s) (as text 2: %s) (as data: %s)",
|
||||
desc.c_str(), e1.what(), e2.what(), ed.what()));
|
||||
throw 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(phosg::string_printf("cannot parse item description \"%s\" (as text: %s) (as data: %s)",
|
||||
desc.c_str(), e1.what(), ed.what()));
|
||||
throw runtime_error(std::format("cannot parse item description \"{}\" (as text: {}) (as data: {})",
|
||||
desc, e1.what(), ed.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,13 +394,13 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
ret.data2d = 0;
|
||||
|
||||
string desc = phosg::tolower(description);
|
||||
if (phosg::ends_with(desc, " meseta")) {
|
||||
if (desc.ends_with(" meseta")) {
|
||||
ret.data1[0] = 0x04;
|
||||
ret.data2d = stol(desc, nullptr, 10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (phosg::starts_with(desc, "disk:")) {
|
||||
if (desc.starts_with("disk:")) {
|
||||
auto tokens = phosg::split(desc, ' ');
|
||||
tokens[0] = tokens[0].substr(5); // Trim off "disk:"
|
||||
if ((tokens[0] == "reverser") || (tokens[0] == "ryuker")) {
|
||||
@@ -413,7 +413,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
if (tokens.size() != 2) {
|
||||
throw runtime_error("invalid tech disk format");
|
||||
}
|
||||
if (!phosg::starts_with(tokens[1], "lv.")) {
|
||||
if (!tokens[1].starts_with("lv.")) {
|
||||
throw runtime_error("invalid tech disk level");
|
||||
}
|
||||
uint8_t tech = technique_for_name(tokens[0]);
|
||||
@@ -426,11 +426,11 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool is_wrapped = phosg::starts_with(desc, "wrapped ");
|
||||
bool is_wrapped = desc.starts_with("wrapped ");
|
||||
if (is_wrapped) {
|
||||
desc = desc.substr(8);
|
||||
}
|
||||
bool is_unidentified = phosg::starts_with(desc, "?");
|
||||
bool is_unidentified = desc.starts_with("?");
|
||||
if (is_unidentified) {
|
||||
size_t z;
|
||||
for (z = 1; z < desc.size(); z++) {
|
||||
@@ -450,7 +450,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
}
|
||||
string prefix = phosg::tolower(name_for_weapon_special[z]);
|
||||
prefix += ' ';
|
||||
if (phosg::starts_with(desc, prefix)) {
|
||||
if (desc.starts_with(prefix)) {
|
||||
weapon_special = z;
|
||||
desc = desc.substr(prefix.size());
|
||||
break;
|
||||
@@ -464,7 +464,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
// then we'll see Sange & Yasha first, which we should skip.
|
||||
size_t lookback = 0;
|
||||
while (lookback < 4) {
|
||||
if (name_it != this->name_index.end() && phosg::starts_with(desc, name_it->first)) {
|
||||
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");
|
||||
@@ -478,7 +478,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
}
|
||||
|
||||
desc = desc.substr(name_it->first.size());
|
||||
if (phosg::starts_with(desc, " ")) {
|
||||
if (desc.starts_with(" ")) {
|
||||
desc = desc.substr(1);
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (phosg::starts_with(token, "+")) {
|
||||
if (token.starts_with("+")) {
|
||||
token = token.substr(1);
|
||||
ret.data1[3] = stoul(token, nullptr, 10);
|
||||
|
||||
@@ -513,7 +513,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
char ch = toupper(token[z]);
|
||||
const char* pos = strchr(s_rank_name_characters, ch);
|
||||
if (!pos) {
|
||||
throw runtime_error(phosg::string_printf("s-rank name contains invalid character %02hhX (%c)", ch, ch));
|
||||
throw runtime_error(std::format("s-rank name contains invalid character {:02X} ({})", ch, ch));
|
||||
}
|
||||
char_indexes[z] = (pos - s_rank_name_characters);
|
||||
}
|
||||
@@ -563,12 +563,12 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
for (const auto& token : phosg::split(desc, ' ')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
} else if (!phosg::starts_with(token, "+")) {
|
||||
} else if (!token.starts_with("+")) {
|
||||
throw runtime_error("invalid armor/shield modifier");
|
||||
}
|
||||
if (phosg::ends_with(token, "def")) {
|
||||
if (token.ends_with("def")) {
|
||||
ret.data1w[3] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||
} else if (phosg::ends_with(token, "evp")) {
|
||||
} else if (token.ends_with("evp")) {
|
||||
ret.data1w[4] = static_cast<uint16_t>(stol(token.substr(1, token.size() - 4), nullptr, 10));
|
||||
} else {
|
||||
ret.data1[5] = stoul(token.substr(1), nullptr, 10);
|
||||
@@ -584,7 +584,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
for (const auto& token : phosg::split(desc, ' ')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
} else if (phosg::starts_with(token, "pb:")) { // Photon blasts
|
||||
} 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");
|
||||
@@ -602,9 +602,9 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
for (const auto& pb_token : pb_tokens) {
|
||||
ret.add_mag_photon_blast(name_to_pb_num.at(pb_token));
|
||||
}
|
||||
} else if (phosg::ends_with(token, "%")) { // Synchro
|
||||
} else if (token.ends_with("%")) { // Synchro
|
||||
ret.data2[0] = stoul(token.substr(0, token.size() - 1), nullptr, 10);
|
||||
} else if (phosg::ends_with(token, "iq")) { // IQ
|
||||
} else if (token.ends_with("iq")) { // IQ
|
||||
ret.data2[1] = stoul(token.substr(0, token.size() - 2), nullptr, 10);
|
||||
} else if (!token.empty() && isdigit(token[0])) { // Stats
|
||||
auto s_tokens = phosg::split(token, '/');
|
||||
@@ -636,7 +636,7 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
}
|
||||
} else if (ret.data1[0] == 0x03) {
|
||||
if (ret.max_stack_size(*this->limits) > 1) {
|
||||
if (phosg::starts_with(desc, "x")) {
|
||||
if (desc.starts_with("x")) {
|
||||
ret.data1[5] = stoul(desc.substr(1), nullptr, 10);
|
||||
} else {
|
||||
ret.data1[5] = 1;
|
||||
@@ -662,11 +662,11 @@ ItemData ItemNameIndex::parse_item_description_phase(const std::string& descript
|
||||
void ItemNameIndex::print_table(FILE* stream) const {
|
||||
auto pmt = this->item_parameter_table;
|
||||
|
||||
fprintf(stream, "WEAPON => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB PJ 1X 1Y 2X 2Y CL A1 A2 A3 A4 A5 TB BF V1 ST* USL ---DIVISOR--- NAME\n");
|
||||
phosg::fwrite_fmt(stream, "WEAPON => ---ID--- TYPE SKIN POINTS FLAG ATPLO ATPHI ATPRQ MSTRQ ATARQ -MST- GND PH SP ATA SB PJ 1X 1Y 2X 2Y CL A1 A2 A3 A4 A5 TB BF V1 ST* USL ---DIVISOR--- NAME\n");
|
||||
for (size_t data1_1 = 0; data1_1 < pmt->num_weapon_classes; data1_1++) {
|
||||
uint8_t v1_replacement = pmt->get_weapon_v1_replacement(data1_1);
|
||||
float sale_divisor = pmt->get_sale_divisor(0x00, data1_1);
|
||||
string divisor_str = phosg::string_printf("%g", sale_divisor);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_weapons_in_class(data1_1);
|
||||
@@ -681,20 +681,20 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
|
||||
fprintf(stream, "00%02zX%02zX => %08" PRIX32 " %04hX %04hX %6" PRIu32 " %04hX %5hu %5hu %5hu %5hu %5hu %5hu %3hhu %02hhX %02hhX %3hhu %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %2hhu* %s %s %s\n",
|
||||
phosg::fwrite_fmt(stream, "00{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:5} {:5} {:5} {:5} {:5} {:3} {:02X} {:02X} {:3} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:02X} {:2}* {} {} {}\n",
|
||||
data1_1,
|
||||
data1_2,
|
||||
w.base.id.load(),
|
||||
w.base.type.load(),
|
||||
w.base.skin.load(),
|
||||
w.base.team_points.load(),
|
||||
w.class_flags.load(),
|
||||
w.atp_min.load(),
|
||||
w.atp_max.load(),
|
||||
w.atp_required.load(),
|
||||
w.mst_required.load(),
|
||||
w.ata_required.load(),
|
||||
w.mst.load(),
|
||||
w.base.id,
|
||||
w.base.type,
|
||||
w.base.skin,
|
||||
w.base.team_points,
|
||||
w.class_flags,
|
||||
w.atp_min,
|
||||
w.atp_max,
|
||||
w.atp_required,
|
||||
w.mst_required,
|
||||
w.ata_required,
|
||||
w.mst,
|
||||
w.max_grind,
|
||||
w.photon,
|
||||
w.special,
|
||||
@@ -716,15 +716,15 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
v1_replacement,
|
||||
stars,
|
||||
is_unsealable ? "YES" : " no",
|
||||
divisor_str.c_str(),
|
||||
name.c_str());
|
||||
divisor_str,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "ARMOR => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB TB FT A4 ST* ---DIVISOR--- NAME\n");
|
||||
phosg::fwrite_fmt(stream, "ARMOR => ---ID--- TYPE SKIN POINTS -DFP- -EVP- BP BE FLAG LVL EFR ETH EIC EDK ELT DFR EVR SB TB 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 = phosg::string_printf("%g", sale_divisor);
|
||||
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);
|
||||
@@ -738,18 +738,18 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
|
||||
fprintf(stream, "01%02zX%02zX => %08" PRIX32 " %04hX %04hX %6" PRIu32 " %5hu %5hu %02hhX %02hhX %04hX %3hhu %3hhu %3hhu %3hhu %3hhu %3hhu %3hhu %3hhu %02hhX %02hhX %02hhX %02hhX %2hhu* %s %s\n",
|
||||
phosg::fwrite_fmt(stream, "01{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:5} {:02X} {:02X} {:04X} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:3} {:02X} {:02X} {:02X} {:02X} {:2}* {} {}\n",
|
||||
data1_1,
|
||||
data1_2,
|
||||
a.base.id.load(),
|
||||
a.base.type.load(),
|
||||
a.base.skin.load(),
|
||||
a.base.team_points.load(),
|
||||
a.dfp.load(),
|
||||
a.evp.load(),
|
||||
a.base.id,
|
||||
a.base.type,
|
||||
a.base.skin,
|
||||
a.base.team_points,
|
||||
a.dfp,
|
||||
a.evp,
|
||||
a.block_particle,
|
||||
a.block_effect,
|
||||
a.class_flags.load(),
|
||||
a.class_flags,
|
||||
static_cast<uint8_t>(a.required_level + 1),
|
||||
a.efr,
|
||||
a.eth,
|
||||
@@ -763,15 +763,15 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
a.flags_type,
|
||||
a.unknown_a4,
|
||||
stars,
|
||||
divisor_str.c_str(),
|
||||
name.c_str());
|
||||
divisor_str,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "UNIT => ---ID--- TYPE SKIN POINTS STAT COUNT ST-MOD ST* ---DIVISOR--- NAME\n");
|
||||
phosg::fwrite_fmt(stream, "UNIT => ---ID--- TYPE SKIN POINTS STAT COUNT ST-MOD ST* ---DIVISOR--- NAME\n");
|
||||
{
|
||||
float sale_divisor = pmt->get_sale_divisor(0x01, 0x03);
|
||||
string divisor_str = phosg::string_printf("%g", sale_divisor);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_units();
|
||||
@@ -785,29 +785,29 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[2] = data1_2;
|
||||
string name = this->describe_item(item);
|
||||
|
||||
fprintf(stream, "0103%02zX => %08" PRIX32 " %04hX %04hX %6" PRIu32 " %04hX %5hu %6hd %2hhu* %s %s\n",
|
||||
phosg::fwrite_fmt(stream, "0103{:02X} => {:08X} {:04X} {:04X} {:6} {:04X} {:5} {:6} {:2}* {} {}\n",
|
||||
data1_2,
|
||||
u.base.id.load(),
|
||||
u.base.type.load(),
|
||||
u.base.skin.load(),
|
||||
u.base.team_points.load(),
|
||||
u.stat.load(),
|
||||
u.stat_amount.load(),
|
||||
u.modifier_amount.load(),
|
||||
u.base.id,
|
||||
u.base.type,
|
||||
u.base.skin,
|
||||
u.base.team_points,
|
||||
u.stat,
|
||||
u.stat_amount,
|
||||
u.modifier_amount,
|
||||
stars,
|
||||
divisor_str.c_str(),
|
||||
name.c_str());
|
||||
divisor_str,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "MAG => ---ID--- TYPE SKIN POINTS FTBL PB AC E1 E2 E3 E4 C1 C2 C3 C4 FLAG ---DIVISOR--- NAME\n");
|
||||
phosg::fwrite_fmt(stream, "MAG => ---ID--- TYPE SKIN POINTS FTBL PB AC E1 E2 E3 E4 C1 C2 C3 C4 FLAG ---DIVISOR--- NAME\n");
|
||||
{
|
||||
size_t data1_1_limit = pmt->num_mags();
|
||||
for (size_t data1_1 = 0; data1_1 < data1_1_limit; data1_1++) {
|
||||
const auto& m = pmt->get_mag(data1_1);
|
||||
|
||||
float sale_divisor = pmt->get_sale_divisor(0x02, data1_1);
|
||||
string divisor_str = phosg::string_printf("%g", sale_divisor);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
ItemData item;
|
||||
@@ -816,13 +816,13 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.data1[2] = 0x00;
|
||||
string name = this->describe_item(item);
|
||||
|
||||
fprintf(stream, "02%02zX00 => %08" PRIX32 " %04hX %04hX %6" PRIu32 " %04hX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %02hhX %04hX %s %s\n",
|
||||
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,
|
||||
m.base.id.load(),
|
||||
m.base.type.load(),
|
||||
m.base.skin.load(),
|
||||
m.base.team_points.load(),
|
||||
m.feed_table.load(),
|
||||
m.base.id,
|
||||
m.base.type,
|
||||
m.base.skin,
|
||||
m.base.team_points,
|
||||
m.feed_table,
|
||||
m.photon_blast,
|
||||
m.activation,
|
||||
m.on_pb_full,
|
||||
@@ -833,16 +833,16 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
m.on_low_hp_flag,
|
||||
m.on_death_flag,
|
||||
m.on_boss_flag,
|
||||
m.class_flags.load(),
|
||||
divisor_str.c_str(),
|
||||
name.c_str());
|
||||
m.class_flags,
|
||||
divisor_str,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "TOOL => ---ID--- TYPE SKIN POINTS COUNT TECH -COST- ITEMFLAG ---DIVISOR--- NAME\n");
|
||||
phosg::fwrite_fmt(stream, "TOOL => ---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 = phosg::string_printf("%g", sale_divisor);
|
||||
string divisor_str = std::format("{:g}", sale_divisor);
|
||||
divisor_str.resize(13, ' ');
|
||||
|
||||
size_t data1_2_limit = pmt->num_tools_in_class(data1_1);
|
||||
@@ -856,34 +856,34 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
item.set_tool_item_amount(*this->limits, 1);
|
||||
string name = this->describe_item(item);
|
||||
|
||||
fprintf(stream, "03%02zX%02zX => %08" PRIX32 " %04hX %04hX %6" PRIu32 " %5hu %04hX %6" PRId32 " %08" PRIX32 " %s %s\n",
|
||||
phosg::fwrite_fmt(stream, "03{:02X}{:02X} => {:08X} {:04X} {:04X} {:6} {:5} {:04X} {:6} {:08X} {} {}\n",
|
||||
data1_1,
|
||||
data1_2,
|
||||
t.base.id.load(),
|
||||
t.base.type.load(),
|
||||
t.base.skin.load(),
|
||||
t.base.team_points.load(),
|
||||
t.amount.load(),
|
||||
t.tech.load(),
|
||||
t.cost.load(),
|
||||
t.item_flags.load(),
|
||||
divisor_str.c_str(),
|
||||
name.c_str());
|
||||
t.base.id,
|
||||
t.base.type,
|
||||
t.base.skin,
|
||||
t.base.team_points,
|
||||
t.amount,
|
||||
t.tech,
|
||||
t.cost,
|
||||
t.item_flags,
|
||||
divisor_str,
|
||||
name);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "CLASS => F GF RF B GB RB Z GZ RZ GR DB JL ZL SH RY RS AT RV MG\n");
|
||||
phosg::fwrite_fmt(stream, "CLASS => F GF RF B GB RB Z GZ RZ GR DB JL ZL SH RY RS AT RV MG\n");
|
||||
for (size_t char_class = 0; char_class < 12; char_class++) {
|
||||
fprintf(stream, "%9s =>", name_for_char_class(char_class));
|
||||
phosg::fwrite_fmt(stream, "{:9} =>", name_for_char_class(char_class));
|
||||
for (size_t tech_num = 0; tech_num < 0x13; tech_num++) {
|
||||
uint8_t max_level = pmt->get_max_tech_level(char_class, tech_num) + 1;
|
||||
if (max_level == 0x00) {
|
||||
fprintf(stream, " ");
|
||||
phosg::fwrite_fmt(stream, " ");
|
||||
} else {
|
||||
fprintf(stream, " %2hhu", max_level);
|
||||
phosg::fwrite_fmt(stream, " {:2}", max_level);
|
||||
}
|
||||
}
|
||||
fprintf(stream, "\n");
|
||||
phosg::fwrite_fmt(stream, "\n");
|
||||
}
|
||||
|
||||
for (size_t table_index = 0; table_index < 8; table_index++) {
|
||||
@@ -891,58 +891,58 @@ void ItemNameIndex::print_table(FILE* stream) const {
|
||||
"Monomate", "Dimate", "Trimate", "Monofluid",
|
||||
"Difluid", "Trifluid", "Antidote", "Antiparalysis",
|
||||
"Sol Atomizer", "Moon Atomizer", "Star Atomizer"};
|
||||
fprintf(stream, "TABLE %02zX => -DEF -POW -DEX MIND -IQ- SYNC\n", table_index);
|
||||
phosg::fwrite_fmt(stream, "TABLE {:02X} => -DEF -POW -DEX MIND -IQ- SYNC\n", table_index);
|
||||
for (size_t which = 0; which < 11; which++) {
|
||||
const auto& res = pmt->get_mag_feed_result(table_index, which);
|
||||
fprintf(stream, "%14s => %4hhd %4hhd %4hhd %4hhd %4hhd %4hhd\n",
|
||||
phosg::fwrite_fmt(stream, "{:14} => {:4} {:4} {:4} {:4} {:4} {:4}\n",
|
||||
names[which], res.def, res.pow, res.dex, res.mind, res.iq, res.synchro);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stream, "SPECIAL => TYPE COUNT ST*\n");
|
||||
phosg::fwrite_fmt(stream, "SPECIAL => TYPE COUNT ST*\n");
|
||||
for (size_t index = 0; index < pmt->num_specials; index++) {
|
||||
const auto& sp = pmt->get_special(index);
|
||||
uint8_t stars = pmt->get_special_stars(index);
|
||||
fprintf(stream, " %02zX => %04hX %5hu %2hu*\n", index, sp.type.load(), sp.amount.load(), stars);
|
||||
phosg::fwrite_fmt(stream, " {:02X} => {:04X} {:5} {:2}*\n", index, sp.type, sp.amount, stars);
|
||||
}
|
||||
|
||||
fprintf(stream, "---USE + -EQUIP => RESULT MLV GND LVL CLS\n");
|
||||
phosg::fwrite_fmt(stream, "---USE + -EQUIP => RESULT MLV GND LVL CLS\n");
|
||||
for (const auto& combo_list_it : pmt->get_all_item_combinations()) {
|
||||
for (const auto& combo : combo_list_it.second) {
|
||||
fprintf(stream, "%02hhX%02hhX%02hhX + %02hhX%02hhX%02hhX => %02hhX%02hhX%02hhX",
|
||||
phosg::fwrite_fmt(stream, "{:02X}{:02X}{:02X} + {:02X}{:02X}{:02X} => {:02X}{:02X}{:02X}",
|
||||
combo.used_item[0], combo.used_item[1], combo.used_item[2],
|
||||
combo.equipped_item[0], combo.equipped_item[1], combo.equipped_item[2],
|
||||
combo.result_item[0], combo.result_item[1], combo.result_item[2]);
|
||||
if (combo.mag_level != 0xFF) {
|
||||
fprintf(stream, " %3hu", combo.mag_level);
|
||||
phosg::fwrite_fmt(stream, " {:3}", combo.mag_level);
|
||||
} else {
|
||||
fprintf(stream, " ");
|
||||
phosg::fwrite_fmt(stream, " ");
|
||||
}
|
||||
if (combo.grind != 0xFF) {
|
||||
fprintf(stream, " %3hu", combo.grind);
|
||||
phosg::fwrite_fmt(stream, " {:3}", combo.grind);
|
||||
} else {
|
||||
fprintf(stream, " ");
|
||||
phosg::fwrite_fmt(stream, " ");
|
||||
}
|
||||
if (combo.level != 0xFF) {
|
||||
fprintf(stream, " %3hu", combo.level);
|
||||
phosg::fwrite_fmt(stream, " {:3}", combo.level);
|
||||
} else {
|
||||
fprintf(stream, " ");
|
||||
phosg::fwrite_fmt(stream, " ");
|
||||
}
|
||||
if (combo.char_class != 0xFF) {
|
||||
fprintf(stream, " %3hu\n", combo.char_class);
|
||||
phosg::fwrite_fmt(stream, " {:3}\n", combo.char_class);
|
||||
} else {
|
||||
fprintf(stream, " \n");
|
||||
phosg::fwrite_fmt(stream, " \n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t num_events = pmt->num_events();
|
||||
for (size_t event_number = 0; event_number < num_events; event_number++) {
|
||||
fprintf(stream, "EV %3zu => PRB\n", event_number);
|
||||
phosg::fwrite_fmt(stream, "EV {:3} => PRB\n", event_number);
|
||||
auto events_list = pmt->get_event_items(event_number);
|
||||
for (size_t z = 0; z < events_list.second; z++) {
|
||||
const auto& event_item = events_list.first[z];
|
||||
fprintf(stream, "%02hhX%02hhX%02hhX => %3hhu\n",
|
||||
phosg::fwrite_fmt(stream, "{:02X}{:02X}{:02X} => {:3}\n",
|
||||
event_item.item[0], event_item.item[1], event_item.item[2], event_item.probability);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-49
@@ -181,16 +181,16 @@ ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV1V2::to_v4() const {
|
||||
|
||||
ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const {
|
||||
WeaponV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.class_flags = this->class_flags.load();
|
||||
ret.atp_min = this->atp_min.load();
|
||||
ret.atp_max = this->atp_max.load();
|
||||
ret.atp_required = this->atp_required.load();
|
||||
ret.mst_required = this->mst_required.load();
|
||||
ret.ata_required = this->ata_required.load();
|
||||
ret.mst = this->mst.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.class_flags = this->class_flags;
|
||||
ret.atp_min = this->atp_min;
|
||||
ret.atp_max = this->atp_max;
|
||||
ret.atp_required = this->atp_required;
|
||||
ret.mst_required = this->mst_required;
|
||||
ret.ata_required = this->ata_required;
|
||||
ret.mst = this->mst;
|
||||
ret.max_grind = this->max_grind;
|
||||
ret.photon = this->photon;
|
||||
ret.special = this->special;
|
||||
@@ -211,16 +211,16 @@ ItemParameterTable::WeaponV4 ItemParameterTable::WeaponGCNTE::to_v4() const {
|
||||
template <bool BE>
|
||||
ItemParameterTable::WeaponV4 ItemParameterTable::WeaponV3T<BE>::to_v4() const {
|
||||
WeaponV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.class_flags = this->class_flags.load();
|
||||
ret.atp_min = this->atp_min.load();
|
||||
ret.atp_max = this->atp_max.load();
|
||||
ret.atp_required = this->atp_required.load();
|
||||
ret.mst_required = this->mst_required.load();
|
||||
ret.ata_required = this->ata_required.load();
|
||||
ret.mst = this->mst.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.class_flags = this->class_flags;
|
||||
ret.atp_min = this->atp_min;
|
||||
ret.atp_max = this->atp_max;
|
||||
ret.atp_required = this->atp_required;
|
||||
ret.mst_required = this->mst_required;
|
||||
ret.ata_required = this->ata_required;
|
||||
ret.mst = this->mst;
|
||||
ret.max_grind = this->max_grind;
|
||||
ret.photon = this->photon;
|
||||
ret.special = this->special;
|
||||
@@ -287,14 +287,14 @@ ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV1V2::to_v4
|
||||
template <bool BE>
|
||||
ItemParameterTable::ArmorOrShieldV4 ItemParameterTable::ArmorOrShieldV3T<BE>::to_v4() const {
|
||||
ArmorOrShieldV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.dfp = this->dfp.load();
|
||||
ret.evp = this->evp.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.dfp = this->dfp;
|
||||
ret.evp = this->evp;
|
||||
ret.block_particle = this->block_particle;
|
||||
ret.block_effect = this->block_effect;
|
||||
ret.class_flags = this->class_flags.load();
|
||||
ret.class_flags = this->class_flags;
|
||||
ret.required_level = this->required_level;
|
||||
ret.efr = this->efr;
|
||||
ret.eth = this->eth;
|
||||
@@ -330,12 +330,12 @@ ItemParameterTable::UnitV4 ItemParameterTable::UnitV1V2::to_v4() const {
|
||||
template <bool BE>
|
||||
ItemParameterTable::UnitV4 ItemParameterTable::UnitV3T<BE>::to_v4() const {
|
||||
UnitV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.stat = this->stat.load();
|
||||
ret.stat_amount = this->stat_amount.load();
|
||||
ret.modifier_amount = this->modifier_amount.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.stat = this->stat;
|
||||
ret.stat_amount = this->stat_amount;
|
||||
ret.modifier_amount = this->modifier_amount;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -377,10 +377,10 @@ ItemParameterTable::MagV4 ItemParameterTable::MagV2::to_v4() const {
|
||||
template <bool BE>
|
||||
ItemParameterTable::MagV4 ItemParameterTable::MagV3T<BE>::to_v4() const {
|
||||
MagV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.feed_table = this->feed_table.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.feed_table = this->feed_table;
|
||||
ret.photon_blast = this->photon_blast;
|
||||
ret.activation = this->activation;
|
||||
ret.on_pb_full = this->on_pb_full;
|
||||
@@ -391,7 +391,7 @@ ItemParameterTable::MagV4 ItemParameterTable::MagV3T<BE>::to_v4() const {
|
||||
ret.on_low_hp_flag = this->on_low_hp_flag;
|
||||
ret.on_death_flag = this->on_death_flag;
|
||||
ret.on_boss_flag = this->on_boss_flag;
|
||||
ret.class_flags = this->class_flags.load();
|
||||
ret.class_flags = this->class_flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -408,13 +408,13 @@ ItemParameterTable::ToolV4 ItemParameterTable::ToolV1V2::to_v4() const {
|
||||
template <bool BE>
|
||||
ItemParameterTable::ToolV4 ItemParameterTable::ToolV3T<BE>::to_v4() const {
|
||||
ToolV4 ret;
|
||||
ret.base.id = this->base.id.load();
|
||||
ret.base.type = this->base.type.load();
|
||||
ret.base.skin = this->base.skin.load();
|
||||
ret.amount = this->amount.load();
|
||||
ret.tech = this->tech.load();
|
||||
ret.cost = this->cost.load();
|
||||
ret.item_flags = this->item_flags.load();
|
||||
ret.base.id = this->base.id;
|
||||
ret.base.type = this->base.type;
|
||||
ret.base.skin = this->base.skin;
|
||||
ret.amount = this->amount;
|
||||
ret.tech = this->tech;
|
||||
ret.cost = this->cost;
|
||||
ret.item_flags = this->item_flags;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -720,7 +720,7 @@ pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id_t(uint32_t tool_table
|
||||
}
|
||||
}
|
||||
}
|
||||
throw out_of_range(phosg::string_printf("invalid tool class %08" PRIX32, item_id));
|
||||
throw out_of_range(std::format("invalid tool class {:08X}", item_id));
|
||||
}
|
||||
|
||||
pair<uint8_t, uint8_t> ItemParameterTable::find_tool_by_id(uint32_t item_id) const {
|
||||
@@ -907,8 +907,8 @@ const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t speci
|
||||
this->parsed_specials.resize(special + 1);
|
||||
}
|
||||
const auto& sp_be = this->r.pget<SpecialBE>(this->offsets_gc_nte->special_data_table + sizeof(SpecialBE) * special);
|
||||
this->parsed_specials[special].type = sp_be.type.load();
|
||||
this->parsed_specials[special].amount = sp_be.amount.load();
|
||||
this->parsed_specials[special].type = sp_be.type;
|
||||
this->parsed_specials[special].amount = sp_be.amount;
|
||||
}
|
||||
return this->parsed_specials[special];
|
||||
} else if (this->offsets_v3_be) {
|
||||
@@ -917,8 +917,8 @@ const ItemParameterTable::Special& ItemParameterTable::get_special(uint8_t speci
|
||||
this->parsed_specials.resize(special + 1);
|
||||
}
|
||||
const auto& sp_be = this->r.pget<SpecialBE>(this->offsets_v3_be->special_data_table + sizeof(SpecialBE) * special);
|
||||
this->parsed_specials[special].type = sp_be.type.load();
|
||||
this->parsed_specials[special].amount = sp_be.amount.load();
|
||||
this->parsed_specials[special].type = sp_be.type;
|
||||
this->parsed_specials[special].amount = sp_be.amount;
|
||||
}
|
||||
return this->parsed_specials[special];
|
||||
} else if (this->offsets_v4) {
|
||||
|
||||
@@ -23,12 +23,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(phosg::string_printf("(row %zu) duplicate canonical ID %08" PRIX32, z, id));
|
||||
throw runtime_error(std::format("(row {}) duplicate canonical ID {:08X}", z, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_any_canonical_id) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu) no canonical ID present in row", z));
|
||||
throw runtime_error(std::format("(row {}) no canonical ID present in row", z));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,25 +46,25 @@ 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(phosg::string_printf("(row %zu version %s) canonical ID %" PRIX32 " is missing from the index", z, phosg::name_for_enum(v), e_id));
|
||||
throw logic_error(std::format("(row {} version {}) canonical ID {:X} 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(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:X} not defined in item parameter table", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
if (!remaining_identifiers.erase(e_id)) {
|
||||
throw runtime_error(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " not in item parameter table's primary identifier list", z, phosg::name_for_enum(v), e_id));
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:X} 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(phosg::string_printf("(row %zu version %s) ID %" PRIX32 " refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||
throw runtime_error(std::format("(row {} version {}) ID {:X} refers to nonexistent canonical ID", z, phosg::name_for_enum(v), e_id));
|
||||
}
|
||||
}
|
||||
|
||||
if (!remaining_identifiers.empty()) {
|
||||
string missing_str = phosg::string_printf("(version %s) not all identifiers in the item parameter table are defined in the translation table; missing:", phosg::name_for_enum(v));
|
||||
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 += phosg::string_printf(" %08" PRIX32, id);
|
||||
missing_str += std::format(" {:08X}", id);
|
||||
}
|
||||
throw runtime_error(missing_str);
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGEncryption> opt_rand_crypt) {
|
||||
void player_use_item(shared_ptr<Client> c, size_t item_index, 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
|
||||
@@ -177,7 +177,7 @@ void player_use_item(shared_ptr<Client> c, size_t item_index, shared_ptr<PSOLFGE
|
||||
// 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 just call rand().) How does this actually work on console PSO?
|
||||
size_t det = random_from_optional_crypt(opt_rand_crypt) % sum;
|
||||
size_t det = rand_crypt->next() % sum;
|
||||
for (size_t z = 0; z < table.second; z++) {
|
||||
const auto& entry = table.first[z];
|
||||
if (det > entry.probability) {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
#include "ServerState.hh"
|
||||
#include "StaticGameData.hh"
|
||||
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
void player_use_item(std::shared_ptr<Client> c, size_t item_index, std::shared_ptr<RandomGenerator> rand_crypt);
|
||||
void player_feed_mag(std::shared_ptr<Client> c, size_t mag_item_index, size_t fed_item_index);
|
||||
|
||||
void apply_mag_feed_result(
|
||||
|
||||
+1
-1
@@ -129,7 +129,7 @@ LevelTableV3BE::LevelTableV3BE(const string& data, bool encrypted) {
|
||||
dest_delta.ata = src_delta.ata;
|
||||
dest_delta.lck = src_delta.lck;
|
||||
dest_delta.tp = src_delta.tp;
|
||||
dest_delta.experience = src_delta.experience.load();
|
||||
dest_delta.experience = src_delta.experience;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -24,13 +24,13 @@ struct CharacterStatsT {
|
||||
|
||||
operator CharacterStatsT<!BE>() const {
|
||||
CharacterStatsT<!BE> ret;
|
||||
ret.atp = this->atp.load();
|
||||
ret.mst = this->mst.load();
|
||||
ret.evp = this->evp.load();
|
||||
ret.hp = this->hp.load();
|
||||
ret.dfp = this->dfp.load();
|
||||
ret.ata = this->ata.load();
|
||||
ret.lck = this->lck.load();
|
||||
ret.atp = this->atp;
|
||||
ret.mst = this->mst;
|
||||
ret.evp = this->evp;
|
||||
ret.hp = this->hp;
|
||||
ret.dfp = this->dfp;
|
||||
ret.ata = this->ata;
|
||||
ret.lck = this->lck;
|
||||
return ret;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
@@ -53,12 +53,12 @@ struct PlayerStatsT {
|
||||
operator PlayerStatsT<!BE>() const {
|
||||
PlayerStatsT<!BE> ret;
|
||||
ret.char_stats = this->char_stats;
|
||||
ret.esp = this->esp.load();
|
||||
ret.height = this->height.load();
|
||||
ret.unknown_a3 = this->unknown_a3.load();
|
||||
ret.level = this->level.load();
|
||||
ret.experience = this->experience.load();
|
||||
ret.meseta = this->meseta.load();
|
||||
ret.esp = this->esp;
|
||||
ret.height = this->height;
|
||||
ret.unknown_a3 = this->unknown_a3;
|
||||
ret.level = this->level;
|
||||
ret.experience = this->experience;
|
||||
ret.meseta = this->meseta;
|
||||
return ret;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
|
||||
+53
-51
@@ -7,6 +7,7 @@
|
||||
#include "Compression.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "SendCommands.hh"
|
||||
#include "ServerState.hh"
|
||||
#include "Text.hh"
|
||||
|
||||
using namespace std;
|
||||
@@ -16,7 +17,7 @@ bool Lobby::FloorItem::visible_to_client(uint8_t client_id) const {
|
||||
}
|
||||
|
||||
Lobby::FloorItemManager::FloorItemManager(uint32_t lobby_id, uint8_t floor)
|
||||
: log(phosg::string_printf("[Lobby:%08" PRIX32 ":FloorItems:%02hhX] ", lobby_id, floor), lobby_log.min_level),
|
||||
: log(std::format("[Lobby:{:08X}:FloorItems:{:02X}] ", lobby_id, floor), lobby_log.min_level),
|
||||
next_drop_number(0) {}
|
||||
|
||||
bool Lobby::FloorItemManager::exists(uint32_t item_id) const {
|
||||
@@ -57,8 +58,8 @@ void Lobby::FloorItemManager::add(shared_ptr<Lobby::FloorItem> fi) {
|
||||
this->queue_for_client[z].emplace(fi->drop_number, fi);
|
||||
}
|
||||
}
|
||||
this->log.info("Added floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
this->log.info_f("Added floor item {:08X} at {:g}, {:g} with drop number {} with flags {:03X}",
|
||||
fi->data.id, fi->pos.x, fi->pos.z, fi->drop_number, fi->flags);
|
||||
}
|
||||
|
||||
std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_id, uint8_t client_id) {
|
||||
@@ -76,8 +77,8 @@ std::shared_ptr<Lobby::FloorItem> Lobby::FloorItemManager::remove(uint32_t item_
|
||||
}
|
||||
}
|
||||
this->items.erase(item_it);
|
||||
this->log.info("Removed floor item %08" PRIX32 " at %g, %g with drop number %" PRIu64 " with flags %03hX",
|
||||
fi->data.id.load(), fi->pos.x.load(), fi->pos.z.load(), fi->drop_number, fi->flags);
|
||||
this->log.info_f("Removed floor item {:08X} at {:g}, {:g} with drop number {} with flags {:03X}",
|
||||
fi->data.id, fi->pos.x, fi->pos.z, fi->drop_number, fi->flags);
|
||||
return fi;
|
||||
}
|
||||
|
||||
@@ -88,7 +89,7 @@ std::unordered_set<std::shared_ptr<Lobby::FloorItem>> Lobby::FloorItemManager::e
|
||||
ret.emplace(this->remove(this->queue_for_client[z].begin()->second->data.id, 0xFF));
|
||||
}
|
||||
}
|
||||
this->log.info("Evicted %zu items", ret.size());
|
||||
this->log.info_f("Evicted {} items", ret.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ void Lobby::FloorItemManager::clear_inaccessible(uint16_t remaining_clients_mask
|
||||
for (uint32_t item_id : item_ids_to_delete) {
|
||||
this->remove(item_id, 0xFF);
|
||||
}
|
||||
this->log.info("Deleted %zu inaccessible items", item_ids_to_delete.size());
|
||||
this->log.info_f("Deleted {} inaccessible items", item_ids_to_delete.size());
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::clear_private() {
|
||||
@@ -115,7 +116,7 @@ void Lobby::FloorItemManager::clear_private() {
|
||||
for (uint32_t item_id : item_ids_to_delete) {
|
||||
this->remove(item_id, 0xFF);
|
||||
}
|
||||
this->log.info("Deleted %zu private items", item_ids_to_delete.size());
|
||||
this->log.info_f("Deleted {} private items", item_ids_to_delete.size());
|
||||
}
|
||||
|
||||
void Lobby::FloorItemManager::clear() {
|
||||
@@ -125,7 +126,7 @@ void Lobby::FloorItemManager::clear() {
|
||||
queue.clear();
|
||||
}
|
||||
this->next_drop_number = 0;
|
||||
this->log.info("Deleted %zu items", num_items);
|
||||
this->log.info_f("Deleted {} items", num_items);
|
||||
}
|
||||
|
||||
uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
||||
@@ -143,7 +144,7 @@ uint32_t Lobby::FloorItemManager::reassign_all_item_ids(uint32_t next_item_id) {
|
||||
|
||||
Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
: server_state(s),
|
||||
log(phosg::string_printf("[%s:%" PRIX32 "] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
|
||||
log(std::format("[{}:{:X}] ", is_game ? "Game" : "Lobby", id), lobby_log.min_level),
|
||||
lobby_id(id),
|
||||
min_level(0),
|
||||
max_level(0xFFFFFFFF),
|
||||
@@ -157,6 +158,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
exp_share_multiplier(0.5),
|
||||
challenge_exp_multiplier(1.0f),
|
||||
random_seed(phosg::random_object<uint32_t>()),
|
||||
rand_crypt(make_shared<DisabledRandomGenerator>()),
|
||||
drop_mode(DropMode::CLIENT),
|
||||
event(0),
|
||||
block(0),
|
||||
@@ -164,10 +166,8 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
max_clients(12),
|
||||
enabled_flags(0),
|
||||
idle_timeout_usecs(0),
|
||||
idle_timeout_event(
|
||||
event_new(s->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &Lobby::dispatch_on_idle_timeout, this),
|
||||
event_free) {
|
||||
this->log.info("Created");
|
||||
idle_timeout_timer(*s->io_context) {
|
||||
this->log.info_f("Created");
|
||||
if (is_game) {
|
||||
this->set_flag(Flag::GAME);
|
||||
}
|
||||
@@ -175,7 +175,7 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id, bool is_game)
|
||||
}
|
||||
|
||||
Lobby::~Lobby() {
|
||||
this->log.info("Deleted");
|
||||
this->log.info_f("Deleted");
|
||||
}
|
||||
|
||||
void Lobby::reset_next_item_ids() {
|
||||
@@ -249,6 +249,12 @@ void Lobby::create_item_creator(Version logic_version) {
|
||||
throw logic_error("invalid lobby base version");
|
||||
}
|
||||
|
||||
shared_ptr<RandomGenerator> rand_crypt;
|
||||
if (s->use_psov2_rand_crypt) {
|
||||
rand_crypt = make_shared<PSOV2Encryption>(this->rand_crypt->seed());
|
||||
} else {
|
||||
rand_crypt = make_shared<MT19937Generator>(this->rand_crypt->seed());
|
||||
}
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
common_item_set,
|
||||
rare_item_set,
|
||||
@@ -262,7 +268,7 @@ void Lobby::create_item_creator(Version logic_version) {
|
||||
(this->mode == GameMode::SOLO) ? GameMode::NORMAL : this->mode,
|
||||
this->difficulty,
|
||||
this->effective_section_id(),
|
||||
this->opt_rand_crypt,
|
||||
rand_crypt,
|
||||
this->quest ? this->quest->battle_rules : nullptr);
|
||||
}
|
||||
|
||||
@@ -300,7 +306,7 @@ void Lobby::load_maps() {
|
||||
this->event,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
this->rand_crypt,
|
||||
this->quest->get_supermap(this->random_seed));
|
||||
} else {
|
||||
auto s = this->require_server_state();
|
||||
@@ -310,7 +316,7 @@ void Lobby::load_maps() {
|
||||
this->event,
|
||||
this->random_seed,
|
||||
this->rare_enemy_rates,
|
||||
this->opt_rand_crypt,
|
||||
this->rand_crypt,
|
||||
s->supermaps_for_variations(this->episode, this->mode, this->difficulty, this->variations));
|
||||
}
|
||||
|
||||
@@ -331,9 +337,9 @@ void Lobby::load_maps() {
|
||||
void Lobby::create_ep3_server() {
|
||||
auto s = this->require_server_state();
|
||||
if (!this->ep3_server) {
|
||||
this->log.info("Creating Episode 3 server state");
|
||||
this->log.info_f("Creating Episode 3 server state");
|
||||
} else {
|
||||
this->log.info("Recreating Episode 3 server state");
|
||||
this->log.info_f("Recreating Episode 3 server state");
|
||||
}
|
||||
auto tourn = this->tournament_match ? this->tournament_match->tournament.lock() : nullptr;
|
||||
|
||||
@@ -343,7 +349,7 @@ void Lobby::create_ep3_server() {
|
||||
.map_index = s->ep3_map_index,
|
||||
.behavior_flags = s->ep3_behavior_flags,
|
||||
.opt_rand_stream = nullptr,
|
||||
.opt_rand_crypt = this->opt_rand_crypt,
|
||||
.rand_crypt = this->rand_crypt,
|
||||
.tournament = tourn,
|
||||
.trap_card_ids = s->ep3_trap_card_ids,
|
||||
};
|
||||
@@ -376,9 +382,9 @@ bool Lobby::any_client_loading() const {
|
||||
if (!lc.get()) {
|
||||
continue;
|
||||
}
|
||||
if (lc->config.check_flag(Client::Flag::LOADING) ||
|
||||
lc->config.check_flag(Client::Flag::LOADING_QUEST) ||
|
||||
lc->config.check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
||||
if (lc->check_flag(Client::Flag::LOADING) ||
|
||||
lc->check_flag(Client::Flag::LOADING_QUEST) ||
|
||||
lc->check_flag(Client::Flag::LOADING_RUNNING_JOINABLE_QUEST)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -419,7 +425,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
this->clients[required_client_id] = c;
|
||||
index = required_client_id;
|
||||
|
||||
} else if (c->config.check_flag(Client::Flag::DEBUG_ENABLED) && (this->mode != GameMode::SOLO)) {
|
||||
} else if (c->check_flag(Client::Flag::DEBUG_ENABLED) && (this->mode != GameMode::SOLO)) {
|
||||
for (index = this->max_clients - 1; index >= min_client_id; index--) {
|
||||
if (!this->clients[index].get()) {
|
||||
this->clients[index] = c;
|
||||
@@ -480,7 +486,7 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
// On BB, we send artificial flag state to fix an Episode 2 bug where the
|
||||
// CCA door lock state is overwritten by quests.
|
||||
if (this->is_game() && (c->version() == Version::BB_V4)) {
|
||||
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
c->set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
|
||||
}
|
||||
|
||||
// If the lobby is recording a battle record, add the player join event
|
||||
@@ -510,17 +516,16 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
|
||||
}
|
||||
|
||||
// There is a player in the lobby, so it is no longer idle
|
||||
if (event_pending(this->idle_timeout_event.get(), EV_TIMEOUT, nullptr)) {
|
||||
event_del(this->idle_timeout_event.get());
|
||||
this->log.info("Idle timeout cancelled");
|
||||
if (this->idle_timeout_timer.cancel()) {
|
||||
this->log.info_f("Idle timeout cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
void Lobby::remove_client(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(phosg::string_printf(
|
||||
"client\'s lobby client id (%hhu) does not match client list (%u)",
|
||||
throw 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)));
|
||||
}
|
||||
@@ -580,9 +585,18 @@ void Lobby::remove_client(shared_ptr<Client> c) {
|
||||
(this->idle_timeout_usecs > 0)) {
|
||||
// If the lobby is persistent but has an idle timeout, make it expire after
|
||||
// the specified time
|
||||
auto tv = phosg::usecs_to_timeval(this->idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &tv);
|
||||
this->log.info("Idle timeout scheduled");
|
||||
this->idle_timeout_timer.expires_after(std::chrono::microseconds(this->idle_timeout_usecs));
|
||||
this->idle_timeout_timer.async_wait([this](std::error_code ec) {
|
||||
if (!ec) {
|
||||
if (this->count_clients() == 0) {
|
||||
this->log.info_f("Idle timeout expired");
|
||||
this->require_server_state()->remove_lobby(this->shared_from_this());
|
||||
} else {
|
||||
this->log.error_f("Idle timeout occurred, but clients are present in lobby");
|
||||
}
|
||||
}
|
||||
});
|
||||
this->log.info_f("Idle timeout scheduled");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,7 +645,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
|
||||
if (this->count_clients() >= this->max_clients) {
|
||||
return JoinError::FULL;
|
||||
}
|
||||
bool debug_enabled = c->config.check_flag(Client::Flag::DEBUG_ENABLED);
|
||||
bool debug_enabled = c->check_flag(Client::Flag::DEBUG_ENABLED);
|
||||
if (!this->version_is_allowed(c->version()) && !debug_enabled) {
|
||||
return JoinError::VERSION_CONFLICT;
|
||||
}
|
||||
@@ -649,7 +663,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
|
||||
return JoinError::SOLO;
|
||||
}
|
||||
if (!debug_enabled &&
|
||||
(this->check_flag(Flag::IS_CLIENT_CUSTOMIZATION) != c->config.check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION))) {
|
||||
(this->check_flag(Flag::IS_CLIENT_CUSTOMIZATION) != c->check_flag(Client::Flag::IS_CLIENT_CUSTOMIZATION))) {
|
||||
return JoinError::VERSION_CONFLICT;
|
||||
}
|
||||
if (!c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES)) {
|
||||
@@ -759,15 +773,15 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr<Client> c, bool consum
|
||||
this->next_item_id_for_client[c->lobby_client_id] = orig_next_item_id;
|
||||
}
|
||||
|
||||
if (c->log.info("Assigned inventory item IDs%s", consume_ids ? "" : " but did not mark IDs as used")) {
|
||||
if (c->log.info_f("Assigned inventory item IDs{}", consume_ids ? "" : " but did not mark IDs as used")) {
|
||||
c->print_inventory(stderr);
|
||||
auto& bank = c->current_bank();
|
||||
if (p->bank.num_items) {
|
||||
bank.assign_ids(0x99000000 + (c->lobby_client_id << 20));
|
||||
c->log.info("Assigned bank item IDs");
|
||||
c->log.info_f("Assigned bank item IDs");
|
||||
c->print_bank(stderr);
|
||||
} else {
|
||||
c->log.info("Bank is empty");
|
||||
c->log.info_f("Bank is empty");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -800,18 +814,6 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||
};
|
||||
}
|
||||
|
||||
void Lobby::dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
auto l = reinterpret_cast<Lobby*>(ctx)->shared_from_this();
|
||||
if (l->count_clients() == 0) {
|
||||
l->log.info("Idle timeout expired");
|
||||
auto s = l->require_server_state();
|
||||
s->remove_lobby(l);
|
||||
} else {
|
||||
l->log.error("Idle timeout occurred, but clients are present in lobby");
|
||||
event_del(l->idle_timeout_event.get());
|
||||
}
|
||||
}
|
||||
|
||||
bool Lobby::compare_shared(const shared_ptr<const Lobby>& a, const shared_ptr<const Lobby>& b) {
|
||||
// Sort keys:
|
||||
// 1. Priority class: has free space < empty (persistent) < full < non-joinable (in quest/battle)
|
||||
|
||||
+2
-5
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <array>
|
||||
@@ -135,7 +134,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::string name;
|
||||
// This seed is also sent to the client for rare enemy generation
|
||||
uint32_t random_seed;
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt;
|
||||
std::shared_ptr<RandomGenerator> rand_crypt;
|
||||
uint8_t allowed_drop_modes;
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<ItemCreator> item_creator; // Always null for lobbies, never null for games
|
||||
@@ -185,7 +184,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
|
||||
// is not zero
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
asio::steady_timer idle_timeout_timer;
|
||||
|
||||
Lobby(std::shared_ptr<ServerState> s, uint32_t id, bool is_game);
|
||||
Lobby(const Lobby&) = delete;
|
||||
@@ -287,8 +286,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
|
||||
|
||||
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static bool compare_shared(const std::shared_ptr<const Lobby>& a, const std::shared_ptr<const Lobby>& b);
|
||||
};
|
||||
|
||||
|
||||
+15
-15
@@ -4,26 +4,26 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_log("", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger command_data_log("[Commands] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger config_log("[Config] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger dns_server_log("[DNSServer] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger function_compiler_log("[FunctionCompiler] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger lobby_log("", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger patch_index_log("[PatchFileIndex] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger player_data_log("", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger proxy_server_log("[ProxyServer] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger replay_log("[ReplaySession] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger server_log("[Server] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger static_game_data_log("[StaticGameData] ", phosg::LogLevel::USE_DEFAULT);
|
||||
phosg::PrefixedLogger channel_exceptions_log("[Channel] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger client_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger command_data_log("[Commands] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger config_log("[Config] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger dns_server_log("[DNSServer] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger function_compiler_log("[FunctionCompiler] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger lobby_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger patch_index_log("[PatchFileIndex] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger player_data_log("", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger proxy_server_log("[ProxyServer] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger replay_log("[ReplaySession] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger server_log("[Server] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
phosg::PrefixedLogger static_game_data_log("[StaticGameData] ", phosg::LogLevel::L_USE_DEFAULT);
|
||||
|
||||
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());
|
||||
log.min_level = phosg::enum_for_name<phosg::LogLevel>(name.c_str());
|
||||
log.min_level = phosg::enum_for_name<phosg::LogLevel>(name);
|
||||
} catch (const out_of_range&) {
|
||||
}
|
||||
}
|
||||
|
||||
+316
-418
File diff suppressed because it is too large
Load Diff
+276
-273
File diff suppressed because it is too large
Load Diff
+8
-11
@@ -52,10 +52,7 @@ class SetDataTableBase {
|
||||
public:
|
||||
virtual ~SetDataTableBase() = default;
|
||||
|
||||
Variations generate_variations(
|
||||
Episode episode,
|
||||
bool is_solo,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt = nullptr) const;
|
||||
Variations generate_variations(Episode episode, bool is_solo, std::shared_ptr<RandomGenerator> rand_crypt) const;
|
||||
virtual Variations::Entry num_available_variations_for_floor(Episode episode, uint8_t floor) const = 0;
|
||||
virtual Variations::Entry num_free_play_variations_for_floor(Episode episode, bool is_solo, uint8_t floor) const = 0;
|
||||
|
||||
@@ -339,7 +336,7 @@ public:
|
||||
uint32_t location_indexes_used;
|
||||
uint32_t location_entries_base_offset;
|
||||
|
||||
RandomState(uint32_t random_seed);
|
||||
explicit RandomState(uint32_t random_seed);
|
||||
size_t rand_int_biased(size_t min_v, size_t max_v);
|
||||
uint32_t next_location_index();
|
||||
void generate_shuffled_location_table(const RandomEnemyLocationsHeader& header, phosg::StringReader r, uint16_t room);
|
||||
@@ -389,7 +386,7 @@ public:
|
||||
std::shared_ptr<const std::string> enemy_sets_data,
|
||||
std::shared_ptr<const std::string> events_data);
|
||||
// Constructor for materialize_random_sections
|
||||
MapFile(uint32_t random_seed);
|
||||
explicit MapFile(uint32_t random_seed);
|
||||
~MapFile() = default;
|
||||
|
||||
inline uint64_t source_hash() const {
|
||||
@@ -913,25 +910,25 @@ public:
|
||||
uint64_t lobby_or_session_id,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t random_seed,
|
||||
uint32_t random_seed, // For client-matched rare enemies (non-BB)
|
||||
std::shared_ptr<const RareEnemyRates> bb_rare_rates,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||
std::vector<std::shared_ptr<const SuperMap>> floor_map_defs);
|
||||
// Constructor for quests
|
||||
MapState(
|
||||
uint64_t lobby_or_session_id,
|
||||
uint8_t difficulty,
|
||||
uint8_t event,
|
||||
uint32_t random_seed,
|
||||
uint32_t random_seed, // For client-matched rare enemies (non-BB)
|
||||
std::shared_ptr<const RareEnemyRates> bb_rare_rates,
|
||||
std::shared_ptr<PSOLFGEncryption> opt_rand_crypt,
|
||||
std::shared_ptr<RandomGenerator> rand_crypt,
|
||||
std::shared_ptr<const SuperMap> quest_map_def);
|
||||
// Constructor for empty maps (used in challenge mode before a quest starts)
|
||||
MapState();
|
||||
|
||||
~MapState() = default;
|
||||
|
||||
void index_super_map(const FloorConfig& floor_config, std::shared_ptr<PSOLFGEncryption> opt_rand_crypt);
|
||||
void index_super_map(const FloorConfig& floor_config, std::shared_ptr<RandomGenerator> rand_crypt);
|
||||
void compute_dynamic_object_base_indexes();
|
||||
|
||||
inline FloorConfig& floor_config(uint8_t floor) {
|
||||
|
||||
+9
-2
@@ -2,14 +2,21 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
MenuItem::MenuItem(uint32_t item_id, const string& name, const string& description, uint32_t 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, std::function<std::string()> get_description, uint32_t flags)
|
||||
MenuItem::MenuItem(uint32_t item_id,
|
||||
const string& name,
|
||||
std::function<std::string()> get_description,
|
||||
uint32_t flags)
|
||||
: item_id(item_id),
|
||||
name(name),
|
||||
description(),
|
||||
|
||||
+20
-17
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -71,21 +72,15 @@ constexpr uint32_t GO_BACK = 0xAAFFFFAA;
|
||||
constexpr uint32_t CHAT_COMMANDS = 0xAA0101AA;
|
||||
constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA0202AA;
|
||||
constexpr uint32_t DROP_NOTIFICATIONS = 0xAA0303AA;
|
||||
constexpr uint32_t BLOCK_PINGS = 0xAA0404AA;
|
||||
constexpr uint32_t INFINITE_HP = 0xAA0505AA;
|
||||
constexpr uint32_t INFINITE_TP = 0xAA0606AA;
|
||||
constexpr uint32_t SWITCH_ASSIST = 0xAA0707AA;
|
||||
constexpr uint32_t BLOCK_EVENTS = 0xAA0808AA;
|
||||
constexpr uint32_t BLOCK_PATCHES = 0xAA0909AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA0A0AAA;
|
||||
constexpr uint32_t VIRTUAL_CLIENT = 0xAA0B0BAA;
|
||||
constexpr uint32_t RED_NAME = 0xAA0C0CAA;
|
||||
constexpr uint32_t BLANK_NAME = 0xAA0D0DAA;
|
||||
constexpr uint32_t SUPPRESS_LOGIN = 0xAA0E0EAA;
|
||||
constexpr uint32_t SKIP_CARD = 0xAA0F0FAA;
|
||||
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA1010AA;
|
||||
constexpr uint32_t EP3_INFINITE_TIME = 0xAA1111AA;
|
||||
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA1212AA;
|
||||
constexpr uint32_t INFINITE_HP = 0xAA0404AA;
|
||||
constexpr uint32_t INFINITE_TP = 0xAA0505AA;
|
||||
constexpr uint32_t SWITCH_ASSIST = 0xAA0606AA;
|
||||
constexpr uint32_t BLOCK_EVENTS = 0xAA0707AA;
|
||||
constexpr uint32_t BLOCK_PATCHES = 0xAA0808AA;
|
||||
constexpr uint32_t SAVE_FILES = 0xAA0909AA;
|
||||
constexpr uint32_t EP3_INFINITE_MESETA = 0xAA0A0AAA;
|
||||
constexpr uint32_t EP3_INFINITE_TIME = 0xAA0B0BAA;
|
||||
constexpr uint32_t EP3_UNMASK_WHISPERS = 0xAA0C0CAA;
|
||||
} // namespace ProxyOptionsMenuItemID
|
||||
|
||||
namespace TeamRewardMenuItemID {
|
||||
@@ -134,8 +129,16 @@ struct MenuItem {
|
||||
std::function<std::string()> get_description;
|
||||
uint32_t flags;
|
||||
|
||||
MenuItem(uint32_t item_id, const std::string& name, const std::string& description, uint32_t flags);
|
||||
MenuItem(uint32_t item_id, const std::string& name, std::function<std::string()> get_description, uint32_t flags);
|
||||
MenuItem(
|
||||
uint32_t item_id,
|
||||
const std::string& name,
|
||||
const std::string& description,
|
||||
uint32_t flags);
|
||||
MenuItem(
|
||||
uint32_t item_id,
|
||||
const std::string& name,
|
||||
std::function<std::string()> get_description,
|
||||
uint32_t flags);
|
||||
};
|
||||
|
||||
struct Menu {
|
||||
|
||||
+39
-33
@@ -1,12 +1,15 @@
|
||||
#include "WindowsPlatform.hh"
|
||||
|
||||
#include "NetworkAddresses.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#ifndef PHOSG_WINDOWS
|
||||
#include <ifaddrs.h>
|
||||
#else
|
||||
#include <iphlpapi.h>
|
||||
#include <ws2tcpip.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
@@ -16,40 +19,17 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
uint32_t resolve_address(const char* address) {
|
||||
struct addrinfo* res0;
|
||||
if (getaddrinfo(address, nullptr, nullptr, &res0)) {
|
||||
auto e = phosg::string_for_error(errno);
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"can\'t resolve hostname %s: %s", address, e.c_str()));
|
||||
}
|
||||
|
||||
std::unique_ptr<struct addrinfo, void (*)(struct addrinfo*)> res0_unique(res0, freeaddrinfo);
|
||||
struct addrinfo* res4 = nullptr;
|
||||
for (struct addrinfo* res = res0; res; res = res->ai_next) {
|
||||
if (res->ai_family == AF_INET) {
|
||||
res4 = res;
|
||||
}
|
||||
}
|
||||
if (!res4) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"can\'t resolve hostname %s: no usable data", address));
|
||||
}
|
||||
|
||||
struct sockaddr_in* res_sin = (struct sockaddr_in*)res4->ai_addr;
|
||||
return ntohl(res_sin->sin_addr.s_addr);
|
||||
}
|
||||
|
||||
map<string, uint32_t> get_local_addresses() {
|
||||
map<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(phosg::string_printf("failed to get interface addresses: %s", s.c_str()));
|
||||
throw runtime_error(std::format("failed to get interface addresses: {}", s));
|
||||
}
|
||||
|
||||
unique_ptr<struct ifaddrs, void (*)(struct ifaddrs*)> ifa(ifa_raw, freeifaddrs);
|
||||
|
||||
map<string, uint32_t> ret;
|
||||
for (struct ifaddrs* i = ifa.get(); i; i = i->ifa_next) {
|
||||
if (!i->ifa_addr) {
|
||||
continue;
|
||||
@@ -63,6 +43,32 @@ map<string, uint32_t> get_local_addresses() {
|
||||
ret.emplace(i->ifa_name, ntohl(sin->sin_addr.s_addr));
|
||||
}
|
||||
|
||||
#else
|
||||
ULONG buffer_size = 0x1000;
|
||||
std::vector<char> buffer(buffer_size);
|
||||
|
||||
auto* adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
|
||||
DWORD result = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, adapters, &buffer_size);
|
||||
if (result == ERROR_BUFFER_OVERFLOW) {
|
||||
buffer.resize(buffer_size);
|
||||
adapters = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(buffer.data());
|
||||
result = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, adapters, &buffer_size);
|
||||
}
|
||||
|
||||
if (result != NO_ERROR) {
|
||||
throw runtime_error(std::format("GetAdaptersAddresses failed: {}", result));
|
||||
}
|
||||
|
||||
for (IP_ADAPTER_ADDRESSES* adapter = adapters; adapter != nullptr; adapter = adapter->Next) {
|
||||
for (IP_ADAPTER_UNICAST_ADDRESS* ua = adapter->FirstUnicastAddress; ua != nullptr; ua = ua->Next) {
|
||||
if (ua->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
sockaddr_in* sa_in = reinterpret_cast<sockaddr_in*>(ua->Address.lpSockaddr);
|
||||
ret.emplace(adapter->AdapterName, ntohl(sa_in->sin_addr.S_un.S_addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ bool is_local_address(const sockaddr_storage& daddr) {
|
||||
}
|
||||
|
||||
string string_for_address(uint32_t address) {
|
||||
return phosg::string_printf("%hhu.%hhu.%hhu.%hhu",
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
// PSO is IPv4-only, so we just treat addresses as uint32_t everywhere because
|
||||
// it's easier
|
||||
|
||||
uint32_t resolve_address(const char* address);
|
||||
std::map<std::string, uint32_t> get_local_addresses();
|
||||
uint32_t get_connected_address(int fd);
|
||||
bool is_loopback_address(uint32_t addr);
|
||||
bool is_local_address(uint32_t daddr);
|
||||
bool is_local_address(const sockaddr_storage& daddr);
|
||||
bool is_local_address(asio::ip::tcp::endpoint& daddr);
|
||||
|
||||
std::string string_for_address(uint32_t address);
|
||||
uint32_t address_for_string(const char* address);
|
||||
|
||||
+4
-4
@@ -60,15 +60,15 @@ std::unordered_map<std::string, std::string> decode_ppk_file(const std::string&
|
||||
decrypt_ppk_data(data, phosg::tolower(filename), password);
|
||||
uint32_t checksum = phosg::crc32(data.data(), data.size());
|
||||
if (checksum != entry.checksum) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"incorrect checksum for file %s (expected %08" PRIX32 "; received %08" PRIX32 ")",
|
||||
filename.c_str(), entry.checksum.load(), checksum));
|
||||
throw 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(phosg::string_printf("archive contains multiple files with the same name (%s)", filename.c_str()));
|
||||
throw runtime_error(std::format("archive contains multiple files with the same name ({})", filename));
|
||||
}
|
||||
offset = entry_offset - 4;
|
||||
}
|
||||
|
||||
+58
-113
@@ -13,100 +13,65 @@ using namespace std;
|
||||
|
||||
// TODO: fix style in this file, especially in psobb functions
|
||||
|
||||
RandomGenerator::RandomGenerator(uint32_t seed) : initial_seed(seed) {}
|
||||
|
||||
DisabledRandomGenerator::DisabledRandomGenerator() : RandomGenerator(0) {}
|
||||
|
||||
uint32_t DisabledRandomGenerator::next() {
|
||||
throw std::runtime_error("Random data cannot be generated in this context");
|
||||
}
|
||||
|
||||
MT19937Generator::MT19937Generator(uint32_t seed) : RandomGenerator(seed), gen(seed) {}
|
||||
|
||||
uint32_t MT19937Generator::next() {
|
||||
return this->gen();
|
||||
}
|
||||
|
||||
// Most ciphers used by PSO are symmetric; alias decrypt to encrypt by default
|
||||
void PSOEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||
this->encrypt(data, size, advance);
|
||||
void PSOEncryption::decrypt(void* data, size_t size) {
|
||||
this->encrypt(data, size);
|
||||
}
|
||||
|
||||
PSOLFGEncryption::PSOLFGEncryption(
|
||||
uint32_t seed, size_t stream_length, size_t end_offset)
|
||||
: stream(stream_length, 0),
|
||||
: RandomGenerator(seed),
|
||||
stream(stream_length, 0),
|
||||
offset(0),
|
||||
end_offset(end_offset),
|
||||
initial_seed(seed),
|
||||
cycles(0) {}
|
||||
end_offset(end_offset) {}
|
||||
|
||||
uint32_t PSOLFGEncryption::next(bool advance) {
|
||||
uint32_t PSOLFGEncryption::next() {
|
||||
if (this->offset == this->end_offset) {
|
||||
this->update_stream();
|
||||
}
|
||||
uint32_t ret = this->stream[this->offset];
|
||||
if (advance) {
|
||||
this->offset++;
|
||||
}
|
||||
return ret;
|
||||
return this->stream[this->offset++];
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void PSOLFGEncryption::encrypt_t(void* vdata, size_t size, bool advance) {
|
||||
if (!advance && (size != 4)) {
|
||||
throw logic_error("cannot peek-encrypt/decrypt with size > 4");
|
||||
}
|
||||
|
||||
size_t uint32_count = size >> 2;
|
||||
size_t extra_bytes = size & 3;
|
||||
U32T<BE>* data = reinterpret_cast<U32T<BE>*>(vdata);
|
||||
for (size_t x = 0; x < uint32_count; x++) {
|
||||
data[x] ^= this->next(advance);
|
||||
}
|
||||
if (extra_bytes) {
|
||||
U32T<BE> last = 0;
|
||||
memcpy(&last, &data[uint32_count], extra_bytes);
|
||||
last ^= this->next(advance);
|
||||
memcpy(&data[uint32_count], &last, extra_bytes);
|
||||
}
|
||||
void PSOLFGEncryption::encrypt(void* vdata, size_t size) {
|
||||
this->encrypt_t<false>(vdata, size);
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void PSOLFGEncryption::encrypt_minus_t(void* vdata, size_t size, bool advance) {
|
||||
if (!advance && (size != 4)) {
|
||||
throw logic_error("cannot peek-encrypt/decrypt with size > 4");
|
||||
}
|
||||
|
||||
size_t uint32_count = size >> 2;
|
||||
size_t extra_bytes = size & 3;
|
||||
U32T<BE>* data = reinterpret_cast<U32T<BE>*>(vdata);
|
||||
for (size_t x = 0; x < uint32_count; x++) {
|
||||
data[x] = this->next(advance) - data[x];
|
||||
}
|
||||
if (extra_bytes) {
|
||||
U32T<BE> last = 0;
|
||||
memcpy(&last, &data[uint32_count], extra_bytes);
|
||||
last = this->next(advance) - last;
|
||||
memcpy(&data[uint32_count], &last, extra_bytes);
|
||||
}
|
||||
void PSOLFGEncryption::encrypt_big_endian(void* vdata, size_t size) {
|
||||
this->encrypt_t<true>(vdata, size);
|
||||
}
|
||||
|
||||
void PSOLFGEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
||||
this->encrypt_t<false>(vdata, size, advance);
|
||||
void PSOLFGEncryption::encrypt_minus(void* vdata, size_t size) {
|
||||
this->encrypt_minus_t<false>(vdata, size);
|
||||
}
|
||||
|
||||
void PSOLFGEncryption::encrypt_big_endian(void* vdata, size_t size, bool advance) {
|
||||
this->encrypt_t<true>(vdata, size, advance);
|
||||
void PSOLFGEncryption::encrypt_big_endian_minus(void* vdata, size_t size) {
|
||||
this->encrypt_minus_t<true>(vdata, size);
|
||||
}
|
||||
|
||||
void PSOLFGEncryption::encrypt_minus(void* vdata, size_t size, bool advance) {
|
||||
this->encrypt_minus_t<false>(vdata, size, advance);
|
||||
}
|
||||
|
||||
void PSOLFGEncryption::encrypt_big_endian_minus(void* vdata, size_t size, bool advance) {
|
||||
this->encrypt_minus_t<true>(vdata, size, advance);
|
||||
}
|
||||
|
||||
void PSOLFGEncryption::encrypt_both_endian(
|
||||
void* le_vdata, void* be_vdata, size_t size, bool advance) {
|
||||
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");
|
||||
}
|
||||
if (!advance && (size != 4)) {
|
||||
throw logic_error("cannot peek-encrypt/decrypt with size > 4");
|
||||
}
|
||||
size >>= 2;
|
||||
|
||||
le_uint32_t* le_data = reinterpret_cast<le_uint32_t*>(le_vdata);
|
||||
be_uint32_t* be_data = reinterpret_cast<be_uint32_t*>(be_vdata);
|
||||
for (size_t x = 0; x < size; x++) {
|
||||
uint32_t key = this->next(advance);
|
||||
uint32_t key = this->next();
|
||||
le_data[x] ^= key;
|
||||
be_data[x] ^= key;
|
||||
}
|
||||
@@ -125,7 +90,6 @@ PSOV2Encryption::PSOV2Encryption(uint32_t seed)
|
||||
for (size_t x = 0; x < 5; x++) {
|
||||
this->update_stream();
|
||||
}
|
||||
this->cycles = 0;
|
||||
}
|
||||
|
||||
void PSOV2Encryption::update_stream() {
|
||||
@@ -136,7 +100,6 @@ void PSOV2Encryption::update_stream() {
|
||||
this->stream[z] -= this->stream[z - 0x18];
|
||||
}
|
||||
this->offset = 1;
|
||||
this->cycles++;
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOV2Encryption::type() const {
|
||||
@@ -174,7 +137,6 @@ PSOV3Encryption::PSOV3Encryption(uint32_t seed)
|
||||
for (size_t x = 0; x < 4; x++) {
|
||||
this->update_stream();
|
||||
}
|
||||
this->cycles = 0;
|
||||
}
|
||||
|
||||
void PSOV3Encryption::update_stream() {
|
||||
@@ -186,7 +148,6 @@ void PSOV3Encryption::update_stream() {
|
||||
this->stream[z] ^= this->stream[z - PHASE2_OFFSET];
|
||||
}
|
||||
this->offset = 0;
|
||||
this->cycles++;
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOV3Encryption::type() const {
|
||||
@@ -199,7 +160,7 @@ PSOBBEncryption::PSOBBEncryption(
|
||||
this->apply_seed(original_seed, seed_size);
|
||||
}
|
||||
|
||||
void PSOBBEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
||||
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");
|
||||
@@ -231,21 +192,13 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
||||
if (size & 1) {
|
||||
throw invalid_argument("size must be a multiple of 2");
|
||||
}
|
||||
if (!advance && (size > 0x100)) {
|
||||
throw logic_error("JSD1 can only peek-encrypt up to 0x100 bytes");
|
||||
}
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
uint8_t v = bytes[z];
|
||||
bytes[z] = v ^ this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset];
|
||||
if (advance) {
|
||||
this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset] -= v;
|
||||
}
|
||||
this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset] -= v;
|
||||
this->state.initial_keys.jsd1_stream_offset++;
|
||||
}
|
||||
if (!advance) {
|
||||
this->state.initial_keys.jsd1_stream_offset -= size;
|
||||
}
|
||||
for (size_t z = 0; z < size; z += 2) {
|
||||
uint8_t a = bytes[z];
|
||||
uint8_t b = bytes[z + 1];
|
||||
@@ -296,7 +249,7 @@ void PSOBBEncryption::encrypt(void* vdata, size_t size, bool advance) {
|
||||
}
|
||||
}
|
||||
|
||||
void PSOBBEncryption::decrypt(void* vdata, size_t size, bool advance) {
|
||||
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");
|
||||
@@ -328,9 +281,6 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size, bool advance) {
|
||||
if (size & 1) {
|
||||
throw invalid_argument("size must be a multiple of 2");
|
||||
}
|
||||
if (!advance && (size > 0x100)) {
|
||||
throw logic_error("JSD1 can only peek-decrypt up to 0x100 bytes");
|
||||
}
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(vdata);
|
||||
for (size_t z = 0; z < size; z += 2) {
|
||||
uint8_t a = bytes[z];
|
||||
@@ -340,14 +290,9 @@ void PSOBBEncryption::decrypt(void* vdata, size_t size, bool advance) {
|
||||
}
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
bytes[z] ^= this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset];
|
||||
if (advance) {
|
||||
this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset] -= bytes[z];
|
||||
}
|
||||
this->state.private_keys.as8[this->state.initial_keys.jsd1_stream_offset] -= bytes[z];
|
||||
this->state.initial_keys.jsd1_stream_offset++;
|
||||
}
|
||||
if (!advance) {
|
||||
this->state.initial_keys.jsd1_stream_offset -= size;
|
||||
}
|
||||
|
||||
} else { // STANDARD or MOCB1
|
||||
if (size & 7) {
|
||||
@@ -676,7 +621,7 @@ PSOV2OrV3DetectorEncryption::PSOV2OrV3DetectorEncryption(
|
||||
v2_matches(v2_matches),
|
||||
v3_matches(v3_matches) {}
|
||||
|
||||
void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
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");
|
||||
@@ -686,29 +631,29 @@ void PSOV2OrV3DetectorEncryption::encrypt(void* data, size_t size, bool advance)
|
||||
|
||||
le_uint32_t decrypted_v2 = encrypted;
|
||||
auto v2_crypt = make_unique<PSOV2Encryption>(this->key);
|
||||
v2_crypt->decrypt(&decrypted_v2, sizeof(decrypted_v2), false);
|
||||
v2_crypt->decrypt(&decrypted_v2, sizeof(decrypted_v2));
|
||||
|
||||
le_uint32_t decrypted_v3 = encrypted;
|
||||
auto v3_crypt = make_unique<PSOV3Encryption>(this->key);
|
||||
v3_crypt->decrypt(&decrypted_v3, sizeof(decrypted_v3), false);
|
||||
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(phosg::string_printf(
|
||||
"unable to determine crypt version (input=%08" PRIX32 ", v2=%08" PRIX32 ", v3=%08" PRIX32 ")",
|
||||
encrypted.load(), decrypted_v2.load(), decrypted_v3.load()));
|
||||
throw 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(phosg::string_printf(
|
||||
"ambiguous crypt version (v2=%08" PRIX32 ", v3=%08" PRIX32 ")",
|
||||
decrypted_v2.load(), decrypted_v3.load()));
|
||||
throw 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;
|
||||
} else {
|
||||
this->active_crypt = std::move(v3_crypt);
|
||||
*reinterpret_cast<le_uint32_t*>(data) = decrypted_v3;
|
||||
}
|
||||
} else {
|
||||
this->active_crypt->encrypt(data, size);
|
||||
}
|
||||
this->active_crypt->encrypt(data, size, advance);
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOV2OrV3DetectorEncryption::type() const {
|
||||
@@ -723,7 +668,7 @@ PSOV2OrV3ImitatorEncryption::PSOV2OrV3ImitatorEncryption(
|
||||
: key(key),
|
||||
detector_crypt(detector_crypt) {}
|
||||
|
||||
void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt) {
|
||||
auto t = this->detector_crypt->type();
|
||||
if (t == Type::V2) {
|
||||
@@ -734,7 +679,7 @@ void PSOV2OrV3ImitatorEncryption::encrypt(void* data, size_t size, bool advance)
|
||||
throw logic_error("detector crypt is not V2 or V3");
|
||||
}
|
||||
}
|
||||
this->active_crypt->encrypt(data, size, advance);
|
||||
this->active_crypt->encrypt(data, size);
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOV2OrV3ImitatorEncryption::type() const {
|
||||
@@ -753,14 +698,14 @@ PSOBBMultiKeyDetectorEncryption::PSOBBMultiKeyDetectorEncryption(
|
||||
expected_first_data(expected_first_data),
|
||||
seed(reinterpret_cast<const char*>(seed), seed_size) {}
|
||||
|
||||
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
void PSOBBMultiKeyDetectorEncryption::encrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt.get()) {
|
||||
throw logic_error("PSOBB multi-key encryption requires client input first");
|
||||
}
|
||||
this->active_crypt->encrypt(data, size, advance);
|
||||
this->active_crypt->encrypt(data, size);
|
||||
}
|
||||
|
||||
void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||
void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size) {
|
||||
if (!this->active_crypt.get()) {
|
||||
if (size != 8) {
|
||||
throw logic_error("initial decryption size does not match expected first data size");
|
||||
@@ -770,7 +715,7 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size, bool adva
|
||||
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->decrypt(test_data.data(), test_data.size(), false);
|
||||
this->active_crypt->decrypt(test_data.data(), test_data.size());
|
||||
if (this->expected_first_data.count(test_data)) {
|
||||
break;
|
||||
}
|
||||
@@ -781,7 +726,7 @@ void PSOBBMultiKeyDetectorEncryption::decrypt(void* data, size_t size, bool adva
|
||||
throw runtime_error("none of the registered private keys are valid for this client");
|
||||
}
|
||||
}
|
||||
this->active_crypt->decrypt(data, size, advance);
|
||||
this->active_crypt->decrypt(data, size);
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOBBMultiKeyDetectorEncryption::type() const {
|
||||
@@ -797,12 +742,12 @@ PSOBBMultiKeyImitatorEncryption::PSOBBMultiKeyImitatorEncryption(
|
||||
seed(reinterpret_cast<const char*>(seed), seed_size),
|
||||
jsd1_use_detector_seed(jsd1_use_detector_seed) {}
|
||||
|
||||
void PSOBBMultiKeyImitatorEncryption::encrypt(void* data, size_t size, bool advance) {
|
||||
this->ensure_crypt()->encrypt(data, size, advance);
|
||||
void PSOBBMultiKeyImitatorEncryption::encrypt(void* data, size_t size) {
|
||||
this->ensure_crypt()->encrypt(data, size);
|
||||
}
|
||||
|
||||
void PSOBBMultiKeyImitatorEncryption::decrypt(void* data, size_t size, bool advance) {
|
||||
this->ensure_crypt()->decrypt(data, size, advance);
|
||||
void PSOBBMultiKeyImitatorEncryption::decrypt(void* data, size_t size) {
|
||||
this->ensure_crypt()->decrypt(data, size);
|
||||
}
|
||||
|
||||
PSOEncryption::Type PSOBBMultiKeyImitatorEncryption::type() const {
|
||||
@@ -835,7 +780,7 @@ JSD0Encryption::JSD0Encryption(const void* seed, size_t seed_size) : key(0) {
|
||||
}
|
||||
}
|
||||
|
||||
void JSD0Encryption::decrypt(void* data, size_t size, bool) {
|
||||
void JSD0Encryption::decrypt(void* data, size_t size) {
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(data);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
bytes[z] ^= this->key;
|
||||
@@ -843,7 +788,7 @@ void JSD0Encryption::decrypt(void* data, size_t size, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
void JSD0Encryption::encrypt(void* data, size_t size, bool) {
|
||||
void JSD0Encryption::encrypt(void* data, size_t size) {
|
||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(data);
|
||||
for (size_t z = 0; z < size; z++) {
|
||||
bytes[z] += this->key;
|
||||
|
||||
+87
-39
@@ -6,6 +6,7 @@
|
||||
#include <memory>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <random>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -14,6 +15,39 @@
|
||||
#include "Text.hh"
|
||||
#include "Types.hh"
|
||||
|
||||
class RandomGenerator {
|
||||
public:
|
||||
virtual ~RandomGenerator() = default;
|
||||
virtual uint32_t next() = 0;
|
||||
|
||||
inline uint32_t seed() const {
|
||||
return this->initial_seed;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint32_t initial_seed;
|
||||
RandomGenerator(uint32_t seed);
|
||||
};
|
||||
|
||||
class DisabledRandomGenerator : public RandomGenerator {
|
||||
public:
|
||||
DisabledRandomGenerator();
|
||||
virtual ~DisabledRandomGenerator() = default;
|
||||
|
||||
virtual uint32_t next();
|
||||
};
|
||||
|
||||
class MT19937Generator : public RandomGenerator {
|
||||
public:
|
||||
explicit MT19937Generator(uint32_t seed);
|
||||
virtual ~MT19937Generator() = default;
|
||||
|
||||
virtual uint32_t next();
|
||||
|
||||
private:
|
||||
std::mt19937 gen;
|
||||
};
|
||||
|
||||
class PSOEncryption {
|
||||
public:
|
||||
enum class Type {
|
||||
@@ -25,14 +59,14 @@ public:
|
||||
|
||||
virtual ~PSOEncryption() = default;
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true) = 0;
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size) = 0;
|
||||
virtual void decrypt(void* data, size_t size);
|
||||
|
||||
inline void encrypt(std::string& data, bool advance = true) {
|
||||
this->encrypt(data.data(), data.size(), advance);
|
||||
inline void encrypt(std::string& data) {
|
||||
this->encrypt(data.data(), data.size());
|
||||
}
|
||||
inline void decrypt(std::string& data, bool advance = true) {
|
||||
this->decrypt(data.data(), data.size(), advance);
|
||||
inline void decrypt(std::string& data) {
|
||||
this->decrypt(data.data(), data.size());
|
||||
}
|
||||
|
||||
virtual Type type() const = 0;
|
||||
@@ -41,28 +75,48 @@ protected:
|
||||
PSOEncryption() = default;
|
||||
};
|
||||
|
||||
class PSOLFGEncryption : public PSOEncryption {
|
||||
class PSOLFGEncryption : public PSOEncryption, public RandomGenerator {
|
||||
public:
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian(void* data, size_t size, bool advance = true);
|
||||
void encrypt_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_big_endian_minus(void* data, size_t size, bool advance = true);
|
||||
void encrypt_both_endian(void* le_data, void* be_data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
void encrypt_big_endian(void* data, size_t size);
|
||||
void encrypt_minus(void* data, size_t size);
|
||||
void encrypt_big_endian_minus(void* data, size_t size);
|
||||
void encrypt_both_endian(void* le_data, void* be_data, size_t size);
|
||||
|
||||
template <bool BE>
|
||||
void encrypt_t(void* data, size_t size, bool advance = true);
|
||||
void encrypt_t(void* vdata, size_t size) {
|
||||
size_t uint32_count = size >> 2;
|
||||
size_t extra_bytes = size & 3;
|
||||
U32T<BE>* data = reinterpret_cast<U32T<BE>*>(vdata);
|
||||
for (size_t x = 0; x < uint32_count; x++) {
|
||||
data[x] ^= this->next();
|
||||
}
|
||||
if (extra_bytes) {
|
||||
U32T<BE> last = 0;
|
||||
memcpy(&last, &data[uint32_count], extra_bytes);
|
||||
last ^= this->next();
|
||||
memcpy(&data[uint32_count], &last, extra_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool BE>
|
||||
void encrypt_minus_t(void* data, size_t size, bool advance = true);
|
||||
|
||||
uint32_t next(bool advance = true);
|
||||
|
||||
inline uint32_t seed() const {
|
||||
return this->initial_seed;
|
||||
}
|
||||
uint32_t absolute_offset() const {
|
||||
return (this->cycles * this->end_offset) + this->offset;
|
||||
void encrypt_minus_t(void* vdata, size_t size) {
|
||||
size_t uint32_count = size >> 2;
|
||||
size_t extra_bytes = size & 3;
|
||||
U32T<BE>* data = reinterpret_cast<U32T<BE>*>(vdata);
|
||||
for (size_t x = 0; x < uint32_count; x++) {
|
||||
data[x] = this->next() - data[x];
|
||||
}
|
||||
if (extra_bytes) {
|
||||
U32T<BE> last = 0;
|
||||
memcpy(&last, &data[uint32_count], extra_bytes);
|
||||
last = this->next() - last;
|
||||
memcpy(&data[uint32_count], &last, extra_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
virtual uint32_t next();
|
||||
|
||||
protected:
|
||||
PSOLFGEncryption(uint32_t seed, size_t stream_length, size_t end_offset);
|
||||
|
||||
@@ -71,8 +125,6 @@ protected:
|
||||
std::vector<uint32_t> stream;
|
||||
size_t offset;
|
||||
size_t end_offset;
|
||||
uint32_t initial_seed;
|
||||
size_t cycles;
|
||||
};
|
||||
|
||||
class PSOV2Encryption : public PSOLFGEncryption {
|
||||
@@ -134,8 +186,8 @@ public:
|
||||
|
||||
PSOBBEncryption(const KeyFile& key, const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
virtual void decrypt(void* data, size_t size);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
@@ -156,7 +208,7 @@ public:
|
||||
const std::unordered_set<uint32_t>& v2_matches,
|
||||
const std::unordered_set<uint32_t>& v3_matches);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
@@ -172,7 +224,7 @@ public:
|
||||
PSOV2OrV3ImitatorEncryption(
|
||||
uint32_t key, std::shared_ptr<PSOV2OrV3DetectorEncryption> client_crypt);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
@@ -194,8 +246,8 @@ public:
|
||||
const void* seed,
|
||||
size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
virtual void decrypt(void* data, size_t size);
|
||||
|
||||
inline std::shared_ptr<const PSOBBEncryption::KeyFile> get_active_key() const {
|
||||
return this->active_key;
|
||||
@@ -222,8 +274,8 @@ public:
|
||||
size_t seed_size,
|
||||
bool jsd1_use_detector_seed);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
virtual void decrypt(void* data, size_t size);
|
||||
|
||||
virtual Type type() const;
|
||||
|
||||
@@ -240,8 +292,8 @@ class JSD0Encryption : public PSOEncryption {
|
||||
public:
|
||||
JSD0Encryption(const void* seed, size_t seed_size);
|
||||
|
||||
virtual void encrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void decrypt(void* data, size_t size, bool advance = true);
|
||||
virtual void encrypt(void* data, size_t size);
|
||||
virtual void decrypt(void* data, size_t size);
|
||||
|
||||
virtual Type type() const = 0;
|
||||
|
||||
@@ -260,7 +312,7 @@ private:
|
||||
U32T<BE> value;
|
||||
|
||||
public:
|
||||
ChallengeTimeT() = default;
|
||||
ChallengeTimeT() : value(0) {}
|
||||
ChallengeTimeT(uint16_t v) {
|
||||
this->encode(v);
|
||||
}
|
||||
@@ -353,7 +405,3 @@ std::string encrypt_pr2_data(const std::string& data, size_t decompressed_size,
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline uint32_t random_from_optional_crypt(std::shared_ptr<PSOLFGEncryption> random_crypt) {
|
||||
return random_crypt ? random_crypt->next() : phosg::random_object<uint32_t>();
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ void PSOGCObjectGraph::Object::print(FILE* stream, size_t indent_level) const {
|
||||
fputc(' ', stream);
|
||||
fputc(' ', stream);
|
||||
}
|
||||
fprintf(stream, "%s +%04hX @ %08" PRIX32 " (VT %08" PRIX32 ": destroy=%08" PRIX32 " update=%08" PRIX32 " render=%08" PRIX32 " render_shadow=%08" PRIX32 ")\n",
|
||||
this->type_name.c_str(),
|
||||
phosg::fwrite_fmt(stream, "{} +{:04X} @ {:08X} (VT {:08X}: destroy={:08X} update={:08X} render={:08X} render_shadow={:08X})\n",
|
||||
this->type_name,
|
||||
this->flags,
|
||||
this->address,
|
||||
this->vtable->address,
|
||||
|
||||
+4
-6
@@ -1,7 +1,5 @@
|
||||
#include "PSOProtocol.hh"
|
||||
|
||||
#include <event2/buffer.h>
|
||||
|
||||
#include <phosg/Strings.hh>
|
||||
#include <stdexcept>
|
||||
|
||||
@@ -190,16 +188,16 @@ void PSOCommandHeader::set_flag(Version version, uint32_t flag) {
|
||||
|
||||
void check_size_v(size_t size, size_t min_size, size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(phosg::string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
throw std::runtime_error(std::format(
|
||||
"command too small (expected at least 0x{:X} bytes, received 0x{:X} bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (max_size < min_size) {
|
||||
max_size = min_size;
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(phosg::string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
throw std::runtime_error(std::format(
|
||||
"command too large (expected at most 0x{:X} bytes, received 0x{:X} bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-5
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/bufferevent.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <functional>
|
||||
@@ -56,13 +55,13 @@ RetT& check_size_generic(
|
||||
size_t min_size,
|
||||
size_t max_size) {
|
||||
if (size < min_size) {
|
||||
throw std::runtime_error(phosg::string_printf(
|
||||
"command too small (expected at least 0x%zX bytes, received 0x%zX bytes)",
|
||||
throw std::runtime_error(std::format(
|
||||
"command too small (expected at least 0x{:X} bytes, received 0x{:X} bytes)",
|
||||
min_size, size));
|
||||
}
|
||||
if (size > max_size) {
|
||||
throw std::runtime_error(phosg::string_printf(
|
||||
"command too large (expected at most 0x%zX bytes, received 0x%zX bytes)",
|
||||
throw std::runtime_error(std::format(
|
||||
"command too large (expected at most 0x{:X} bytes, received 0x{:X} bytes)",
|
||||
max_size, size));
|
||||
}
|
||||
return *reinterpret_cast<RetT*>(data);
|
||||
|
||||
+31
-23
@@ -3,6 +3,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
@@ -13,6 +14,13 @@
|
||||
|
||||
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>(
|
||||
mtime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return sctp.time_since_epoch().count();
|
||||
}
|
||||
|
||||
PatchFileIndex::File::File(PatchFileIndex* index)
|
||||
: index(index),
|
||||
crc32(0),
|
||||
@@ -22,7 +30,7 @@ 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;
|
||||
patch_index_log.info("Loading data for %s", relative_path.c_str());
|
||||
patch_index_log.info_f("Loading data for {}", relative_path);
|
||||
this->loaded_data = make_shared<string>(phosg::load_file(full_path));
|
||||
this->size = this->loaded_data->size();
|
||||
}
|
||||
@@ -37,10 +45,10 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
try {
|
||||
string metadata_text = phosg::load_file(metadata_cache_filename);
|
||||
metadata_cache_json = phosg::JSON::parse(metadata_text);
|
||||
patch_index_log.info("Loaded patch metadata cache from %s", metadata_cache_filename.c_str());
|
||||
patch_index_log.info_f("Loaded patch metadata cache from {}", metadata_cache_filename);
|
||||
} catch (const exception& e) {
|
||||
metadata_cache_json = phosg::JSON::dict();
|
||||
patch_index_log.warning("Cannot load patch metadata cache from %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
patch_index_log.warning_f("Cannot load patch metadata cache from {}: {}", metadata_cache_filename, e.what());
|
||||
}
|
||||
|
||||
// Assuming it's rare for patch files to change, we skip writing the metadata
|
||||
@@ -54,36 +62,38 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
|
||||
string relative_dirs = phosg::join(path_directories, "/");
|
||||
string full_dir_path = root_dir + '/' + relative_dirs;
|
||||
patch_index_log.info("Listing directory %s", full_dir_path.c_str());
|
||||
patch_index_log.info_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();
|
||||
|
||||
for (const auto& item : phosg::list_directory(full_dir_path)) {
|
||||
// Skip invisible files (e.g. .DS_Store on macOS)
|
||||
if (phosg::starts_with(item, ".")) {
|
||||
if (item.starts_with(".")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string relative_item_path = relative_dirs + '/' + item;
|
||||
string full_item_path = root_dir + '/' + relative_item_path;
|
||||
if (phosg::isdir(full_item_path)) {
|
||||
if (std::filesystem::is_directory(full_item_path)) {
|
||||
collect_dir(item);
|
||||
} else if (phosg::isfile(full_item_path)) {
|
||||
|
||||
auto st = phosg::stat(full_item_path);
|
||||
} else if (std::filesystem::is_regular_file(full_item_path)) {
|
||||
|
||||
auto f = 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
|
||||
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);
|
||||
uint64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (static_cast<uint64_t>(st.st_mtime) != cached_mtime) {
|
||||
int64_t cached_mtime = cache_item_json.get_int(1);
|
||||
if (file_mtime != cached_mtime) {
|
||||
throw runtime_error("file has been modified");
|
||||
}
|
||||
if (static_cast<uint64_t>(st.st_size) != cached_size) {
|
||||
if (std::filesystem::file_size(full_item_path) != cached_size) {
|
||||
throw runtime_error("file size has changed");
|
||||
}
|
||||
f->size = cached_size;
|
||||
@@ -110,7 +120,7 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
chunk_crcs_item.emplace_back(chunk_crc);
|
||||
}
|
||||
new_metadata_cache_json.emplace(
|
||||
relative_item_path, phosg::JSON::list({f->size, st.st_mtime, f->crc32, std::move(chunk_crcs_item)}));
|
||||
relative_item_path, phosg::JSON::list({f->size, file_mtime, f->crc32, std::move(chunk_crcs_item)}));
|
||||
should_write_metadata_cache = true;
|
||||
|
||||
} else {
|
||||
@@ -123,13 +133,11 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
this->files_by_patch_order.emplace_back(f);
|
||||
this->files_by_name.emplace(relative_item_path, f);
|
||||
if (compute_crc32s_message.empty()) {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " from cache)",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32);
|
||||
patch_index_log.info_f("Added file {} ({} bytes; {} chunks; {:08X} from cache)",
|
||||
full_item_path, f->size, f->chunk_crcs.size(), f->crc32);
|
||||
} else {
|
||||
patch_index_log.info(
|
||||
"Added file %s (%" PRIu32 " bytes; %zu chunks; %08" PRIX32 " [%s])",
|
||||
full_item_path.c_str(), f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message.c_str());
|
||||
patch_index_log.info_f("Added file {} ({} bytes; {} chunks; {:08X} [{}])",
|
||||
full_item_path, f->size, f->chunk_crcs.size(), f->crc32, compute_crc32s_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,12 +150,12 @@ PatchFileIndex::PatchFileIndex(const string& root_dir)
|
||||
if (should_write_metadata_cache) {
|
||||
try {
|
||||
phosg::save_file(metadata_cache_filename, new_metadata_cache_json.serialize());
|
||||
patch_index_log.info("Saved patch metadata cache to %s", metadata_cache_filename.c_str());
|
||||
patch_index_log.info_f("Saved patch metadata cache to {}", metadata_cache_filename);
|
||||
} catch (const exception& e) {
|
||||
patch_index_log.warning("Cannot save patch metadata cache to %s: %s", metadata_cache_filename.c_str(), e.what());
|
||||
patch_index_log.warning_f("Cannot save patch metadata cache to {}: {}", metadata_cache_filename, e.what());
|
||||
}
|
||||
} else {
|
||||
patch_index_log.info("No files were modified; skipping metadata cache update");
|
||||
patch_index_log.info_f("No files were modified; skipping metadata cache update");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
#include "PatchServer.hh"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Network.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "EventUtils.hh"
|
||||
#include "Loggers.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ReceiveCommands.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static atomic<uint64_t> next_id(1);
|
||||
|
||||
PatchServer::Client::Client(
|
||||
shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs)
|
||||
: server(server),
|
||||
id(next_id++),
|
||||
log(phosg::string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level),
|
||||
channel(bev, 0, version, 1, nullptr, nullptr, this, phosg::string_printf("C-%" PRIX64, this->id), phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::FG_GREEN),
|
||||
idle_timeout_usecs(idle_timeout_usecs),
|
||||
idle_timeout_event(
|
||||
event_new(bufferevent_get_base(bev), -1, EV_TIMEOUT, &PatchServer::Client::dispatch_idle_timeout, this),
|
||||
event_free) {
|
||||
this->reschedule_timeout_event();
|
||||
|
||||
// Don't print data sent to patch clients to the logs. The patch server
|
||||
// protocol is fully understood and data logs for patch clients are generally
|
||||
// more annoying than helpful at this point.
|
||||
if (hide_data_from_logs) {
|
||||
this->channel.terminal_recv_color = phosg::TerminalFormat::END;
|
||||
this->channel.terminal_send_color = phosg::TerminalFormat::END;
|
||||
}
|
||||
|
||||
this->log.info("Created");
|
||||
}
|
||||
|
||||
void PatchServer::Client::reschedule_timeout_event() {
|
||||
struct timeval idle_tv = phosg::usecs_to_timeval(this->idle_timeout_usecs);
|
||||
event_add(this->idle_timeout_event.get(), &idle_tv);
|
||||
}
|
||||
|
||||
void PatchServer::Client::dispatch_idle_timeout(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<Client*>(ctx)->idle_timeout();
|
||||
}
|
||||
|
||||
void PatchServer::Client::idle_timeout() {
|
||||
this->log.info("Idle timeout expired");
|
||||
auto s = this->server.lock();
|
||||
if (s) {
|
||||
auto c = this->shared_from_this();
|
||||
s->disconnect_client(c);
|
||||
} else {
|
||||
this->channel.disconnect();
|
||||
this->log.info("Server is deleted; cannot disconnect client");
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::send_server_init(shared_ptr<Client> c) const {
|
||||
uint32_t server_key = phosg::random_object<uint32_t>();
|
||||
uint32_t client_key = phosg::random_object<uint32_t>();
|
||||
|
||||
S_ServerInit_Patch_02 cmd;
|
||||
cmd.copyright.encode("Patch Server. Copyright SonicTeam, LTD. 2001");
|
||||
cmd.server_key = server_key;
|
||||
cmd.client_key = client_key;
|
||||
c->channel.send(0x02, 0x00, cmd);
|
||||
|
||||
c->channel.crypt_out = make_shared<PSOV2Encryption>(server_key);
|
||||
c->channel.crypt_in = make_shared<PSOV2Encryption>(client_key);
|
||||
}
|
||||
|
||||
void PatchServer::send_message_box(shared_ptr<Client> c, const string& text) const {
|
||||
phosg::StringWriter w;
|
||||
try {
|
||||
if (c->version() == Version::PC_PATCH) {
|
||||
w.write(tt_encode_marked_optional(text, c->channel.language, true));
|
||||
} else if (c->version() == Version::BB_PATCH) {
|
||||
w.write(tt_encode_marked_optional(add_color(text), c->channel.language, true));
|
||||
} else {
|
||||
throw logic_error("non-patch client on patch server");
|
||||
}
|
||||
} catch (const runtime_error& e) {
|
||||
phosg::log_warning("Failed to encode message for patch message box command: %s", e.what());
|
||||
return;
|
||||
}
|
||||
w.put_u16(0);
|
||||
while (w.str().size() & 3) {
|
||||
w.put_u8(0);
|
||||
}
|
||||
c->channel.send(0x13, 0x00, w.str());
|
||||
}
|
||||
|
||||
void PatchServer::send_enter_directory(shared_ptr<Client> c, const string& dir) const {
|
||||
S_EnterDirectory_Patch_09 cmd = {{dir, 1}};
|
||||
c->channel.send(0x09, 0x00, cmd);
|
||||
}
|
||||
|
||||
void PatchServer::on_02(shared_ptr<Client> c, string& data) {
|
||||
check_size_v(data.size(), 0);
|
||||
c->channel.send(0x04, 0x00); // This requests the user's login information
|
||||
}
|
||||
|
||||
void PatchServer::change_to_directory(
|
||||
shared_ptr<Client> c,
|
||||
vector<string>& client_path_directories,
|
||||
const vector<string>& file_path_directories) const {
|
||||
// First, exit all leaf directories that don't match the desired path
|
||||
while (!client_path_directories.empty() &&
|
||||
((client_path_directories.size() > file_path_directories.size()) ||
|
||||
(client_path_directories.back() != file_path_directories[client_path_directories.size() - 1]))) {
|
||||
c->channel.send(0x0A, 0x00);
|
||||
client_path_directories.pop_back();
|
||||
}
|
||||
|
||||
// At this point, client_path_directories should be a prefix of
|
||||
// file_path_directories (or should match exactly)
|
||||
if (client_path_directories.size() > file_path_directories.size()) {
|
||||
throw logic_error("did not exit all necessary directories");
|
||||
}
|
||||
for (size_t x = 0; x < client_path_directories.size(); x++) {
|
||||
if (client_path_directories[x] != file_path_directories[x]) {
|
||||
throw logic_error("intermediate path is not a prefix of final path");
|
||||
}
|
||||
}
|
||||
|
||||
// Second, enter all necessary leaf directories
|
||||
while (client_path_directories.size() < file_path_directories.size()) {
|
||||
const string& dir = file_path_directories[client_path_directories.size()];
|
||||
this->send_enter_directory(c, dir);
|
||||
client_path_directories.emplace_back(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_04(shared_ptr<Client> c, string& data) {
|
||||
const auto& cmd = check_size_t<C_Login_Patch_04>(data);
|
||||
|
||||
string username = cmd.username.decode();
|
||||
string password = cmd.password.decode();
|
||||
|
||||
// There are 3 cases here:
|
||||
// - No login information at all: just proceed without checking credentials
|
||||
// - Username: check that account exists if allow_unregistered_users is off
|
||||
// - Username and password: call verify_bb
|
||||
if (!username.empty() && !password.empty()) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, &password, false);
|
||||
|
||||
} catch (const AccountIndex::incorrect_password& e) {
|
||||
c->channel.send(0x15, 0x03);
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
if (!this->config->allow_unregistered_users) {
|
||||
c->channel.send(0x15, 0x08);
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!username.empty() && !this->config->allow_unregistered_users) {
|
||||
try {
|
||||
this->config->account_index->from_bb_credentials(username, nullptr, false);
|
||||
} catch (const AccountIndex::missing_account& e) {
|
||||
c->channel.send(0x15, 0x08);
|
||||
this->disconnect_client(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->config->message.empty()) {
|
||||
this->send_message_box(c, this->config->message.c_str());
|
||||
}
|
||||
|
||||
const auto& index = this->config->patch_file_index;
|
||||
if (index.get()) {
|
||||
c->channel.send(0x0B, 0x00); // Start patch session; go to root directory
|
||||
|
||||
vector<string> path_directories;
|
||||
for (const auto& file : index->all_files()) {
|
||||
this->change_to_directory(c, path_directories, file->path_directories);
|
||||
|
||||
S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}};
|
||||
c->channel.send(0x0C, 0x00, req);
|
||||
c->patch_file_checksum_requests.emplace_back(file);
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
|
||||
c->channel.send(0x0D, 0x00); // End of checksum requests
|
||||
|
||||
} else {
|
||||
// No patch index present: just do something that will satisfy the client
|
||||
// without actually checking or downloading any files
|
||||
this->send_enter_directory(c, ".");
|
||||
this->send_enter_directory(c, "data");
|
||||
this->send_enter_directory(c, "scene");
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x0A, 0x00);
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_0F(shared_ptr<Client> c, string& data) {
|
||||
auto& cmd = check_size_t<C_FileInformation_Patch_0F>(data);
|
||||
auto& req = c->patch_file_checksum_requests.at(cmd.request_id);
|
||||
req.crc32 = cmd.checksum;
|
||||
req.size = cmd.size;
|
||||
req.response_received = true;
|
||||
}
|
||||
|
||||
void PatchServer::on_10(shared_ptr<Client> c, string&) {
|
||||
S_StartFileDownloads_Patch_11 start_cmd = {0, 0};
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (!req.response_received) {
|
||||
throw runtime_error("client did not respond to checksum request");
|
||||
}
|
||||
if (req.needs_update()) {
|
||||
c->log.info("File %s needs update (CRC: %08" PRIX32 "/%08" PRIX32 ", size: %" PRIu32 "/%" PRIu32 ")",
|
||||
req.file->name.c_str(), req.file->crc32, req.crc32, req.file->size, req.size);
|
||||
start_cmd.total_bytes += req.file->size;
|
||||
start_cmd.num_files++;
|
||||
} else {
|
||||
c->log.info("File %s is up to date", req.file->name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (start_cmd.num_files) {
|
||||
c->channel.send(0x11, 0x00, start_cmd);
|
||||
vector<string> path_directories;
|
||||
for (const auto& req : c->patch_file_checksum_requests) {
|
||||
if (req.needs_update()) {
|
||||
this->change_to_directory(c, path_directories, req.file->path_directories);
|
||||
|
||||
S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}};
|
||||
c->channel.send(0x06, 0x00, open_cmd);
|
||||
|
||||
for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) {
|
||||
auto data = req.file->load_data();
|
||||
size_t chunk_size = min<uint32_t>(req.file->size - (x * 0x4000), 0x4000);
|
||||
|
||||
vector<pair<const void*, size_t>> blocks;
|
||||
S_WriteFileHeader_Patch_07 cmd_header = {x, req.file->chunk_crcs[x], chunk_size};
|
||||
blocks.emplace_back(&cmd_header, sizeof(cmd_header));
|
||||
blocks.emplace_back(data->data() + (x * 0x4000), chunk_size);
|
||||
c->channel.send(0x07, 0x00, blocks);
|
||||
}
|
||||
|
||||
S_CloseCurrentFile_Patch_08 close_cmd = {0};
|
||||
c->channel.send(0x08, 0x00, close_cmd);
|
||||
}
|
||||
}
|
||||
this->change_to_directory(c, path_directories, {});
|
||||
}
|
||||
|
||||
c->channel.send(0x12, 0x00);
|
||||
}
|
||||
|
||||
void PatchServer::disconnect_client(shared_ptr<Client> c) {
|
||||
if (c->channel.virtual_network_id) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64 " on N-%" PRIu64, c->id, c->channel.virtual_network_id);
|
||||
} else if (c->channel.bev) {
|
||||
server_log.info("Client disconnected: C-%" PRIX64, c->id);
|
||||
} else {
|
||||
server_log.info("Client C-%" PRIX64 " removed from patch server", c->id);
|
||||
}
|
||||
|
||||
this->channel_to_client.erase(&c->channel);
|
||||
c->channel.disconnect();
|
||||
|
||||
// We can't just let c be destroyed here, since disconnect_client can be
|
||||
// called from within the client's channel's receive handler. So, we instead
|
||||
// move it to another set, which we'll clear in an immediately-enqueued
|
||||
// callback after the current event. This will also call the client's
|
||||
// disconnect hooks (if any).
|
||||
this->clients_to_destroy.insert(std::move(c));
|
||||
this->enqueue_destroy_clients();
|
||||
}
|
||||
|
||||
void PatchServer::enqueue_destroy_clients() {
|
||||
auto tv = phosg::usecs_to_timeval(0);
|
||||
event_add(this->destroy_clients_ev.get(), &tv);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_destroy_clients(evutil_socket_t, short, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->clients_to_destroy.clear();
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_accept(
|
||||
struct evconnlistener* listener, evutil_socket_t fd,
|
||||
struct sockaddr* address, int socklen, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_accept(listener, fd, address, socklen);
|
||||
}
|
||||
|
||||
void PatchServer::dispatch_on_listen_error(
|
||||
struct evconnlistener* listener, void* ctx) {
|
||||
reinterpret_cast<PatchServer*>(ctx)->on_listen_error(listener);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr*, int) {
|
||||
struct sockaddr_storage remote_addr;
|
||||
phosg::get_socket_addresses(fd, nullptr, &remote_addr);
|
||||
if (this->config->banned_ipv4_ranges->check(remote_addr)) {
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int listen_fd = evconnlistener_get_fd(listener);
|
||||
ListeningSocket* listening_socket;
|
||||
try {
|
||||
listening_socket = &this->listening_sockets.at(listen_fd);
|
||||
} catch (const out_of_range& e) {
|
||||
server_log.warning("Can\'t determine version for socket %d; disconnecting client", listen_fd);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
struct bufferevent* bev = bufferevent_socket_new(this->base.get(), fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
|
||||
auto c = make_shared<Client>(
|
||||
this->shared_from_this(),
|
||||
bev,
|
||||
listening_socket->version,
|
||||
this->config->idle_timeout_usecs,
|
||||
this->config->hide_data_from_logs);
|
||||
c->channel.on_command_received = PatchServer::on_client_input;
|
||||
c->channel.on_error = PatchServer::on_client_error;
|
||||
c->channel.context_obj = this;
|
||||
this->channel_to_client.emplace(&c->channel, c);
|
||||
|
||||
server_log.info("Patch client connected: C-%" PRIX64 " on fd %d via %d (%s)",
|
||||
c->id, fd, listen_fd, listening_socket->addr_str.c_str());
|
||||
|
||||
this->send_server_init(c);
|
||||
}
|
||||
|
||||
void PatchServer::on_listen_error(struct evconnlistener* listener) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.error("Failure on listening socket %d: %d (%s)",
|
||||
evconnlistener_get_fd(listener), err, evutil_socket_error_to_string(err));
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
|
||||
void PatchServer::on_client_input(Channel& ch, uint16_t command, uint32_t, std::string& data) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 0x02:
|
||||
server->on_02(c, data);
|
||||
break;
|
||||
case 0x04:
|
||||
server->on_04(c, data);
|
||||
break;
|
||||
case 0x0F:
|
||||
server->on_0F(c, data);
|
||||
break;
|
||||
case 0x10:
|
||||
server->on_10(c, data);
|
||||
break;
|
||||
default:
|
||||
throw runtime_error("invalid command");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
server_log.warning("Error processing client command: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::on_client_error(Channel& ch, short events) {
|
||||
PatchServer* server = reinterpret_cast<PatchServer*>(ch.context_obj);
|
||||
shared_ptr<Client> c = server->channel_to_client.at(&ch);
|
||||
|
||||
if (events & BEV_EVENT_ERROR) {
|
||||
int err = EVUTIL_SOCKET_ERROR();
|
||||
server_log.warning("Client caused error %d (%s)", err, evutil_socket_error_to_string(err));
|
||||
}
|
||||
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
|
||||
server->disconnect_client(c);
|
||||
}
|
||||
}
|
||||
|
||||
PatchServer::PatchServer(shared_ptr<const Config> config)
|
||||
: config(config) {
|
||||
if (config->shared_base) {
|
||||
this->base = config->shared_base;
|
||||
this->base_is_shared = true;
|
||||
} else {
|
||||
this->base.reset(event_base_new(), event_base_free);
|
||||
this->base_is_shared = false;
|
||||
}
|
||||
this->destroy_clients_ev.reset(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT, &PatchServer::dispatch_destroy_clients, this), event_free);
|
||||
if (!this->base_is_shared) {
|
||||
this->th = thread(&PatchServer::thread_fn, this);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::schedule_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
event_base_loopexit(this->base.get(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::wait_for_stop() {
|
||||
if (!this->base_is_shared) {
|
||||
this->th.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& socket_path, Version version) {
|
||||
int fd = phosg::listen(socket_path, 0, SOMAXCONN);
|
||||
server_log.info("Listening on Unix socket %s on fd %d as %s", socket_path.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, const string& addr, int port, Version version) {
|
||||
if (port == 0) {
|
||||
this->listen(addr_str, addr, version);
|
||||
} else {
|
||||
int fd = phosg::listen(addr, port, SOMAXCONN);
|
||||
string netloc_str = phosg::render_netloc(addr, port);
|
||||
server_log.info("Listening on TCP interface %s on fd %d as %s", netloc_str.c_str(), fd, addr_str.c_str());
|
||||
this->add_socket(addr_str, fd, version);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchServer::listen(const std::string& addr_str, int port, Version version) {
|
||||
this->listen(addr_str, "", port, version);
|
||||
}
|
||||
|
||||
PatchServer::ListeningSocket::ListeningSocket(PatchServer* s, const std::string& addr_str, int fd, Version version)
|
||||
: addr_str(addr_str),
|
||||
fd(fd),
|
||||
version(version),
|
||||
listener(evconnlistener_new(s->base.get(), PatchServer::dispatch_on_listen_accept, s, LEV_OPT_REUSEABLE, 0, this->fd),
|
||||
evconnlistener_free) {
|
||||
evconnlistener_set_error_cb(this->listener.get(), PatchServer::dispatch_on_listen_error);
|
||||
}
|
||||
|
||||
void PatchServer::add_socket(const std::string& addr_str, int fd, Version version) {
|
||||
this->listening_sockets.emplace(piecewise_construct, forward_as_tuple(fd), forward_as_tuple(this, addr_str, fd, version));
|
||||
}
|
||||
|
||||
void PatchServer::thread_fn() {
|
||||
event_base_loop(this->base.get(), EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
}
|
||||
|
||||
void PatchServer::set_config(std::shared_ptr<const Config> config) {
|
||||
if (this->base_is_shared) {
|
||||
this->config = config;
|
||||
} else {
|
||||
forward_to_event_thread(this->base, [s = this->shared_from_this(), config = std::move(config)]() {
|
||||
s->config = config;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "Account.hh"
|
||||
#include "Channel.hh"
|
||||
#include "IPV4RangeSet.hh"
|
||||
#include "PatchFileIndex.hh"
|
||||
#include "Version.hh"
|
||||
|
||||
class PatchServer : public std::enable_shared_from_this<PatchServer> {
|
||||
public:
|
||||
struct Config {
|
||||
bool allow_unregistered_users;
|
||||
bool hide_data_from_logs;
|
||||
uint64_t idle_timeout_usecs;
|
||||
std::string message;
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<const PatchFileIndex> patch_file_index;
|
||||
std::shared_ptr<const IPV4RangeSet> banned_ipv4_ranges;
|
||||
std::shared_ptr<struct event_base> shared_base;
|
||||
};
|
||||
|
||||
PatchServer() = delete;
|
||||
explicit PatchServer(std::shared_ptr<const Config> config);
|
||||
PatchServer(const PatchServer&) = delete;
|
||||
PatchServer(PatchServer&&) = delete;
|
||||
PatchServer& operator=(const PatchServer&) = delete;
|
||||
PatchServer& operator=(PatchServer&&) = delete;
|
||||
virtual ~PatchServer() = default;
|
||||
|
||||
void schedule_stop();
|
||||
void wait_for_stop();
|
||||
|
||||
void listen(const std::string& addr_str, const std::string& socket_path, Version version);
|
||||
void listen(const std::string& addr_str, const std::string& addr, int port, Version version);
|
||||
void listen(const std::string& addr_str, int port, Version version);
|
||||
void add_socket(const std::string& addr_str, int fd, Version version);
|
||||
|
||||
void set_config(std::shared_ptr<const Config> config);
|
||||
|
||||
private:
|
||||
class Client : public std::enable_shared_from_this<Client> {
|
||||
public:
|
||||
std::weak_ptr<PatchServer> server;
|
||||
uint64_t id;
|
||||
phosg::PrefixedLogger log;
|
||||
|
||||
Channel channel;
|
||||
std::vector<PatchFileChecksumRequest> patch_file_checksum_requests;
|
||||
uint64_t idle_timeout_usecs;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
|
||||
|
||||
Client(
|
||||
std::shared_ptr<PatchServer> server,
|
||||
struct bufferevent* bev,
|
||||
Version version,
|
||||
uint64_t idle_timeout_usecs,
|
||||
bool hide_data_from_logs);
|
||||
~Client() = default;
|
||||
|
||||
void reschedule_timeout_event();
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
static void dispatch_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
void idle_timeout();
|
||||
};
|
||||
|
||||
struct ListeningSocket {
|
||||
std::string addr_str;
|
||||
int fd;
|
||||
Version version;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
|
||||
ListeningSocket(PatchServer* s, const std::string& name, int fd, Version version);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
bool base_is_shared;
|
||||
std::shared_ptr<const Config> config;
|
||||
|
||||
std::unordered_set<std::shared_ptr<Client>> clients_to_destroy;
|
||||
std::shared_ptr<struct event> destroy_clients_ev;
|
||||
|
||||
std::unordered_map<int, ListeningSocket> listening_sockets;
|
||||
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
|
||||
|
||||
std::thread th;
|
||||
|
||||
void send_server_init(std::shared_ptr<Client> c) const;
|
||||
void send_message_box(std::shared_ptr<Client> c, const std::string& text) const;
|
||||
void send_enter_directory(std::shared_ptr<Client> c, const std::string& dir) const;
|
||||
void change_to_directory(
|
||||
std::shared_ptr<Client> c,
|
||||
std::vector<std::string>& client_path_directories,
|
||||
const std::vector<std::string>& file_path_directories) const;
|
||||
void on_02(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_04(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_0F(std::shared_ptr<Client> c, std::string& data);
|
||||
void on_10(std::shared_ptr<Client> c, std::string& data);
|
||||
|
||||
void disconnect_client(std::shared_ptr<Client> c);
|
||||
|
||||
void enqueue_destroy_clients();
|
||||
static void dispatch_destroy_clients(evutil_socket_t, short, void* ctx);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
|
||||
void on_listen_accept(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* address, int socklen);
|
||||
void on_listen_error(struct evconnlistener* listener);
|
||||
|
||||
static void on_client_input(Channel& ch, uint16_t command, uint32_t flag, std::string& data);
|
||||
static void on_client_error(Channel& ch, short events);
|
||||
|
||||
void thread_fn();
|
||||
};
|
||||
+42
-39
@@ -19,27 +19,10 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<struct event_base> base)
|
||||
: base(base),
|
||||
clear_expired_files_event(
|
||||
event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this),
|
||||
event_free) {
|
||||
auto tv = phosg::usecs_to_timeval(30 * 1000 * 1000);
|
||||
event_add(this->clear_expired_files_event.get(), &tv);
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT>
|
||||
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
|
||||
size_t ret = 0;
|
||||
for (auto it = m.begin(); it != m.end();) {
|
||||
if (it->second.use_count() <= 1) {
|
||||
it = m.erase(it);
|
||||
ret++;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
PlayerFilesManager::PlayerFilesManager(std::shared_ptr<asio::io_context> io_context)
|
||||
: io_context(io_context),
|
||||
clear_expired_files_timer(*this->io_context) {
|
||||
this->schedule_callback();
|
||||
}
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> PlayerFilesManager::get_system(const std::string& filename) {
|
||||
@@ -98,22 +81,42 @@ void PlayerFilesManager::set_bank(const std::string& filename, std::shared_ptr<P
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) {
|
||||
auto* self = reinterpret_cast<PlayerFilesManager*>(ctx);
|
||||
size_t num_deleted = erase_unused(self->loaded_system_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired system file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_character_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired character file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_guild_card_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(self->loaded_bank_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info("Cleared %zu expired bank file(s)", num_deleted);
|
||||
}
|
||||
void PlayerFilesManager::schedule_callback() {
|
||||
this->clear_expired_files_timer.expires_after(std::chrono::seconds(30));
|
||||
this->clear_expired_files_timer.async_wait(bind(&PlayerFilesManager::clear_expired_files, this));
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT>
|
||||
size_t erase_unused(std::unordered_map<KeyT, std::shared_ptr<ValueT>>& m) {
|
||||
size_t ret = 0;
|
||||
for (auto it = m.begin(); it != m.end();) {
|
||||
if (it->second.use_count() <= 1) {
|
||||
it = m.erase(it);
|
||||
ret++;
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void PlayerFilesManager::clear_expired_files() {
|
||||
size_t num_deleted = erase_unused(this->loaded_system_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info_f("Cleared {} expired system file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(this->loaded_character_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info_f("Cleared {} expired character file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(this->loaded_guild_card_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info_f("Cleared {} expired Guild Card file(s)", num_deleted);
|
||||
}
|
||||
num_deleted = erase_unused(this->loaded_bank_files);
|
||||
if (num_deleted) {
|
||||
player_data_log.info_f("Cleared {} expired bank file(s)", num_deleted);
|
||||
}
|
||||
|
||||
this->schedule_callback();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <array>
|
||||
#include <asio.hpp>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
class PlayerFilesManager {
|
||||
public:
|
||||
explicit PlayerFilesManager(std::shared_ptr<struct event_base> base);
|
||||
explicit PlayerFilesManager(std::shared_ptr<asio::io_context> io_context);
|
||||
~PlayerFilesManager() = default;
|
||||
|
||||
std::shared_ptr<PSOBBBaseSystemFile> get_system(const std::string& filename);
|
||||
@@ -35,13 +35,14 @@ public:
|
||||
void set_bank(const std::string& filename, std::shared_ptr<PlayerBank200> file);
|
||||
|
||||
private:
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> clear_expired_files_event;
|
||||
std::shared_ptr<asio::io_context> io_context;
|
||||
asio::steady_timer clear_expired_files_timer;
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBBaseSystemFile>> loaded_system_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBCharacterFile>> loaded_character_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PSOBBGuildCardFile>> loaded_guild_card_files;
|
||||
std::unordered_map<std::string, std::shared_ptr<PlayerBank200>> loaded_bank_files;
|
||||
|
||||
static void clear_expired_files(evutil_socket_t fd, short events, void* ctx);
|
||||
void schedule_callback();
|
||||
void clear_expired_files();
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ struct PlayerInventoryItemT {
|
||||
ret.unknown_a1 = this->unknown_a1;
|
||||
ret.extension_data1 = this->extension_data1;
|
||||
ret.extension_data2 = this->extension_data2;
|
||||
ret.flags = this->flags.load();
|
||||
ret.flags = this->flags;
|
||||
ret.data = this->data;
|
||||
ret.data.id.store_raw(phosg::bswap32(ret.data.id.load_raw()));
|
||||
return ret;
|
||||
@@ -97,8 +97,8 @@ struct PlayerBankItemT {
|
||||
operator PlayerBankItemT<!BE>() const {
|
||||
PlayerBankItemT<!BE> ret;
|
||||
ret.data = this->data;
|
||||
ret.amount = this->amount.load();
|
||||
ret.present = this->present.load();
|
||||
ret.amount = this->amount;
|
||||
ret.present = this->present;
|
||||
return ret;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
@@ -409,8 +409,8 @@ struct PlayerBankT {
|
||||
template <size_t DestSlotCount, bool DestBE>
|
||||
operator PlayerBankT<DestSlotCount, DestBE>() const {
|
||||
PlayerBankT<DestSlotCount, DestBE> ret;
|
||||
ret.num_items = std::min<size_t>(ret.items.size(), this->num_items.load());
|
||||
ret.meseta = this->meseta.load();
|
||||
ret.num_items = std::min<size_t>(ret.items.size(), this->num_items);
|
||||
ret.meseta = this->meseta;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.items.size(), this->items.size()); z++) {
|
||||
ret.items[z] = this->items[z];
|
||||
}
|
||||
|
||||
+122
-97
@@ -209,25 +209,25 @@ struct PlayerVisualConfigT {
|
||||
PlayerVisualConfigT<!BE> ret;
|
||||
ret.name = this->name;
|
||||
ret.unknown_a2 = this->unknown_a2;
|
||||
ret.name_color = this->name_color.load();
|
||||
ret.name_color = this->name_color;
|
||||
ret.extra_model = this->extra_model;
|
||||
ret.unused = this->unused;
|
||||
ret.name_color_checksum = this->name_color_checksum.load();
|
||||
ret.name_color_checksum = this->name_color_checksum;
|
||||
ret.section_id = this->section_id;
|
||||
ret.char_class = this->char_class;
|
||||
ret.validation_flags = this->validation_flags;
|
||||
ret.version = this->version;
|
||||
ret.class_flags = this->class_flags.load();
|
||||
ret.costume = this->costume.load();
|
||||
ret.skin = this->skin.load();
|
||||
ret.face = this->face.load();
|
||||
ret.head = this->head.load();
|
||||
ret.hair = this->hair.load();
|
||||
ret.hair_r = this->hair_r.load();
|
||||
ret.hair_g = this->hair_g.load();
|
||||
ret.hair_b = this->hair_b.load();
|
||||
ret.proportion_x = this->proportion_x.load();
|
||||
ret.proportion_y = this->proportion_y.load();
|
||||
ret.class_flags = this->class_flags;
|
||||
ret.costume = this->costume;
|
||||
ret.skin = this->skin;
|
||||
ret.face = this->face;
|
||||
ret.head = this->head;
|
||||
ret.hair = this->hair;
|
||||
ret.hair_r = this->hair_r;
|
||||
ret.hair_g = this->hair_g;
|
||||
ret.hair_b = this->hair_b;
|
||||
ret.proportion_x = this->proportion_x;
|
||||
ret.proportion_y = this->proportion_y;
|
||||
return ret;
|
||||
}
|
||||
} __attribute__((packed));
|
||||
@@ -428,7 +428,7 @@ struct GuildCardBB {
|
||||
operator GuildCardGCT<BE, DescriptionLength>() const {
|
||||
GuildCardGCT<BE, DescriptionLength> ret;
|
||||
ret.player_tag = 0x00010000;
|
||||
ret.guild_card_number = this->guild_card_number.load();
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.name.encode(this->name.decode(this->language), this->language);
|
||||
ret.description.encode(this->description.decode(this->language), this->language);
|
||||
ret.present = this->present;
|
||||
@@ -443,7 +443,7 @@ struct GuildCardBB {
|
||||
template <bool BE, size_t DescriptionLength>
|
||||
GuildCardGCT<BE, DescriptionLength>::operator GuildCardBB() const {
|
||||
GuildCardBB ret;
|
||||
ret.guild_card_number = this->guild_card_number.load();
|
||||
ret.guild_card_number = this->guild_card_number;
|
||||
ret.name.encode(this->name.decode(this->language), this->language);
|
||||
ret.description.encode(this->description.decode(this->language), this->language);
|
||||
ret.present = this->present;
|
||||
@@ -526,7 +526,7 @@ struct ChallengeAwardStateT {
|
||||
|
||||
operator ChallengeAwardStateT<!BE>() const {
|
||||
ChallengeAwardStateT<!BE> ret;
|
||||
ret.rank_award_flags = this->rank_award_flags.load();
|
||||
ret.rank_award_flags = this->rank_award_flags;
|
||||
ret.maximum_rank = this->maximum_rank;
|
||||
return ret;
|
||||
}
|
||||
@@ -573,33 +573,30 @@ template <bool BE>
|
||||
struct PlayerRecordsChallengeV3T {
|
||||
// Offsets are (1) relative to start of C5 entry, and (2) relative to start
|
||||
// of save file structure
|
||||
struct Stats {
|
||||
/* 00:1C */ U16T<BE> title_color = 0x7FFF; // XRGB1555
|
||||
/* 02:1E */ parray<uint8_t, 2> unknown_u0;
|
||||
/* 04:20 */ parray<ChallengeTimeT<BE>, 9> times_ep1_online;
|
||||
/* 28:44 */ parray<ChallengeTimeT<BE>, 5> times_ep2_online;
|
||||
/* 3C:58 */ parray<ChallengeTimeT<BE>, 9> times_ep1_offline;
|
||||
/* 60:7C */ uint8_t grave_is_ep2 = 0;
|
||||
/* 61:7D */ uint8_t grave_stage_num = 0;
|
||||
/* 62:7E */ uint8_t grave_floor = 0;
|
||||
/* 63:7F */ uint8_t unknown_g0 = 0;
|
||||
/* 64:80 */ U16T<BE> grave_deaths = 0;
|
||||
/* 66:82 */ parray<uint8_t, 2> unknown_u4;
|
||||
/* 68:84 */ U32T<BE> grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC
|
||||
/* 6C:88 */ U32T<BE> grave_defeated_by_enemy_rt_index = 0;
|
||||
/* 70:8C */ F32T<BE> grave_x = 0.0f;
|
||||
/* 74:90 */ F32T<BE> grave_y = 0.0f;
|
||||
/* 78:94 */ F32T<BE> grave_z = 0.0f;
|
||||
/* 7C:98 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
|
||||
/* 90:AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
|
||||
/* B0:CC */ parray<uint8_t, 4> unknown_m5;
|
||||
/* B4:D0 */ parray<U32T<BE>, 3> unknown_t6;
|
||||
/* C0:DC */ ChallengeAwardStateT<BE> ep1_online_award_state;
|
||||
/* C8:E4 */ ChallengeAwardStateT<BE> ep2_online_award_state;
|
||||
/* D0:EC */ ChallengeAwardStateT<BE> ep1_offline_award_state;
|
||||
/* D8:F4 */
|
||||
} __attribute__((packed));
|
||||
/* 0000:001C */ Stats stats;
|
||||
/* 0000:001C */ U16T<BE> title_color = 0x7FFF; // XRGB1555
|
||||
/* 0002:001E */ parray<uint8_t, 2> unknown_u0;
|
||||
/* 0004:0020 */ parray<ChallengeTimeT<BE>, 9> times_ep1_online;
|
||||
/* 0028:0044 */ parray<ChallengeTimeT<BE>, 5> times_ep2_online;
|
||||
/* 003C:0058 */ parray<ChallengeTimeT<BE>, 9> times_ep1_offline;
|
||||
/* 0060:007C */ uint8_t grave_is_ep2 = 0;
|
||||
/* 0061:007D */ uint8_t grave_stage_num = 0;
|
||||
/* 0062:007E */ uint8_t grave_floor = 0;
|
||||
/* 0063:007F */ uint8_t unknown_g0 = 0;
|
||||
/* 0064:0080 */ U16T<BE> grave_deaths = 0;
|
||||
/* 0066:0082 */ parray<uint8_t, 2> unknown_u4;
|
||||
/* 0068:0084 */ U32T<BE> grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC
|
||||
/* 006C:0088 */ U32T<BE> grave_defeated_by_enemy_rt_index = 0;
|
||||
/* 0070:008C */ F32T<BE> grave_x = 0.0f;
|
||||
/* 0074:0090 */ F32T<BE> grave_y = 0.0f;
|
||||
/* 0078:0094 */ F32T<BE> grave_z = 0.0f;
|
||||
/* 007C:0098 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
|
||||
/* 0090:00AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
|
||||
/* 00B0:00CC */ parray<uint8_t, 4> unknown_m5;
|
||||
/* 00B4:00D0 */ parray<U32T<BE>, 3> unknown_t6;
|
||||
/* 00C0:00DC */ ChallengeAwardStateT<BE> ep1_online_award_state;
|
||||
/* 00C8:00E4 */ ChallengeAwardStateT<BE> ep2_online_award_state;
|
||||
/* 00D0:00EC */ ChallengeAwardStateT<BE> ep1_offline_award_state;
|
||||
/* 00D8:00F4 */
|
||||
// On Episode 3, there are special cases that apply to this field - if the
|
||||
// text ends with certain strings, the player will have particle effects
|
||||
// emanate from their character in the lobby every 2 seconds. The effects are:
|
||||
@@ -616,6 +613,34 @@ using PlayerRecordsChallengeV3BE = PlayerRecordsChallengeV3T<true>;
|
||||
check_struct_size(PlayerRecordsChallengeV3, 0x100);
|
||||
check_struct_size(PlayerRecordsChallengeV3BE, 0x100);
|
||||
|
||||
struct PlayerRecordsChallengeEp3 {
|
||||
/* 00:1C */ be_uint16_t title_color = 0x7FFF; // XRGB1555
|
||||
/* 02:1E */ parray<uint8_t, 2> unknown_u0;
|
||||
/* 04:20 */ parray<ChallengeTimeT<true>, 9> times_ep1_online;
|
||||
/* 28:44 */ parray<ChallengeTimeT<true>, 5> times_ep2_online;
|
||||
/* 3C:58 */ parray<ChallengeTimeT<true>, 9> times_ep1_offline;
|
||||
/* 60:7C */ uint8_t grave_is_ep2 = 0;
|
||||
/* 61:7D */ uint8_t grave_stage_num = 0;
|
||||
/* 62:7E */ uint8_t grave_floor = 0;
|
||||
/* 63:7F */ uint8_t unknown_g0 = 0;
|
||||
/* 64:80 */ be_uint16_t grave_deaths = 0;
|
||||
/* 66:82 */ parray<uint8_t, 2> unknown_u4;
|
||||
/* 68:84 */ be_uint32_t grave_time = 0; // Encoded as in PlayerRecordsChallengeDCPC
|
||||
/* 6C:88 */ be_uint32_t grave_defeated_by_enemy_rt_index = 0;
|
||||
/* 70:8C */ be_float grave_x = 0.0f;
|
||||
/* 74:90 */ be_float grave_y = 0.0f;
|
||||
/* 78:94 */ be_float grave_z = 0.0f;
|
||||
/* 7C:98 */ pstring<TextEncoding::ASCII, 0x14> grave_team;
|
||||
/* 90:AC */ pstring<TextEncoding::ASCII, 0x20> grave_message;
|
||||
/* B0:CC */ parray<uint8_t, 4> unknown_m5;
|
||||
/* B4:D0 */ parray<be_uint32_t, 3> unknown_t6;
|
||||
/* C0:DC */ ChallengeAwardStateT<true> ep1_online_award_state;
|
||||
/* C8:E4 */ ChallengeAwardStateT<true> ep2_online_award_state;
|
||||
/* D0:EC */ ChallengeAwardStateT<true> ep1_offline_award_state;
|
||||
/* D8:F4 */
|
||||
} __attribute__((packed));
|
||||
check_struct_size(PlayerRecordsChallengeEp3, 0xD8);
|
||||
|
||||
struct PlayerRecordsChallengeBB {
|
||||
/* 0000 */ le_uint16_t title_color = 0x7FFF; // XRGB1555
|
||||
/* 0002 */ parray<uint8_t, 2> unknown_u0;
|
||||
@@ -653,32 +678,32 @@ struct PlayerRecordsChallengeBB {
|
||||
|
||||
template <bool BE>
|
||||
PlayerRecordsChallengeBB(const PlayerRecordsChallengeV3T<BE>& rec)
|
||||
: title_color(rec.stats.title_color.load()),
|
||||
unknown_u0(rec.stats.unknown_u0),
|
||||
times_ep1_online(rec.stats.times_ep1_online),
|
||||
times_ep2_online(rec.stats.times_ep2_online),
|
||||
times_ep1_offline(rec.stats.times_ep1_offline),
|
||||
grave_is_ep2(rec.stats.grave_is_ep2),
|
||||
grave_stage_num(rec.stats.grave_stage_num),
|
||||
grave_floor(rec.stats.grave_floor),
|
||||
unknown_g0(rec.stats.unknown_g0),
|
||||
grave_deaths(rec.stats.grave_deaths.load()),
|
||||
unknown_u4(rec.stats.unknown_u4),
|
||||
grave_time(rec.stats.grave_time.load()),
|
||||
grave_defeated_by_enemy_rt_index(rec.stats.grave_defeated_by_enemy_rt_index.load()),
|
||||
grave_x(rec.stats.grave_x.load()),
|
||||
grave_y(rec.stats.grave_y.load()),
|
||||
grave_z(rec.stats.grave_z.load()),
|
||||
grave_team(rec.stats.grave_team.decode(), 1),
|
||||
grave_message(rec.stats.grave_message.decode(), 1),
|
||||
unknown_m5(rec.stats.unknown_m5),
|
||||
ep1_online_award_state(rec.stats.ep1_online_award_state),
|
||||
ep2_online_award_state(rec.stats.ep2_online_award_state),
|
||||
ep1_offline_award_state(rec.stats.ep1_offline_award_state),
|
||||
: title_color(rec.title_color),
|
||||
unknown_u0(rec.unknown_u0),
|
||||
times_ep1_online(rec.times_ep1_online),
|
||||
times_ep2_online(rec.times_ep2_online),
|
||||
times_ep1_offline(rec.times_ep1_offline),
|
||||
grave_is_ep2(rec.grave_is_ep2),
|
||||
grave_stage_num(rec.grave_stage_num),
|
||||
grave_floor(rec.grave_floor),
|
||||
unknown_g0(rec.unknown_g0),
|
||||
grave_deaths(rec.grave_deaths),
|
||||
unknown_u4(rec.unknown_u4),
|
||||
grave_time(rec.grave_time),
|
||||
grave_defeated_by_enemy_rt_index(rec.grave_defeated_by_enemy_rt_index),
|
||||
grave_x(rec.grave_x),
|
||||
grave_y(rec.grave_y),
|
||||
grave_z(rec.grave_z),
|
||||
grave_team(rec.grave_team.decode(), 1),
|
||||
grave_message(rec.grave_message.decode(), 1),
|
||||
unknown_m5(rec.unknown_m5),
|
||||
ep1_online_award_state(rec.ep1_online_award_state),
|
||||
ep2_online_award_state(rec.ep2_online_award_state),
|
||||
ep1_offline_award_state(rec.ep1_offline_award_state),
|
||||
rank_title(rec.rank_title.decode(), 1),
|
||||
unknown_l7(rec.unknown_l7) {
|
||||
for (size_t z = 0; z < std::min<size_t>(this->unknown_t6.size(), rec.stats.unknown_t6.size()); z++) {
|
||||
this->unknown_t6[z] = rec.stats.unknown_t6[z].load();
|
||||
for (size_t z = 0; z < std::min<size_t>(this->unknown_t6.size(), rec.unknown_t6.size()); z++) {
|
||||
this->unknown_t6[z] = rec.unknown_t6[z];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -687,31 +712,31 @@ struct PlayerRecordsChallengeBB {
|
||||
template <bool BE>
|
||||
operator PlayerRecordsChallengeV3T<BE>() const {
|
||||
PlayerRecordsChallengeV3T<BE> ret;
|
||||
ret.stats.title_color = this->title_color.load();
|
||||
ret.stats.unknown_u0 = this->unknown_u0;
|
||||
ret.stats.times_ep1_online = this->times_ep1_online;
|
||||
ret.stats.times_ep2_online = this->times_ep2_online;
|
||||
ret.stats.times_ep1_offline = this->times_ep1_offline;
|
||||
ret.stats.grave_is_ep2 = this->grave_is_ep2;
|
||||
ret.stats.grave_stage_num = this->grave_stage_num;
|
||||
ret.stats.grave_floor = this->grave_floor;
|
||||
ret.stats.unknown_g0 = this->unknown_g0;
|
||||
ret.stats.grave_deaths = this->grave_deaths.load();
|
||||
ret.stats.unknown_u4 = this->unknown_u4;
|
||||
ret.stats.grave_time = this->grave_time.load();
|
||||
ret.stats.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index.load();
|
||||
ret.stats.grave_x = this->grave_x.load();
|
||||
ret.stats.grave_y = this->grave_y.load();
|
||||
ret.stats.grave_z = this->grave_z.load();
|
||||
ret.stats.grave_team.encode(this->grave_team.decode(), 1);
|
||||
ret.stats.grave_message.encode(this->grave_message.decode(), 1);
|
||||
ret.stats.unknown_m5 = this->unknown_m5;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.stats.unknown_t6.size(), this->unknown_t6.size()); z++) {
|
||||
ret.stats.unknown_t6[z] = this->unknown_t6[z].load();
|
||||
ret.title_color = this->title_color;
|
||||
ret.unknown_u0 = this->unknown_u0;
|
||||
ret.times_ep1_online = this->times_ep1_online;
|
||||
ret.times_ep2_online = this->times_ep2_online;
|
||||
ret.times_ep1_offline = this->times_ep1_offline;
|
||||
ret.grave_is_ep2 = this->grave_is_ep2;
|
||||
ret.grave_stage_num = this->grave_stage_num;
|
||||
ret.grave_floor = this->grave_floor;
|
||||
ret.unknown_g0 = this->unknown_g0;
|
||||
ret.grave_deaths = this->grave_deaths;
|
||||
ret.unknown_u4 = this->unknown_u4;
|
||||
ret.grave_time = this->grave_time;
|
||||
ret.grave_defeated_by_enemy_rt_index = this->grave_defeated_by_enemy_rt_index;
|
||||
ret.grave_x = this->grave_x;
|
||||
ret.grave_y = this->grave_y;
|
||||
ret.grave_z = this->grave_z;
|
||||
ret.grave_team.encode(this->grave_team.decode(), 1);
|
||||
ret.grave_message.encode(this->grave_message.decode(), 1);
|
||||
ret.unknown_m5 = this->unknown_m5;
|
||||
for (size_t z = 0; z < std::min<size_t>(ret.unknown_t6.size(), this->unknown_t6.size()); z++) {
|
||||
ret.unknown_t6[z] = this->unknown_t6[z];
|
||||
}
|
||||
ret.stats.ep1_online_award_state = this->ep1_online_award_state;
|
||||
ret.stats.ep2_online_award_state = this->ep2_online_award_state;
|
||||
ret.stats.ep1_offline_award_state = this->ep1_offline_award_state;
|
||||
ret.ep1_online_award_state = this->ep1_online_award_state;
|
||||
ret.ep2_online_award_state = this->ep2_online_award_state;
|
||||
ret.ep1_offline_award_state = this->ep1_offline_award_state;
|
||||
ret.rank_title.encode(this->rank_title.decode(), 1);
|
||||
ret.unknown_l7 = this->unknown_l7;
|
||||
return ret;
|
||||
@@ -731,14 +756,14 @@ struct PlayerRecordsBattleT {
|
||||
operator PlayerRecordsBattleT<!BE>() const {
|
||||
PlayerRecordsBattleT<!BE> ret;
|
||||
for (size_t z = 0; z < this->place_counts.size(); z++) {
|
||||
ret.place_counts[z] = this->place_counts[z].load();
|
||||
ret.place_counts[z] = this->place_counts[z];
|
||||
}
|
||||
ret.disconnect_count = this->disconnect_count.load();
|
||||
ret.disconnect_count = this->disconnect_count;
|
||||
for (size_t z = 0; z < this->unknown_a1.size(); z++) {
|
||||
ret.unknown_a1[z] = this->unknown_a1[z].load();
|
||||
ret.unknown_a1[z] = this->unknown_a1[z];
|
||||
}
|
||||
for (size_t z = 0; z < this->unknown_a2.size(); z++) {
|
||||
ret.unknown_a2[z] = this->unknown_a2[z].load();
|
||||
ret.unknown_a2[z] = this->unknown_a2[z];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -985,9 +1010,9 @@ struct SymbolChatT {
|
||||
|
||||
operator SymbolChatT<!BE>() const {
|
||||
SymbolChatT<!BE> ret;
|
||||
ret.spec = this->spec.load();
|
||||
ret.spec = this->spec;
|
||||
for (size_t z = 0; z < this->corner_objects.size(); z++) {
|
||||
ret.corner_objects[z] = this->corner_objects[z].load();
|
||||
ret.corner_objects[z] = this->corner_objects[z];
|
||||
}
|
||||
ret.face_parts = this->face_parts;
|
||||
return ret;
|
||||
|
||||
+1479
-1575
File diff suppressed because it is too large
Load Diff
+3
-12
@@ -1,15 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "Client.hh"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "ProxyServer.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
void on_proxy_command(
|
||||
std::shared_ptr<ProxyServer::LinkedSession> ses,
|
||||
bool from_server,
|
||||
uint16_t command,
|
||||
uint32_t flag,
|
||||
std::string& data);
|
||||
asio::awaitable<void> on_proxy_command(std::shared_ptr<Client> c, bool from_server, std::unique_ptr<Channel::Message> msg);
|
||||
asio::awaitable<void> handle_proxy_server_commands(std::shared_ptr<Client> c, std::shared_ptr<ProxySession> ses, std::shared_ptr<Channel> channel);
|
||||
|
||||
-1047
File diff suppressed because it is too large
Load Diff
@@ -1,316 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <event2/event.h>
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "PSOEncryption.hh"
|
||||
#include "PSOProtocol.hh"
|
||||
#include "ServerState.hh"
|
||||
|
||||
class ProxyServer : public std::enable_shared_from_this<ProxyServer> {
|
||||
public:
|
||||
ProxyServer() = delete;
|
||||
ProxyServer(const ProxyServer&) = delete;
|
||||
ProxyServer(ProxyServer&&) = delete;
|
||||
ProxyServer(
|
||||
std::shared_ptr<struct event_base> base,
|
||||
std::shared_ptr<ServerState> state);
|
||||
virtual ~ProxyServer() = default;
|
||||
|
||||
void listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination = nullptr);
|
||||
|
||||
void connect_virtual_client(struct bufferevent* bev, uint64_t virtual_network_id, uint16_t server_port);
|
||||
|
||||
struct LinkedSession : std::enable_shared_from_this<LinkedSession> {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
phosg::PrefixedLogger log;
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
|
||||
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
Channel client_channel;
|
||||
Channel server_channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
enum class DisconnectAction {
|
||||
LONG_TIMEOUT = 0,
|
||||
MEDIUM_TIMEOUT,
|
||||
SHORT_TIMEOUT,
|
||||
CLOSE_IMMEDIATELY,
|
||||
};
|
||||
DisconnectAction disconnect_action;
|
||||
|
||||
uint8_t prev_server_command_bytes[6];
|
||||
uint32_t remote_ip_crc;
|
||||
bool enable_remote_ip_crc_patch;
|
||||
|
||||
uint32_t sub_version;
|
||||
std::string character_name;
|
||||
std::string serial_number2; // Only used for DC sessions
|
||||
uint64_t hardware_id;
|
||||
std::string login_command_bb;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
uint32_t challenge_rank_color_override;
|
||||
std::string challenge_rank_title_override;
|
||||
int64_t remote_guild_card_number;
|
||||
parray<uint8_t, 0x20> remote_client_config_data;
|
||||
Client::Config config;
|
||||
// A null handler in here means to forward the response to the remote server
|
||||
std::deque<std::function<void(uint32_t return_value, uint32_t checksum)>> function_call_return_handler_queue;
|
||||
ItemData next_drop_item;
|
||||
uint32_t next_item_id;
|
||||
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
PASSTHROUGH,
|
||||
INTERCEPT,
|
||||
};
|
||||
DropMode drop_mode;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<MapState> map_state;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
std::string name;
|
||||
uint8_t language = 0;
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
size_t lobby_client_id;
|
||||
size_t leader_client_id;
|
||||
uint16_t floor;
|
||||
VectorXZF pos;
|
||||
bool is_in_game;
|
||||
bool is_in_quest;
|
||||
uint8_t lobby_event;
|
||||
uint8_t lobby_difficulty;
|
||||
uint8_t lobby_section_id;
|
||||
GameMode lobby_mode;
|
||||
Episode lobby_episode;
|
||||
uint32_t lobby_random_seed;
|
||||
uint64_t client_ping_start_time = 0;
|
||||
uint64_t server_ping_start_time = 0;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
struct SavingFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
bool is_download;
|
||||
size_t total_size;
|
||||
std::string data;
|
||||
|
||||
SavingFile(
|
||||
const std::string& basename,
|
||||
const std::string& output_filename,
|
||||
size_t total_size,
|
||||
bool is_download);
|
||||
};
|
||||
std::unordered_map<std::string, SavingFile> saving_files;
|
||||
|
||||
// TODO: This first constructor should be private
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const Client::Config& config);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<Login> login,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->client_channel.version;
|
||||
}
|
||||
inline uint8_t language() const {
|
||||
return this->client_channel.language;
|
||||
}
|
||||
inline uint32_t effective_sub_version() const {
|
||||
return this->config.check_flag(Client::Flag::PROXY_VIRTUAL_CLIENT)
|
||||
? default_sub_version_for_version(this->version())
|
||||
: this->sub_version;
|
||||
}
|
||||
void set_version(Version v);
|
||||
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
uint32_t sub_version,
|
||||
const std::string& character_name,
|
||||
const std::string& serial_number2,
|
||||
uint64_t hardware_id,
|
||||
const XBNetworkLocation& xb_netloc,
|
||||
const parray<le_uint32_t, 3>& xb_9E_unknown_a1a);
|
||||
void resume(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt,
|
||||
std::string&& login_command_bb);
|
||||
void resume(Channel&& client_channel);
|
||||
void resume_inner(
|
||||
Channel&& client_channel,
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt);
|
||||
void connect();
|
||||
|
||||
static uint64_t timeout_for_disconnect_action(DisconnectAction action);
|
||||
static void dispatch_on_timeout(evutil_socket_t fd, short what, void* ctx);
|
||||
static void on_input(Channel& ch, uint16_t, uint32_t, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
void on_timeout();
|
||||
|
||||
void update_channel_names();
|
||||
|
||||
void clear_lobby_players(size_t num_slots);
|
||||
|
||||
void set_drop_mode(DropMode new_mode);
|
||||
|
||||
void send_to_game_server(const char* error_message = nullptr);
|
||||
void disconnect();
|
||||
bool is_connected() const;
|
||||
};
|
||||
|
||||
std::shared_ptr<LinkedSession> get_session() const;
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
|
||||
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
|
||||
|
||||
std::shared_ptr<LinkedSession> create_logged_in_session(
|
||||
std::shared_ptr<Login> login,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const Client::Config& config);
|
||||
void delete_session(uint64_t id);
|
||||
|
||||
size_t num_sessions() const;
|
||||
|
||||
size_t delete_disconnected_sessions();
|
||||
|
||||
private:
|
||||
struct ListeningSocket {
|
||||
ProxyServer* server;
|
||||
|
||||
phosg::PrefixedLogger log;
|
||||
uint16_t port;
|
||||
phosg::scoped_fd fd;
|
||||
std::unique_ptr<struct evconnlistener, void (*)(struct evconnlistener*)> listener;
|
||||
Version version;
|
||||
struct sockaddr_storage default_destination;
|
||||
|
||||
ListeningSocket(
|
||||
ProxyServer* server,
|
||||
const std::string& addr,
|
||||
uint16_t port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static void dispatch_on_listen_accept(struct evconnlistener* listener,
|
||||
evutil_socket_t fd, struct sockaddr* address, int socklen, void* ctx);
|
||||
static void dispatch_on_listen_error(struct evconnlistener* listener, void* ctx);
|
||||
void on_listen_accept(int fd);
|
||||
void on_listen_error();
|
||||
};
|
||||
|
||||
struct UnlinkedSession {
|
||||
std::weak_ptr<ProxyServer> server;
|
||||
uint64_t id;
|
||||
|
||||
phosg::PrefixedLogger log;
|
||||
Channel channel;
|
||||
uint16_t local_port;
|
||||
struct sockaddr_storage next_destination;
|
||||
|
||||
std::shared_ptr<PSOBBMultiKeyDetectorEncryption> detector_crypt;
|
||||
|
||||
// Temporary state used just before resuming a LinkedSession. These aren't
|
||||
// just local variables inside on_input because XB requires two commands to
|
||||
// get started (9E and 9F), so we need to store this state somewhere between
|
||||
// those commands.
|
||||
std::shared_ptr<Login> login;
|
||||
uint32_t sub_version = 0;
|
||||
std::string character_name;
|
||||
Client::Config config;
|
||||
std::string login_command_bb;
|
||||
std::string serial_number2;
|
||||
uint64_t hardware_id;
|
||||
XBNetworkLocation xb_netloc;
|
||||
parray<le_uint32_t, 3> xb_9E_unknown_a1a;
|
||||
|
||||
UnlinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint64_t id,
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t port,
|
||||
Version version);
|
||||
|
||||
std::shared_ptr<ProxyServer> require_server() const;
|
||||
std::shared_ptr<ServerState> require_server_state() const;
|
||||
|
||||
inline Version version() const {
|
||||
return this->channel.version;
|
||||
}
|
||||
|
||||
void set_login(std::shared_ptr<Login> login);
|
||||
|
||||
void receive_and_process_commands();
|
||||
|
||||
static void on_input(Channel& ch, uint16_t command, uint32_t flag, std::string& msg);
|
||||
static void on_error(Channel& ch, short events);
|
||||
};
|
||||
|
||||
std::shared_ptr<struct event_base> base;
|
||||
std::shared_ptr<struct event> destroy_sessions_ev;
|
||||
std::shared_ptr<ServerState> state;
|
||||
std::map<int, std::shared_ptr<ListeningSocket>> listeners;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<UnlinkedSession>> id_to_unlinked_session;
|
||||
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> id_to_linked_session;
|
||||
uint64_t next_unlinked_session_id;
|
||||
uint64_t next_logged_out_session_id;
|
||||
|
||||
static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx);
|
||||
void destroy_sessions();
|
||||
|
||||
void on_client_connect(
|
||||
struct bufferevent* bev,
|
||||
uint64_t virtual_network_id,
|
||||
uint16_t listen_port,
|
||||
Version version,
|
||||
const struct sockaddr_storage* default_destination);
|
||||
|
||||
static constexpr uint64_t FIRST_UNLINKED_SESSION_ID = 0x0000000000000001;
|
||||
static constexpr uint64_t FIRST_LINKED_LOGGED_OUT_SESSION_ID = 0x0000000080000000;
|
||||
static constexpr uint64_t FIRST_LINKED_LOGGED_IN_SESSION_ID = 0x00000000FFFFFFFF;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "ProxySession.hh"
|
||||
|
||||
#include "ServerState.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
size_t ProxySession::num_proxy_sessions = 0;
|
||||
|
||||
ProxySession::ProxySession(shared_ptr<Channel> server_channel, const PersistentConfig* pc)
|
||||
: server_channel(server_channel) {
|
||||
if (pc) {
|
||||
this->remote_guild_card_number = pc->remote_guild_card_number;
|
||||
this->remote_bb_security_token = pc->remote_bb_security_token;
|
||||
this->remote_client_config_data = pc->remote_client_config_data;
|
||||
this->enable_remote_ip_crc_patch = pc->enable_remote_ip_crc_patch;
|
||||
} else if (is_v4(this->server_channel->version)) {
|
||||
this->remote_guild_card_number = 0;
|
||||
}
|
||||
this->num_proxy_sessions++;
|
||||
}
|
||||
|
||||
ProxySession::~ProxySession() {
|
||||
this->num_proxy_sessions--;
|
||||
}
|
||||
|
||||
void ProxySession::set_drop_mode(shared_ptr<ServerState> s, Version version, int64_t override_random_seed, DropMode new_mode) {
|
||||
this->drop_mode = new_mode;
|
||||
if (this->drop_mode == DropMode::INTERCEPT) {
|
||||
shared_ptr<const RareItemSet> rare_item_set;
|
||||
shared_ptr<const CommonItemSet> common_item_set;
|
||||
switch (version) {
|
||||
case Version::PC_PATCH:
|
||||
case Version::BB_PATCH:
|
||||
case Version::GC_EP3_NTE:
|
||||
case Version::GC_EP3:
|
||||
throw runtime_error("cannot create item creator for this base version");
|
||||
case Version::DC_NTE:
|
||||
case Version::DC_11_2000:
|
||||
case Version::DC_V1:
|
||||
// TODO: We should probably have a v1 common item set at some point too
|
||||
common_item_set = s->common_item_set_v2;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v1");
|
||||
break;
|
||||
case Version::DC_V2:
|
||||
case Version::PC_NTE:
|
||||
case Version::PC_V2:
|
||||
common_item_set = s->common_item_set_v2;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v2");
|
||||
break;
|
||||
case Version::GC_NTE:
|
||||
case Version::GC_V3:
|
||||
case Version::XB_V3:
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v3");
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
common_item_set = s->common_item_set_v3_v4;
|
||||
rare_item_set = s->rare_item_sets.at("rare-table-v4");
|
||||
break;
|
||||
default:
|
||||
throw logic_error("invalid lobby base version");
|
||||
}
|
||||
auto rand_crypt = make_shared<MT19937Generator>((override_random_seed >= 0) ? override_random_seed : this->lobby_random_seed);
|
||||
this->item_creator = make_shared<ItemCreator>(
|
||||
common_item_set,
|
||||
rare_item_set,
|
||||
s->armor_random_set,
|
||||
s->tool_random_set,
|
||||
s->weapon_random_sets.at(this->lobby_difficulty),
|
||||
s->tekker_adjustment_set,
|
||||
s->item_parameter_table(version),
|
||||
s->item_stack_limits(version),
|
||||
this->lobby_episode,
|
||||
(this->lobby_mode == GameMode::SOLO) ? GameMode::NORMAL : this->lobby_mode,
|
||||
this->lobby_difficulty,
|
||||
this->lobby_section_id,
|
||||
std::move(rand_crypt),
|
||||
// TODO: Can we get battle rules here somehow?
|
||||
nullptr);
|
||||
} else {
|
||||
this->item_creator.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void ProxySession::clear_lobby_players(size_t num_slots) {
|
||||
this->lobby_players.clear();
|
||||
this->lobby_players.resize(num_slots);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "WindowsPlatform.hh"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Channel.hh"
|
||||
#include "ItemCreator.hh"
|
||||
#include "Map.hh"
|
||||
|
||||
struct ServerState;
|
||||
|
||||
struct ProxySession {
|
||||
static size_t num_proxy_sessions;
|
||||
|
||||
std::shared_ptr<Channel> server_channel;
|
||||
|
||||
parray<uint8_t, 6> prev_server_command_bytes;
|
||||
uint32_t remote_ip_crc = 0;
|
||||
bool received_reconnect = false;
|
||||
bool enable_remote_ip_crc_patch = false;
|
||||
|
||||
struct LobbyPlayer {
|
||||
uint32_t guild_card_number = 0;
|
||||
uint64_t xb_user_id = 0;
|
||||
std::string name;
|
||||
uint8_t language = 0;
|
||||
uint8_t section_id = 0;
|
||||
uint8_t char_class = 0;
|
||||
};
|
||||
std::vector<LobbyPlayer> lobby_players;
|
||||
bool is_in_game = false;
|
||||
bool is_in_quest = false;
|
||||
uint8_t leader_client_id = 0;
|
||||
uint8_t lobby_event = 0;
|
||||
uint8_t lobby_difficulty = 0;
|
||||
uint8_t lobby_section_id = 0;
|
||||
GameMode lobby_mode = GameMode::NORMAL;
|
||||
Episode lobby_episode = Episode::EP1;
|
||||
uint32_t lobby_random_seed = 0;
|
||||
uint64_t server_ping_start_time = 0;
|
||||
|
||||
int64_t remote_guild_card_number = -1;
|
||||
uint32_t remote_bb_security_token = 0;
|
||||
parray<uint8_t, 0x28> remote_client_config_data;
|
||||
|
||||
enum class DropMode {
|
||||
DISABLED = 0,
|
||||
PASSTHROUGH,
|
||||
INTERCEPT,
|
||||
};
|
||||
DropMode drop_mode = DropMode::PASSTHROUGH;
|
||||
std::shared_ptr<std::string> quest_dat_data;
|
||||
std::shared_ptr<ItemCreator> item_creator;
|
||||
std::shared_ptr<MapState> map_state;
|
||||
// TODO: Be less lazy and track item IDs correctly in proxy games. (Then
|
||||
// change this to use the actual client's next item ID, not this hardcoded
|
||||
// default.)
|
||||
uint32_t next_item_id = 0x44000000;
|
||||
|
||||
struct PersistentConfig {
|
||||
uint32_t account_id;
|
||||
uint32_t remote_guild_card_number;
|
||||
uint32_t remote_bb_security_token;
|
||||
parray<uint8_t, 0x28> remote_client_config_data;
|
||||
bool enable_remote_ip_crc_patch;
|
||||
std::unique_ptr<asio::steady_timer> expire_timer;
|
||||
};
|
||||
|
||||
explicit ProxySession(std::shared_ptr<Channel> server_channel, const PersistentConfig* pc);
|
||||
~ProxySession();
|
||||
|
||||
struct SavingFile {
|
||||
std::string basename;
|
||||
std::string output_filename;
|
||||
bool is_download;
|
||||
size_t total_size;
|
||||
std::string data;
|
||||
};
|
||||
std::unordered_map<std::string, SavingFile> saving_files;
|
||||
|
||||
void set_drop_mode(std::shared_ptr<ServerState> s, Version version, int64_t override_random_seed, DropMode new_mode);
|
||||
|
||||
void clear_lobby_players(size_t num_slots);
|
||||
};
|
||||
+86
-84
@@ -1,6 +1,7 @@
|
||||
#include "Quest.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <mutex>
|
||||
#include <phosg/Encoding.hh>
|
||||
#include <phosg/Filesystem.hh>
|
||||
@@ -91,16 +92,16 @@ 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(phosg::string_printf(
|
||||
"decompressed size (%zu) does not match expected size (%zu)",
|
||||
throw runtime_error(std::format(
|
||||
"decompressed size ({}) does not match expected size ({})",
|
||||
decompressed_size, sizeof(Episode3::MapDefinitionTrial)));
|
||||
}
|
||||
return decrypted.substr(0x28);
|
||||
|
||||
} else {
|
||||
if (header->decompressed_size & 0xFFF00000) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"decompressed_size too large (%08" PRIX32 ")", header->decompressed_size.load()));
|
||||
throw runtime_error(std::format(
|
||||
"decompressed_size too large ({:08X})", header->decompressed_size));
|
||||
}
|
||||
|
||||
if (!skip_checksum) {
|
||||
@@ -109,8 +110,8 @@ 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(phosg::string_printf(
|
||||
"incorrect decrypted data section checksum: expected %08" PRIX32 "; received %08" PRIX32,
|
||||
throw runtime_error(std::format(
|
||||
"incorrect decrypted data section checksum: expected {:08X}; received {:08X}",
|
||||
expected_crc, actual_crc));
|
||||
}
|
||||
}
|
||||
@@ -127,11 +128,11 @@ string decrypt_download_quest_data_section(
|
||||
size_t decompressed_size = prs_decompress_size(
|
||||
decrypted.data() + sizeof(HeaderT),
|
||||
decrypted.size() - sizeof(HeaderT));
|
||||
size_t expected_decompressed_size = header->decompressed_size.load();
|
||||
size_t expected_decompressed_size = header->decompressed_size;
|
||||
if ((decompressed_size != expected_decompressed_size) &&
|
||||
(decompressed_size != expected_decompressed_size - 8)) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"decompressed size (%zu) does not match expected size (%zu)",
|
||||
throw runtime_error(std::format(
|
||||
"decompressed size ({}) does not match expected size ({})",
|
||||
decompressed_size, expected_decompressed_size));
|
||||
}
|
||||
|
||||
@@ -153,8 +154,8 @@ 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(phosg::string_printf(
|
||||
"decompressed size (%zu) does not match size in header (%" PRId32 ")",
|
||||
throw runtime_error(std::format(
|
||||
"decompressed size ({}) does not match size in header ({})",
|
||||
actual_decompressed_size, expected_decompressed_size));
|
||||
}
|
||||
|
||||
@@ -180,7 +181,7 @@ string find_seed_and_decrypt_download_quest_data_section(
|
||||
0, 0x100000000, 0x1000, num_threads);
|
||||
|
||||
if (!result.empty() && (result_seed < 0x100000000)) {
|
||||
static_game_data_log.info("Found seed %08" PRIX64, result_seed);
|
||||
static_game_data_log.info_f("Found seed {:08X}", result_seed);
|
||||
return result;
|
||||
} else {
|
||||
throw runtime_error("no seed found");
|
||||
@@ -224,9 +225,9 @@ void VersionedQuest::assert_valid() const {
|
||||
|
||||
string VersionedQuest::bin_filename() const {
|
||||
if (this->episode == Episode::EP3) {
|
||||
return phosg::string_printf("m%06" PRIu32 "p_e.bin", this->quest_number);
|
||||
return std::format("m{:06}p_e.bin", this->quest_number);
|
||||
} else {
|
||||
return phosg::string_printf("quest%" PRIu32 ".bin", this->quest_number);
|
||||
return std::format("quest{}.bin", this->quest_number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +235,7 @@ string VersionedQuest::dat_filename() const {
|
||||
if (this->episode == Episode::EP3) {
|
||||
throw logic_error("Episode 3 quests do not have .dat files");
|
||||
} else {
|
||||
return phosg::string_printf("quest%" PRIu32 ".dat", this->quest_number);
|
||||
return std::format("quest{}.dat", this->quest_number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +243,7 @@ string VersionedQuest::pvr_filename() const {
|
||||
if (this->episode == Episode::EP3) {
|
||||
throw logic_error("Episode 3 quests do not have .pvr files");
|
||||
} else {
|
||||
return phosg::string_printf("quest%" PRIu32 ".pvr", this->quest_number);
|
||||
return std::format("quest{}.pvr", this->quest_number);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,18 +251,18 @@ string VersionedQuest::xb_filename() const {
|
||||
if (this->episode == Episode::EP3) {
|
||||
throw logic_error("Episode 3 quests do not have Xbox filenames");
|
||||
} else {
|
||||
return phosg::string_printf("quest%" PRIu32 "_%c.dat", this->quest_number, tolower(char_for_language_code(this->language)));
|
||||
return std::format("quest{}_{}.dat", this->quest_number, static_cast<char>(tolower(char_for_language_code(this->language))));
|
||||
}
|
||||
}
|
||||
|
||||
string VersionedQuest::encode_qst() const {
|
||||
unordered_map<string, shared_ptr<const string>> files;
|
||||
files.emplace(phosg::string_printf("quest%" PRIu32 ".bin", this->quest_number), this->bin_contents);
|
||||
files.emplace(phosg::string_printf("quest%" PRIu32 ".dat", this->quest_number), this->dat_contents);
|
||||
files.emplace(std::format("quest{}.bin", this->quest_number), this->bin_contents);
|
||||
files.emplace(std::format("quest{}.dat", this->quest_number), this->dat_contents);
|
||||
if (this->pvr_contents) {
|
||||
files.emplace(phosg::string_printf("quest%" PRIu32 ".pvr", this->quest_number), this->pvr_contents);
|
||||
files.emplace(std::format("quest{}.pvr", this->quest_number), this->pvr_contents);
|
||||
}
|
||||
string xb_filename = phosg::string_printf("quest%" PRIu32 "_%c.dat", quest_number, tolower(char_for_language_code(language)));
|
||||
string xb_filename = std::format("quest{}_{}.dat", quest_number, static_cast<char>(tolower(char_for_language_code(language))));
|
||||
return encode_qst_file(files, this->name, this->quest_number, xb_filename, this->version, this->is_dlq_encoded);
|
||||
}
|
||||
|
||||
@@ -325,85 +326,85 @@ uint32_t Quest::versions_key(Version v, uint8_t language) {
|
||||
|
||||
void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
||||
if (this->quest_number != vq->quest_number) {
|
||||
throw logic_error(phosg::string_printf(
|
||||
"incorrect versioned quest number (existing: %08" PRIX32 ", new: %08" PRIX32 ")",
|
||||
throw logic_error(std::format(
|
||||
"incorrect versioned quest number (existing: {:08X}, new: {:08X})",
|
||||
this->quest_number, vq->quest_number));
|
||||
}
|
||||
if (this->category_id != vq->category_id) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version is in a different category (existing: %08" PRIX32 ", new: %08" PRIX32 ")",
|
||||
throw runtime_error(std::format(
|
||||
"quest version is in a different category (existing: {:08X}, new: {:08X})",
|
||||
this->category_id, vq->category_id));
|
||||
}
|
||||
if (this->episode != vq->episode) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version is in a different episode (existing: %s, new: %s)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version is in a different episode (existing: {}, new: {})",
|
||||
name_for_episode(this->episode), name_for_episode(vq->episode)));
|
||||
}
|
||||
if (this->allow_start_from_chat_command != vq->allow_start_from_chat_command) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different allow_start_from_chat_command state (existing: %s, new: %s)",
|
||||
throw 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", vq->allow_start_from_chat_command ? "true" : "false"));
|
||||
}
|
||||
if (this->joinable != vq->joinable) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different joinability state (existing: %s, new: %s)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different joinability state (existing: {}, new: {})",
|
||||
this->joinable ? "true" : "false", vq->joinable ? "true" : "false"));
|
||||
}
|
||||
if (this->max_players != vq->max_players) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different maximum player count (existing: %hhu, new: %hhu)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different maximum player count (existing: {}, new: {})",
|
||||
this->max_players, vq->max_players));
|
||||
}
|
||||
if (this->lock_status_register != vq->lock_status_register) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different lock status register (existing: %04hX, new: %04hX)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different lock status register (existing: {:04X}, new: {:04X})",
|
||||
this->lock_status_register, vq->lock_status_register));
|
||||
}
|
||||
if (!this->battle_rules != !vq->battle_rules) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different battle rules presence state (existing: %s, new: %s)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different battle rules presence state (existing: {}, new: {})",
|
||||
this->battle_rules ? "present" : "absent", vq->battle_rules ? "present" : "absent"));
|
||||
}
|
||||
if (this->battle_rules && (*this->battle_rules != *vq->battle_rules)) {
|
||||
string existing_str = this->battle_rules->json().serialize();
|
||||
string new_str = vq->battle_rules->json().serialize();
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has different battle rules (existing: %s, new: %s)",
|
||||
existing_str.c_str(), new_str.c_str()));
|
||||
throw runtime_error(std::format(
|
||||
"quest version has different battle rules (existing: {}, new: {})",
|
||||
existing_str, new_str));
|
||||
}
|
||||
if (this->challenge_template_index != vq->challenge_template_index) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has different challenge template index (existing: %zd, new: %zd)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has different challenge template index (existing: {}, new: {})",
|
||||
this->challenge_template_index, vq->challenge_template_index));
|
||||
}
|
||||
if (this->description_flag != vq->description_flag) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has different description flag (existing: %02hhX, new: %02hhX)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has different description flag (existing: {:02X}, new: {:02X})",
|
||||
this->description_flag, vq->description_flag));
|
||||
}
|
||||
if (!this->available_expression != !vq->available_expression) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has available expression but root quest does not, or vice versa (existing: %s, new: %s)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has available expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||
this->available_expression ? "present" : "absent", vq->available_expression ? "present" : "absent"));
|
||||
}
|
||||
if (this->available_expression && *this->available_expression != *vq->available_expression) {
|
||||
string existing_str = this->available_expression->str();
|
||||
string new_str = vq->available_expression->str();
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different available expression (existing: %s, new: %s)",
|
||||
existing_str.c_str(), new_str.c_str()));
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different available expression (existing: {}, new: {})",
|
||||
existing_str, new_str));
|
||||
}
|
||||
if (!this->enabled_expression != !vq->enabled_expression) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has enabled expression but root quest does not, or vice versa (existing: %s, new: %s)",
|
||||
throw runtime_error(std::format(
|
||||
"quest version has enabled expression but root quest does not, or vice versa (existing: {}, new: {})",
|
||||
this->enabled_expression ? "present" : "absent", vq->enabled_expression ? "present" : "absent"));
|
||||
}
|
||||
if (this->enabled_expression && *this->enabled_expression != *vq->enabled_expression) {
|
||||
string existing_str = this->enabled_expression->str();
|
||||
string new_str = vq->enabled_expression->str();
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"quest version has a different enabled expression (existing: %s, new: %s)",
|
||||
existing_str.c_str(), new_str.c_str()));
|
||||
throw runtime_error(std::format(
|
||||
"quest version has a different enabled expression (existing: {}, new: {})",
|
||||
existing_str, new_str));
|
||||
}
|
||||
|
||||
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
||||
@@ -441,8 +442,8 @@ std::shared_ptr<const SuperMap> Quest::get_supermap(int64_t random_seed) const {
|
||||
if (save_to_cache) {
|
||||
this->supermap = supermap;
|
||||
}
|
||||
static_game_data_log.info("Constructed %s supermap for quest %" PRIu32 " (%s)",
|
||||
save_to_cache ? "cacheable" : "temporary", this->quest_number, this->name.c_str());
|
||||
static_game_data_log.info_f("Constructed {} supermap for quest {} ({})",
|
||||
save_to_cache ? "cacheable" : "temporary", this->quest_number, this->name);
|
||||
|
||||
return supermap;
|
||||
}
|
||||
@@ -561,11 +562,12 @@ QuestIndex::QuestIndex(
|
||||
};
|
||||
|
||||
string cat_path = directory + "/" + cat->directory_name;
|
||||
if (!phosg::isdir(cat_path)) {
|
||||
static_game_data_log.warning("Quest category directory %s is missing; skipping it", cat_path.c_str());
|
||||
if (!std::filesystem::is_directory(cat_path)) {
|
||||
static_game_data_log.warning_f("Quest category directory {} is missing; skipping it", cat_path);
|
||||
continue;
|
||||
}
|
||||
for (string filename : phosg::list_directory_sorted(cat_path)) {
|
||||
for (const auto& item : std::filesystem::directory_iterator(cat_path)) {
|
||||
string filename = item.path().filename().string();
|
||||
if (filename == ".DS_Store") {
|
||||
continue;
|
||||
}
|
||||
@@ -575,16 +577,16 @@ QuestIndex::QuestIndex(
|
||||
try {
|
||||
string orig_filename = filename;
|
||||
string file_data;
|
||||
if (phosg::ends_with(filename, ".gci")) {
|
||||
if (filename.ends_with(".gci")) {
|
||||
file_data = decode_gci_data(phosg::load_file(file_path));
|
||||
filename.resize(filename.size() - 4);
|
||||
} else if (phosg::ends_with(filename, ".vms")) {
|
||||
} else if (filename.ends_with(".vms")) {
|
||||
file_data = decode_vms_data(phosg::load_file(file_path));
|
||||
filename.resize(filename.size() - 4);
|
||||
} else if (phosg::ends_with(filename, ".dlq")) {
|
||||
} else if (filename.ends_with(".dlq")) {
|
||||
file_data = decode_dlq_data(phosg::load_file(file_path));
|
||||
filename.resize(filename.size() - 4);
|
||||
} else if (phosg::ends_with(filename, ".bin.txt")) {
|
||||
} else if (filename.ends_with(".bin.txt")) {
|
||||
string include_dir = phosg::dirname(file_path);
|
||||
assembled = make_unique<AssembledQuestScript>(assemble_quest_script(
|
||||
phosg::load_file(file_path),
|
||||
@@ -592,7 +594,7 @@ QuestIndex::QuestIndex(
|
||||
{include_dir, "system/quests/includes", "system/client-functions/System"}));
|
||||
file_data = std::move(assembled->data);
|
||||
filename.resize(filename.size() - 4);
|
||||
if (phosg::ends_with(filename, ".bin")) {
|
||||
if (filename.ends_with(".bin")) {
|
||||
filename.push_back('d');
|
||||
}
|
||||
} else {
|
||||
@@ -624,11 +626,11 @@ QuestIndex::QuestIndex(
|
||||
} else if (extension == "qst") {
|
||||
auto files = decode_qst_data(file_data);
|
||||
for (auto& it : files) {
|
||||
if (phosg::ends_with(it.first, ".bin")) {
|
||||
if (it.first.ends_with(".bin")) {
|
||||
add_bin_file(file_basename, orig_filename, std::move(it.second), nullptr);
|
||||
} else if (phosg::ends_with(it.first, ".dat")) {
|
||||
} else if (it.first.ends_with(".dat")) {
|
||||
add_dat_file(file_basename, orig_filename, std::move(it.second));
|
||||
} else if (phosg::ends_with(it.first, ".pvr")) {
|
||||
} 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);
|
||||
@@ -637,7 +639,7 @@ QuestIndex::QuestIndex(
|
||||
}
|
||||
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("(%s) Failed to load quest file: (%s)", filename.c_str(), e.what());
|
||||
static_game_data_log.warning_f("({}) Failed to load quest file: ({})", filename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -919,41 +921,41 @@ QuestIndex::QuestIndex(
|
||||
auto category_name = this->category_index->at(vq->category_id)->name;
|
||||
string filenames_str = entry.filename;
|
||||
if (dat_filedata) {
|
||||
filenames_str += phosg::string_printf("/%s", dat_filedata->filename.c_str());
|
||||
filenames_str += std::format("/{}", dat_filedata->filename);
|
||||
}
|
||||
if (pvr_filedata) {
|
||||
filenames_str += phosg::string_printf("/%s", pvr_filedata->filename.c_str());
|
||||
filenames_str += std::format("/{}", pvr_filedata->filename);
|
||||
}
|
||||
if (json_filedata) {
|
||||
filenames_str += phosg::string_printf("/%s", json_filedata->filename.c_str());
|
||||
filenames_str += std::format("/{}", json_filedata->filename);
|
||||
}
|
||||
auto q_it = this->quests_by_number.find(vq->quest_number);
|
||||
if (q_it != this->quests_by_number.end()) {
|
||||
q_it->second->add_version(vq);
|
||||
static_game_data_log.info("(%s) Added %s %c version of quest %" PRIu32 " (%s)",
|
||||
filenames_str.c_str(),
|
||||
static_game_data_log.info_f("({}) Added {} {} version of quest {} ({})",
|
||||
filenames_str,
|
||||
phosg::name_for_enum(vq->version),
|
||||
char_for_language_code(vq->language),
|
||||
vq->quest_number,
|
||||
vq->name.c_str());
|
||||
vq->name);
|
||||
} else {
|
||||
auto q = make_shared<Quest>(vq);
|
||||
this->quests_by_number.emplace(vq->quest_number, q);
|
||||
this->quests_by_name.emplace(vq->name, q);
|
||||
this->quests_by_category_id_and_number[q->category_id].emplace(vq->quest_number, q);
|
||||
static_game_data_log.info("(%s) Created %s %c quest %" PRIu32 " (%s) (%s, %s (%" PRIu32 "), %s)",
|
||||
filenames_str.c_str(),
|
||||
static_game_data_log.info_f("({}) Created {} {} quest {} ({}) ({}, {} ({}), {})",
|
||||
filenames_str,
|
||||
phosg::name_for_enum(vq->version),
|
||||
char_for_language_code(vq->language),
|
||||
vq->quest_number,
|
||||
vq->name.c_str(),
|
||||
vq->name,
|
||||
name_for_episode(vq->episode),
|
||||
category_name.c_str(),
|
||||
category_name,
|
||||
vq->category_id,
|
||||
vq->joinable ? "joinable" : "not joinable");
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
static_game_data_log.warning("(%s) Failed to index quest file: %s", basename.c_str(), e.what());
|
||||
static_game_data_log.warning_f("({}) Failed to index quest file: {}", basename, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1198,8 +1200,8 @@ string decode_gci_data(
|
||||
|
||||
size_t expected_decompressed_bytes = dlq_header.decompressed_size - 8;
|
||||
if (decompressed_bytes < expected_decompressed_bytes) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"GCI decompressed data is smaller than expected size (have 0x%zX bytes, expected 0x%zX bytes)",
|
||||
throw 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));
|
||||
}
|
||||
|
||||
@@ -1247,8 +1249,8 @@ string decode_gci_data(
|
||||
|
||||
size_t decompressed_size = prs_decompress_size(decrypted);
|
||||
if (decompressed_size != sizeof(Episode3::MapDefinition)) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"decompressed quest is 0x%zX bytes; expected 0x%zX bytes",
|
||||
throw runtime_error(std::format(
|
||||
"decompressed quest is 0x{:X} bytes; expected 0x{:X} bytes",
|
||||
decompressed_size, sizeof(Episode3::MapDefinition)));
|
||||
}
|
||||
return decrypted;
|
||||
@@ -1394,7 +1396,7 @@ static unordered_map<string, string> decode_qst_data_t(const string& data) {
|
||||
|
||||
for (const auto& it : file_remaining_bytes) {
|
||||
if (it.second) {
|
||||
throw runtime_error(phosg::string_printf("expected %zu (0x%zX) more bytes for file %s", it.second, it.second, it.first.c_str()));
|
||||
throw runtime_error(std::format("expected {} (0x{:X}) more bytes for file {}", it.second, it.second, it.first));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+192
-188
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <phosg/Math.hh>
|
||||
#include <phosg/Strings.hh>
|
||||
@@ -117,7 +118,7 @@ static string escape_string(const string& data, TextEncoding encoding = TextEnco
|
||||
} else if (ch == '\t') {
|
||||
ret += "\\t";
|
||||
} else if (static_cast<uint8_t>(ch) < 0x20) {
|
||||
ret += phosg::string_printf("\\x%02hhX", ch);
|
||||
ret += std::format("\\x{:02X}", ch);
|
||||
} else if (ch == '\'') {
|
||||
ret += "\\\'";
|
||||
} else if (ch == '\"') {
|
||||
@@ -221,8 +222,8 @@ struct QuestScriptOpcodeDefinition {
|
||||
flags(flags) {}
|
||||
|
||||
std::string str() const {
|
||||
string name_str = this->qedit_name ? phosg::string_printf("%s (qedit: %s)", this->name, this->qedit_name) : this->name;
|
||||
return phosg::string_printf("%04hX: %s flags=%04hX", this->opcode, name_str.c_str(), this->flags);
|
||||
string name_str = this->qedit_name ? std::format("{} (qedit: {})", this->name, this->qedit_name) : this->name;
|
||||
return std::format("{:04X}: {} flags={:04X}", this->opcode, name_str, this->flags);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -406,13 +407,16 @@ static const QuestScriptOpcodeDefinition opcode_defs[] = {
|
||||
// Sets a regA to 0 if it's nonzero and vice versa
|
||||
{0x12, "rev", nullptr, {REG}, F_V0_V4},
|
||||
|
||||
// Sets flagA to 1
|
||||
// Sets flagA to 1. Sends 6x75.
|
||||
{0x13, "gset", nullptr, {INT16}, F_V0_V4},
|
||||
|
||||
// Clears flagA to 0
|
||||
// Clears flagA to 0. Sends 6x75 on BB, but does not send anything on other
|
||||
// versions.
|
||||
{0x14, "gclear", nullptr, {INT16}, F_V0_V4},
|
||||
|
||||
// Inverts flagA
|
||||
// Inverts flagA. Like the above two opcodes, sends 6x75 if the flag is set
|
||||
// by this opcode. Only BB sends 6x75 if the flag is cleared by this
|
||||
// opcode.
|
||||
{0x15, "grev", nullptr, {INT16}, F_V0_V4},
|
||||
|
||||
// If regB is nonzero, sets flagA; otherwise, clears it
|
||||
@@ -2849,7 +2853,7 @@ opcodes_for_version(Version v) {
|
||||
continue;
|
||||
}
|
||||
if (!index.emplace(def.opcode, &def).second) {
|
||||
throw logic_error(phosg::string_printf("duplicate definition for opcode %04hX", def.opcode));
|
||||
throw logic_error(std::format("duplicate definition for opcode {:04X}", def.opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2872,12 +2876,12 @@ opcodes_by_name_for_version(Version v) {
|
||||
continue;
|
||||
}
|
||||
if (def.name && !index.emplace(phosg::tolower(def.name), &def).second) {
|
||||
throw logic_error(phosg::string_printf("duplicate definition for opcode %04hX", def.opcode));
|
||||
throw logic_error(std::format("duplicate definition for opcode {:04X}", def.opcode));
|
||||
}
|
||||
if (def.qedit_name) {
|
||||
string lower_qedit_name = phosg::tolower(phosg::tolower(def.qedit_name));
|
||||
if ((lower_qedit_name != def.name) && !index.emplace(lower_qedit_name, &def).second) {
|
||||
throw logic_error(phosg::string_printf("duplicate definition for opcode %04hX", def.opcode));
|
||||
throw logic_error(std::format("duplicate definition for opcode {:04X}", def.opcode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2903,7 +2907,7 @@ void check_opcode_definitions() {
|
||||
for (Version v : versions) {
|
||||
const auto& opcodes_by_name = opcodes_by_name_for_version(v);
|
||||
const auto& opcodes = opcodes_for_version(v);
|
||||
phosg::log_info("Version %s has %zu opcodes with %zu mnemonics", phosg::name_for_enum(v), opcodes.size(), opcodes_by_name.size());
|
||||
phosg::log_info_f("Version {} has {} opcodes with {} mnemonics", phosg::name_for_enum(v), opcodes.size(), opcodes_by_name.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2916,7 +2920,7 @@ std::string disassemble_quest_script(
|
||||
bool use_qedit_names) {
|
||||
phosg::StringReader r(data, size);
|
||||
deque<string> lines;
|
||||
lines.emplace_back(phosg::string_printf(".version %s", phosg::name_for_enum(version)));
|
||||
lines.emplace_back(std::format(".version {}", phosg::name_for_enum(version)));
|
||||
|
||||
bool use_wstrs = false;
|
||||
size_t code_offset = 0;
|
||||
@@ -2944,8 +2948,8 @@ std::string disassemble_quest_script(
|
||||
} else {
|
||||
language = 1;
|
||||
}
|
||||
lines.emplace_back(phosg::string_printf(".quest_num %hu", header.quest_number.load()));
|
||||
lines.emplace_back(phosg::string_printf(".language %hhu", header.language));
|
||||
lines.emplace_back(std::format(".quest_num {}", header.quest_number));
|
||||
lines.emplace_back(std::format(".language {}", header.language));
|
||||
lines.emplace_back(".name " + escape_string(header.name.decode(language)));
|
||||
lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language)));
|
||||
lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language)));
|
||||
@@ -2964,8 +2968,8 @@ std::string disassemble_quest_script(
|
||||
} else {
|
||||
language = 1;
|
||||
}
|
||||
lines.emplace_back(phosg::string_printf(".quest_num %hu", header.quest_number.load()));
|
||||
lines.emplace_back(phosg::string_printf(".language %hhu", header.language));
|
||||
lines.emplace_back(std::format(".quest_num {}", header.quest_number));
|
||||
lines.emplace_back(std::format(".language {}", header.language));
|
||||
lines.emplace_back(".name " + escape_string(header.name.decode(language)));
|
||||
lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language)));
|
||||
lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language)));
|
||||
@@ -2986,8 +2990,8 @@ std::string disassemble_quest_script(
|
||||
} else {
|
||||
language = 1;
|
||||
}
|
||||
lines.emplace_back(phosg::string_printf(".quest_num %hu", header.quest_number.load()));
|
||||
lines.emplace_back(phosg::string_printf(".language %hhu", header.language));
|
||||
lines.emplace_back(std::format(".quest_num {}", header.quest_number));
|
||||
lines.emplace_back(std::format(".language {}", header.language));
|
||||
lines.emplace_back(".name " + escape_string(header.name.decode(language)));
|
||||
lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language)));
|
||||
lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language)));
|
||||
@@ -3003,9 +3007,9 @@ std::string disassemble_quest_script(
|
||||
} else {
|
||||
language = 1;
|
||||
}
|
||||
lines.emplace_back(phosg::string_printf(".quest_num %hu", header.quest_number.load()));
|
||||
lines.emplace_back(phosg::string_printf(".episode %s", name_for_header_episode_number(header.episode)));
|
||||
lines.emplace_back(phosg::string_printf(".max_players %hhu", header.max_players ? header.max_players : 4));
|
||||
lines.emplace_back(std::format(".quest_num {}", header.quest_number));
|
||||
lines.emplace_back(std::format(".episode {}", name_for_header_episode_number(header.episode)));
|
||||
lines.emplace_back(std::format(".max_players {}", header.max_players ? header.max_players : 4));
|
||||
if (header.joinable) {
|
||||
lines.emplace_back(".joinable");
|
||||
}
|
||||
@@ -3047,7 +3051,7 @@ std::string disassemble_quest_script(
|
||||
while (!function_table_r.eof()) {
|
||||
try {
|
||||
uint32_t label_index = function_table.size();
|
||||
string name = (label_index == 0) ? "start" : phosg::string_printf("label%04" PRIX32, label_index);
|
||||
string name = (label_index == 0) ? "start" : std::format("label{:04X}", label_index);
|
||||
uint32_t offset = function_table_r.get_u32l();
|
||||
auto l = make_shared<Label>(name, offset, label_index);
|
||||
if (label_index == 0) {
|
||||
@@ -3124,10 +3128,10 @@ std::string disassemble_quest_script(
|
||||
}
|
||||
|
||||
if (def == nullptr) {
|
||||
dasm_line = phosg::string_printf(".unknown %04hX", opcode);
|
||||
dasm_line = std::format(".unknown {:04X}", opcode);
|
||||
} else {
|
||||
const char* op_name = (use_qedit_names && def->qedit_name) ? def->qedit_name : def->name;
|
||||
dasm_line = op_name ? op_name : phosg::string_printf("[%04hX]", opcode);
|
||||
dasm_line = op_name ? op_name : std::format("[{:04X}]", opcode);
|
||||
if (!version_has_args || !(def->flags & F_ARGS)) {
|
||||
dasm_line.resize(0x20, ' ');
|
||||
bool is_first_arg = true;
|
||||
@@ -3142,13 +3146,13 @@ std::string disassemble_quest_script(
|
||||
arg_stack_values.emplace_back(ArgStackValue::Type::LABEL, label_id);
|
||||
}
|
||||
if (label_id >= function_table.size()) {
|
||||
dasm_arg = phosg::string_printf("label%04" PRIX32, label_id);
|
||||
dasm_arg = std::format("label{:04X}", label_id);
|
||||
} else {
|
||||
auto& l = function_table.at(label_id);
|
||||
if (reassembly_mode) {
|
||||
dasm_arg = phosg::string_printf("label%04" PRIX32, label_id);
|
||||
dasm_arg = std::format("label{:04X}", label_id);
|
||||
} else {
|
||||
dasm_arg = phosg::string_printf("label%04" PRIX32 " /* %04" PRIX32 " */", label_id, l->offset);
|
||||
dasm_arg = std::format("label{:04X} /* {:04X} */", label_id, l->offset);
|
||||
}
|
||||
l->references.emplace(opcode_start_offset);
|
||||
l->add_data_type(arg.data_type);
|
||||
@@ -3167,13 +3171,13 @@ std::string disassemble_quest_script(
|
||||
dasm_arg += (dasm_arg.empty() ? "[" : ", ");
|
||||
uint32_t label_id = cmd_r.get_u16l();
|
||||
if (label_id >= function_table.size()) {
|
||||
dasm_arg += phosg::string_printf("label%04" PRIX32, label_id);
|
||||
dasm_arg += std::format("label{:04X}", label_id);
|
||||
} else {
|
||||
auto& l = function_table.at(label_id);
|
||||
if (reassembly_mode) {
|
||||
dasm_arg += phosg::string_printf("label%04" PRIX32, label_id);
|
||||
dasm_arg += std::format("label{:04X}", label_id);
|
||||
} else {
|
||||
dasm_arg += phosg::string_printf("label%04" PRIX32 " /* %04" PRIX32 " */", label_id, l->offset);
|
||||
dasm_arg += std::format("label{:04X} /* {:04X} */", label_id, l->offset);
|
||||
}
|
||||
l->references.emplace(opcode_start_offset);
|
||||
l->add_data_type(arg.data_type);
|
||||
@@ -3194,7 +3198,7 @@ std::string disassemble_quest_script(
|
||||
if (def->flags & F_PASS) {
|
||||
arg_stack_values.emplace_back((def->opcode == 0x004C) ? ArgStackValue::Type::REG_PTR : ArgStackValue::Type::REG, reg);
|
||||
}
|
||||
dasm_arg = phosg::string_printf("r%hhu", reg);
|
||||
dasm_arg = std::format("r{}", reg);
|
||||
break;
|
||||
}
|
||||
case Type::REG_SET: {
|
||||
@@ -3203,7 +3207,7 @@ std::string disassemble_quest_script(
|
||||
}
|
||||
uint8_t num_regs = cmd_r.get_u8();
|
||||
for (size_t z = 0; z < num_regs; z++) {
|
||||
dasm_arg += phosg::string_printf("%sr%hhu", (dasm_arg.empty() ? "[" : ", "), cmd_r.get_u8());
|
||||
dasm_arg += std::format("{}r{}", (dasm_arg.empty() ? "[" : ", "), cmd_r.get_u8());
|
||||
}
|
||||
if (dasm_arg.empty()) {
|
||||
dasm_arg = "[]";
|
||||
@@ -3217,7 +3221,7 @@ std::string disassemble_quest_script(
|
||||
throw logic_error("REG_SET_FIXED cannot be pushed to arg stack");
|
||||
}
|
||||
uint8_t first_reg = cmd_r.get_u8();
|
||||
dasm_arg = phosg::string_printf("r%hhu-r%hhu", first_reg, static_cast<uint8_t>(first_reg + arg.count - 1));
|
||||
dasm_arg = std::format("r{}-r{}", first_reg, static_cast<uint8_t>(first_reg + arg.count - 1));
|
||||
break;
|
||||
}
|
||||
case Type::REG32_SET_FIXED: {
|
||||
@@ -3225,7 +3229,7 @@ std::string disassemble_quest_script(
|
||||
throw logic_error("REG32_SET_FIXED cannot be pushed to arg stack");
|
||||
}
|
||||
uint32_t first_reg = cmd_r.get_u32l();
|
||||
dasm_arg = phosg::string_printf("r%" PRIu32 "-r%" PRIu32, first_reg, static_cast<uint32_t>(first_reg + arg.count - 1));
|
||||
dasm_arg = std::format("r{}-r{}", first_reg, static_cast<uint32_t>(first_reg + arg.count - 1));
|
||||
break;
|
||||
}
|
||||
case Type::INT8: {
|
||||
@@ -3233,7 +3237,7 @@ std::string disassemble_quest_script(
|
||||
if (def->flags & F_PASS) {
|
||||
arg_stack_values.emplace_back(ArgStackValue::Type::INT, v);
|
||||
}
|
||||
dasm_arg = phosg::string_printf("0x%02hhX", v);
|
||||
dasm_arg = std::format("0x{:02X}", v);
|
||||
break;
|
||||
}
|
||||
case Type::INT16: {
|
||||
@@ -3241,7 +3245,7 @@ std::string disassemble_quest_script(
|
||||
if (def->flags & F_PASS) {
|
||||
arg_stack_values.emplace_back(ArgStackValue::Type::INT, v);
|
||||
}
|
||||
dasm_arg = phosg::string_printf("0x%04hX", v);
|
||||
dasm_arg = std::format("0x{:04X}", v);
|
||||
break;
|
||||
}
|
||||
case Type::INT32: {
|
||||
@@ -3249,7 +3253,7 @@ std::string disassemble_quest_script(
|
||||
if (def->flags & F_PASS) {
|
||||
arg_stack_values.emplace_back(ArgStackValue::Type::INT, v);
|
||||
}
|
||||
dasm_arg = phosg::string_printf("0x%08" PRIX32, v);
|
||||
dasm_arg = std::format("0x{:08X}", v);
|
||||
break;
|
||||
}
|
||||
case Type::FLOAT32: {
|
||||
@@ -3257,7 +3261,7 @@ std::string disassemble_quest_script(
|
||||
if (def->flags & F_PASS) {
|
||||
arg_stack_values.emplace_back(ArgStackValue::Type::INT, as_type<uint32_t>(v));
|
||||
}
|
||||
dasm_arg = phosg::string_printf("%g", v);
|
||||
dasm_arg = std::format("{:g}", v);
|
||||
break;
|
||||
}
|
||||
case Type::CSTRING:
|
||||
@@ -3297,7 +3301,7 @@ std::string disassemble_quest_script(
|
||||
dasm_line += "... ";
|
||||
|
||||
if (def->args.size() != arg_stack_values.size()) {
|
||||
dasm_line += phosg::string_printf("/* matching error: expected %zu arguments, received %zu arguments */",
|
||||
dasm_line += std::format("/* matching error: expected {} arguments, received {} arguments */",
|
||||
def->args.size(), arg_stack_values.size());
|
||||
} else {
|
||||
bool is_first_arg = true;
|
||||
@@ -3311,11 +3315,11 @@ std::string disassemble_quest_script(
|
||||
case Arg::Type::LABEL32:
|
||||
switch (arg_value.type) {
|
||||
case ArgStackValue::Type::REG:
|
||||
dasm_arg = phosg::string_printf("r%" PRIu32 "/* warning: cannot determine label data type */", arg_value.as_int);
|
||||
dasm_arg = std::format("r{}/* warning: cannot determine label data type */", arg_value.as_int);
|
||||
break;
|
||||
case ArgStackValue::Type::LABEL:
|
||||
case ArgStackValue::Type::INT:
|
||||
dasm_arg = phosg::string_printf("label%04" PRIX32, arg_value.as_int);
|
||||
dasm_arg = std::format("label{:04X}", arg_value.as_int);
|
||||
try {
|
||||
auto l = function_table.at(arg_value.as_int);
|
||||
l->add_data_type(arg_def.data_type);
|
||||
@@ -3331,10 +3335,10 @@ std::string disassemble_quest_script(
|
||||
case Arg::Type::REG32:
|
||||
switch (arg_value.type) {
|
||||
case ArgStackValue::Type::REG:
|
||||
dasm_arg = phosg::string_printf("regs[r%" PRIu32 "]", arg_value.as_int);
|
||||
dasm_arg = std::format("regs[r{}]", arg_value.as_int);
|
||||
break;
|
||||
case ArgStackValue::Type::INT:
|
||||
dasm_arg = phosg::string_printf("r%" PRIu32, arg_value.as_int);
|
||||
dasm_arg = std::format("r{}", arg_value.as_int);
|
||||
break;
|
||||
default:
|
||||
dasm_arg = "/* invalid-type */";
|
||||
@@ -3344,10 +3348,10 @@ std::string disassemble_quest_script(
|
||||
case Arg::Type::REG32_SET_FIXED:
|
||||
switch (arg_value.type) {
|
||||
case ArgStackValue::Type::REG:
|
||||
dasm_arg = phosg::string_printf("regs[r%" PRIu32 "]-regs[r%" PRIu32 "+%hhu]", arg_value.as_int, arg_value.as_int, static_cast<uint8_t>(arg_def.count - 1));
|
||||
dasm_arg = std::format("regs[r{}]-regs[r{}+{}]", arg_value.as_int, arg_value.as_int, static_cast<uint8_t>(arg_def.count - 1));
|
||||
break;
|
||||
case ArgStackValue::Type::INT:
|
||||
dasm_arg = phosg::string_printf("r%" PRIu32 "-r%hhu", arg_value.as_int, static_cast<uint8_t>(arg_value.as_int + arg_def.count - 1));
|
||||
dasm_arg = std::format("r{}-r{}", arg_value.as_int, static_cast<uint8_t>(arg_value.as_int + arg_def.count - 1));
|
||||
break;
|
||||
default:
|
||||
dasm_arg = "/* invalid-type */";
|
||||
@@ -3358,13 +3362,13 @@ std::string disassemble_quest_script(
|
||||
case Arg::Type::INT32:
|
||||
switch (arg_value.type) {
|
||||
case ArgStackValue::Type::REG:
|
||||
dasm_arg = phosg::string_printf("r%" PRIu32, arg_value.as_int);
|
||||
dasm_arg = std::format("r{}", arg_value.as_int);
|
||||
break;
|
||||
case ArgStackValue::Type::REG_PTR:
|
||||
dasm_arg = phosg::string_printf("&r%" PRIu32, arg_value.as_int);
|
||||
dasm_arg = std::format("&r{}", arg_value.as_int);
|
||||
break;
|
||||
case ArgStackValue::Type::INT:
|
||||
dasm_arg = phosg::string_printf("0x%" PRIX32 " /* %" PRIu32 " */", arg_value.as_int, arg_value.as_int);
|
||||
dasm_arg = std::format("0x{:X} /* {} */", arg_value.as_int, arg_value.as_int);
|
||||
break;
|
||||
default:
|
||||
dasm_arg = "/* invalid-type */";
|
||||
@@ -3373,10 +3377,10 @@ std::string disassemble_quest_script(
|
||||
case Arg::Type::FLOAT32:
|
||||
switch (arg_value.type) {
|
||||
case ArgStackValue::Type::REG:
|
||||
dasm_arg = phosg::string_printf("f%" PRIu32, arg_value.as_int);
|
||||
dasm_arg = std::format("f{}", arg_value.as_int);
|
||||
break;
|
||||
case ArgStackValue::Type::INT:
|
||||
dasm_arg = phosg::string_printf("%g", as_type<float>(arg_value.as_int));
|
||||
dasm_arg = std::format("{:g}", as_type<float>(arg_value.as_int));
|
||||
break;
|
||||
default:
|
||||
dasm_arg = "/* invalid-type */";
|
||||
@@ -3411,13 +3415,13 @@ std::string disassemble_quest_script(
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
dasm_line = phosg::string_printf(".failed (%s)", e.what());
|
||||
dasm_line = std::format(".failed ({})", e.what());
|
||||
}
|
||||
phosg::strip_trailing_whitespace(dasm_line);
|
||||
|
||||
string line_text;
|
||||
if (reassembly_mode) {
|
||||
line_text = phosg::string_printf(" %s", dasm_line.c_str());
|
||||
line_text = std::format(" {}", dasm_line);
|
||||
} else {
|
||||
string hex_data = phosg::format_data_string(cmd_r.preadx(opcode_start_offset, cmd_r.where() - opcode_start_offset), nullptr, phosg::FormatDataFlags::HEX_ONLY);
|
||||
if (hex_data.size() > 14) {
|
||||
@@ -3425,7 +3429,7 @@ std::string disassemble_quest_script(
|
||||
hex_data += "...";
|
||||
}
|
||||
hex_data.resize(16, ' ');
|
||||
line_text = phosg::string_printf(" %04zX %s %s", opcode_start_offset, hex_data.c_str(), dasm_line.c_str());
|
||||
line_text = std::format(" {:04X} {} {}", opcode_start_offset, hex_data, dasm_line);
|
||||
}
|
||||
dasm_lines.emplace(opcode_start_offset, DisassemblyLine(std::move(line_text), cmd_r.where()));
|
||||
}
|
||||
@@ -3440,23 +3444,23 @@ std::string disassemble_quest_script(
|
||||
lines.emplace_back();
|
||||
}
|
||||
if (reassembly_mode) {
|
||||
lines.emplace_back(phosg::string_printf("%s@0x%04" PRIX32 ":", l->name.c_str(), l->label_index));
|
||||
lines.emplace_back(std::format("{}@0x{:04X}:", l->name, l->label_index));
|
||||
} else {
|
||||
lines.emplace_back(phosg::string_printf("%s:", l->name.c_str()));
|
||||
lines.emplace_back(std::format("{}:", l->name));
|
||||
if (l->references.size() == 1) {
|
||||
lines.emplace_back(phosg::string_printf(" // Referenced by instruction at %04zX", *l->references.begin()));
|
||||
lines.emplace_back(std::format(" // Referenced by instruction at {:04X}", *l->references.begin()));
|
||||
} else if (!l->references.empty()) {
|
||||
vector<string> tokens;
|
||||
tokens.reserve(l->references.size());
|
||||
for (size_t reference_offset : l->references) {
|
||||
tokens.emplace_back(phosg::string_printf("%04zX", reference_offset));
|
||||
tokens.emplace_back(std::format("{:04X}", reference_offset));
|
||||
}
|
||||
lines.emplace_back(" // Referenced by instructions at " + phosg::join(tokens, ", "));
|
||||
}
|
||||
}
|
||||
|
||||
if (l->type_flags == 0) {
|
||||
lines.emplace_back(phosg::string_printf(" // Could not determine data type; disassembling as code"));
|
||||
lines.emplace_back(std::format(" // Could not determine data type; disassembling as code"));
|
||||
l->add_data_type(Arg::DataType::SCRIPT);
|
||||
}
|
||||
|
||||
@@ -3491,18 +3495,18 @@ std::string disassemble_quest_script(
|
||||
lines.emplace_back(format_and_indent_data(cmd_r.pgetv(struct_end_offset, remaining_size), remaining_size, struct_end_offset));
|
||||
}
|
||||
} else {
|
||||
lines.emplace_back(phosg::string_printf(" // As raw data (0x%zX bytes; too small for referenced type)", size));
|
||||
lines.emplace_back(std::format(" // As raw data (0x{:X} bytes; too small for referenced type)", size));
|
||||
lines.emplace_back(format_and_indent_data(cmd_r.pgetv(l->offset, size), size, l->offset));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (l->has_data_type(Arg::DataType::DATA)) {
|
||||
lines.emplace_back(phosg::string_printf(" // As raw data (0x%zX bytes)", size));
|
||||
lines.emplace_back(std::format(" // As raw data (0x{:X} bytes)", size));
|
||||
lines.emplace_back(format_and_indent_data(cmd_r.pgetv(l->offset, size), size, l->offset));
|
||||
}
|
||||
if (l->has_data_type(Arg::DataType::CSTRING)) {
|
||||
lines.emplace_back(phosg::string_printf(" // As C string (0x%zX bytes)", size));
|
||||
lines.emplace_back(std::format(" // As C string (0x{:X} bytes)", size));
|
||||
string str_data = cmd_r.pread(l->offset, size);
|
||||
phosg::strip_trailing_zeroes(str_data);
|
||||
string formatted;
|
||||
@@ -3514,100 +3518,100 @@ std::string disassemble_quest_script(
|
||||
} else {
|
||||
formatted = escape_string(str_data, encoding_for_language(language));
|
||||
}
|
||||
lines.emplace_back(phosg::string_printf(" %04" PRIX32 " %s", l->offset, formatted.c_str()));
|
||||
lines.emplace_back(std::format(" {:04X} {}", l->offset, formatted));
|
||||
}
|
||||
print_as_struct.template operator()<Arg::DataType::PLAYER_VISUAL_CONFIG, PlayerVisualConfig>([&](const PlayerVisualConfig& visual) -> void {
|
||||
lines.emplace_back(" // As PlayerVisualConfig");
|
||||
string name = escape_string(visual.name.decode(language));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX name %s", l->offset + offsetof(PlayerVisualConfig, name), name.c_str()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX name_color %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color.load()));
|
||||
lines.emplace_back(std::format(" {:04X} name {}", l->offset + offsetof(PlayerVisualConfig, name), name));
|
||||
lines.emplace_back(std::format(" {:04X} name_color {:08X}", l->offset + offsetof(PlayerVisualConfig, name_color), visual.name_color));
|
||||
string a2_str = phosg::format_data_string(visual.unknown_a2.data(), sizeof(visual.unknown_a2));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a2 %s", l->offset + offsetof(PlayerVisualConfig, unknown_a2), a2_str.c_str()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX extra_model %02hhX", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model));
|
||||
lines.emplace_back(std::format(" {:04X} a2 {}", l->offset + offsetof(PlayerVisualConfig, unknown_a2), a2_str));
|
||||
lines.emplace_back(std::format(" {:04X} extra_model {:02X}", l->offset + offsetof(PlayerVisualConfig, extra_model), visual.extra_model));
|
||||
string unused = phosg::format_data_string(visual.unused.data(), visual.unused.bytes());
|
||||
lines.emplace_back(phosg::string_printf(" %04zX unused %s", l->offset + offsetof(PlayerVisualConfig, unused), unused.c_str()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX name_color_cs %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, name_color_checksum), visual.name_color_checksum.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX section_id %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, name_for_section_id(visual.section_id)));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX char_class %02hhX (%s)", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class)));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX validation_flags %02hhX", l->offset + offsetof(PlayerVisualConfig, validation_flags), visual.validation_flags));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX version %02hhX", l->offset + offsetof(PlayerVisualConfig, version), visual.version));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX class_flags %08" PRIX32, l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX costume %04hX", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX skin %04hX", l->offset + offsetof(PlayerVisualConfig, skin), visual.skin.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX face %04hX", l->offset + offsetof(PlayerVisualConfig, face), visual.face.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX head %04hX", l->offset + offsetof(PlayerVisualConfig, head), visual.head.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX hair %04hX", l->offset + offsetof(PlayerVisualConfig, hair), visual.hair.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX hair_color %04hX, %04hX, %04hX", l->offset + offsetof(PlayerVisualConfig, hair_r), visual.hair_r.load(), visual.hair_g.load(), visual.hair_b.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX proportion %g, %g", l->offset + offsetof(PlayerVisualConfig, proportion_x), visual.proportion_x.load(), visual.proportion_y.load()));
|
||||
lines.emplace_back(std::format(" {:04X} unused {}", l->offset + offsetof(PlayerVisualConfig, unused), unused));
|
||||
lines.emplace_back(std::format(" {:04X} name_color_cs {:08X}", l->offset + offsetof(PlayerVisualConfig, name_color_checksum), visual.name_color_checksum));
|
||||
lines.emplace_back(std::format(" {:04X} section_id {:02X} ({})", l->offset + offsetof(PlayerVisualConfig, section_id), visual.section_id, name_for_section_id(visual.section_id)));
|
||||
lines.emplace_back(std::format(" {:04X} char_class {:02X} ({})", l->offset + offsetof(PlayerVisualConfig, char_class), visual.char_class, name_for_char_class(visual.char_class)));
|
||||
lines.emplace_back(std::format(" {:04X} validation_flags {:02X}", l->offset + offsetof(PlayerVisualConfig, validation_flags), visual.validation_flags));
|
||||
lines.emplace_back(std::format(" {:04X} version {:02X}", l->offset + offsetof(PlayerVisualConfig, version), visual.version));
|
||||
lines.emplace_back(std::format(" {:04X} class_flags {:08X}", l->offset + offsetof(PlayerVisualConfig, class_flags), visual.class_flags));
|
||||
lines.emplace_back(std::format(" {:04X} costume {:04X}", l->offset + offsetof(PlayerVisualConfig, costume), visual.costume));
|
||||
lines.emplace_back(std::format(" {:04X} skin {:04X}", l->offset + offsetof(PlayerVisualConfig, skin), visual.skin));
|
||||
lines.emplace_back(std::format(" {:04X} face {:04X}", l->offset + offsetof(PlayerVisualConfig, face), visual.face));
|
||||
lines.emplace_back(std::format(" {:04X} head {:04X}", l->offset + offsetof(PlayerVisualConfig, head), visual.head));
|
||||
lines.emplace_back(std::format(" {:04X} hair {:04X}", l->offset + offsetof(PlayerVisualConfig, hair), visual.hair));
|
||||
lines.emplace_back(std::format(" {:04X} hair_color {:04X}, {:04X}, {:04X}", l->offset + offsetof(PlayerVisualConfig, hair_r), visual.hair_r, visual.hair_g, visual.hair_b));
|
||||
lines.emplace_back(std::format(" {:04X} proportion {:g}, {:g}", l->offset + offsetof(PlayerVisualConfig, proportion_x), visual.proportion_x, visual.proportion_y));
|
||||
});
|
||||
print_as_struct.template operator()<Arg::DataType::PLAYER_STATS, PlayerStats>([&](const PlayerStats& stats) -> void {
|
||||
lines.emplace_back(" // As PlayerStats");
|
||||
lines.emplace_back(phosg::string_printf(" %04zX atp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.atp), stats.char_stats.atp.load(), stats.char_stats.atp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX mst %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.mst), stats.char_stats.mst.load(), stats.char_stats.mst.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX evp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.evp), stats.char_stats.evp.load(), stats.char_stats.evp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX hp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.hp), stats.char_stats.hp.load(), stats.char_stats.hp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX dfp %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.dfp), stats.char_stats.dfp.load(), stats.char_stats.dfp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX ata %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.ata), stats.char_stats.ata.load(), stats.char_stats.ata.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX lck %04hX /* %hu */", l->offset + offsetof(PlayerStats, char_stats.lck), stats.char_stats.lck.load(), stats.char_stats.lck.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX esp %04hX /* %hu */", l->offset + offsetof(PlayerStats, esp), stats.esp.load(), stats.esp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX height %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, height), stats.height.load_raw(), stats.height.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a3 %08" PRIX32 " /* %g */", l->offset + offsetof(PlayerStats, unknown_a3), stats.unknown_a3.load_raw(), stats.unknown_a3.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX level %08" PRIX32 " /* level %" PRIu32 " */", l->offset + offsetof(PlayerStats, level), stats.level.load(), stats.level.load() + 1));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX experience %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, experience), stats.experience.load(), stats.experience.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX meseta %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(PlayerStats, meseta), stats.meseta.load(), stats.meseta.load()));
|
||||
lines.emplace_back(std::format(" {:04X} atp {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.atp), stats.char_stats.atp, stats.char_stats.atp));
|
||||
lines.emplace_back(std::format(" {:04X} mst {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.mst), stats.char_stats.mst, stats.char_stats.mst));
|
||||
lines.emplace_back(std::format(" {:04X} evp {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.evp), stats.char_stats.evp, stats.char_stats.evp));
|
||||
lines.emplace_back(std::format(" {:04X} hp {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.hp), stats.char_stats.hp, stats.char_stats.hp));
|
||||
lines.emplace_back(std::format(" {:04X} dfp {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.dfp), stats.char_stats.dfp, stats.char_stats.dfp));
|
||||
lines.emplace_back(std::format(" {:04X} ata {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.ata), stats.char_stats.ata, stats.char_stats.ata));
|
||||
lines.emplace_back(std::format(" {:04X} lck {:04X} /* {} */", l->offset + offsetof(PlayerStats, char_stats.lck), stats.char_stats.lck, stats.char_stats.lck));
|
||||
lines.emplace_back(std::format(" {:04X} esp {:04X} /* {} */", l->offset + offsetof(PlayerStats, esp), stats.esp, stats.esp));
|
||||
lines.emplace_back(std::format(" {:04X} height {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, height), stats.height.load_raw(), stats.height));
|
||||
lines.emplace_back(std::format(" {:04X} a3 {:08X} /* {:g} */", l->offset + offsetof(PlayerStats, unknown_a3), stats.unknown_a3.load_raw(), stats.unknown_a3));
|
||||
lines.emplace_back(std::format(" {:04X} level {:08X} /* level {} */", l->offset + offsetof(PlayerStats, level), stats.level, stats.level + 1));
|
||||
lines.emplace_back(std::format(" {:04X} experience {:08X} /* {} */", l->offset + offsetof(PlayerStats, experience), stats.experience, stats.experience));
|
||||
lines.emplace_back(std::format(" {:04X} meseta {:08X} /* {} */", l->offset + offsetof(PlayerStats, meseta), stats.meseta, stats.meseta));
|
||||
});
|
||||
print_as_struct.template operator()<Arg::DataType::RESIST_DATA, ResistData>([&](const ResistData& resist) -> void {
|
||||
lines.emplace_back(" // As ResistData");
|
||||
lines.emplace_back(phosg::string_printf(" %04zX evp_bonus %04hX /* %hu */", l->offset + offsetof(ResistData, evp_bonus), resist.evp_bonus.load(), resist.evp_bonus.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX efr %04hX /* %hu */", l->offset + offsetof(ResistData, efr), resist.efr.load(), resist.efr.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX eic %04hX /* %hu */", l->offset + offsetof(ResistData, eic), resist.eic.load(), resist.eic.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX eth %04hX /* %hu */", l->offset + offsetof(ResistData, eth), resist.eth.load(), resist.eth.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX elt %04hX /* %hu */", l->offset + offsetof(ResistData, elt), resist.elt.load(), resist.elt.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX edk %04hX /* %hu */", l->offset + offsetof(ResistData, edk), resist.edk.load(), resist.edk.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a6), resist.unknown_a6.load(), resist.unknown_a6.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a7 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a7), resist.unknown_a7.load(), resist.unknown_a7.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a8 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a8), resist.unknown_a8.load(), resist.unknown_a8.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a9 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, unknown_a9), resist.unknown_a9.load(), resist.unknown_a9.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX dfp_bonus %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(ResistData, dfp_bonus), resist.dfp_bonus.load(), resist.dfp_bonus.load()));
|
||||
lines.emplace_back(std::format(" {:04X} evp_bonus {:04X} /* {} */", l->offset + offsetof(ResistData, evp_bonus), resist.evp_bonus, resist.evp_bonus));
|
||||
lines.emplace_back(std::format(" {:04X} efr {:04X} /* {} */", l->offset + offsetof(ResistData, efr), resist.efr, resist.efr));
|
||||
lines.emplace_back(std::format(" {:04X} eic {:04X} /* {} */", l->offset + offsetof(ResistData, eic), resist.eic, resist.eic));
|
||||
lines.emplace_back(std::format(" {:04X} eth {:04X} /* {} */", l->offset + offsetof(ResistData, eth), resist.eth, resist.eth));
|
||||
lines.emplace_back(std::format(" {:04X} elt {:04X} /* {} */", l->offset + offsetof(ResistData, elt), resist.elt, resist.elt));
|
||||
lines.emplace_back(std::format(" {:04X} edk {:04X} /* {} */", l->offset + offsetof(ResistData, edk), resist.edk, resist.edk));
|
||||
lines.emplace_back(std::format(" {:04X} a6 {:08X} /* {} */", l->offset + offsetof(ResistData, unknown_a6), resist.unknown_a6, resist.unknown_a6));
|
||||
lines.emplace_back(std::format(" {:04X} a7 {:08X} /* {} */", l->offset + offsetof(ResistData, unknown_a7), resist.unknown_a7, resist.unknown_a7));
|
||||
lines.emplace_back(std::format(" {:04X} a8 {:08X} /* {} */", l->offset + offsetof(ResistData, unknown_a8), resist.unknown_a8, resist.unknown_a8));
|
||||
lines.emplace_back(std::format(" {:04X} a9 {:08X} /* {} */", l->offset + offsetof(ResistData, unknown_a9), resist.unknown_a9, resist.unknown_a9));
|
||||
lines.emplace_back(std::format(" {:04X} dfp_bonus {:08X} /* {} */", l->offset + offsetof(ResistData, dfp_bonus), resist.dfp_bonus, resist.dfp_bonus));
|
||||
});
|
||||
print_as_struct.template operator()<Arg::DataType::ATTACK_DATA, AttackData>([&](const AttackData& attack) -> void {
|
||||
lines.emplace_back(" // As AttackData");
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a1 %04hX /* %hd */", l->offset + offsetof(AttackData, unknown_a1), attack.unknown_a1.load(), attack.unknown_a1.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX atp %04hX /* %hd */", l->offset + offsetof(AttackData, atp), attack.atp.load(), attack.atp.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX ata_bonus %04hX /* %hd */", l->offset + offsetof(AttackData, ata_bonus), attack.ata_bonus.load(), attack.ata_bonus.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a4 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a4), attack.unknown_a4.load(), attack.unknown_a4.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX distance_x %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, distance_x), attack.distance_x.load_raw(), attack.distance_x.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX angle_x %08" PRIX32 " /* %" PRIu32 "/65536 */", l->offset + offsetof(AttackData, angle_x), attack.angle_x.load_raw(), attack.angle_x.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX distance_y %08" PRIX32 " /* %g */", l->offset + offsetof(AttackData, distance_y), attack.distance_y.load_raw(), attack.distance_y.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a8 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a8), attack.unknown_a8.load(), attack.unknown_a8.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a9 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a9), attack.unknown_a9.load(), attack.unknown_a9.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a10 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a10), attack.unknown_a10.load(), attack.unknown_a10.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a11 %04hX /* %hu */", l->offset + offsetof(AttackData, unknown_a11), attack.unknown_a11.load(), attack.unknown_a11.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a12 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a12), attack.unknown_a12.load(), attack.unknown_a12.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a13 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a13), attack.unknown_a13.load(), attack.unknown_a13.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a14 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a14), attack.unknown_a14.load(), attack.unknown_a14.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a15 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a15), attack.unknown_a15.load(), attack.unknown_a15.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a16 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(AttackData, unknown_a16), attack.unknown_a16.load(), attack.unknown_a16.load()));
|
||||
lines.emplace_back(std::format(" {:04X} a1 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a1), attack.unknown_a1, attack.unknown_a1));
|
||||
lines.emplace_back(std::format(" {:04X} atp {:04X} /* {} */", l->offset + offsetof(AttackData, atp), attack.atp, attack.atp));
|
||||
lines.emplace_back(std::format(" {:04X} ata_bonus {:04X} /* {} */", l->offset + offsetof(AttackData, ata_bonus), attack.ata_bonus, attack.ata_bonus));
|
||||
lines.emplace_back(std::format(" {:04X} a4 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a4), attack.unknown_a4, attack.unknown_a4));
|
||||
lines.emplace_back(std::format(" {:04X} distance_x {:08X} /* {:g} */", l->offset + offsetof(AttackData, distance_x), attack.distance_x.load_raw(), attack.distance_x));
|
||||
lines.emplace_back(std::format(" {:04X} angle_x {:08X} /* {}/65536 */", l->offset + offsetof(AttackData, angle_x), attack.angle_x.load_raw(), attack.angle_x));
|
||||
lines.emplace_back(std::format(" {:04X} distance_y {:08X} /* {:g} */", l->offset + offsetof(AttackData, distance_y), attack.distance_y.load_raw(), attack.distance_y));
|
||||
lines.emplace_back(std::format(" {:04X} a8 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a8), attack.unknown_a8, attack.unknown_a8));
|
||||
lines.emplace_back(std::format(" {:04X} a9 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a9), attack.unknown_a9, attack.unknown_a9));
|
||||
lines.emplace_back(std::format(" {:04X} a10 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a10), attack.unknown_a10, attack.unknown_a10));
|
||||
lines.emplace_back(std::format(" {:04X} a11 {:04X} /* {} */", l->offset + offsetof(AttackData, unknown_a11), attack.unknown_a11, attack.unknown_a11));
|
||||
lines.emplace_back(std::format(" {:04X} a12 {:08X} /* {} */", l->offset + offsetof(AttackData, unknown_a12), attack.unknown_a12, attack.unknown_a12));
|
||||
lines.emplace_back(std::format(" {:04X} a13 {:08X} /* {} */", l->offset + offsetof(AttackData, unknown_a13), attack.unknown_a13, attack.unknown_a13));
|
||||
lines.emplace_back(std::format(" {:04X} a14 {:08X} /* {} */", l->offset + offsetof(AttackData, unknown_a14), attack.unknown_a14, attack.unknown_a14));
|
||||
lines.emplace_back(std::format(" {:04X} a15 {:08X} /* {} */", l->offset + offsetof(AttackData, unknown_a15), attack.unknown_a15, attack.unknown_a15));
|
||||
lines.emplace_back(std::format(" {:04X} a16 {:08X} /* {} */", l->offset + offsetof(AttackData, unknown_a16), attack.unknown_a16, attack.unknown_a16));
|
||||
});
|
||||
print_as_struct.template operator()<Arg::DataType::MOVEMENT_DATA, MovementData>([&](const MovementData& movement) -> void {
|
||||
lines.emplace_back(" // As MovementData");
|
||||
lines.emplace_back(phosg::string_printf(" %04zX idle_move_speed %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, idle_move_speed), movement.idle_move_speed.load_raw(), movement.idle_move_speed.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX idle_anim_speed %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, idle_animation_speed), movement.idle_animation_speed.load_raw(), movement.idle_animation_speed.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX move_speed %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, move_speed), movement.move_speed.load_raw(), movement.move_speed.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX animation_speed %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, animation_speed), movement.animation_speed.load_raw(), movement.animation_speed.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a1 %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, unknown_a1), movement.unknown_a1.load_raw(), movement.unknown_a1.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a2 %08" PRIX32 " /* %g */", l->offset + offsetof(MovementData, unknown_a2), movement.unknown_a2.load_raw(), movement.unknown_a2.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a3 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a3), movement.unknown_a3.load(), movement.unknown_a3.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a4 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a4), movement.unknown_a4.load(), movement.unknown_a4.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a5 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a5), movement.unknown_a5.load(), movement.unknown_a5.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a6 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a6), movement.unknown_a6.load(), movement.unknown_a6.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a7 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a7), movement.unknown_a7.load(), movement.unknown_a7.load()));
|
||||
lines.emplace_back(phosg::string_printf(" %04zX a8 %08" PRIX32 " /* %" PRIu32 " */", l->offset + offsetof(MovementData, unknown_a8), movement.unknown_a8.load(), movement.unknown_a8.load()));
|
||||
lines.emplace_back(std::format(" {:04X} idle_move_speed {:08X} /* {:g} */", l->offset + offsetof(MovementData, idle_move_speed), movement.idle_move_speed.load_raw(), movement.idle_move_speed));
|
||||
lines.emplace_back(std::format(" {:04X} idle_anim_speed {:08X} /* {:g} */", l->offset + offsetof(MovementData, idle_animation_speed), movement.idle_animation_speed.load_raw(), movement.idle_animation_speed));
|
||||
lines.emplace_back(std::format(" {:04X} move_speed {:08X} /* {:g} */", l->offset + offsetof(MovementData, move_speed), movement.move_speed.load_raw(), movement.move_speed));
|
||||
lines.emplace_back(std::format(" {:04X} animation_speed {:08X} /* {:g} */", l->offset + offsetof(MovementData, animation_speed), movement.animation_speed.load_raw(), movement.animation_speed));
|
||||
lines.emplace_back(std::format(" {:04X} a1 {:08X} /* {:g} */", l->offset + offsetof(MovementData, unknown_a1), movement.unknown_a1.load_raw(), movement.unknown_a1));
|
||||
lines.emplace_back(std::format(" {:04X} a2 {:08X} /* {:g} */", l->offset + offsetof(MovementData, unknown_a2), movement.unknown_a2.load_raw(), movement.unknown_a2));
|
||||
lines.emplace_back(std::format(" {:04X} a3 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a3), movement.unknown_a3, movement.unknown_a3));
|
||||
lines.emplace_back(std::format(" {:04X} a4 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a4), movement.unknown_a4, movement.unknown_a4));
|
||||
lines.emplace_back(std::format(" {:04X} a5 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a5), movement.unknown_a5, movement.unknown_a5));
|
||||
lines.emplace_back(std::format(" {:04X} a6 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a6), movement.unknown_a6, movement.unknown_a6));
|
||||
lines.emplace_back(std::format(" {:04X} a7 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a7), movement.unknown_a7, movement.unknown_a7));
|
||||
lines.emplace_back(std::format(" {:04X} a8 {:08X} /* {} */", l->offset + offsetof(MovementData, unknown_a8), movement.unknown_a8, movement.unknown_a8));
|
||||
});
|
||||
if (l->has_data_type(Arg::DataType::IMAGE_DATA)) {
|
||||
const void* data = cmd_r.pgetv(l->offset, size);
|
||||
auto decompressed = prs_decompress_with_meta(data, size);
|
||||
lines.emplace_back(phosg::string_printf(" // As decompressed image data (0x%zX bytes)", decompressed.data.size()));
|
||||
lines.emplace_back(std::format(" // As decompressed image data (0x{:X} bytes)", decompressed.data.size()));
|
||||
lines.emplace_back(format_and_indent_data(decompressed.data.data(), decompressed.data.size(), 0));
|
||||
if (decompressed.input_bytes_used < size) {
|
||||
size_t compressed_end_offset = l->offset + decompressed.input_bytes_used;
|
||||
@@ -3622,7 +3626,7 @@ std::string disassemble_quest_script(
|
||||
while (r.remaining() >= sizeof(VectorXYZTF)) {
|
||||
size_t offset = l->offset + cmd_r.where();
|
||||
const auto& e = r.get<VectorXYZTF>();
|
||||
lines.emplace_back(phosg::string_printf(" %04zX vector x=%g, y=%g, z=%g, t=%g", offset, e.x.load(), e.y.load(), e.z.load(), e.t.load()));
|
||||
lines.emplace_back(std::format(" {:04X} vector x={:g}, y={:g}, z={:g}, t={:g}", offset, e.x, e.y, e.z, e.t));
|
||||
}
|
||||
if (r.remaining() > 0) {
|
||||
size_t struct_end_offset = l->offset + r.where();
|
||||
@@ -3697,7 +3701,7 @@ Episode find_quest_episode_from_script(const void* data, size_t size, Version ve
|
||||
}
|
||||
|
||||
if (def == nullptr) {
|
||||
throw runtime_error(phosg::string_printf("unknown quest opcode %04hX", opcode));
|
||||
throw runtime_error(std::format("unknown quest opcode {:04X}", opcode));
|
||||
}
|
||||
|
||||
if (def->flags & F_RET) {
|
||||
@@ -3774,7 +3778,7 @@ Episode find_quest_episode_from_script(const void* data, size_t size, Version ve
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
phosg::log_warning("Cannot determine episode from quest script (%s)", e.what());
|
||||
phosg::log_warning_f("Cannot determine episode from quest script ({})", e.what());
|
||||
}
|
||||
|
||||
if (found_episodes.size() > 1) {
|
||||
@@ -3798,7 +3802,7 @@ Episode episode_for_quest_episode_number(uint8_t episode_number) {
|
||||
case 0x02:
|
||||
return Episode::EP4;
|
||||
default:
|
||||
throw runtime_error(phosg::string_printf("invalid episode number %02hhX", episode_number));
|
||||
throw runtime_error(std::format("invalid episode number {:02X}", episode_number));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3811,7 +3815,7 @@ struct RegisterAssigner {
|
||||
unordered_set<size_t> offsets;
|
||||
|
||||
std::string str() const {
|
||||
return phosg::string_printf("Register(%p, name=\"%s\", number=%hd)", this, this->name.c_str(), this->number);
|
||||
return std::format("Register(name=\"{}\", number={})", this->name, this->number);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3857,7 +3861,7 @@ struct RegisterAssigner {
|
||||
}
|
||||
this->numbered_regs.at(reg->number) = reg;
|
||||
} else if (reg->number != number) {
|
||||
throw runtime_error(phosg::string_printf("register %s is assigned multiple numbers", reg->name.c_str()));
|
||||
throw runtime_error(std::format("register {} is assigned multiple numbers", reg->name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3865,10 +3869,10 @@ struct RegisterAssigner {
|
||||
if (reg->name.empty()) {
|
||||
reg->name = name;
|
||||
if (!this->named_regs.emplace(reg->name, reg).second) {
|
||||
throw runtime_error(phosg::string_printf("name %s is already assigned to a different register", reg->name.c_str()));
|
||||
throw runtime_error(std::format("name {} is already assigned to a different register", reg->name));
|
||||
}
|
||||
} else if (reg->name != name) {
|
||||
throw runtime_error(phosg::string_printf("register %hd is assigned multiple names", reg->number));
|
||||
throw runtime_error(std::format("register {} is assigned multiple names", reg->number));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3879,11 +3883,11 @@ struct RegisterAssigner {
|
||||
if (reg->number < 0) {
|
||||
reg->number = number;
|
||||
if (this->numbered_regs.at(reg->number)) {
|
||||
throw logic_error(phosg::string_printf("register number %hd assigned multiple times", reg->number));
|
||||
throw logic_error(std::format("register number {} assigned multiple times", reg->number));
|
||||
}
|
||||
this->numbered_regs.at(reg->number) = reg;
|
||||
} else if (reg->number != static_cast<int16_t>(number)) {
|
||||
throw runtime_error(phosg::string_printf("assigning different register number %hhu over existing register number %hd", number, reg->number));
|
||||
throw runtime_error(std::format("assigning different register number {} over existing register number {}", number, reg->number));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3891,15 +3895,15 @@ struct RegisterAssigner {
|
||||
if (!first_reg->next) {
|
||||
first_reg->next = second_reg;
|
||||
} else if (first_reg->next != second_reg) {
|
||||
throw runtime_error(phosg::string_printf("register %s must come after %s, but is already constrained to another register", second_reg->name.c_str(), first_reg->name.c_str()));
|
||||
throw runtime_error(std::format("register {} must come after {}, but is already constrained to another register", second_reg->name, first_reg->name));
|
||||
}
|
||||
if (!second_reg->prev) {
|
||||
second_reg->prev = first_reg;
|
||||
} else if (second_reg->prev != first_reg) {
|
||||
throw runtime_error(phosg::string_printf("register %s must come before %s, but is already constrained to another register", first_reg->name.c_str(), second_reg->name.c_str()));
|
||||
throw runtime_error(std::format("register {} must come before {}, but is already constrained to another register", first_reg->name, second_reg->name));
|
||||
}
|
||||
if ((first_reg->number >= 0) && (second_reg->number >= 0) && (first_reg->number != ((second_reg->number - 1) & 0xFF))) {
|
||||
throw runtime_error(phosg::string_printf("register %s must come before %s, but both registers already have non-consecutive numbers", first_reg->name.c_str(), second_reg->name.c_str()));
|
||||
throw runtime_error(std::format("register {} must come before {}, but both registers already have non-consecutive numbers", first_reg->name, second_reg->name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3956,13 +3960,13 @@ struct RegisterAssigner {
|
||||
// At this point, all registers should be assigned
|
||||
for (const auto& it : this->named_regs) {
|
||||
if (it.second->number < 0) {
|
||||
throw logic_error(phosg::string_printf("register %s was not assigned", it.second->name.c_str()));
|
||||
throw logic_error(std::format("register {} was not assigned", it.second->name));
|
||||
}
|
||||
}
|
||||
for (size_t z = 0; z < 0x100; z++) {
|
||||
auto reg = this->numbered_regs[z];
|
||||
if (reg && (reg->number != static_cast<int16_t>(z))) {
|
||||
throw logic_error(phosg::string_printf("register %zu has incorrect number %hd", z, reg->number));
|
||||
throw logic_error(std::format("register {} has incorrect number {}", z, reg->number));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4003,9 +4007,9 @@ AssembledQuestScript assemble_quest_script(
|
||||
fn();
|
||||
} catch (const exception& e) {
|
||||
if (line.filename.empty()) {
|
||||
throw runtime_error(phosg::string_printf("(__main__:%zu) %s", line.number, e.what()));
|
||||
throw runtime_error(std::format("(__main__:{}) {}", line.number, e.what()));
|
||||
} else {
|
||||
throw runtime_error(phosg::string_printf("(%s:%zu) %s", line.filename.c_str(), line.number, e.what()));
|
||||
throw runtime_error(std::format("({}:{}) {}", line.filename, line.number, e.what()));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4054,7 +4058,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
|
||||
// Process all includes
|
||||
for (size_t z = 0; z < lines.size(); z++) {
|
||||
if (phosg::starts_with(lines[z].text, ".include ")) {
|
||||
if (lines[z].text.starts_with(".include ")) {
|
||||
string filename = lines[z].text.substr(9);
|
||||
phosg::strip_leading_whitespace(filename);
|
||||
|
||||
@@ -4062,21 +4066,21 @@ AssembledQuestScript assemble_quest_script(
|
||||
unordered_set<string> seen_filenames;
|
||||
for (ssize_t index = lines[z].parent_index; index >= 0; index = lines[index].parent_index) {
|
||||
if (!seen_filenames.emplace(lines.at(index).filename).second) {
|
||||
throw runtime_error(phosg::string_printf("detected cycle while including %s", filename.c_str()));
|
||||
throw runtime_error(std::format("detected cycle while including {}", filename));
|
||||
}
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (const auto& include_dir : script_include_directories) {
|
||||
string include_path = include_dir + "/" + filename;
|
||||
if (phosg::isfile(include_path)) {
|
||||
if (std::filesystem::is_regular_file(include_path)) {
|
||||
found = true;
|
||||
include_file(filename, phosg::load_file(filename), z);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
throw runtime_error(phosg::string_printf("included file %s not found in any include directory", filename.c_str()));
|
||||
throw runtime_error(std::format("included file {} not found in any include directory", filename));
|
||||
}
|
||||
|
||||
// We leave the .include line there; it will be ignored in the logic below
|
||||
@@ -4099,27 +4103,27 @@ AssembledQuestScript assemble_quest_script(
|
||||
}
|
||||
wrap_exceptions_with_line_ref(line, [&]() -> void {
|
||||
if (line.text[0] == '.') {
|
||||
if (phosg::starts_with(line.text, ".include ")) {
|
||||
if (line.text.starts_with(".include ")) {
|
||||
// Nothing to do
|
||||
} else if (phosg::starts_with(line.text, ".version ")) {
|
||||
} else if (line.text.starts_with(".version ")) {
|
||||
string name = line.text.substr(9);
|
||||
phosg::strip_leading_whitespace(name);
|
||||
quest_version = phosg::enum_for_name<Version>(name.c_str());
|
||||
} else if (phosg::starts_with(line.text, ".name ")) {
|
||||
quest_version = phosg::enum_for_name<Version>(name);
|
||||
} else if (line.text.starts_with(".name ")) {
|
||||
quest_name = phosg::parse_data_string(line.text.substr(6));
|
||||
} else if (phosg::starts_with(line.text, ".short_desc ")) {
|
||||
} else if (line.text.starts_with(".short_desc ")) {
|
||||
quest_short_desc = phosg::parse_data_string(line.text.substr(12));
|
||||
} else if (phosg::starts_with(line.text, ".long_desc ")) {
|
||||
} else if (line.text.starts_with(".long_desc ")) {
|
||||
quest_long_desc = phosg::parse_data_string(line.text.substr(11));
|
||||
} else if (phosg::starts_with(line.text, ".quest_num ")) {
|
||||
} else if (line.text.starts_with(".quest_num ")) {
|
||||
quest_num = stoul(line.text.substr(11), nullptr, 0);
|
||||
} else if (phosg::starts_with(line.text, ".language ")) {
|
||||
} else if (line.text.starts_with(".language ")) {
|
||||
quest_language = stoul(line.text.substr(10), nullptr, 0);
|
||||
} else if (phosg::starts_with(line.text, ".episode ")) {
|
||||
} else if (line.text.starts_with(".episode ")) {
|
||||
quest_episode = episode_for_token_name(line.text.substr(9));
|
||||
} else if (phosg::starts_with(line.text, ".max_players ")) {
|
||||
} else if (line.text.starts_with(".max_players ")) {
|
||||
quest_max_players = stoul(line.text.substr(12), nullptr, 0);
|
||||
} else if (phosg::starts_with(line.text, ".joinable ")) {
|
||||
} else if (line.text.starts_with(".joinable ")) {
|
||||
quest_joinable = true;
|
||||
}
|
||||
}
|
||||
@@ -4145,7 +4149,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
map<ssize_t, shared_ptr<Label>> labels_by_index;
|
||||
for (const auto& line : lines) {
|
||||
wrap_exceptions_with_line_ref(line, [&]() -> void {
|
||||
if (phosg::ends_with(line.text, ":")) {
|
||||
if (line.text.ends_with(":")) {
|
||||
auto label = make_shared<Label>();
|
||||
label->name = line.text.substr(0, line.text.size() - 1);
|
||||
size_t at_offset = label->name.find('@');
|
||||
@@ -4153,7 +4157,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
try {
|
||||
label->index = stoul(label->name.substr(at_offset + 1), nullptr, 0);
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(phosg::string_printf("invalid index in label (%s)", e.what()));
|
||||
throw runtime_error(std::format("invalid index in label ({})", e.what()));
|
||||
}
|
||||
label->name.resize(at_offset);
|
||||
if (label->name == "start" && label->index != 0) {
|
||||
@@ -4168,9 +4172,9 @@ AssembledQuestScript assemble_quest_script(
|
||||
if (label->index >= 0) {
|
||||
auto index_emplace_ret = labels_by_index.emplace(label->index, label);
|
||||
if (label->index >= 0 && !index_emplace_ret.second) {
|
||||
throw runtime_error(phosg::string_printf(
|
||||
"duplicate label index: %zd (0x%zX) from %s and %s",
|
||||
label->index, label->index, label->name.c_str(), index_emplace_ret.first->second->name.c_str()));
|
||||
throw runtime_error(std::format(
|
||||
"duplicate label index: {} (0x{:X}) from {} and {}",
|
||||
label->index, label->index, label->name, index_emplace_ret.first->second->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4282,7 +4286,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
auto get_native_include = [&](const std::string& filename) -> std::string {
|
||||
for (const auto& include_dir : native_include_directories) {
|
||||
string path = include_dir + "/" + filename;
|
||||
if (phosg::isfile(path)) {
|
||||
if (std::filesystem::is_regular_file(path)) {
|
||||
return phosg::load_file(path);
|
||||
}
|
||||
}
|
||||
@@ -4298,7 +4302,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
return;
|
||||
}
|
||||
|
||||
if (phosg::ends_with(line.text, ":")) {
|
||||
if (line.text.ends_with(":")) {
|
||||
size_t at_offset = line.text.find('@');
|
||||
string label_name = line.text.substr(0, (at_offset == string::npos) ? (line.text.size() - 1) : at_offset);
|
||||
labels_by_name.at(label_name)->offset = code_w.size();
|
||||
@@ -4306,26 +4310,26 @@ AssembledQuestScript assemble_quest_script(
|
||||
}
|
||||
|
||||
if (line.text[0] == '.') {
|
||||
if (phosg::starts_with(line.text, ".data ")) {
|
||||
if (line.text.starts_with(".data ")) {
|
||||
code_w.write(phosg::parse_data_string(line.text.substr(6)));
|
||||
} else if (phosg::starts_with(line.text, ".zero ")) {
|
||||
} else if (line.text.starts_with(".zero ")) {
|
||||
size_t size = stoull(line.text.substr(6), nullptr, 0);
|
||||
code_w.extend_by(size, 0x00);
|
||||
} else if (phosg::starts_with(line.text, ".zero_until ")) {
|
||||
} else if (line.text.starts_with(".zero_until ")) {
|
||||
size_t size = stoull(line.text.substr(12), nullptr, 0);
|
||||
code_w.extend_to(size, 0x00);
|
||||
} else if (phosg::starts_with(line.text, ".align ")) {
|
||||
} else if (line.text.starts_with(".align ")) {
|
||||
size_t alignment = stoull(line.text.substr(7), nullptr, 0);
|
||||
while (code_w.size() % alignment) {
|
||||
code_w.put_u8(0);
|
||||
}
|
||||
} else if (phosg::starts_with(line.text, ".include ")) {
|
||||
} else if (line.text.starts_with(".include ")) {
|
||||
// This was already handled in a previous phase
|
||||
} else if (phosg::starts_with(line.text, ".include_bin ")) {
|
||||
} else if (line.text.starts_with(".include_bin ")) {
|
||||
string filename = line.text.substr(13);
|
||||
phosg::strip_whitespace(filename);
|
||||
code_w.write(get_native_include(filename));
|
||||
} else if (phosg::starts_with(line.text, ".include_native ")) {
|
||||
} else if (line.text.starts_with(".include_native ")) {
|
||||
string filename = line.text.substr(16);
|
||||
phosg::strip_whitespace(filename);
|
||||
string native_text = get_native_include(filename);
|
||||
@@ -4358,18 +4362,18 @@ AssembledQuestScript assemble_quest_script(
|
||||
|
||||
if (opcode_def->args.empty()) {
|
||||
if (line_tokens.size() > 1) {
|
||||
throw runtime_error(phosg::string_printf("arguments not allowed for %s", opcode_def->name));
|
||||
throw runtime_error(std::format("arguments not allowed for {}", opcode_def->name));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (line_tokens.size() < 2) {
|
||||
throw runtime_error(phosg::string_printf("arguments required for %s", opcode_def->name));
|
||||
throw runtime_error(std::format("arguments required for {}", opcode_def->name));
|
||||
}
|
||||
phosg::strip_trailing_whitespace(line_tokens[1]);
|
||||
phosg::strip_leading_whitespace(line_tokens[1]);
|
||||
|
||||
if (phosg::starts_with(line_tokens[1], "...")) {
|
||||
if (line_tokens[1].starts_with("...")) {
|
||||
if (!use_args) {
|
||||
throw runtime_error("\'...\' can only be used with F_ARGS opcodes");
|
||||
}
|
||||
@@ -4477,7 +4481,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
if (write_as_str) {
|
||||
if (arg[0] == '\"') {
|
||||
code_w.put_u8(0x4E); // arg_pushs
|
||||
if (phosg::starts_with(arg, "bin:")) {
|
||||
if (arg.starts_with("bin:")) {
|
||||
add_cstr(phosg::parse_data_string(arg.substr(4)), true);
|
||||
} else {
|
||||
add_cstr(phosg::parse_data_string(arg), false);
|
||||
@@ -4509,7 +4513,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
};
|
||||
|
||||
auto split_set = [&](const string& text) -> vector<string> {
|
||||
if (!phosg::starts_with(text, "[") || !phosg::ends_with(text, "]")) {
|
||||
if (!text.starts_with("[") || !text.ends_with("]")) {
|
||||
throw runtime_error("incorrect syntax for set-valued argument");
|
||||
}
|
||||
auto values = phosg::split(text.substr(1, text.size() - 2), ',');
|
||||
@@ -4555,19 +4559,19 @@ AssembledQuestScript assemble_quest_script(
|
||||
break;
|
||||
}
|
||||
case Type::INT8:
|
||||
code_w.put_u8(stol(arg, nullptr, 0));
|
||||
code_w.put_u8(stoll(arg, nullptr, 0));
|
||||
break;
|
||||
case Type::INT16:
|
||||
code_w.put_u16l(stol(arg, nullptr, 0));
|
||||
code_w.put_u16l(stoll(arg, nullptr, 0));
|
||||
break;
|
||||
case Type::INT32:
|
||||
code_w.put_u32l(stol(arg, nullptr, 0));
|
||||
code_w.put_u32l(stoll(arg, nullptr, 0));
|
||||
break;
|
||||
case Type::FLOAT32:
|
||||
code_w.put_u32l(stof(arg, nullptr));
|
||||
break;
|
||||
case Type::CSTRING:
|
||||
if (phosg::starts_with(arg, "bin:")) {
|
||||
if (arg.starts_with("bin:")) {
|
||||
add_cstr(phosg::parse_data_string(arg.substr(4)), true);
|
||||
} else {
|
||||
add_cstr(phosg::parse_data_string(arg), false);
|
||||
@@ -4578,7 +4582,7 @@ AssembledQuestScript assemble_quest_script(
|
||||
}
|
||||
}
|
||||
} catch (const exception& e) {
|
||||
throw runtime_error(phosg::string_printf("(arg %zu) %s", z + 1, e.what()));
|
||||
throw runtime_error(std::format("(arg {}) {}", z + 1, e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user