diff --git a/CMakeLists.txt b/CMakeLists.txt index de8d7a6..38a7829 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -615,7 +615,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} PREFIX "Source Files" FILES ${SOUR set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/Win32D.rc) add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES}) target_link_libraries(spicetools_spice - PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard + PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api lua_static imm32 dwmapi CpuFeatures::cpu_features smx) set_target_properties(spicetools_spice PROPERTIES PREFIX "") set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice") @@ -630,7 +630,7 @@ endif() set(RESOURCE_FILES build/manifest.manifest build/manifest64.rc build/icon.rc cfg/Win32D.rc) add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES}) target_link_libraries(spicetools_spice64 - PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard + PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api64 lua_static imm32 dwmapi CpuFeatures::cpu_features smx) set_target_properties(spicetools_spice64 PROPERTIES PREFIX "") set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64") @@ -648,7 +648,7 @@ set(SOURCE_FILES ${SOURCE_FILES} launcher/options.h launcher/options.cpp) set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.rc) add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES}) target_link_libraries(spicetools_cfg - PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard + PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winscard winhttp PRIVATE fmt-header-only discord-rpc imgui hash-library minhook openvr_api lua_static imm32 dwmapi CpuFeatures::cpu_features smx) set_target_properties(spicetools_cfg PROPERTIES PREFIX "") set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg") diff --git a/overlay/windows/patch_manager.cpp b/overlay/windows/patch_manager.cpp index c0b0900..3305886 100644 --- a/overlay/windows/patch_manager.cpp +++ b/overlay/windows/patch_manager.cpp @@ -1,8 +1,11 @@ #include "patch_manager.h" #include +#include #include +#include #include +#include #include "external/rapidjson/document.h" #include "external/rapidjson/writer.h" #include "external/hash-library/sha256.h" @@ -36,6 +39,7 @@ using namespace rapidjson; namespace overlay::windows { robin_hood::unordered_map>> DLL_MAP; + robin_hood::unordered_map>> DLL_MAP_ORG; // configuration std::string PatchManager::config_path; @@ -43,6 +47,90 @@ namespace overlay::windows { bool PatchManager::setting_auto_apply = false; std::vector PatchManager::setting_auto_apply_list; std::vector PatchManager::setting_patches_enabled; + std::map PatchManager::setting_union_patches_enabled; + char PatchManager::patch_url[1024]; + std::filesystem::path LOCAL_PATCHES_PATH("patches"); + std::filesystem::path LOCAL_PATCHES_JSON_PATH("patches/patches.json"); + + // utility + std::string getFromUrl(const std::string& url) { + log_info("patchmanager", "Fetch from: {}", url); + std::string result; + + auto components = URL_COMPONENTS {}; + components.dwStructSize = sizeof(components); + components.dwHostNameLength = -1; + components.dwUrlPathLength = -1; + + auto wideUrl = std::wstring(url.begin(), url.end()); + if (!WinHttpCrackUrl(wideUrl.c_str(), 0, 0, &components)) { + log_warning("patchmanager", "failed to crack URL: {}", GetLastError()); + return result; + } + + auto session = WinHttpOpen(L"spice2x", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, nullptr, nullptr, 0); + auto session_ = std::unique_ptr(session, WinHttpCloseHandle); + if (!session) { + log_warning("patchmanager", "failed to open session: {}", GetLastError()); + return result; + } + + auto hostname = std::wstring(components.lpszHostName, components.dwHostNameLength); + auto connect = WinHttpConnect(session, hostname.c_str(), components.nPort, 0); + auto connect_ = std::unique_ptr(connect, WinHttpCloseHandle); + if (!connect) { + log_warning("patchmanager", "failed to open connect: {}", GetLastError()); + return result; + } + + auto flags = 0; + if (components.nScheme == INTERNET_SCHEME_HTTPS) { + flags = WINHTTP_FLAG_SECURE; + } + + auto urlPath = std::wstring(components.lpszUrlPath, components.dwUrlPathLength); + auto request = WinHttpOpenRequest(connect, L"GET", urlPath.c_str(), nullptr, nullptr, nullptr, flags); + auto request_ = std::unique_ptr(request, WinHttpCloseHandle); + if (!request) { + log_warning("patchmanager", "failed to open request: {}", GetLastError()); + return result; + } + + if (!WinHttpSendRequest(request, nullptr, 0, nullptr, 0, 0, 0)) { + log_warning("patchmanager", "failed to send request: {}", GetLastError()); + return result; + } + + if (!WinHttpReceiveResponse(request, nullptr)) { + log_warning("patchmanager", "failed to receive response: {}", GetLastError()); + return result; + } + + DWORD statusCode = 0; + DWORD statusCodeSize = sizeof(statusCode); + DWORD queryFlags = WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER; + + if (!WinHttpQueryHeaders(request, queryFlags, nullptr, &statusCode, &statusCodeSize, nullptr)) { + log_warning("patchmanager", "failed to query status code: {}", GetLastError()); + return result; + } + + if (statusCode != 200) { + log_warning("patchmanager", "failed to fetch URL: got unexpected status code {}", statusCode); + return result; + } + + DWORD bytesRead = 0; + std::vector buffer(4096); + while (WinHttpReadData(request, buffer.data(), buffer.size(), &bytesRead)) { + if (bytesRead == 0) { + break; + } + result.append(buffer.data(), bytesRead); + } + + return result; + } // patches std::vector PatchManager::patches; @@ -55,12 +143,14 @@ namespace overlay::windows { this->init_pos = ImVec2(10, 10); config_path = std::string(getenv("APPDATA")) + "\\spicetools_patch_manager.json"; if (!patches_initialized) { + fileutils::dir_create(LOCAL_PATCHES_PATH); + patch_url[0] = 0; if (cfg::CONFIGURATOR_STANDALONE) { apply_patches = true; } if (apply_patches) { if (fileutils::file_exists(config_path)) { - this->config_load(); + config_load(); } - this->reload_patches(apply_patches); + reload_local_patches(apply_patches); } } } @@ -72,22 +162,88 @@ namespace overlay::windows { // check if initialized if (!patches_initialized) { if (fileutils::file_exists(config_path)) { - this->config_load(); + config_load(); } - this->reload_patches(); + reload_local_patches(); } // check for unapplied patch - bool unapplied_patches = false; for (auto &patch : patches) { if (patch.enabled && patch.last_status == PatchStatus::Disabled) { - unapplied_patches = true; break; } } // game code info ImGui::Text("%s", avs::game::get_identifier().c_str()); + ImGui::SameLine(); + + std::string identifiers; + identifiers += avs::game::DLL_NAME + " / " + get_game_identifier((MODULE_PATH / avs::game::DLL_NAME).string()) + "\n"; + + if (avs::game::DLL_NAME == "jubeat.dll") { + std::string music_db_path = (MODULE_PATH / "music_db.dll").string(); + if (fileutils::file_exists(music_db_path)) { + identifiers += "music_db.dll / " + get_game_identifier(music_db_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkmdxp3.dll" || avs::game::DLL_NAME == "arkmdxp4.dll" || avs::game::DLL_NAME == "arkmdxbio2.dll") { + std::string gamemdx_path = (MODULE_PATH / "gamemdx.dll").string(); + if (fileutils::file_exists(gamemdx_path)) { + identifiers += "gamemdx.dll / " + get_game_identifier(gamemdx_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkndd.dll") { + std::string gamendd_path = (MODULE_PATH / "gamendd.dll").string(); + if (fileutils::file_exists(gamendd_path)) { + identifiers += "gamendd.dll / " + get_game_identifier(gamendd_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkkep.dll") { + std::string game_path = (MODULE_PATH / "game.dll").string(); + if (fileutils::file_exists(game_path)) { + identifiers += "game.dll / " + get_game_identifier(game_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkjc9.dll") { + std::string gamejc9_path = (MODULE_PATH / "gamejc9.dll").string(); + if (fileutils::file_exists(gamejc9_path)) { + identifiers += "gamejc9.dll / " + get_game_identifier(gamejc9_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkkdm.dll") { + std::string gamekdm_path = (MODULE_PATH / "gamekdm.dll").string(); + if (fileutils::file_exists(gamekdm_path)) { + identifiers += "gamekdm.dll / " + get_game_identifier(gamekdm_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkmmd.dll") { + std::string gamemmd_path = (MODULE_PATH / "gamemmd.dll").string(); + if (fileutils::file_exists(gamemmd_path)) { + identifiers += "gamemmd.dll / " + get_game_identifier(gamemmd_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arkklp.dll") { + std::string lpac_path = (MODULE_PATH / "lpac.dll").string(); + if (fileutils::file_exists(lpac_path)) { + identifiers += "lpac.dll / " + get_game_identifier(lpac_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "arknck.dll") { + std::string weac_path = (MODULE_PATH / "weac.dll").string(); + if (fileutils::file_exists(weac_path)) { + identifiers += "weac.dll / " + get_game_identifier(weac_path) + "\n"; + } + } else if (avs::game::DLL_NAME == "gdxg.dll") { + std::string game_path = (MODULE_PATH / "game.dll").string(); + if (fileutils::file_exists(game_path)) { + identifiers += "game.dll / " + get_game_identifier(game_path) + "\n"; + } + } + + ImGui::HelpMarker(identifiers.c_str()); + + if (cfg::CONFIGURATOR_STANDALONE) { + ImGui::SameLine(ImGui::GetWindowWidth() - 120); + if (ImGui::Button("Hard Apply")) { + hard_apply_patches(); + reload_local_patches(); + } + ImGui::SameLine(); + ImGui::HelpMarker("This option writes the selected patches on the module."); + } // auto apply checkbox ImGui::AlignTextToFramePadding(); @@ -105,7 +261,7 @@ namespace overlay::windows { if (cfg::CONFIGURATOR_STANDALONE) { // auto safe for configurator version - this->config_save(); + config_save(); config_dirty = false; } else { @@ -113,25 +269,54 @@ namespace overlay::windows { // manual safe for live version ImGui::SameLine(); if (ImGui::Button("Save")) { - this->config_save(); + config_save(); } } } - // apply button - if (unapplied_patches) { + if (ImGui::Button("Set Source")) { + ImGui::OpenPopup("Set Source"); + //patch_url[0] = 0; + } + if (ImGui::BeginPopupModal("Set Source")) { + ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "Third party remote patches may contain faulty or malicious patches. Use at your own risk."); + ImGui::SetNextItemWidth(300.f); + ImGui::InputText("", patch_url, sizeof(patch_url)); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.f, 0.3f, 1.f)); - if (ImGui::Button("Apply")) { - for (auto &patch : patches) { - if (patch.enabled && patch.last_status == PatchStatus::Disabled) { - if (apply_patch(patch, true)) { - patch.last_status = PatchStatus::Enabled; - } - } + ImGui::Text("URL"); + + bool is_valid_url = false; + if (ImGui::Button("Set")) { + std::string url = patch_url; + if (url.find("http://") == 0 || url.find("https://") == 0) { + is_valid_url = true; } + if (!is_valid_url) { + ImGui::OpenPopup("URL error"); + } + } + + if (ImGui::BeginPopupModal("URL error")) { + ImGui::Text("URL is not valid"); + if (ImGui::Button("Close")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + + if (is_valid_url) { + reload_remote_patches(); + } + + if (is_valid_url) { + ImGui::CloseCurrentPopup(); } - ImGui::PopStyleColor(); + + ImGui::SameLine(); + if (ImGui::Button("Close")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); } // check for empty list @@ -151,6 +336,10 @@ namespace overlay::windows { t.join(); } } else { + // display label for modules with missing patches + for (const auto& module : missing_modules) { + ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), ("Could not fetch patches for " + module).c_str()); + } // draw patches for (auto &patch : patches) { @@ -165,39 +354,84 @@ namespace overlay::windows { // push style int style_color_pushed = 0; switch (patch_status) { - case PatchStatus::Error: - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f)); + case PatchStatus::Error: + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.f, 0.f, 1.f)); + style_color_pushed++; + break; + case PatchStatus::Enabled: + if (setting_auto_apply && patch.enabled && patch.type != PatchType::Union) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 1.f, 0.f, 1.f)); style_color_pushed++; - break; - case PatchStatus::Enabled: - if (setting_auto_apply && patch.enabled) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.f, 1.f)); - style_color_pushed++; - } - break; - case PatchStatus::Disabled: - if (patch.enabled) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.3f, 1.f, 0.3f, 1.f)); - style_color_pushed++; - } - break; - default: - break; + } + break; + case PatchStatus::Disabled: + break; + default: + break; } // checkbox auto patch_name = patch.name; - if (patch.enabled) { - patch_name += setting_auto_apply ? " (Locked)" : " (Saved)"; - } - if (patch.unverified) { - patch_name += " (Unverified)"; + if (patch.type != PatchType::Union) { + if (patch.enabled) { + patch_name += setting_auto_apply ? " (Locked)" : " (Saved)"; + } + if (patch.unverified) { + patch_name += " (Unverified)"; + } + } else { + for (auto it = patch.patches_union.begin(); it != patch.patches_union.end(); ++it) { + if (it->name == patch.selected_union_name) { + auto it2 = DLL_MAP_ORG.find(it->dll_name); + if (it2 == DLL_MAP_ORG.end()) { + DLL_MAP_ORG[it->dll_name] = + std::unique_ptr>( + fileutils::bin_read(MODULE_PATH / it->dll_name)); + it2 = DLL_MAP_ORG.find(it->dll_name); + } + const auto& file = it2->second; + if (0 != memcmp(file->data() + it->offset, it->data.get(), it->data_len)) { + patch_name += " (Locked)"; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 1.f, 0.f, 1.f)); + style_color_pushed++; + } + break; + } + } } - if (ImGui::Checkbox(patch_name.c_str(), &patch_checked)) { + + if (patch.type == PatchType::Union) { + ImGui::SetNextItemWidth(150.0f); + if (ImGui::BeginCombo(patch_name.c_str(), patch.selected_union_name.c_str())) { + for (const auto& union_patch : patch.patches_union) { + if (ImGui::Selectable(union_patch.name.c_str())) { + patch.selected_union_name = union_patch.name; + patch.saved = true; + config_dirty = true; + if (cfg::CONFIGURATOR_STANDALONE) { + setting_auto_apply = true; + } + apply_patch(patch, true); + } + } + ImGui::EndCombo(); + } + } else if (ImGui::Checkbox(patch_name.c_str(), &patch_checked)) { config_dirty = true; switch (patch_status) { - case PatchStatus::Enabled: - case PatchStatus::Disabled: + case PatchStatus::Enabled: + case PatchStatus::Disabled: + if (patch_checked) { + patch.saved = true; + if (cfg::CONFIGURATOR_STANDALONE) { + setting_auto_apply = true; + } + } + patch.enabled = patch_checked; + apply_patch(patch, patch_checked); + break; + case PatchStatus::Error: + if (cfg::CONFIGURATOR_STANDALONE) { if (patch_checked) { patch.saved = true; if (cfg::CONFIGURATOR_STANDALONE) { @@ -205,21 +439,10 @@ namespace overlay::windows { } } patch.enabled = patch_checked; - apply_patch(patch, patch_checked); - break; - case PatchStatus::Error: - if (cfg::CONFIGURATOR_STANDALONE) { - if (patch_checked) { - patch.saved = true; - if (cfg::CONFIGURATOR_STANDALONE) { - setting_auto_apply = true; - } - } - patch.enabled = patch_checked; - } - break; - default: - break; + } + break; + default: + break; } // update status @@ -233,22 +456,6 @@ namespace overlay::windows { // help marker std::string description = patch.description; - if (!patch.patches_memory.empty() && patch.patches_memory.size() <= 10) { - if (!description.empty()) description += "\n\n"; - description += "Patch Data:"; - for (auto &mp : patch.patches_memory) { - if (!description.empty()) description += "\n\n"; - description += fmt::format( - "{:#08X}: {} -> {}", - mp.data_offset ? mp.data_offset : (uint64_t) mp.data_offset_ptr, - bin2hex(mp.data_disabled.get(), mp.data_enabled_len), - bin2hex(mp.data_enabled.get(), mp.data_enabled_len)); - if (description.length() > 512) { - description = patch.description; - break; - } - } - } if (!description.empty()) { ImGui::SameLine(); ImGui::HelpMarker(description.c_str()); @@ -257,6 +464,81 @@ namespace overlay::windows { } } + bool PatchManager::check_module_patches(const std::string& module_name) { + for (const auto& patch : patches) { + for (const auto& memory_patch : patch.patches_memory) { + if (memory_patch.dll_name == module_name) { + return true; + } + } + } + return false; + } + + void PatchManager::hard_apply_patches() { + std::vector written_list; + for (auto& patch : patches) { + switch (patch.type) { + case PatchType::Memory: + for (auto& memory_patch : patch.patches_memory) { + auto dll_path = MODULE_PATH / memory_patch.dll_name; + if (std::find(written_list.begin(), written_list.end(), dll_path.string()) == written_list.end()) { + written_list.push_back(dll_path.string()); + auto dll_bak_path = std::filesystem::path(dll_path.string() + ".bak"); + try { + if (!fileutils::file_exists(dll_bak_path)) { + std::filesystem::copy(dll_path, dll_bak_path); + } + } catch (const std::filesystem::filesystem_error& e) { + log_info("patchmanager", "filesystem error: {}", e.what()); + } + } + auto dll_data = fileutils::bin_read(dll_path); + if (dll_data) { + auto max_len = std::max(memory_patch.data_disabled_len, memory_patch.data_enabled_len); + if (memory_patch.data_offset + max_len <= dll_data->size()) { + if (patch.enabled) { + memcpy(dll_data->data() + memory_patch.data_offset, memory_patch.data_enabled.get(), memory_patch.data_enabled_len); + } else { + memcpy(dll_data->data() + memory_patch.data_offset, memory_patch.data_disabled.get(), memory_patch.data_disabled_len); + } + fileutils::bin_write(dll_path, dll_data->data(), dll_data->size()); + } + } + } + break; + case PatchType::Union: + for (auto& union_patch : patch.patches_union) { + if (union_patch.name == patch.selected_union_name) { + auto dll_path = MODULE_PATH / union_patch.dll_name; + if (std::find(written_list.begin(), written_list.end(), dll_path.string()) == written_list.end()) { + written_list.push_back(dll_path.string()); + auto dll_bak_path = std::filesystem::path(dll_path.string() + ".bak"); + try { + if (!fileutils::file_exists(dll_bak_path)) { + std::filesystem::copy(dll_path, dll_bak_path); + } + } catch (const std::filesystem::filesystem_error& e) { + log_info("patchmanager", "filesystem error: {}", e.what()); + } + } + auto dll_data = fileutils::bin_read(dll_path); + if (dll_data) { + if (union_patch.offset + union_patch.data_len <= dll_data->size()) { + memcpy(dll_data->data() + union_patch.offset, union_patch.data.get(), union_patch.data_len); + fileutils::bin_write(dll_path, dll_data->data(), dll_data->size()); + } + } + break; + } + } + break; + default: + break; + } + } + } + void PatchManager::config_load() { log_info("patchmanager", "loading config"); @@ -312,6 +594,16 @@ namespace overlay::windows { } } } + // read enabled union patches + auto patches_union_enabled = doc.FindMember("union_patches_enabled"); + if (patches_union_enabled != doc.MemberEnd() && patches_union_enabled->value.IsObject()) { + setting_union_patches_enabled.clear(); + for (auto it = patches_union_enabled->value.MemberBegin(); it != patches_union_enabled->value.MemberEnd(); ++it) { + if (it->name.IsString() && it->value.IsString()) { + setting_union_patches_enabled[it->name.GetString()] = it->value.GetString(); + } + } + } } } } @@ -319,8 +611,10 @@ namespace overlay::windows { static std::string patch_hash(PatchData &patch) { SHA256 hash; hash.add(patch.game_code.c_str(), patch.game_code.length()); - hash.add(&patch.datecode_min, sizeof(patch.datecode_min)); - hash.add(&patch.datecode_max, sizeof(patch.datecode_max)); + if (patch.datecode_min != 0 || patch.datecode_max != 0) { + hash.add(&patch.datecode_min, sizeof(patch.datecode_min)); + hash.add(&patch.datecode_max, sizeof(patch.datecode_max)); + } hash.add(patch.name.c_str(), patch.name.length()); hash.add(patch.description.c_str(), patch.description.length()); return hash.getHash(); @@ -333,7 +627,8 @@ namespace overlay::windows { doc.Parse( "{" " \"auto_apply\": []," - " \"patches_enabled\": []" + " \"patches_enabled\": []," + " \"union_patches_enabled\": {}" "}" ); @@ -362,29 +657,40 @@ namespace overlay::windows { // get enabled patches auto &doc_patches_enabled = doc["patches_enabled"]; + auto &doc_union_patches_enable = doc["union_patches_enabled"]; for (auto &patch : patches) { - // hash patch and find entry - auto hash = patch_hash(patch); - auto entry = std::find( + if (patch.type != PatchType::Union) { + // hash patch and find entry + auto hash = patch_hash(patch); + auto entry = std::find( setting_patches_enabled.begin(), setting_patches_enabled.end(), hash); - // enable hash if known as enabled, overridden and missing from list - if ((patch.last_status == PatchStatus::Enabled && patch.enabled) - || (cfg::CONFIGURATOR_STANDALONE - && patch.last_status == PatchStatus::Error && patch.enabled)) { - if (entry == setting_patches_enabled.end()) { - setting_patches_enabled.emplace_back(hash); + // enable hash if known as enabled, overridden and missing from list + if ((patch.last_status == PatchStatus::Enabled && patch.enabled) + || (cfg::CONFIGURATOR_STANDALONE + && patch.last_status == PatchStatus::Error && patch.enabled)) { + if (entry == setting_patches_enabled.end()) { + setting_patches_enabled.emplace_back(hash); + } } - } - // disable hash if patch known as disabled - if (patch.last_status == PatchStatus::Disabled - || (cfg::CONFIGURATOR_STANDALONE - && patch.last_status == PatchStatus::Error && !patch.enabled)) { - if (entry != setting_patches_enabled.end()) { - setting_patches_enabled.erase(entry); + // disable hash if patch known as disabled + if (patch.last_status == PatchStatus::Disabled + || (cfg::CONFIGURATOR_STANDALONE + && patch.last_status == PatchStatus::Error && !patch.enabled)) { + if (entry != setting_patches_enabled.end()) { + setting_patches_enabled.erase(entry); + } + } + } else { + std::string key = patch.name + ":" + patch.game_code; + auto it = setting_union_patches_enabled.find(key); + if (it == setting_union_patches_enabled.end()) { // Not found + setting_union_patches_enabled[key] = patch.selected_union_name; + } else { // Found + it->second = patch.selected_union_name; } } } @@ -395,6 +701,12 @@ namespace overlay::windows { doc_patches_enabled.PushBack(hash_value, doc.GetAllocator()); } + for (auto& it : setting_union_patches_enabled) { + const std::string& key = it.first; + const std::string& val = it.second; + doc_union_patches_enable.AddMember(StringRef(key.c_str()), StringRef(val.c_str()), doc.GetAllocator()); + } + // build JSON StringBuffer buffer; Writer writer(buffer); @@ -408,47 +720,50 @@ namespace overlay::windows { } } - void PatchManager::reload_patches(bool apply_patches) { - - // announce reload - if (apply_patches) { - log_info("patchmanager", "reloading and applying patches"); - } else { - log_info("patchmanager", "reloading patches"); + std::string get_game_identifier(const std::string& dll_path) + { + std::ifstream file(dll_path, std::ios::binary); + if (!file) { + log_warning("patchmanager", "Failed to open file: {}", dll_path); + return ""; } - // clear old patches - patches.clear(); + // read the DOS header + IMAGE_DOS_HEADER dos_header; + file.read(reinterpret_cast(&dos_header), sizeof(dos_header)); + if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) { + log_warning("patchmanager", "Invalid DOS signature: {}", dll_path); + return ""; + } - // load embedded patches from resources - auto patches_json = resutil::load_file_string(IDR_PATCHES); - this->append_patches(patches_json, apply_patches); + // move to the NT headers + file.seekg(dos_header.e_lfanew); - // automatic patch file detection - std::filesystem::path autodetect_paths[] { - "patches.json", - MODULE_PATH / "patches.json", - std::filesystem::path("..") / "patches.json", - }; - for (const auto &path : autodetect_paths) { - if (fileutils::file_exists(path)) { - std::string contents = fileutils::text_read(path); - if (!contents.empty()) { - log_info("patchmanager", "appending patches from {}", path.string()); - this->append_patches(contents, apply_patches); - break; - } else { - log_warning("patchmanager", "failed reading contents from {}", path.string()); - } - } + // read the NT headers + IMAGE_NT_HEADERS nt_headers; + file.read(reinterpret_cast(&nt_headers), sizeof(nt_headers)); + if (nt_headers.Signature != IMAGE_NT_SIGNATURE) { + log_warning("patchmanager", "Invalid NT signature: {}", dll_path); + return ""; } - // show amount of patches - log_info("patchmanager", "loaded {} patches", patches.size()); - patches_initialized = true; + // get the TimeDateStamp and AddressOfEntryPoint from the file header + uint32_t time_date_stamp = nt_headers.FileHeader.TimeDateStamp; + uint32_t address_of_entry_point = nt_headers.OptionalHeader.AddressOfEntryPoint; + + // concatenate TimeDateStamp and AddressOfEntryPoint + std::ostringstream oss; + oss << time_date_stamp << address_of_entry_point; + std::string identifier = oss.str(); + + return identifier; } - void PatchManager::append_patches(std::string &patches_json, bool apply_patches) { + bool PatchManager::idr_patches_loaded = false; + + void PatchManager::load_embedded_patches(bool apply_patches) { + // load embedded patches from resources + auto patches_json = resutil::load_file_string(IDR_PATCHES); // parse document Document doc; @@ -504,10 +819,13 @@ namespace overlay::windows { .type = PatchType::Unknown, .preset = preset, .patches_memory = std::vector(), + .patches_union = std::vector(), + .selected_union_name = "", .last_status = PatchStatus::Disabled, .saved = false, .hash = "", .unverified = false, + .peIdentifier = std::string() }; // determine patch type @@ -575,141 +893,61 @@ namespace overlay::windows { // check patch type switch (patch_data.type) { - case PatchType::Memory: { + case PatchType::Memory: { - // iterate memory patches - auto patches_it = patch.FindMember("patches"); - if (patches_it == patch.MemberEnd() + // iterate memory patches + auto patches_it = patch.FindMember("patches"); + if (patches_it == patch.MemberEnd() || !patches_it->value.IsArray()) { - log_warning("patchmanager", "unable to get patches for {}", - name_it->value.GetString()); - continue; - } - for (auto &memory_patch : patches_it->value.GetArray()) { - - // validate data - auto data_disabled_it = memory_patch.FindMember("dataDisabled"); - if (data_disabled_it == memory_patch.MemberEnd() - || !data_disabled_it->value.IsString()) { - log_warning("patchmanager", "unable to get data for {}", - name_it->value.GetString()); - continue; - } - auto data_enabled_it = memory_patch.FindMember("dataEnabled"); - if (data_enabled_it == memory_patch.MemberEnd() - || !data_enabled_it->value.IsString()) { - log_warning("patchmanager", "unable to get data for {}", - name_it->value.GetString()); - continue; - } - - // get hex strings - auto data_disabled_hex = data_disabled_it->value.GetString(); - auto data_enabled_hex = data_enabled_it->value.GetString(); - auto data_disabled_hex_len = strlen(data_disabled_hex); - auto data_enabled_hex_len = strlen(data_enabled_hex); - if ((data_disabled_hex_len % 2) != 0 || (data_enabled_hex_len % 2) != 0) { - log_warning("patchmanager", "patch hex data length has odd length for {}", - name_it->value.GetString()); - continue; - } - - // convert to binary - std::shared_ptr data_disabled(new uint8_t[data_disabled_hex_len / 2]); - std::shared_ptr data_enabled(new uint8_t[data_enabled_hex_len / 2]); - if (!hex2bin(data_disabled_hex, data_disabled.get()) - || (!hex2bin(data_enabled_hex, data_enabled.get()))) { - log_warning("patchmanager", "failed to parse patch data from hex for {}", - name_it->value.GetString()); - continue; - } - - // get DLL name - auto dll_name_it = memory_patch.FindMember("dllName"); - if (dll_name_it == memory_patch.MemberEnd() - || !dll_name_it->value.IsString()) { - log_warning("patchmanager", "unable to get dllName for {}", - name_it->value.GetString()); - continue; - } - std::string dll_name = dll_name_it->value.GetString(); - - // IIDX omnimix dll name fix - if (dll_name == "bm2dx.dll" && avs::game::is_model("LDJ") && avs::game::REV[0] == 'X') { - dll_name = avs::game::DLL_NAME; - } - - // BST 1/2 combined release dll name fix - if (dll_name == "beatstream.dll" && - (avs::game::DLL_NAME == "beatstream1.dll" - || avs::game::DLL_NAME == "beatstream2.dll")) - { - dll_name = avs::game::DLL_NAME; - } - - // build memory patch data - MemoryPatch memory_patch_data { - .dll_name = dll_name, - .data_disabled = std::move(data_disabled), - .data_disabled_len = data_disabled_hex_len / 2, - .data_enabled = std::move(data_enabled), - .data_enabled_len = data_enabled_hex_len / 2, - .data_offset = 0, - }; - - // get data offset - auto data_offset_it = memory_patch.FindMember("dataOffset"); - if (data_offset_it == memory_patch.MemberEnd()) { - log_warning("patchmanager", "unable to get dataOffset for {}", - name_it->value.GetString()); - continue; - } - if (data_offset_it->value.IsUint64()) { - memory_patch_data.data_offset = data_offset_it->value.GetUint64(); - } else if (data_offset_it->value.IsString()) { - std::stringstream ss; - ss << data_offset_it->value.GetString(); - ss >> memory_patch_data.data_offset; - if (!ss.good() || !ss.eof()) { - log_warning("patchmanager", "invalid dataOffset for {}", - name_it->value.GetString()); - continue; - } - } else { - log_warning("patchmanager", "unable to get dataOffset for {}", - name_it->value.GetString()); - continue; - } - - // move to list - patch_data.patches_memory.emplace_back(memory_patch_data); - } - break; + log_warning("patchmanager", "unable to get patches for {}", + name_it->value.GetString()); + continue; } - case PatchType::Signature: { + for (auto &memory_patch : patches_it->value.GetArray()) { // validate data - auto data_signature_it = patch.FindMember("signature"); - if (data_signature_it == patch.MemberEnd() - || !data_signature_it->value.IsString()) { + auto data_disabled_it = memory_patch.FindMember("dataDisabled"); + if (data_disabled_it == memory_patch.MemberEnd() + || !data_disabled_it->value.IsString()) { log_warning("patchmanager", "unable to get data for {}", - name_it->value.GetString()); + name_it->value.GetString()); continue; } - auto data_replacement_it = patch.FindMember("replacement"); - if (data_replacement_it == patch.MemberEnd() - || !data_replacement_it->value.IsString()) { + auto data_enabled_it = memory_patch.FindMember("dataEnabled"); + if (data_enabled_it == memory_patch.MemberEnd() + || !data_enabled_it->value.IsString()) { log_warning("patchmanager", "unable to get data for {}", - name_it->value.GetString()); + name_it->value.GetString()); + continue; + } + + // get hex strings + auto data_disabled_hex = data_disabled_it->value.GetString(); + auto data_enabled_hex = data_enabled_it->value.GetString(); + auto data_disabled_hex_len = strlen(data_disabled_hex); + auto data_enabled_hex_len = strlen(data_enabled_hex); + if ((data_disabled_hex_len % 2) != 0 || (data_enabled_hex_len % 2) != 0) { + log_warning("patchmanager", "patch hex data length has odd length for {}", + name_it->value.GetString()); + continue; + } + + // convert to binary + std::shared_ptr data_disabled(new uint8_t[data_disabled_hex_len / 2]); + std::shared_ptr data_enabled(new uint8_t[data_enabled_hex_len / 2]); + if (!hex2bin(data_disabled_hex, data_disabled.get()) + || (!hex2bin(data_enabled_hex, data_enabled.get()))) { + log_warning("patchmanager", "failed to parse patch data from hex for {}", + name_it->value.GetString()); continue; } // get DLL name - auto dll_name_it = patch.FindMember("dllName"); - if (dll_name_it == patch.MemberEnd() + auto dll_name_it = memory_patch.FindMember("dllName"); + if (dll_name_it == memory_patch.MemberEnd() || !dll_name_it->value.IsString()) { log_warning("patchmanager", "unable to get dllName for {}", - name_it->value.GetString()); + name_it->value.GetString()); continue; } std::string dll_name = dll_name_it->value.GetString(); @@ -722,71 +960,151 @@ namespace overlay::windows { // BST 1/2 combined release dll name fix if (dll_name == "beatstream.dll" && (avs::game::DLL_NAME == "beatstream1.dll" - || avs::game::DLL_NAME == "beatstream2.dll")) + || avs::game::DLL_NAME == "beatstream2.dll")) { dll_name = avs::game::DLL_NAME; } - // get optional offset - int offset = 0; - auto offset_it = patch.FindMember("offset"); - if (offset_it != patch.MemberEnd()) { - bool invalid = false; - if (offset_it->value.IsInt64()) { - offset = offset_it->value.GetInt64(); - } else if (offset_it->value.IsString()) { - std::stringstream ss; - ss << offset_it->value.GetString(); - ss >> offset; - invalid = !ss.good() || !ss.eof(); - } else { - invalid = true; - } - if (invalid) { - log_warning("patchmanager", "invalid offset for {}", - name_it->value.GetString()); - } - } + // build memory patch data + MemoryPatch memory_patch_data { + .dll_name = dll_name, + .data_disabled = std::move(data_disabled), + .data_disabled_len = data_disabled_hex_len / 2, + .data_enabled = std::move(data_enabled), + .data_enabled_len = data_enabled_hex_len / 2, + .data_offset = 0, + }; - // get optional usage - int usage = 0; - auto usage_it = patch.FindMember("usage"); - if (usage_it != patch.MemberEnd()) { - bool invalid = false; - if (usage_it->value.IsInt64()) { - usage = usage_it->value.GetInt64(); - } else if (usage_it->value.IsString()) { - std::stringstream ss; - ss << usage_it->value.GetString(); - ss >> usage; - invalid = !ss.good() || !ss.eof(); - } else { - invalid = true; - } - if (invalid) { - log_warning("patchmanager", "invalid usage for {}", - name_it->value.GetString()); + // get data offset + auto data_offset_it = memory_patch.FindMember("dataOffset"); + if (data_offset_it == memory_patch.MemberEnd()) { + log_warning("patchmanager", "unable to get dataOffset for {}", + name_it->value.GetString()); + continue; + } + if (data_offset_it->value.IsUint64()) { + memory_patch_data.data_offset = data_offset_it->value.GetUint64(); + } else if (data_offset_it->value.IsString()) { + std::stringstream ss; + ss << data_offset_it->value.GetString(); + ss >> memory_patch_data.data_offset; + if (!ss.good() || !ss.eof()) { + log_warning("patchmanager", "invalid dataOffset for {}", + name_it->value.GetString()); + continue; } + } else { + log_warning("patchmanager", "unable to get dataOffset for {}", + name_it->value.GetString()); + continue; } - // build signature patch - SignaturePatch signature_data = { - .dll_name = dll_name, - .signature = data_signature_it->value.GetString(), - .replacement = data_replacement_it->value.GetString(), - .offset = offset, - .usage = usage, - }; - - // convert to memory patch - patch_data.patches_memory.emplace_back(signature_data.to_memory(&patch_data)); - patch_data.type = PatchType::Memory; - break; + // move to list + patch_data.patches_memory.emplace_back(memory_patch_data); } - case PatchType::Unknown: - default: - log_warning("patchmanager", "unknown patch type: {}", patch_data.type); - break; + break; + } + case PatchType::Signature: { + + // validate data + auto data_signature_it = patch.FindMember("signature"); + if (data_signature_it == patch.MemberEnd() + || !data_signature_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } + auto data_replacement_it = patch.FindMember("replacement"); + if (data_replacement_it == patch.MemberEnd() + || !data_replacement_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } + + // get DLL name + auto dll_name_it = patch.FindMember("dllName"); + if (dll_name_it == patch.MemberEnd() + || !dll_name_it->value.IsString()) { + log_warning("patchmanager", "unable to get dllName for {}", + name_it->value.GetString()); + continue; + } + std::string dll_name = dll_name_it->value.GetString(); + + // IIDX omnimix dll name fix + if (dll_name == "bm2dx.dll" && avs::game::is_model("LDJ") && avs::game::REV[0] == 'X') { + dll_name = avs::game::DLL_NAME; + } + + // BST 1/2 combined release dll name fix + if (dll_name == "beatstream.dll" && + (avs::game::DLL_NAME == "beatstream1.dll" + || avs::game::DLL_NAME == "beatstream2.dll")) + { + dll_name = avs::game::DLL_NAME; + } + + // get optional offset + int offset = 0; + auto offset_it = patch.FindMember("offset"); + if (offset_it != patch.MemberEnd()) { + bool invalid = false; + if (offset_it->value.IsInt64()) { + offset = offset_it->value.GetInt64(); + } else if (offset_it->value.IsString()) { + std::stringstream ss; + ss << offset_it->value.GetString(); + ss >> offset; + invalid = !ss.good() || !ss.eof(); + } else { + invalid = true; + } + if (invalid) { + log_warning("patchmanager", "invalid offset for {}", + name_it->value.GetString()); + } + } + + // get optional usage + int usage = 0; + auto usage_it = patch.FindMember("usage"); + if (usage_it != patch.MemberEnd()) { + bool invalid = false; + if (usage_it->value.IsInt64()) { + usage = usage_it->value.GetInt64(); + } else if (usage_it->value.IsString()) { + std::stringstream ss; + ss << usage_it->value.GetString(); + ss >> usage; + invalid = !ss.good() || !ss.eof(); + } else { + invalid = true; + } + if (invalid) { + log_warning("patchmanager", "invalid usage for {}", + name_it->value.GetString()); + } + } + + // build signature patch + SignaturePatch signature_data = { + .dll_name = dll_name, + .signature = data_signature_it->value.GetString(), + .replacement = data_replacement_it->value.GetString(), + .offset = offset, + .usage = usage, + }; + + // convert to memory patch + patch_data.patches_memory.emplace_back(signature_data.to_memory(&patch_data)); + patch_data.type = PatchType::Memory; + break; + } + case PatchType::Unknown: + default: + log_warning("patchmanager", "unknown patch type: {}", patch_data.type); + break; } // auto apply @@ -797,159 +1115,809 @@ namespace overlay::windows { // remember patch patches.emplace_back(patch_data); + + // check if any patch from IDR_PATCHES is loaded based on the datecode + for (auto &patch : patches) { + if (avs::game::is_ext(patch.datecode_min, patch.datecode_max)) { + idr_patches_loaded = true; + break; + } + } } } - PatchStatus is_patch_active(PatchData &patch) { + bool PatchManager::load_remote_patches_for_dll(const std::string& url, const std::string& dll_name, bool apply_patches) { + log_info("patchmanager", "load remote patches for {}", dll_name); + std::string identifier = get_game_identifier((MODULE_PATH / dll_name).string()); + std::string url_cpy = url; + if (url_cpy.back() != '/') + url_cpy += '/'; + std::string json_path = fmt::format("{}{}.json", url_cpy, identifier); + try { + auto patches_json = getFromUrl(json_path); + if (!patches_json.empty()) { + append_patches(patches_json, apply_patches); + std::filesystem::path save_path = LOCAL_PATCHES_PATH / (identifier + ".json"); + fileutils::text_write(save_path, patches_json); + return true; + } else { + log_warning("patchmanager", "failed to fetch patches JSON for {}", dll_name); + } + } catch (const std::exception& e) { + log_warning("patchmanager", "exception occurred while loading remote patches JSON for {}: {}", dll_name, e.what()); + } + return false; + } - // check patch type - switch (patch.type) { + bool PatchManager::load_local_patches_for_dll(const std::string& dll_name, bool apply_patches) { + log_info("patchmanager", "load local patches for {}", dll_name); + size_t old_patches_num = patches.size(); + std::filesystem::path patches_json_path = LOCAL_PATCHES_JSON_PATH; + std::string identifier = get_game_identifier((MODULE_PATH / dll_name).string()); + std::filesystem::path identifier_json_path = LOCAL_PATCHES_PATH / (identifier + ".json"); + auto filter = [&identifier](const PatchData& patch_data) { + return identifier == patch_data.peIdentifier; + }; + if (fileutils::file_exists(patches_json_path)) { + std::string content = fileutils::text_read(patches_json_path); + append_patches(content, apply_patches, filter); + if (old_patches_num != patches.size()) { + log_info("patchmanager", "loaded {} patches from patches.json", patches.size() - old_patches_num); + return true; + } + } + if (old_patches_num == patches.size() && fileutils::file_exists(identifier_json_path)) { + std::string content = fileutils::text_read(identifier_json_path); + append_patches(content, apply_patches); + if (old_patches_num != patches.size()) { + log_info("patchmanager", "loaded {}", identifier_json_path.string()); + return true; + } + } + return false; + } + + void PatchManager::reload_local_patches(bool apply_patches) { + // announce reload + if (apply_patches) { + log_info("patchmanager", "reloading (local) and applying patches"); + } else { + log_info("patchmanager", "reloading (local) patches"); + } + + // clear old patches + patches.clear(); + + missing_modules.clear(); + + // load embedded patches from resources + load_embedded_patches(apply_patches); + + load_local_patches_for_dll(avs::game::DLL_NAME, apply_patches); + + if (avs::game::DLL_NAME == "jubeat.dll") { + if (!load_local_patches_for_dll("music_db.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkmdxp3.dll" || avs::game::DLL_NAME == "arkmdxp4.dll" || avs::game::DLL_NAME == "arkmdxbio2.dll") { + if (!load_local_patches_for_dll("gamemdx.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkndd.dll") { + if (!load_local_patches_for_dll("gamendd.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkkep.dll") { + if (!load_local_patches_for_dll("game.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkjc9.dll") { + if (!load_local_patches_for_dll("gamejc9.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkkdm.dll") { + if (!load_local_patches_for_dll("gamekdm.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkmmd.dll") { + if (!load_local_patches_for_dll("gamemmd.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arkklp.dll") { + if (!load_local_patches_for_dll("lpac.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "arknck.dll") { + if (!load_local_patches_for_dll("weac.dll", apply_patches)) { + + } + } else if (avs::game::DLL_NAME == "gdxg.dll") { + if (!load_local_patches_for_dll("game.dll", apply_patches)) { + + } + } + + // show amount of patches + log_info("patchmanager", "loaded {} patches", patches.size()); + patches_initialized = true; + } + + void PatchManager::reload_remote_patches(bool apply_patches) { + // announce reload + if (apply_patches) { + log_info("patchmanager", "reloading (remote) and applying patches"); + } else { + log_info("patchmanager", "reloading (remote) patches"); + } + + // clear old patches + patches.clear(); + + missing_modules.clear(); + + // load embedded patches from resources + load_embedded_patches(apply_patches); + + // load patches for main dll + load_remote_patches_for_dll(patch_url, avs::game::DLL_NAME, apply_patches); + + // check for additional patches based on module name + if (avs::game::DLL_NAME == "jubeat.dll") { + load_remote_patches_for_dll(patch_url, "music_db.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkmdxp3.dll" || avs::game::DLL_NAME == "arkmdxp4.dll" || avs::game::DLL_NAME == "arkmdxbio2.dll") { + load_remote_patches_for_dll(patch_url, "gamemdx.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkndd.dll") { + load_remote_patches_for_dll(patch_url, "gamendd.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkkep.dll") { + load_remote_patches_for_dll(patch_url, "game.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkjc9.dll") { + load_remote_patches_for_dll(patch_url, "gamejc9.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkkdm.dll") { + load_remote_patches_for_dll(patch_url, "gamekdm.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkmmd.dll") { + load_remote_patches_for_dll(patch_url, "gamemmd.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arkklp.dll") { + load_remote_patches_for_dll(patch_url, "lpac.dll", apply_patches); + } else if (avs::game::DLL_NAME == "arknck.dll") { + load_remote_patches_for_dll(patch_url, "weac.dll", apply_patches); + } else if (avs::game::DLL_NAME == "gdxg.dll") { + load_remote_patches_for_dll(patch_url, "game.dll", apply_patches); + } + + // show amount of patches + log_info("patchmanager", "loaded (remote) {} patches", patches.size()); + patches_initialized = true; + } + + void PatchManager::append_patches(std::string &patches_json, bool apply_patches, std::function filter) { + + // parse document + Document doc; + doc.Parse(patches_json.c_str()); + + // check parse error + auto error = doc.GetParseError(); + if (error) { + log_warning("patchmanager", "config parse error: {}", error); + } + + // iterate patches + for (auto &patch : doc.GetArray()) { + + // verfiy patch data + auto name_it = patch.FindMember("name"); + if (name_it == patch.MemberEnd() || !name_it->value.IsString()) { + log_warning("patchmanager", "failed to parse patch name"); + continue; + } + auto game_code_it = patch.FindMember("gameCode"); + if (game_code_it == patch.MemberEnd() || !game_code_it->value.IsString()) { + log_warning("patchmanager", "failed to parse game code for {}", + name_it->value.GetString()); + continue; + } + auto description_it = patch.FindMember("description"); + if (description_it == patch.MemberEnd() || !description_it->value.IsString()) { + log_warning("patchmanager", "failed to parse description for {}", + name_it->value.GetString()); + continue; + } + auto type_it = patch.FindMember("type"); + if (type_it == patch.MemberEnd() || !type_it->value.IsString()) { + log_warning("patchmanager", "failed to parse type for {}", + name_it->value.GetString()); + continue; + } + auto pe_identifier_it = patch.FindMember("peIdentifier"); + const char* pe_identifier = ""; + if (pe_identifier_it != patch.MemberEnd() && pe_identifier_it->value.IsString()) { + pe_identifier = pe_identifier_it->value.GetString(); + } + auto preset_it = patch.FindMember("preset"); + bool preset = false; + if (preset_it != patch.MemberEnd() && preset_it->value.IsBool()) { + preset = preset_it->value.GetBool(); + } + + // build patch data + PatchData patch_data { + .enabled = false, + .game_code = game_code_it->value.GetString(), + .name = name_it->value.GetString(), + .description = description_it->value.GetString(), + .type = PatchType::Unknown, + .preset = preset, + .patches_memory = std::vector(), + .patches_union = std::vector(), + .selected_union_name = std::string(), + .last_status = PatchStatus::Disabled, + .saved = false, + .hash = "", + .unverified = false, + .peIdentifier = std::string(pe_identifier) + }; + + if (filter && !filter(patch_data)) { + continue; + } + + // determine patch type + auto type_str = type_it->value.GetString(); + if (!_stricmp(type_str, "memory")) { + patch_data.type = PatchType::Memory; + } else if (!_stricmp(type_str, "signature")) { + patch_data.type = PatchType::Signature; + } else if (!stricmp(type_str, "union")) { + patch_data.type = PatchType::Union; + } + + if (patch_data.type == PatchType::Union) { + patch_data.enabled = true; + } + + // check for skip + if (!avs::game::is_model(patch_data.game_code.c_str())) { + log_info("patchmanager", "skip (is_model): {}", patch_data.name); + continue; + } + + // generate hash + patch_data.hash = patch_hash(patch_data); + + // check for existing + bool existing = false; + for (auto &added_patch : patches) { + if (added_patch.hash == patch_data.hash) { + existing = true; + break; + } + } + if (existing) { + continue; + } + + // hash check for enabled + for (auto &enabled_entry : setting_patches_enabled) { + if (patch_data.hash == enabled_entry) { + patch_data.enabled = true; + patch_data.saved = true; + } + } + + // check patch type + switch (patch_data.type) { case PatchType::Memory: { - // iterate patches - bool enabled = false; - bool disabled = false; - for (auto &memory_patch : patch.patches_memory) { - auto max_size = std::max(memory_patch.data_enabled_len, memory_patch.data_disabled_len); + // iterate memory patches + auto patches_it = patch.FindMember("patches"); + if (patches_it == patch.MemberEnd() + || !patches_it->value.IsArray()) { + log_warning("patchmanager", "unable to get patches for {}", + name_it->value.GetString()); + continue; + } + for (auto& memory_patch : patches_it->value.GetArray()) { - // check for error to not try to get the pointer every frame - if (memory_patch.fatal_error) { - if (cfg::CONFIGURATOR_STANDALONE) { - patch.unverified = true; - return patch.enabled ? PatchStatus::Enabled : PatchStatus::Disabled; - } - return PatchStatus::Error; + // validate data + auto data_disabled_it = memory_patch.FindMember("dataDisabled"); + if (data_disabled_it == memory_patch.MemberEnd() + || !data_disabled_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } + auto data_enabled_it = memory_patch.FindMember("dataEnabled"); + if (data_enabled_it == memory_patch.MemberEnd() + || !data_enabled_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; } - // check data pointer - if (memory_patch.data_offset_ptr == nullptr) { + // get hex strings + auto data_disabled_hex = data_disabled_it->value.GetString(); + auto data_enabled_hex = data_enabled_it->value.GetString(); + auto data_disabled_hex_len = strlen(data_disabled_hex); + auto data_enabled_hex_len = strlen(data_enabled_hex); + if ((data_disabled_hex_len % 2) != 0 || (data_enabled_hex_len % 2) != 0) { + log_warning("patchmanager", "patch hex data length has odd length for {}", + name_it->value.GetString()); + continue; + } - // check if file exists - auto dll_path = MODULE_PATH / memory_patch.dll_name; - if (!fileutils::file_exists(dll_path)) { + // convert to binary + std::shared_ptr data_disabled(new uint8_t[data_disabled_hex_len / 2]); + std::shared_ptr data_enabled(new uint8_t[data_enabled_hex_len / 2]); + if (!hex2bin(data_disabled_hex, data_disabled.get()) + || (!hex2bin(data_enabled_hex, data_enabled.get()))) { + log_warning("patchmanager", "failed to parse patch data from hex for {}", + name_it->value.GetString()); + continue; + } - // file does not exist so that's pretty fatal - memory_patch.fatal_error = true; - return PatchStatus::Error; + // get DLL name + auto dll_name_it = memory_patch.FindMember("dllName"); + if (dll_name_it == memory_patch.MemberEnd() + || !dll_name_it->value.IsString()) { + log_warning("patchmanager", "unable to get dllName for {}", + name_it->value.GetString()); + continue; + } + std::string dll_name = dll_name_it->value.GetString(); + + // IIDX omnimix dll name fix + if (dll_name == "bm2dx.dll" && avs::game::is_model("LDJ") && avs::game::REV[0] == 'X') { + dll_name = avs::game::DLL_NAME; + } + + // BST 1/2 combined release dll name fix + if (dll_name == "beatstream.dll" && + (avs::game::DLL_NAME == "beatstream1.dll" + || avs::game::DLL_NAME == "beatstream2.dll")) + { + dll_name = avs::game::DLL_NAME; + } + + // build memory patch data + MemoryPatch memory_patch_data { + .dll_name = dll_name, + .data_disabled = std::move(data_disabled), + .data_disabled_len = data_disabled_hex_len / 2, + .data_enabled = std::move(data_enabled), + .data_enabled_len = data_enabled_hex_len / 2, + .data_offset = 0, + }; + + // get data offset + auto data_offset_it = memory_patch.FindMember("offset"); + if (data_offset_it == memory_patch.MemberEnd()) { + log_warning("patchmanager", "unable to get offset for {}", + name_it->value.GetString()); + continue; + } + if (data_offset_it->value.IsUint64()) { + memory_patch_data.data_offset = data_offset_it->value.GetUint64(); + } else if (data_offset_it->value.IsString()) { + std::stringstream ss; + ss << data_offset_it->value.GetString(); + ss >> memory_patch_data.data_offset; + if (!ss.good() || !ss.eof()) { + log_warning("patchmanager", "invalid offset for {}", + name_it->value.GetString()); + continue; } + } else { + log_warning("patchmanager", "unable to get offset for {}", + name_it->value.GetString()); + continue; + } - // standalone mode - if (cfg::CONFIGURATOR_STANDALONE) { + // move to list + patch_data.patches_memory.emplace_back(memory_patch_data); + } + break; + } + case PatchType::Signature: { - // load file into dll map if missing - auto it = DLL_MAP.find(memory_patch.dll_name); - if (it == DLL_MAP.end()) { - DLL_MAP[memory_patch.dll_name] = - std::unique_ptr>( - fileutils::bin_read(dll_path)); - } + // validate data + auto data_signature_it = patch.FindMember("signature"); + if (data_signature_it == patch.MemberEnd() + || !data_signature_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } + auto data_replacement_it = patch.FindMember("replacement"); + if (data_replacement_it == patch.MemberEnd() + || !data_replacement_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } - // check bounds - if (memory_patch.data_offset + max_size >= DLL_MAP[memory_patch.dll_name]->size()) { + // get DLL name + auto dll_name_it = patch.FindMember("dllName"); + if (dll_name_it == patch.MemberEnd() + || !dll_name_it->value.IsString()) { + log_warning("patchmanager", "unable to get dllName for {}", + name_it->value.GetString()); + continue; + } + std::string dll_name = dll_name_it->value.GetString(); - // offset outside of file bounds - memory_patch.fatal_error = true; - return PatchStatus::Error; - } else { + // IIDX omnimix dll name fix + if (dll_name == "bm2dx.dll" && avs::game::is_model("LDJ") && avs::game::REV[0] == 'X') { + dll_name = avs::game::DLL_NAME; + } - // just remember raw file offset - memory_patch.data_offset_ptr = reinterpret_cast(memory_patch.data_offset); - } + // BST 1/2 combined release dll name fix + if (dll_name == "beatstream.dll" && + (avs::game::DLL_NAME == "beatstream1.dll" + || avs::game::DLL_NAME == "beatstream2.dll")) + { + dll_name = avs::game::DLL_NAME; + } - } else { + // get optional offset + int offset = 0; + auto offset_it = patch.FindMember("offset"); + if (offset_it != patch.MemberEnd()) { + bool invalid = false; + if (offset_it->value.IsInt64()) { + offset = offset_it->value.GetInt64(); + } else if (offset_it->value.IsString()) { + std::stringstream ss; + ss << offset_it->value.GetString(); + ss >> offset; + invalid = !ss.good() || !ss.eof(); + } else { + invalid = true; + } + if (invalid) { + log_warning("patchmanager", "invalid offset for {}", + name_it->value.GetString()); + } + } - // get module - auto module = libutils::try_module(dll_path); - if (!module) { + // get optional usage + int usage = 0; + auto usage_it = patch.FindMember("usage"); + if (usage_it != patch.MemberEnd()) { + bool invalid = false; + if (usage_it->value.IsInt64()) { + usage = usage_it->value.GetInt64(); + } else if (usage_it->value.IsString()) { + std::stringstream ss; + ss << usage_it->value.GetString(); + ss >> usage; + invalid = !ss.good() || !ss.eof(); + } else { + invalid = true; + } + if (invalid) { + log_warning("patchmanager", "invalid usage for {}", + name_it->value.GetString()); + } + } - // no fatal error, might just not be loaded yet - return PatchStatus::Error; - } + // build signature patch + SignaturePatch signature_data = { + .dll_name = dll_name, + .signature = data_signature_it->value.GetString(), + .replacement = data_replacement_it->value.GetString(), + .offset = offset, + .usage = usage, + }; + + // convert to memory patch + patch_data.patches_memory.emplace_back(signature_data.to_memory(&patch_data)); + patch_data.type = PatchType::Memory; + break; + } + case PatchType::Union: { + // iterate union patches + auto patches_it = patch.FindMember("patches"); + if (patches_it == patch.MemberEnd() + || !patches_it->value.IsArray()) { + log_warning("patchmanager", "unable to get patches for {}", + name_it->value.GetString()); + continue; + } + for (auto& union_patch : patches_it->value.GetArray()) { - // convert offset to RVA - auto offset = libutils::offset2rva(dll_path, memory_patch.data_offset); - if (offset == -1) { + // validate data + auto union_name_it = union_patch.FindMember("name"); + if (union_name_it == union_patch.MemberEnd() + || !union_name_it->value.IsString()) { + log_warning("patchmanager", "unable to get name for {}", + name_it->value.GetString()); + continue; + } + auto union_patch_it = union_patch.FindMember("patch"); + if (union_patch_it == union_patch.MemberEnd() + || !union_patch_it->value.IsObject()) { + log_warning("patchmanager", "unable to get patch for {}", + name_it->value.GetString()); + continue; + } - // RVA not found means unrecoverable - memory_patch.fatal_error = true; - return PatchStatus::Error; - } + // get patch data + auto union_dll_name_it = union_patch_it->value.FindMember("dllName"); + if (union_dll_name_it == union_patch_it->value.MemberEnd() + || !union_dll_name_it->value.IsString()) { + log_warning("patchmanager", "unable to get dllName for {}", + name_it->value.GetString()); + continue; + } + auto union_data_it = union_patch_it->value.FindMember("data"); + if (union_data_it == union_patch_it->value.MemberEnd() + || !union_data_it->value.IsString()) { + log_warning("patchmanager", "unable to get data for {}", + name_it->value.GetString()); + continue; + } + auto union_offset_it = union_patch_it->value.FindMember("offset"); + if (union_offset_it == union_patch_it->value.MemberEnd()) { + log_warning("patchmanager", "unable to get offset for {}", + name_it->value.GetString()); + continue; + } - // get module information - MODULEINFO module_info {}; - if (!GetModuleInformation( - GetCurrentProcess(), - module, - &module_info, - sizeof(MODULEINFO))) { + // get hex string + auto union_data_hex = union_data_it->value.GetString(); + auto union_data_hex_len = strlen(union_data_hex); + if ((union_data_hex_len % 2) != 0) { + log_warning("patchmanager", "patch hex data length has odd length for {}", + name_it->value.GetString()); + continue; + } + // convert to binary + std::shared_ptr union_data(new uint8_t[union_data_hex_len / 2]); + if (!hex2bin(union_data_hex, union_data.get())) { + log_warning("patchmanager", "failed to parse patch data from hex for {}", + name_it->value.GetString()); + continue; + } - // hmm, maybe try again sometime, not fatal - return PatchStatus::Error; - } + // get DLL name + std::string union_dll_name = union_dll_name_it->value.GetString(); - // check bounds - auto max_offset = static_cast(offset) + max_size; - auto image_size = static_cast(module_info.SizeOfImage); - if (max_offset >= image_size) { + // IIDX omnimix dll name fix + if (union_dll_name == "bm2dx.dll" && avs::game::is_model("LDJ") && avs::game::REV[0] == 'X') { + union_dll_name = avs::game::DLL_NAME; + } - // outside of bounds, invalid patch, fatal - memory_patch.fatal_error = true; - return PatchStatus::Error; - } + // BST 1/2 combined release dll name fix + if (union_dll_name == "beatstream.dll" && + (avs::game::DLL_NAME == "beatstream1.dll" + || avs::game::DLL_NAME == "beatstream2.dll")) + { + union_dll_name = avs::game::DLL_NAME; + } - // save pointer - auto dll_base = reinterpret_cast(module_info.lpBaseOfDll); - memory_patch.data_offset_ptr = reinterpret_cast(dll_base + offset); + // get offset + uint64_t union_offset = 0; + if (union_offset_it->value.IsUint64()) { + union_offset = union_offset_it->value.GetUint64(); + } else if (union_offset_it->value.IsString()) { + std::stringstream ss; + ss << union_offset_it->value.GetString(); + ss >> union_offset; + if (!ss.good() || !ss.eof()) { + log_warning("patchmanager", "invalid offset for {}", + name_it->value.GetString()); + continue; } + } else { + log_warning("patchmanager", "unable to get offset for {}", + name_it->value.GetString()); + continue; + } + + // build union patch + UnionPatch union_patch_data{ + .name = union_name_it->value.GetString(), + .dll_name = union_dll_name, + .data = std::move(union_data), + .data_len = union_data_hex_len / 2, + .offset = union_offset, + }; + + // move to list + patch_data.patches_union.emplace_back(union_patch_data); + } + std::string key = patch_data.name + ":" + patch_data.game_code; + if (setting_union_patches_enabled.find(key) != setting_union_patches_enabled.end()) { + patch_data.selected_union_name = setting_union_patches_enabled[key]; + } + // set selected union patch based on matching hex value from source DLL + if (patch_data.selected_union_name.empty() && !patch_data.patches_union.empty()) { + auto dll_path = MODULE_PATH / patch_data.patches_union[0].dll_name; + if (fileutils::file_exists(dll_path)) { + auto dll_data = std::unique_ptr>(fileutils::bin_read(dll_path)); + if (dll_data) { + for (const auto& union_patch : patch_data.patches_union) { + if (union_patch.offset + union_patch.data_len <= dll_data->size()) { + if (memcmp(&(*dll_data)[union_patch.offset], union_patch.data.get(), union_patch.data_len) == 0) { + patch_data.selected_union_name = union_patch.name; + break; + } + } + } + } + } + } + break; + } + case PatchType::Unknown: + default: + log_warning("patchmanager", "unknown patch type: {}", patch_data.type); + break; + } + + // auto apply + if (apply_patches && setting_auto_apply && patch_data.enabled) { + log_misc("patchmanager", "auto apply: {}", patch_data.name); + apply_patch(patch_data, true); + } + + // remember patch + patches.emplace_back(patch_data); + } + } + + PatchStatus is_patch_active(PatchData &patch) { + + // check patch type + switch (patch.type) { + case PatchType::Memory: { + + // iterate patches + bool enabled = false; + bool disabled = false; + for (auto &memory_patch : patch.patches_memory) { + auto max_size = std::max(memory_patch.data_enabled_len, memory_patch.data_disabled_len); + + // check for error to not try to get the pointer every frame + if (memory_patch.fatal_error) { + if (cfg::CONFIGURATOR_STANDALONE) { + patch.unverified = true; + return patch.enabled ? PatchStatus::Enabled : PatchStatus::Disabled; + } + return PatchStatus::Error; + } + + // check data pointer + if (memory_patch.data_offset_ptr == nullptr) { + + // check if file exists + auto dll_path = MODULE_PATH / memory_patch.dll_name; + if (!fileutils::file_exists(dll_path)) { + + // file does not exist so that's pretty fatal + memory_patch.fatal_error = true; + return PatchStatus::Error; } // standalone mode if (cfg::CONFIGURATOR_STANDALONE) { - auto &file = DLL_MAP[memory_patch.dll_name]; - if (!file) { - return PatchStatus::Error; + + // load file into dll map if missing + auto it = DLL_MAP.find(memory_patch.dll_name); + if (it == DLL_MAP.end()) { + DLL_MAP[memory_patch.dll_name] = + std::unique_ptr>( + fileutils::bin_read(dll_path)); } - memory_patch.data_offset_ptr = &(*file)[memory_patch.data_offset]; - } - // virtual protect - memutils::VProtectGuard guard(memory_patch.data_offset_ptr, max_size); + // check bounds + if (memory_patch.data_offset + max_size >= DLL_MAP[memory_patch.dll_name]->size()) { + + // offset outside of file bounds + memory_patch.fatal_error = true; + return PatchStatus::Error; + } else { + + // just remember raw file offset + memory_patch.data_offset_ptr = reinterpret_cast(memory_patch.data_offset); + } - // compare - if (!guard.is_bad_address() && !memcmp( - memory_patch.data_enabled.get(), - memory_patch.data_offset_ptr, - memory_patch.data_enabled_len)) - { - enabled = true; - } else if (!guard.is_bad_address() && !memcmp( - memory_patch.data_disabled.get(), - memory_patch.data_offset_ptr, - memory_patch.data_disabled_len)) - { - disabled = true; } else { + + // get module + auto module = libutils::try_module(dll_path); + if (!module) { + + // no fatal error, might just not be loaded yet + return PatchStatus::Error; + } + + // convert offset to RVA + auto offset = libutils::offset2rva(dll_path, memory_patch.data_offset); + if (offset == -1) { + + // RVA not found means unrecoverable + memory_patch.fatal_error = true; + return PatchStatus::Error; + } + + // get module information + MODULEINFO module_info {}; + if (!GetModuleInformation( + GetCurrentProcess(), + module, + &module_info, + sizeof(MODULEINFO))) { + + // hmm, maybe try again sometime, not fatal + return PatchStatus::Error; + } + + // check bounds + auto max_offset = static_cast(offset) + max_size; + auto image_size = static_cast(module_info.SizeOfImage); + if (max_offset >= image_size) { + + // outside of bounds, invalid patch, fatal + memory_patch.fatal_error = true; + return PatchStatus::Error; + } + + // save pointer + auto dll_base = reinterpret_cast(module_info.lpBaseOfDll); + memory_patch.data_offset_ptr = reinterpret_cast(dll_base + offset); + } + } + + // standalone mode + if (cfg::CONFIGURATOR_STANDALONE) { + auto &file = DLL_MAP[memory_patch.dll_name]; + if (!file) { return PatchStatus::Error; } + memory_patch.data_offset_ptr = &(*file)[memory_patch.data_offset]; } - // check detection flags - if (enabled && disabled) { - return PatchStatus::Error; - } else if (enabled) { - return PatchStatus::Enabled; - } else if (disabled) { - return PatchStatus::Disabled; + // virtual protect + memutils::VProtectGuard guard(memory_patch.data_offset_ptr, max_size); + + // compare + if (!guard.is_bad_address() && !memcmp( + memory_patch.data_enabled.get(), + memory_patch.data_offset_ptr, + memory_patch.data_enabled_len)) + { + enabled = true; + } else if (!guard.is_bad_address() && !memcmp( + memory_patch.data_disabled.get(), + memory_patch.data_offset_ptr, + memory_patch.data_disabled_len)) + { + disabled = true; } else { return PatchStatus::Error; } } - case PatchType::Signature: { + // check detection flags + if (enabled && disabled) { return PatchStatus::Error; - } - case PatchType::Unknown: - default: + } else if (enabled) { + return PatchStatus::Enabled; + } else if (disabled) { + return PatchStatus::Disabled; + } else { return PatchStatus::Error; + } + } + case PatchType::Signature: { + return PatchStatus::Error; + } + case PatchType::Union: { + return PatchStatus::Enabled; + } + case PatchType::Unknown: + default: + return PatchStatus::Error; } } @@ -957,122 +1925,200 @@ namespace overlay::windows { // check patch type switch (patch.type) { - case PatchType::Memory: { + case PatchType::Memory: { + + // iterate memory patches + for (auto &memory_patch : patch.patches_memory) { + + /* + * we won't use the cached data_offset_ptr here + * that makes it more reliable, also only happens on load/toggle + */ + + // determine source/target buffer/size + uint8_t *src_buf = active + ? memory_patch.data_disabled.get() + : memory_patch.data_enabled.get(); + size_t src_len = active + ? memory_patch.data_disabled_len + : memory_patch.data_enabled_len; + uint8_t *target_buf = active + ? memory_patch.data_enabled.get() + : memory_patch.data_disabled.get(); + size_t target_len = active + ? memory_patch.data_enabled_len + : memory_patch.data_disabled_len; + + // standalone mode + if (cfg::CONFIGURATOR_STANDALONE) { + + // load file into dll map if missing + auto it = DLL_MAP.find(memory_patch.dll_name); + if (it == DLL_MAP.end()) { + DLL_MAP[memory_patch.dll_name] = + std::unique_ptr>( + fileutils::bin_read(MODULE_PATH / memory_patch.dll_name)); + } - // iterate memory patches - for (auto &memory_patch : patch.patches_memory) { - - /* - * we won't use the cached data_offset_ptr here - * that makes it more reliable, also only happens on load/toggle - */ - - // determine source/target buffer/size - uint8_t *src_buf = active - ? memory_patch.data_disabled.get() - : memory_patch.data_enabled.get(); - size_t src_len = active - ? memory_patch.data_disabled_len - : memory_patch.data_enabled_len; - uint8_t *target_buf = active - ? memory_patch.data_enabled.get() - : memory_patch.data_disabled.get(); - size_t target_len = active - ? memory_patch.data_enabled_len - : memory_patch.data_disabled_len; + // find file + auto &dll_file = DLL_MAP[memory_patch.dll_name]; + if (!dll_file) { + return false; + } - // standalone mode - if (cfg::CONFIGURATOR_STANDALONE) { + // check bounds + auto max_len = std::max(src_len, target_len); + if (memory_patch.data_offset + max_len >= dll_file->size()) { + return false; + } - // load file into dll map if missing - auto it = DLL_MAP.find(memory_patch.dll_name); - if (it == DLL_MAP.end()) { - DLL_MAP[memory_patch.dll_name] = - std::unique_ptr>( - fileutils::bin_read(MODULE_PATH / memory_patch.dll_name)); + // copy target to memory if src matches + auto offset_ptr = &(*dll_file)[memory_patch.data_offset]; + if (memcmp(offset_ptr, src_buf, src_len) == 0) { + memcpy(offset_ptr, target_buf, target_len); + } + + } else { + + // check pointer + auto max_len = std::max(src_len, target_len); + uint8_t *offset_ptr = memory_patch.data_offset_ptr; + if (offset_ptr == nullptr) { + + // check if file exists + auto dll_path = MODULE_PATH / memory_patch.dll_name; + if (!fileutils::file_exists(dll_path)) { + return false; } - // find file - auto &dll_file = DLL_MAP[memory_patch.dll_name]; - if (!dll_file) { + // get module + auto module = libutils::try_module(dll_path); + if (!module) { return false; } - // check bounds - auto max_len = std::max(src_len, target_len); - if (memory_patch.data_offset + max_len >= dll_file->size()) { + // convert offset to RVA + auto offset = libutils::offset2rva(dll_path, (intptr_t) memory_patch.data_offset); + if (offset == -1) { return false; } - // copy target to memory if src matches - auto offset_ptr = &(*dll_file)[memory_patch.data_offset]; - if (memcmp(offset_ptr, src_buf, src_len) == 0) { - memcpy(offset_ptr, target_buf, target_len); + // get module information + MODULEINFO module_info {}; + if (!GetModuleInformation( + GetCurrentProcess(), + module, + &module_info, + sizeof(MODULEINFO))) { + return false; } - } else { + // transmute pointer + auto dll_base = reinterpret_cast(module_info.lpBaseOfDll); + auto dll_image_size = static_cast(module_info.SizeOfImage); - // check pointer - auto max_len = std::max(src_len, target_len); - uint8_t *offset_ptr = memory_patch.data_offset_ptr; - if (offset_ptr == nullptr) { + // check bounds + auto max_offset = static_cast(offset + max_len); + if (max_offset >= dll_image_size) { + return false; + } - // check if file exists - auto dll_path = MODULE_PATH / memory_patch.dll_name; - if (!fileutils::file_exists(dll_path)) { - return false; - } + // get pointer + offset_ptr = &dll_base[offset]; + } - // get module - auto module = libutils::try_module(dll_path); - if (!module) { - return false; - } + // virtual protect + memutils::VProtectGuard guard(offset_ptr, max_len); - // convert offset to RVA - auto offset = libutils::offset2rva(dll_path, (intptr_t) memory_patch.data_offset); - if (offset == -1) { - return false; - } + // copy target to memory if src matches + if (memcmp(offset_ptr, src_buf, src_len) == 0) { + memcpy(offset_ptr, target_buf, target_len); + } + } + } - // get module information - MODULEINFO module_info {}; - if (!GetModuleInformation( - GetCurrentProcess(), - module, - &module_info, - sizeof(MODULEINFO))) { - return false; - } + // success + return true; + } + case PatchType::Signature: { + return false; + } + case PatchType::Union: { + // Find the selected union patch + auto it = std::find_if(patch.patches_union.begin(), patch.patches_union.end(), + [&](const UnionPatch& up) { return up.name == patch.selected_union_name; }); + if (it != patch.patches_union.end()) { + UnionPatch& union_patch = *it; + if (cfg::CONFIGURATOR_STANDALONE) { + auto it2 = DLL_MAP.find(union_patch.dll_name); + if (it2 == DLL_MAP.end()) { + DLL_MAP[union_patch.dll_name] = + std::unique_ptr>( + fileutils::bin_read(MODULE_PATH / union_patch.dll_name)); + } - // transmute pointer - auto dll_base = reinterpret_cast(module_info.lpBaseOfDll); - auto dll_image_size = static_cast(module_info.SizeOfImage); + // find file + auto& dll_file = DLL_MAP[union_patch.dll_name]; + if (!dll_file) { + return false; + } + // Apply the selected union patch + union_patch.data_offset_ptr = reinterpret_cast(union_patch.offset + &(*dll_file)[0]); + if (union_patch.data_offset_ptr != nullptr) { + memutils::VProtectGuard guard(union_patch.data_offset_ptr, union_patch.data_len); + memcpy(union_patch.data_offset_ptr, union_patch.data.get(), union_patch.data_len); + return true; + } + } + else { + // check pointer + // check if file exists + auto dll_path = MODULE_PATH / union_patch.dll_name; + if (!fileutils::file_exists(dll_path)) { + return false; + } - // check bounds - auto max_offset = static_cast(offset + max_len); - if (max_offset >= dll_image_size) { - return false; - } + // get module + auto module = libutils::try_module(dll_path); + if (!module) { + return false; + } - // get pointer - offset_ptr = &dll_base[offset]; - } + // convert offset to RVA + auto offset = libutils::offset2rva(dll_path, (intptr_t)union_patch.offset); + if (offset == -1) { + return false; + } + + // get module information + MODULEINFO module_info{}; + if (!GetModuleInformation( + GetCurrentProcess(), + module, + &module_info, + sizeof(MODULEINFO))) { + return false; + } - // virtual protect - memutils::VProtectGuard guard(offset_ptr, max_len); + // transmute pointer + auto dll_base = reinterpret_cast(module_info.lpBaseOfDll); + auto dll_image_size = static_cast(module_info.SizeOfImage); - // copy target to memory if src matches - if (memcmp(offset_ptr, src_buf, src_len) == 0) { - memcpy(offset_ptr, target_buf, target_len); - } + // check bounds + auto max_offset = static_cast(offset + union_patch.data_len); + if (max_offset >= dll_image_size) { + return false; } + + // get pointer + uint8_t* offset_ptr = &dll_base[offset]; + union_patch.data_offset_ptr = offset_ptr; + memutils::VProtectGuard guard(offset_ptr, union_patch.data_len); + memcpy(union_patch.data_offset_ptr, union_patch.data.get(), union_patch.data_len); + return true; } - // success - return true; } - case PatchType::Signature: { return false; } default: { @@ -1234,4 +2280,4 @@ namespace overlay::windows { .data_offset_ptr = data_offset_ptr, }; } -} +} \ No newline at end of file diff --git a/overlay/windows/patch_manager.h b/overlay/windows/patch_manager.h index e61d63c..e755acb 100644 --- a/overlay/windows/patch_manager.h +++ b/overlay/windows/patch_manager.h @@ -1,6 +1,8 @@ #pragma once #include "overlay/window.h" +#include +#include namespace overlay::windows { @@ -8,6 +10,7 @@ namespace overlay::windows { Unknown, Memory, Signature, + Union, }; enum class PatchStatus { @@ -16,6 +19,14 @@ namespace overlay::windows { Enabled, }; + enum class PatchUrlStatus { + Valid, + Invalid, + Unapplied, + ValidButNoData, + Partial, + }; + struct MemoryPatch { std::string dll_name = ""; std::shared_ptr data_disabled = nullptr; @@ -36,28 +47,51 @@ namespace overlay::windows { MemoryPatch to_memory(PatchData *patch); }; + struct UnionPatch { + std::string name = ""; + std::string dll_name = ""; + std::shared_ptr data = nullptr; + size_t data_len = 0; + uint64_t offset = 0; + uint8_t* data_offset_ptr = nullptr; + std::shared_ptr initial_data = nullptr; + }; + struct PatchData { bool enabled; std::string game_code; - int datecode_min, datecode_max; + int datecode_min = 0; + int datecode_max = 0; std::string name, description; PatchType type; bool preset; std::vector patches_memory; + std::vector patches_union; + std::string selected_union_name = ""; PatchStatus last_status; bool saved; std::string hash; bool unverified = false; + std::string peIdentifier; }; + std::string get_game_identifier(const std::string& dll_path); + class PatchManager : public Window { public: PatchManager(SpiceOverlay *overlay, bool apply_patches = false); ~PatchManager() override; + bool check_module_patches(const std::string& module_name); void build_content() override; - void reload_patches(bool apply_patches = false); + void reload_local_patches(bool apply_patches = false); + void reload_remote_patches(bool apply_patches = false); + bool load_local_patches_for_dll(const std::string& dll_name, bool apply_patches); + bool load_remote_patches_for_dll(const std::string& url, const std::string& dll_name, bool apply_patches); + void hard_apply_patches(); + void load_embedded_patches(bool apply_patches); + std::vector missing_modules; private: @@ -67,15 +101,20 @@ namespace overlay::windows { static bool setting_auto_apply; static std::vector setting_auto_apply_list; static std::vector setting_patches_enabled; + static std::map setting_union_patches_enabled; + static char patch_url[1024]; + static bool idr_patches_loaded; // patches static std::vector patches; static bool patches_initialized; + bool fetch_additional_patches(const std::string& source, const std::string& target_dll_name, bool apply_patches); + void config_load(); void config_save(); - void append_patches(std::string &patches_json, bool apply_patches = false); + void append_patches(std::string &patches_json, bool apply_patches = false, std::function filter = std::function()); }; PatchStatus is_patch_active(PatchData &patch);