diff --git a/src/ClientFunctionIndex.cc b/src/ClientFunctionIndex.cc index 27d4cc4c..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); @@ -444,6 +461,37 @@ ClientFunctionIndex::ClientFunctionIndex(const std::string& root_dir, bool raise } } } + + for (const char* probe_name : {"DragonVisualFix", "PsoPeepsDragonVisualFixPC", "RaresInQuests"}) { + 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); + 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( @@ -455,15 +503,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; } diff --git a/system/client-functions/DragonVisualFix.s b/system/client-functions/DragonVisualFix.s new file mode 100644 index 00000000..c56dddb4 --- /dev/null +++ b/system/client-functions/DragonVisualFix.s @@ -0,0 +1,24 @@ +.meta visibility="all" +.meta name="Dragon fix" +.meta description="Skips the bad Dragon\nBML selector path." + +entry_ptr: +reloc0: + .offsetof start +start: + .include WriteCodeBlocks + + + + .versions 2OJW + + .data 0x00420326 + .data 2 + .data 0x14EB + + + + .all_versions + + .data 0x00000000 + .data 0x00000000 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