add xbox patch support

This commit is contained in:
Martin Michelsen
2024-01-21 21:56:48 -08:00
parent db3cecdd2b
commit 80a57f9d3e
147 changed files with 584 additions and 198 deletions
+4 -2
View File
@@ -555,13 +555,15 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
};
auto send_version_detect_or_send_call = [args, ses, send_call]() {
if (is_gc(ses->version()) &&
bool is_gc = ::is_gc(ses->version());
bool is_xb = (ses->version() == Version::XB_V3);
if ((is_gc || is_xb) &&
ses->config.specific_version == default_specific_version_for_version(ses->version(), -1)) {
auto s = ses->require_server_state();
send_function_call(
ses->client_channel,
ses->config,
s->function_code_index->name_to_function.at("VersionDetect"));
s->function_code_index->name_to_function.at(is_xb ? "VersionDetectXB" : "VersionDetectGC"));
ses->function_call_return_handler_queue.emplace_back(send_call);
} else {
send_call(ses->config.specific_version, 0);
+2 -1
View File
@@ -2177,7 +2177,8 @@ struct S_ServerTime_B1 {
// newserv supports exploiting a bug in the USA version of Episode 3, which
// re-enables the use of this command on that version of the game. See
// system/ppc/Episode3USAQuestBufferOverflow.s for further details.
// system/client-functions/Episode3USAQuestBufferOverflow.ppc.s for further
// details.
struct S_ExecuteCode_B2 {
// If code_size == 0, no code is executed, but checksumming may still occur.
+117 -43
View File
@@ -10,6 +10,7 @@
#ifdef HAVE_RESOURCE_FILE
#include <resource_file/Emulators/PPC32Emulator.hh>
#include <resource_file/Emulators/X86Emulator.hh>
#endif
#include "CommandFormats.hh"
@@ -133,28 +134,74 @@ shared_ptr<CompiledFunctionCode> compile_function_code(
ret->index = 0;
ret->hide_from_patches_menu = false;
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
auto assembled = PPC32Emulator::assemble(text, {directory});
ret->code = std::move(assembled.code);
ret->label_offsets = std::move(assembled.label_offsets);
for (const auto& it : assembled.metadata_keys) {
if (it.first == "hide_from_patches_menu") {
ret->hide_from_patches_menu = true;
} else if (it.first == "index") {
if (it.second.size() != 1) {
throw runtime_error("invalid index value in .meta directive");
}
ret->index = it.second[0];
} else if (it.first == "name") {
ret->long_name = it.second;
} else if (it.first == "description") {
ret->description = it.second;
} else {
throw runtime_error("unknown metadata key: " + it.first);
}
unordered_set<string> get_include_stack;
function<string(const string&)> get_include = [&](const string& name) -> string {
const char* arch_name_token;
switch (arch) {
case CompiledFunctionCode::Architecture::POWERPC:
arch_name_token = "ppc";
break;
case CompiledFunctionCode::Architecture::X86:
arch_name_token = "x86";
break;
case CompiledFunctionCode::Architecture::SH4:
arch_name_token = "sh4";
break;
default:
throw runtime_error("unknown architecture");
}
string asm_filename = string_printf("%s/%s.%s.inc.s", directory.c_str(), name.c_str(), arch_name_token);
if (isfile(asm_filename)) {
if (!get_include_stack.emplace(name).second) {
throw runtime_error("mutual recursion between includes: " + name);
}
EmulatorBase::AssembleResult ret;
switch (arch) {
case CompiledFunctionCode::Architecture::POWERPC:
ret = PPC32Emulator::assemble(load_file(asm_filename), get_include);
break;
case CompiledFunctionCode::Architecture::X86:
ret = X86Emulator::assemble(load_file(asm_filename), get_include);
break;
case CompiledFunctionCode::Architecture::SH4:
throw runtime_error("cannot compuile SH-4 assembly");
default:
throw runtime_error("unknown architecture");
}
get_include_stack.erase(name);
return ret.code;
}
string bin_filename = directory + "/" + name + ".inc.bin";
if (isfile(bin_filename)) {
return load_file(bin_filename);
}
throw runtime_error("data not found for include: " + name + " (from " + asm_filename + " or " + bin_filename + ")");
};
EmulatorBase::AssembleResult assembled;
if (arch == CompiledFunctionCode::Architecture::POWERPC) {
assembled = PPC32Emulator::assemble(text, get_include);
} else if (arch == CompiledFunctionCode::Architecture::X86) {
throw runtime_error("x86 assembler is not implemented");
assembled = X86Emulator::assemble(text, get_include);
}
ret->code = std::move(assembled.code);
ret->label_offsets = std::move(assembled.label_offsets);
for (const auto& it : assembled.metadata_keys) {
if (it.first == "hide_from_patches_menu") {
ret->hide_from_patches_menu = true;
} else if (it.first == "index") {
if (it.second.size() != 1) {
throw runtime_error("invalid index value in .meta directive");
}
ret->index = it.second[0];
} else if (it.first == "name") {
ret->long_name = it.second;
} else if (it.first == "description") {
ret->description = it.second;
} else {
throw runtime_error("unknown metadata key: " + it.first);
}
}
set<uint32_t> reloc_indexes;
@@ -191,31 +238,57 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
}
uint32_t next_menu_item_id = 0;
for (const auto& filename : list_directory(directory)) {
if (!ends_with(filename, ".s") || ends_with(filename, ".inc.s")) {
continue;
}
bool is_patch = ends_with(filename, ".patch.s");
string name = filename.substr(0, filename.size() - (is_patch ? 8 : 2));
// Check for specific_version token
uint32_t specific_version = 0;
string short_name = name;
if (is_patch &&
(filename.size() >= 13) &&
(filename[filename.size() - 13] == '.') &&
isdigit(filename[filename.size() - 12]) &&
(filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') &&
(filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') &&
(isdigit(filename[filename.size() - 9]) || filename[filename.size() - 9] == 'T')) {
specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9];
short_name = filename.substr(0, filename.size() - 13);
}
for (const auto& filename : list_directory_sorted(directory)) {
try {
if (!ends_with(filename, ".s")) {
continue;
}
string name = filename.substr(0, filename.size() - 2);
if (ends_with(name, ".inc")) {
continue;
}
bool is_patch = ends_with(name, ".patch");
if (is_patch) {
name.resize(name.size() - 6);
}
// Figure out the version or specific_version
CompiledFunctionCode::Architecture arch = CompiledFunctionCode::Architecture::UNKNOWN;
uint32_t specific_version = 0;
string short_name = name;
if (ends_with(name, ".ppc")) {
arch = CompiledFunctionCode::Architecture::POWERPC;
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".x86")) {
arch = CompiledFunctionCode::Architecture::X86;
name.resize(name.size() - 4);
short_name = name;
} else if (ends_with(name, ".sh4")) {
arch = CompiledFunctionCode::Architecture::SH4;
name.resize(name.size() - 4);
short_name = name;
} else if (is_patch && (name.size() >= 5) && (name[name.size() - 5] == '.')) {
specific_version = (name[name.size() - 4] << 24) | (name[name.size() - 3] << 16) | (name[name.size() - 2] << 8) | name[name.size() - 1];
if (specific_version_is_gc(specific_version)) {
arch = CompiledFunctionCode::Architecture::POWERPC;
} else if (specific_version_is_xb(specific_version) || specific_version_is_bb(specific_version)) {
arch = CompiledFunctionCode::Architecture::X86;
} else {
throw runtime_error("unable to determine architecture from specific_version");
}
short_name = name.substr(0, name.size() - 5);
}
if (arch == CompiledFunctionCode::Architecture::UNKNOWN) {
throw runtime_error("unable to determine architecture");
}
string path = directory + "/" + filename;
string text = load_file(path);
auto code = compile_function_code(CompiledFunctionCode::Architecture::POWERPC, directory, name, text);
auto code = compile_function_code(arch, directory, name, text);
if (code->index != 0) {
if (!this->index_to_function.emplace(code->index, code).second) {
throw runtime_error(string_printf(
@@ -240,7 +313,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) {
index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch));
} catch (const exception& e) {
function_compiler_log.warning("Failed to compile function %s: %s", name.c_str(), e.what());
function_compiler_log.warning("Failed to compile function %s: %s", filename.c_str(), e.what());
}
}
}
@@ -252,6 +325,7 @@ shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version)
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;
fprintf(stderr, "patch: [%s] vs [%s]\n", it.first.c_str(), suffix.c_str());
if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) {
ret->items.emplace_back(
fn->menu_item_id,
+2 -1
View File
@@ -18,7 +18,8 @@ void set_function_compiler_available(bool is_available);
struct CompiledFunctionCode {
enum class Architecture {
POWERPC = 0, // GC
UNKNOWN = 0,
POWERPC, // GC
X86, // PC, XB, BB
SH4, // Dreamcast
};
+9 -5
View File
@@ -1324,10 +1324,10 @@ Action a_assemble_quest_script(
Action a_assemble_all_patches(
"assemble-all-patches", "\
assemble-all-patches\n\
Assemble all patches in the system/ppc directory, and produce two compiled\n\
.bin files for each patch (one unencrypted, for most PSO versions, and one\n\
encrypted, for PSO JP v1.04, JP Ep3, and Ep3 Trial Edition). The output\n\
files are written to the system/ppc directory.\n",
Assemble all patches in the system/client-functions directory, and produce\n\
two compiled .bin files for each patch (one unencrypted, for most PSO\n\
versions, and one encrypted, for PSO GC JP v1.04, JP Ep3, and Ep3 Trial\n\
Edition). The output files are saved in system/client-functions.\n",
+[](Arguments&) {
ServerState s;
s.load_objects_and_upstream_dependents("functions");
@@ -1351,7 +1351,11 @@ Action a_assemble_all_patches(
process_code(it.second, 0, 0, 0);
}
try {
process_code(s.function_code_index->name_to_function.at("VersionDetect"), 0, 0, 0);
process_code(s.function_code_index->name_to_function.at("VersionDetectGC"), 0, 0, 0);
} catch (const out_of_range&) {
}
try {
process_code(s.function_code_index->name_to_function.at("VersionDetectXB"), 0, 0, 0);
} catch (const out_of_range&) {
}
try {
+5 -5
View File
@@ -240,7 +240,7 @@ static void send_main_menu(shared_ptr<Client> c) {
if (!s->is_replay) {
if (!s->function_code_index->patch_menu_empty(c->config.specific_version)) {
main_menu->items.emplace_back(MainMenuItemID::PATCHES, "Patches",
"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
"Change game\nbehaviors", MenuItem::Flag::INVISIBLE_ON_DC | MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
if (!s->dol_file_index->empty()) {
main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, "Programs",
@@ -877,10 +877,10 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
c->channel.language = base_cmd->language;
set_console_client_flags(c, base_cmd->sub_version);
// See system/ppc/Episode3USAQuestBufferOverflow.s for where this value gets
// set. We use this to determine if the client has already run the code or
// not; sending it again when the client has already run it will likely cause
// the client to crash.
// See system/client-functions/Episode3USAQuestBufferOverflow.ppc.s for where
// this value gets set. We use this to determine if the client has already run
// the code or not; sending it again when the client has already run it will
// likely cause the client to crash.
if (base_cmd->unused1 == 0x5F5CA297) {
c->config.clear_flag(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL);
c->config.clear_flag(Client::Flag::NO_SEND_FUNCTION_CALL);
+5 -3
View File
@@ -322,7 +322,7 @@ void send_update_client_config(shared_ptr<Client> c, bool always_send) {
void send_quest_buffer_overflow(shared_ptr<Client> c) {
// PSO Episode 3 USA doesn't natively support the B2 command, but we can add
// it back to the game with some tricky commands. For details on how this
// works, see system/ppc/Episode3USAQuestBufferOverflow.s.
// works, see system/client-functions/Episode3USAQuestBufferOverflow.ppc.s.
auto fn = c->require_server_state()->function_code_index->name_to_function.at("Episode3USAQuestBufferOverflow");
if (fn->code.size() > 0x400) {
throw runtime_error("Episode 3 buffer overflow code must be a single segment");
@@ -355,9 +355,11 @@ void prepare_client_for_patches(shared_ptr<Client> c, function<void()> on_comple
if (!c) {
return;
}
if (is_gc(c->version()) &&
bool is_gc = ::is_gc(c->version());
bool is_xb = (c->version() == Version::XB_V3);
if ((is_gc || is_xb) &&
c->config.specific_version == default_specific_version_for_version(c->version(), -1)) {
send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect"));
send_function_call(c, s->function_code_index->name_to_function.at(is_xb ? "VersionDetectXB" : "VersionDetectGC"));
c->function_call_response_queue.emplace_back([wc = weak_ptr<Client>(c), on_complete](uint32_t specific_version, uint32_t) -> void {
auto c = wc.lock();
if (!c) {
+1 -1
View File
@@ -1427,7 +1427,7 @@ void ServerState::load_quest_index() {
void ServerState::compile_functions() {
config_log.info("Compiling client functions");
this->function_code_index = make_shared<FunctionCodeIndex>("system/ppc");
this->function_code_index = make_shared<FunctionCodeIndex>("system/client-functions");
}
void ServerState::load_dol_files() {
+42 -1
View File
@@ -204,7 +204,7 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
// to set the specific_version based on sub_version. Fortunately, all
// versions that share sub_version values also support send_function_call,
// so for those versions we get the specific_version later by sending the
// VersionDetect call.
// VersionDetectGC or VersionDetectXB call.
switch (version) {
case Version::GC_NTE:
return 0x334F4A54; // 3OJT
@@ -242,11 +242,52 @@ uint32_t default_specific_version_for_version(Version version, int64_t sub_versi
default:
return 0x33000000;
}
case Version::XB_V3:
return 0x344F0000;
case Version::BB_V4:
return 0x354F3030;
default:
return 0x00000000;
}
}
bool specific_version_is_gc(uint32_t specific_version) {
// GC specific_versions are 3GRV, where G is [OE], R is [JEP], V is [0-9T]
if ((specific_version & 0xFF000000) != 0x33000000) {
return false;
}
char game = specific_version >> 16;
if ((game != 'O') && (game != 'S')) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return (isdigit(revision) || (revision == 'T'));
}
bool specific_version_is_xb(uint32_t specific_version) {
// XB specific_versions are 4ORV, where R is [JEP], V is [BDU]
if ((specific_version & 0xFFFF0000) != 0x344F0000) {
return false;
}
char region = specific_version >> 8;
if ((region != 'J') && (region != 'E') && (region != 'P')) {
return false;
}
char revision = specific_version;
return ((revision == 'B') || (revision == 'D') || (revision == 'U'));
}
bool specific_version_is_bb(uint32_t specific_version) {
// TODO: We should actually find a way to determine BB specific_versions, but
// there are so many mods out there, and there's a patch server anyway, so it
// seems not worth the effort
return specific_version == 0x35303030;
}
const char* file_path_token_for_version(Version version) {
switch (version) {
case Version::PC_PATCH:
+3
View File
@@ -139,6 +139,9 @@ inline bool uses_utf16(Version version) {
}
uint32_t default_specific_version_for_version(Version version, int64_t sub_version);
bool specific_version_is_gc(uint32_t specific_version);
bool specific_version_is_xb(uint32_t specific_version);
bool specific_version_is_bb(uint32_t specific_version);
enum class ServerBehavior {
PC_CONSOLE_DETECT = 0,