From 5d58c2467c56b808359a66d4fea0fce81e9449c9 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 05:56:52 -0400 Subject: [PATCH 01/13] Add PC WriteCodeBlocks no-op test --- .../PsoPeepsPCWriteCodeNoopTest.s | 25 +++++++++++++++++++ system/client-functions/RaresInQuests.s | 13 ++-------- 2 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 system/client-functions/PsoPeepsPCWriteCodeNoopTest.s diff --git a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s new file mode 100644 index 00000000..98848e22 --- /dev/null +++ b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s @@ -0,0 +1,25 @@ +.meta visibility="all" +.meta name="PC write noop" +.meta description="PC-only test.\nWrites NOPs over\nexisting NOPs." + +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocks + + + + .versions 2OJW + + .data 0x004E03DE + .data 2 + nop + nop + + + + .all_versions + + .data 0x00000000 + .data 0x00000000 diff --git a/system/client-functions/RaresInQuests.s b/system/client-functions/RaresInQuests.s index 4550a098..a16d7fd8 100644 --- a/system/client-functions/RaresInQuests.s +++ b/system/client-functions/RaresInQuests.s @@ -43,17 +43,8 @@ start: - .versions 2OJW - - .data 0x004DFC9A - .data 2 - nop - nop - - .data 0x004E03F4 - .data 2 - nop - nop + # PSO Peeps: PC 2OJW temporarily disabled while testing PC WriteCodeBlocks. + # The disk-patched bytes work, but runtime menu delivery is crashing. From 41f05b1fe507d42c973ba89976127cf66873038f Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:01:21 -0400 Subject: [PATCH 02/13] Test PC WriteCodeBlocks against writable data --- system/client-functions/PsoPeepsPCWriteCodeNoopTest.s | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s index 98848e22..fd75c601 100644 --- a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s +++ b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s @@ -1,6 +1,6 @@ .meta visibility="all" -.meta name="PC write noop" -.meta description="PC-only test.\nWrites NOPs over\nexisting NOPs." +.meta name="PC data noop" +.meta description="PC-only test.\nWrites zeros over\nwritable data." entry_ptr: reloc0: @@ -12,10 +12,9 @@ start: .versions 2OJW - .data 0x004E03DE + .data 0x0068854E .data 2 - nop - nop + add [eax], al From b9a621e7ccb26e9b9101e6d1dfc1af38c3fab0ae Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:12:32 -0400 Subject: [PATCH 03/13] Use VirtualProtect for PC WriteCodeBlocks --- .../PsoPeepsPCWriteCodeNoopTest.s | 9 +- .../System/WriteCodeBlocks.inc.s | 104 +++++++++++++++++- 2 files changed, 108 insertions(+), 5 deletions(-) diff --git a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s index fd75c601..3161d9a5 100644 --- a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s +++ b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s @@ -1,6 +1,6 @@ .meta visibility="all" -.meta name="PC data noop" -.meta description="PC-only test.\nWrites zeros over\nwritable data." +.meta name="PC text noop" +.meta description="PC-only test.\nWrites NOPs over\nexisting code NOPs." entry_ptr: reloc0: @@ -12,9 +12,10 @@ start: .versions 2OJW - .data 0x0068854E + .data 0x004E03DE .data 2 - add [eax], al + nop + nop diff --git a/system/client-functions/System/WriteCodeBlocks.inc.s b/system/client-functions/System/WriteCodeBlocks.inc.s index 52e1fd14..69bed3c2 100644 --- a/system/client-functions/System/WriteCodeBlocks.inc.s +++ b/system/client-functions/System/WriteCodeBlocks.inc.s @@ -118,7 +118,109 @@ first_patch_header: -.versions 2OJW 2OJZ 50YJ 59NJ 59NL +.versions 2OJW + +start: + push ebx + push esi + push edi + sub esp, 8 # [esp] = oldProtect, [esp + 4] = tempProtect + jmp get_patch_data_ptr +get_patch_data_ptr_ret: + pop ebx # ebx = patch header + + # Resolve kernel32!VirtualProtect using pso.exe's 2OJW IAT: + # GetModuleHandleA = 0x0064D114 + # GetProcAddress = 0x0064D128 + # + # Build "KERNEL32.dll\0" on stack. + sub esp, 16 + mov dword [esp], 0x4E52454B # "KERN" + mov dword [esp + 4], 0x32334C45 # "EL32" + mov dword [esp + 8], 0x6C6C642E # ".dll" + mov dword [esp + 12], 0 + push esp + call [0x0064D114] # GetModuleHandleA + add esp, 16 + test eax, eax + jz fail + + mov esi, eax # esi = kernel32 module handle + + # Build "VirtualProtect\0" on stack. + sub esp, 16 + mov dword [esp], 0x74726956 # "Virt" + mov dword [esp + 4], 0x506C6175 # "ualP" + mov dword [esp + 8], 0x65746F72 # "rote" + mov dword [esp + 12], 0x00007463 # "ct\0\0" + push esp + push esi + call [0x0064D128] # GetProcAddress + add esp, 16 + test eax, eax + jz fail + + mov edi, eax # edi = VirtualProtect + +apply_next_patch: + cmp dword [ebx + 4], 0 + jne copy_code_and_apply_again + + add esp, 8 + pop edi + pop esi + pop ebx + mov eax, 1 + ret + +copy_code_and_apply_again: + # VirtualProtect(dest, size, PAGE_EXECUTE_READWRITE, &oldProtect) + lea eax, [esp] + push eax + push 0x40 + push dword [ebx + 4] + push dword [ebx] + call edi + test eax, eax + jz fail + + xor ecx, ecx # ecx = offset + mov edx, [ebx] # edx = dest addr +copy_next_byte: + mov al, [ebx + ecx + 8] # copy one byte to dest + mov [edx + ecx], al + inc ecx # offset++ + cmp [ebx + 4], ecx # check if all bytes have been copied + jne copy_next_byte + + # VirtualProtect(dest, size, oldProtect, &tempProtect) + mov esi, [ebx + 4] # esi = size; VirtualProtect preserves esi + mov edx, [esp] # edx = oldProtect + lea eax, [esp + 4] + push eax + push edx + push esi + push dword [ebx] + call edi + + lea ebx, [ebx + esi + 8] # advance to next block + jmp apply_next_patch + +fail: + add esp, 8 + pop edi + pop esi + pop ebx + xor eax, eax + ret + +get_patch_data_ptr: + call get_patch_data_ptr_ret +first_patch_header: + + + +.versions 2OJZ 50YJ 59NJ 59NL start: push ebx From c1a5063ba8b991d9f53627e8c201f7c367624daa Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:18:51 -0400 Subject: [PATCH 04/13] Use VirtualProtect for PC WriteCodeBlocks --- .../PsoPeepsPCWriteCodeNoopTest.s | 25 ------------------- system/client-functions/RaresInQuests.s | 13 ++++++++-- 2 files changed, 11 insertions(+), 27 deletions(-) delete mode 100644 system/client-functions/PsoPeepsPCWriteCodeNoopTest.s diff --git a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s b/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s deleted file mode 100644 index 3161d9a5..00000000 --- a/system/client-functions/PsoPeepsPCWriteCodeNoopTest.s +++ /dev/null @@ -1,25 +0,0 @@ -.meta visibility="all" -.meta name="PC text noop" -.meta description="PC-only test.\nWrites NOPs over\nexisting code NOPs." - -entry_ptr: -reloc0: - .offsetof start -start: - .include WriteCodeBlocks - - - - .versions 2OJW - - .data 0x004E03DE - .data 2 - nop - nop - - - - .all_versions - - .data 0x00000000 - .data 0x00000000 diff --git a/system/client-functions/RaresInQuests.s b/system/client-functions/RaresInQuests.s index a16d7fd8..4550a098 100644 --- a/system/client-functions/RaresInQuests.s +++ b/system/client-functions/RaresInQuests.s @@ -43,8 +43,17 @@ start: - # PSO Peeps: PC 2OJW temporarily disabled while testing PC WriteCodeBlocks. - # The disk-patched bytes work, but runtime menu delivery is crashing. + .versions 2OJW + + .data 0x004DFC9A + .data 2 + nop + nop + + .data 0x004E03F4 + .data 2 + nop + nop From e8b80a3ede21d0f07831ca62013e56ec001ca1e1 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:33:21 -0400 Subject: [PATCH 05/13] Add PC Dragon Visual Fix patch --- .../PsoPeepsDragonVisualFixPC.s | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 system/client-functions/PsoPeepsDragonVisualFixPC.s diff --git a/system/client-functions/PsoPeepsDragonVisualFixPC.s b/system/client-functions/PsoPeepsDragonVisualFixPC.s new file mode 100644 index 00000000..f0c88cd1 --- /dev/null +++ b/system/client-functions/PsoPeepsDragonVisualFixPC.s @@ -0,0 +1,24 @@ +.meta visibility="all" +.meta name="Dragon Visual Fix" +.meta description="PC-only fix.\nSkips the bad Dragon\nBML selector path." + +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocks + + + + .versions 2OJW + + .data 0x00420326 + .data 2 + jmp +0x14 + + + + .all_versions + + .data 0x00000000 + .data 0x00000000 From 3e527bf979dbcd85341f9a4182bf7ad116d51b58 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:47:55 -0400 Subject: [PATCH 06/13] Encode PC Dragon Visual Fix bytes directly --- system/client-functions/PsoPeepsDragonVisualFixPC.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/client-functions/PsoPeepsDragonVisualFixPC.s b/system/client-functions/PsoPeepsDragonVisualFixPC.s index f0c88cd1..a84e8953 100644 --- a/system/client-functions/PsoPeepsDragonVisualFixPC.s +++ b/system/client-functions/PsoPeepsDragonVisualFixPC.s @@ -14,7 +14,7 @@ .data 0x00420326 .data 2 - jmp +0x14 + .data 0x14EB From ea2d87cacb8e3ff676b309c5ebc0cc6660bac1f3 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:52:43 -0400 Subject: [PATCH 07/13] Rename Dragon Visual Fix patch --- .../{PsoPeepsDragonVisualFixPC.s => DragonVisualFix.s} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename system/client-functions/{PsoPeepsDragonVisualFixPC.s => DragonVisualFix.s} (100%) diff --git a/system/client-functions/PsoPeepsDragonVisualFixPC.s b/system/client-functions/DragonVisualFix.s similarity index 100% rename from system/client-functions/PsoPeepsDragonVisualFixPC.s rename to system/client-functions/DragonVisualFix.s From 943bb20cec3bffaa6a3b446305d648f0b3d073da Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 06:54:41 -0400 Subject: [PATCH 08/13] Shorten Dragon Visual Fix menu label --- system/client-functions/DragonVisualFix.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/client-functions/DragonVisualFix.s b/system/client-functions/DragonVisualFix.s index a84e8953..70e2ca55 100644 --- a/system/client-functions/DragonVisualFix.s +++ b/system/client-functions/DragonVisualFix.s @@ -1,6 +1,6 @@ .meta visibility="all" .meta name="Dragon Visual Fix" -.meta description="PC-only fix.\nSkips the bad Dragon\nBML selector path." +.meta description="Skips the bad Dragon\nBML selector path." entry_ptr: reloc0: From eb7457a436411be4676f48103e7e862c5c3dc421 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 11:49:46 -0400 Subject: [PATCH 09/13] Log PC patch menu function filtering --- src/ClientFunctionIndex.cc | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ClientFunctionIndex.cc b/src/ClientFunctionIndex.cc index 27d4cc4c..273e2f17 100644 --- a/src/ClientFunctionIndex.cc +++ b/src/ClientFunctionIndex.cc @@ -455,15 +455,44 @@ std::shared_ptr ClientFunctionIndex::patch_switches_menu( auto map_it = this->functions_by_specific_version.find(specific_version); if (map_it != this->functions_by_specific_version.end()) { + client_functions_log.warning_f( + "Patch menu debug: building menu for specific_version={} with {} function entries", + str_for_specific_version(specific_version), + map_it->second.size()); + for (auto [name, fn] : map_it->second) { - if (fn->appears_in_patches_menu() && !server_auto_patches_enabled.count(fn->short_name)) { + bool appears = fn->appears_in_patches_menu(); + bool server_auto = server_auto_patches_enabled.count(fn->short_name); + bool client_enabled = client_auto_patches_enabled.count(fn->short_name); + bool dragon_debug = + (fn->short_name.find("Dragon") != std::string::npos) || + (fn->long_name.find("Dragon") != std::string::npos); + + if (dragon_debug || appears) { + client_functions_log.warning_f( + "Patch menu debug: key={} short={} long={} visibility={} appears={} server_auto={} client_enabled={} menu_item_id={:08X}", + name, + fn->short_name, + fn->long_name, + phosg::name_for_enum(fn->visibility), + appears, + server_auto, + client_enabled, + static_cast(fn->menu_item_id)); + } + + if (appears && !server_auto) { std::string item_text; - item_text.push_back(client_auto_patches_enabled.count(fn->short_name) ? '*' : '-'); + item_text.push_back(client_enabled ? '*' : '-'); item_text += fn->long_name.empty() ? fn->short_name : fn->long_name; ret->items.emplace_back( fn->menu_item_id, item_text, fn->description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL_RUNS_CODE); } } + } else { + client_functions_log.warning_f( + "Patch menu debug: no functions for specific_version={}", + str_for_specific_version(specific_version)); } return ret; } From 2c66407e8b067dc1439ecdbf5b925de33ecdd805 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 11:58:44 -0400 Subject: [PATCH 10/13] Probe Dragon client function indexing --- src/ClientFunctionIndex.cc | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/ClientFunctionIndex.cc b/src/ClientFunctionIndex.cc index 273e2f17..28f67835 100644 --- a/src/ClientFunctionIndex.cc +++ b/src/ClientFunctionIndex.cc @@ -444,6 +444,37 @@ ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise } } } + + for (const char* probe_name : {"DragonVisualFix", "PsoPeepsDragonVisualFixPC", "RaresInQuests"}) { + for (uint32_t probe_sv : {SPECIFIC_VERSION_PC_V2_JP, SPECIFIC_VERSION_X86_INDETERMINATE}) { + std::string key = cache_key(probe_name, probe_sv); + auto all_it = this->all_functions.find(key); + auto map_it = this->functions_by_specific_version.find(probe_sv); + bool in_version_map = false; + if (map_it != this->functions_by_specific_version.end()) { + in_version_map = map_it->second.count(key); + } + client_functions_log.warning_f( + "Client function probe: name={} sv={} key={} all_functions={} version_map={} map_size={}", + probe_name, + str_for_specific_version(probe_sv), + key, + all_it != this->all_functions.end(), + in_version_map, + map_it == this->functions_by_specific_version.end() ? 0 : map_it->second.size()); + if (all_it != this->all_functions.end()) { + const auto& fn = all_it->second; + client_functions_log.warning_f( + "Client function probe detail: short={} long={} visibility={} specific_version={} arch={} menu_item_id={:08X}", + fn->short_name, + fn->long_name, + phosg::name_for_enum(fn->visibility), + str_for_specific_version(fn->specific_version), + name_for_architecture(fn->arch), + static_cast(fn->menu_item_id)); + } + } + } } std::shared_ptr ClientFunctionIndex::patch_switches_menu( From 52087e50a398d9d616757f9dc3848e3408adf0dd Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 12:13:26 -0400 Subject: [PATCH 11/13] Fix PC client function probe version type --- src/ClientFunctionIndex.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ClientFunctionIndex.cc b/src/ClientFunctionIndex.cc index 28f67835..f7442c73 100644 --- a/src/ClientFunctionIndex.cc +++ b/src/ClientFunctionIndex.cc @@ -446,7 +446,7 @@ ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise } for (const char* probe_name : {"DragonVisualFix", "PsoPeepsDragonVisualFixPC", "RaresInQuests"}) { - for (uint32_t probe_sv : {SPECIFIC_VERSION_PC_V2_JP, SPECIFIC_VERSION_X86_INDETERMINATE}) { + for (uint32_t probe_sv : {0x324F4A57u, SPECIFIC_VERSION_X86_INDETERMINATE}) { std::string key = cache_key(probe_name, probe_sv); auto all_it = this->all_functions.find(key); auto map_it = this->functions_by_specific_version.find(probe_sv); From 0bf07a882c6d4ebde53cab5b13d69ac850cbd072 Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 12:18:15 -0400 Subject: [PATCH 12/13] Log Dragon client function loading --- src/ClientFunctionIndex.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ClientFunctionIndex.cc b/src/ClientFunctionIndex.cc index f7442c73..879e1b56 100644 --- a/src/ClientFunctionIndex.cc +++ b/src/ClientFunctionIndex.cc @@ -251,6 +251,9 @@ ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise add_directory(item_path); } else if (item_path.ends_with(".s") && std::filesystem::is_regular_file(item_path)) { client_functions_log.debug_f("Adding {} from {}", item_name, item_path); + if (item_name.find("Dragon") != std::string::npos) { + client_functions_log.warning_f("Dragon source load debug: adding {} from {}", item_name, item_path); + } if (!source_files.emplace(item_name, phosg::load_file(item_path)).second) { throw std::runtime_error(std::format("Duplicate source filename: {}", item_name)); } @@ -285,6 +288,20 @@ ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise throw std::runtime_error(std::format("({} preprocessing) {}", source_filename, e.what())); } + if (source_filename.find("Dragon") != std::string::npos) { + client_functions_log.warning_f( + "Dragon preprocess debug: source={} produced {} version chunk(s)", + source_filename, + preprocessed.size()); + for (const auto& [debug_sv, debug_source] : preprocessed) { + client_functions_log.warning_f( + "Dragon preprocess debug: source={} sv={} chunk_size={}", + source_filename, + str_for_specific_version(debug_sv), + debug_source.size()); + } + } + for (const auto& [specific_version, source] : preprocessed) { std::shared_ptr fn = std::make_shared(); fn->short_name = source_filename.substr(0, source_filename.size() - 2); From 1f9ef0c3b6c999a6421c7d8d5a2c3c687d7f9d6b Mon Sep 17 00:00:00 2001 From: James Osborne Date: Sun, 7 Jun 2026 12:24:05 -0400 Subject: [PATCH 13/13] Fix Dragon Visual Fix source encoding --- system/client-functions/DragonVisualFix.s | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/system/client-functions/DragonVisualFix.s b/system/client-functions/DragonVisualFix.s index 70e2ca55..c56dddb4 100644 --- a/system/client-functions/DragonVisualFix.s +++ b/system/client-functions/DragonVisualFix.s @@ -1,24 +1,24 @@ -.meta visibility="all" -.meta name="Dragon Visual Fix" -.meta description="Skips the bad Dragon\nBML selector path." +.meta visibility="all" +.meta name="Dragon fix" +.meta description="Skips the bad Dragon\nBML selector path." -entry_ptr: -reloc0: - .offsetof start -start: - .include WriteCodeBlocks +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocks - .versions 2OJW + .versions 2OJW - .data 0x00420326 - .data 2 + .data 0x00420326 + .data 2 .data 0x14EB - .all_versions + .all_versions - .data 0x00000000 - .data 0x00000000 + .data 0x00000000 + .data 0x00000000