add xbox patch support
This commit is contained in:
+4
-2
@@ -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);
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user