switch to coroutine execution model

This commit is contained in:
Martin Michelsen
2025-04-30 21:43:06 -07:00
parent f65b1f1c14
commit cc99050964
160 changed files with 269127 additions and 227736 deletions
+41 -39
View File
@@ -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
View File
@@ -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);
+409
View File
@@ -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;
+228
View File
@@ -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();
}
};
+160
View File
@@ -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;
}
+252
View File
@@ -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
View File
@@ -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());
-187
View File
@@ -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());
}
}
}
-54
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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, &timestamp, 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, &timestamp, 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
View File
@@ -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
View File
@@ -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);
+7 -8
View File
@@ -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
View File
@@ -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);
};
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
}
}
+36 -42
View File
@@ -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();
}
}
}
+6 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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);
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -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 {
+5 -5
View File
@@ -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));
}
}
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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
View File
@@ -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]);
}
+3 -3
View File
@@ -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;
+90 -90
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,6 +1,5 @@
#pragma once
#include <event2/event.h>
#include <stdint.h>
#include <memory>
-62
View File
@@ -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;
}
-45
View File
@@ -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
View File
@@ -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());
}
}
}
-3
View File
@@ -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.
+196
View File
@@ -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());
}
}
}
+48
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+26 -98
View File
@@ -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
View File
@@ -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);
}
}
-1
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+165 -125
View File
@@ -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
View File
@@ -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
View File
@@ -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}
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+276 -273
View File
File diff suppressed because it is too large Load Diff
+8 -11
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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));
}
+2 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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>();
}
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
}
-478
View File
@@ -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;
});
}
}
-128
View File
@@ -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
View File
@@ -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();
}
+6 -5
View File
@@ -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();
};
+5 -5
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+3 -12
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
-316
View File
@@ -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;
};
+88
View File
@@ -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);
}
+88
View File
@@ -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
View File
@@ -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
View File
@@ -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