From 31eb639adeed31b672b56411c3e2e7eb73f15fb0 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 2 Oct 2020 22:33:42 +0200 Subject: [PATCH 1/6] Added Premake Files For PatchingSystem --- premake5.lua | 1 + src/xenia/app/premake5.lua | 1 + src/xenia/gpu/d3d12/premake5.lua | 2 ++ src/xenia/gpu/vulkan/premake5.lua | 2 ++ src/xenia/patcher/premake5.lua | 14 ++++++++++++++ 5 files changed, 20 insertions(+) create mode 100644 src/xenia/patcher/premake5.lua diff --git a/premake5.lua b/premake5.lua index f603b59af..73530547a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -285,6 +285,7 @@ workspace("xenia") include("src/xenia/hid") include("src/xenia/hid/nop") include("src/xenia/kernel") + include("src/xenia/patcher") include("src/xenia/ui") include("src/xenia/ui/spirv") include("src/xenia/ui/vulkan") diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 64ebf0db2..28a42b98e 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -28,6 +28,7 @@ project("xenia-app") "xenia-ui", "xenia-ui-spirv", "xenia-ui-vulkan", + "xenia-patcher", "xenia-vfs", }) links({ diff --git a/src/xenia/gpu/d3d12/premake5.lua b/src/xenia/gpu/d3d12/premake5.lua index bc6e95653..3c62ec900 100644 --- a/src/xenia/gpu/d3d12/premake5.lua +++ b/src/xenia/gpu/d3d12/premake5.lua @@ -39,6 +39,7 @@ project("xenia-gpu-d3d12-trace-viewer") "xenia-ui", "xenia-ui-d3d12", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", @@ -86,6 +87,7 @@ project("xenia-gpu-d3d12-trace-dump") "xenia-ui", "xenia-ui-d3d12", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index a95f8f492..333f3d74a 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -45,6 +45,7 @@ project("xenia-gpu-vulkan-trace-viewer") "xenia-ui-spirv", "xenia-ui-vulkan", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", @@ -112,6 +113,7 @@ project("xenia-gpu-vulkan-trace-dump") "xenia-ui-spirv", "xenia-ui-vulkan", "xenia-vfs", + "xenia-patcher", }) links({ "aes_128", diff --git a/src/xenia/patcher/premake5.lua b/src/xenia/patcher/premake5.lua new file mode 100644 index 000000000..ac8f255c4 --- /dev/null +++ b/src/xenia/patcher/premake5.lua @@ -0,0 +1,14 @@ +project_root = "../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-patcher") + uuid("e1c75f76-9e7b-48f6-b17e-dbd20f7a1592") + kind("StaticLib") + language("C++") + links({ + "xenia-base" + }) + defines({ + }) + recursive_platform_files() From c73cdb506a68138069853234cb143bac49520016 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 9 Oct 2020 21:02:32 +0200 Subject: [PATCH 2/6] Initial support for xex patching --- src/xenia/app/emulator_window.cc | 4 + src/xenia/app/xenia_main.cc | 4 + src/xenia/emulator.cc | 2 + src/xenia/emulator.h | 5 + src/xenia/kernel/kernel_state.cc | 3 + src/xenia/kernel/user_module.cc | 14 +++ src/xenia/kernel/user_module.h | 2 + src/xenia/patcher/patch_db.cc | 164 +++++++++++++++++++++++++++++++ src/xenia/patcher/patch_db.h | 78 +++++++++++++++ src/xenia/patcher/patcher.cc | 81 +++++++++++++++ src/xenia/patcher/patcher.h | 37 +++++++ 11 files changed, 394 insertions(+) create mode 100644 src/xenia/patcher/patch_db.cc create mode 100644 src/xenia/patcher/patch_db.h create mode 100644 src/xenia/patcher/patcher.cc create mode 100644 src/xenia/patcher/patcher.h diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 7e6d9e6b7..fb60e3057 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -1004,6 +1004,10 @@ void EmulatorWindow::UpdateTitle() { sb.Append(u8" (Preloading shaders\u2026)"); } + patcher::Patcher* patcher = emulator()->patcher(); + if (patcher && patcher->IsAnyPatchApplied()) { + sb.Append(u8" [Patches Applied]"); + } window_->SetTitle(sb.to_string_view()); } diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index ed6f1e9f1..a4f4fbcb0 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -509,6 +509,10 @@ void EmulatorApp::EmulatorThread() { }); }); + emulator_->on_patch_apply.AddListener([this]() { + app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); }); + }); + emulator_->on_terminate.AddListener([]() { if (cvars::discord) { discord::DiscordPresence::NotPlaying(); diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 0fbf74d18..d0a7861b7 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -219,6 +219,8 @@ X_STATUS Emulator::Setup( // Bring up the virtual filesystem used by the kernel. file_system_ = std::make_unique(); + patcher_ = std::make_unique(storage_root_); + // Shared kernel state. kernel_state_ = std::make_unique(this); diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 96d81d00a..0e3b9164b 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -21,6 +21,7 @@ #include "xenia/base/exception_handler.h" #include "xenia/kernel/kernel_state.h" #include "xenia/memory.h" +#include "xenia/patcher/patcher.h" #include "xenia/vfs/virtual_file_system.h" #include "xenia/xbox.h" @@ -154,6 +155,8 @@ class Emulator { // This is effectively the guest operating system. kernel::KernelState* kernel_state() const { return kernel_state_.get(); } + patcher::Patcher* patcher() const { return patcher_.get(); } + // Initializes the emulator and configures all components. // The given window is used for display and the provided functions are used // to create subsystems as required. @@ -202,6 +205,7 @@ class Emulator { public: xe::Delegate on_launch; xe::Delegate on_shader_storage_initialization; + xe::Delegate<> on_patch_apply; xe::Delegate<> on_terminate; xe::Delegate<> on_exit; @@ -237,6 +241,7 @@ class Emulator { std::unique_ptr export_resolver_; std::unique_ptr file_system_; + std::unique_ptr patcher_; std::unique_ptr kernel_state_; diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 7d60aac64..8c514eb17 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -406,6 +406,9 @@ object_ref KernelState::LoadUserModule( } module->Dump(); + emulator_->patcher()->ApplyPatchesForTitle(memory_, module->title_id(), + module->hash()); + emulator_->on_patch_apply(); if (module->is_dll_module() && module->entry_point() && call_entry) { // Call DllMain(DLL_PROCESS_ATTACH): diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index a85a919c6..407922833 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -13,6 +13,7 @@ #include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" +#include "xenia/base/xxhash.h" #include "xenia/cpu/elf_module.h" #include "xenia/cpu/processor.h" #include "xenia/cpu/xex_module.h" @@ -72,6 +73,12 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { // Load the module. result = LoadFromMemory(mmap->data(), mmap->size()); + if (XSUCCEEDED(result)) { + XXH3_state_t hash_state; + XXH3_64bits_reset(&hash_state); + XXH3_64bits_update(&hash_state, mmap->data(), mmap->size()); + hash_ = XXH3_64bits_digest(&hash_state); + } } else { std::vector buffer(fs_entry->size()); @@ -93,6 +100,12 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { // Load the module. result = LoadFromMemory(buffer.data(), bytes_read); + // Generate xex hash + XXH3_state_t hash_state; + XXH3_64bits_reset(&hash_state); + XXH3_64bits_update(&hash_state, buffer.data(), bytes_read); + hash_ = XXH3_64bits_digest(&hash_state); + // Close the file. file->Destroy(); } @@ -128,6 +141,7 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { } } + XELOGI("Module hash: {:16X} for {}", hash_, name_); return LoadXexContinue(); } diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index f904f8a4f..7b949ce86 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -38,6 +38,7 @@ class UserModule : public XModule { const std::string& path() const override { return path_; } const std::string& name() const override { return name_; } + uint64_t hash() const { return hash_; } enum ModuleFormat { kModuleFormatUndefined = 0, @@ -100,6 +101,7 @@ class UserModule : public XModule { std::string name_; std::string path_; + uint64_t hash_ = -1; uint32_t guest_xex_header_ = 0; ModuleFormat module_format_ = kModuleFormatUndefined; diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc new file mode 100644 index 000000000..9135e254c --- /dev/null +++ b/src/xenia/patcher/patch_db.cc @@ -0,0 +1,164 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include + +#include "xenia/base/cvar.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/logging.h" +#include "xenia/memory.h" + +#include "xenia/patcher/patch_db.h" + +DEFINE_bool(apply_patches, true, "Enables patching functionality", "General"); + +namespace xe { +namespace patcher { + +PatchDB::PatchDB(const std::filesystem::path patches_root) { + patches_root_ = patches_root; + LoadPatches(); +} + +PatchDB::~PatchDB() {} + +void PatchDB::LoadPatches() { + if (!cvars::apply_patches) { + return; + } + + const std::filesystem::path patches_directory = patches_root_ / "patches"; + const std::vector& patch_files = + filesystem::ListFiles(patches_directory); + const std::regex file_name_regex_match = + std::regex("^[A-Fa-f0-9]{8}.*\\.patch$"); + + for (const xe::filesystem::FileInfo& patch_file : patch_files) { + // Skip files that doesn't have only title_id as name and .patch as + // extension + if (!std::regex_match(path_to_utf8(patch_file.name), + file_name_regex_match)) { + XELOGE("PatchDB: Skipped loading file {} due to incorrect filename", + path_to_utf8(patch_file.name)); + continue; + } + + const PatchFileEntry loaded_title_patches = + ReadPatchFile(patch_file.path / patch_file.name); + if (loaded_title_patches.title_id != -1) { + loaded_patches.push_back(loaded_title_patches); + } + } + XELOGI("PatchDB: Loaded patches for {} titles", loaded_patches.size()); +} + +PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) { + PatchFileEntry patchFile; + std::shared_ptr patch_toml_fields; + + try { + patch_toml_fields = cpptoml::parse_file(path_to_utf8(file_path)); + } catch (...) { + XELOGE("PatchDB: Cannot load patch file: {}", path_to_utf8(file_path)); + patchFile.title_id = -1; + return patchFile; + }; + + auto title_name = patch_toml_fields->get_as("title_name"); + auto title_id = patch_toml_fields->get_as("title_id"); + auto title_hash = patch_toml_fields->get_as("hash"); + + patchFile.title_id = strtoul((*title_id).c_str(), NULL, 16); + patchFile.hash = strtoull((*title_hash).c_str(), NULL, 16); + patchFile.title_name = *title_name; + + auto patch_table = patch_toml_fields->get_table_array("patch"); + + for (auto patch_table_entry : *patch_table) { + PatchInfoEntry patch = PatchInfoEntry(); + auto patch_name = *patch_table_entry->get_as("name"); + auto patch_desc = *patch_table_entry->get_as("desc"); + auto patch_author = *patch_table_entry->get_as("author"); + auto is_enabled = *patch_table_entry->get_as("is_enabled"); + + patch.id = 0; // Todo(Gliniak): Implement id for future GUI stuff + patch.patch_name = patch_name; + patch.patch_desc = patch_desc; + patch.patch_author = patch_author; + patch.is_enabled = is_enabled; + + // Iterate through all available data sizes + for (const auto& type : patch_data_types_size) { + auto patch_entries = ReadPatchData(type.first, patch_table_entry); + + for (const PatchDataEntry& patch_entry : *patch_entries) { + patch.patch_data.push_back(patch_entry); + } + } + patchFile.patch_info.push_back(patch); + } + return patchFile; +} + +std::vector* PatchDB::ReadPatchData( + const std::string size_type, + const std::shared_ptr& patch_table) { + std::vector* patch_data = new std::vector(); + auto patch_data_tarr = patch_table->get_table_array(size_type); + + if (!patch_data_tarr) { + return patch_data; + } + + for (const auto& patch_data_table : *patch_data_tarr) { + uint32_t address = *patch_data_table->get_as("address"); + uint64_t value = *patch_data_table->get_as("value"); + + if (size_type == "f32") { + float fvalue = float(*patch_data_table->get_as("value")); + value = *(reinterpret_cast(&fvalue)); + } else if (size_type == "f64") { + double dvalue = *patch_data_table->get_as("value"); + value = *(reinterpret_cast(&dvalue)); + } + + PatchDataEntry patchData = {GetAllocSize(size_type), address, value}; + patch_data->push_back(patchData); + } + return patch_data; +} + +std::vector PatchDB::GetTitlePatches(uint32_t title_id, + const uint64_t hash) { + std::vector title_patches; + + std::copy_if(loaded_patches.cbegin(), loaded_patches.cend(), + std::back_inserter(title_patches), + [=](const PatchFileEntry entry) { + return entry.title_id == title_id && + (!entry.hash || entry.hash == hash); + }); + + return title_patches; +} + +uint8_t PatchDB::GetAllocSize(const std::string provided_size) { + uint8_t alloc_size = 0; + + auto itr = patch_data_types_size.find(provided_size); + if (itr == patch_data_types_size.cend()) { + XELOGW("PatchDB: Unsupported patch type!"); + return alloc_size; + } + + alloc_size = uint8_t(itr->second); + return alloc_size; +} + +} // namespace patcher +} // namespace xe diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h new file mode 100644 index 000000000..585336e7f --- /dev/null +++ b/src/xenia/patcher/patch_db.h @@ -0,0 +1,78 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_PATCH_DB_H_ +#define XENIA_PATCH_DB_H_ + +#include + +#include "third_party/cpptoml/include/cpptoml.h" + +namespace xe { +namespace patcher { +struct PatchDataEntry { + const uint8_t alloc_size_; + const uint32_t memory_address_; + const uint64_t new_value_; + + PatchDataEntry(const uint8_t alloc_size, const uint32_t memory_address, + const uint64_t new_value) + : alloc_size_(alloc_size), + memory_address_(memory_address), + new_value_(new_value){}; +}; + +struct PatchInfoEntry { + uint32_t id; + std::string patch_name; + std::string patch_desc; + std::string patch_author; + std::vector patch_data; + bool is_enabled; +}; + +struct PatchFileEntry { + uint32_t title_id; + std::string title_name; + uint64_t hash = 0; + std::vector patch_info; +}; + +class PatchDB { + public: + PatchDB(const std::filesystem::path patches_root); + ~PatchDB(); + + void LoadPatches(); + + PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path); + std::vector* ReadPatchData( + const std::string size_type, + const std::shared_ptr& patch_table); + + std::vector GetTitlePatches(uint32_t title_id, + const uint64_t hash); + std::vector& GetAllPatches() { return loaded_patches; } + + private: + std::vector loaded_patches; + std::filesystem::path patches_root_; + + uint8_t GetAllocSize(const std::string provided_size); + + const std::map patch_data_types_size = { + {"f64", sizeof(uint64_t)}, {"f32", sizeof(uint32_t)}, + {"be64", sizeof(uint64_t)}, {"be32", sizeof(uint32_t)}, + {"be16", sizeof(uint16_t)}, {"be8", sizeof(uint8_t)}}; +}; + +} // namespace patcher +} // namespace xe + +#endif // XENIA_PATCH_LOADER_H_ diff --git a/src/xenia/patcher/patcher.cc b/src/xenia/patcher/patcher.cc new file mode 100644 index 000000000..76bfadc65 --- /dev/null +++ b/src/xenia/patcher/patcher.cc @@ -0,0 +1,81 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ +#include "xenia/patcher/patcher.h" + +#include "xenia/base/logging.h" + +namespace xe { +namespace patcher { + +Patcher::Patcher(const std::filesystem::path patches_root) { + is_any_patch_applied_ = false; + patch_db = new PatchDB(patches_root); +} + +Patcher::~Patcher() {} + +void Patcher::ApplyPatchesForTitle(Memory* memory, const uint32_t title_id, + const uint64_t hash) { + const auto title_patches = patch_db->GetTitlePatches(title_id, hash); + + for (const PatchFileEntry& patchFile : title_patches) { + for (const PatchInfoEntry& patchEntry : patchFile.patch_info) { + if (!patchEntry.is_enabled) { + continue; + } + XELOGE("Patcher: Applying patch for: {}({:08X}) - {}", + patchFile.title_name, patchFile.title_id, patchEntry.patch_name); + ApplyPatch(memory, &patchEntry); + } + } +} + +void Patcher::ApplyPatch(Memory* memory, const PatchInfoEntry* patch) { + for (const PatchDataEntry& patch_data_entry : patch->patch_data) { + uint32_t old_address_protect = 0; + auto address = memory->TranslateVirtual(patch_data_entry.memory_address_); + auto heap = memory->LookupHeap(patch_data_entry.memory_address_); + if (!heap) { + continue; + } + + heap->QueryProtect(patch_data_entry.memory_address_, &old_address_protect); + + heap->Protect(patch_data_entry.memory_address_, + patch_data_entry.alloc_size_, + kMemoryProtectRead | kMemoryProtectWrite); + + switch (patch_data_entry.alloc_size_) { + case 1: + xe::store_and_swap(address, uint8_t(patch_data_entry.new_value_)); + break; + case 2: + xe::store_and_swap(address, uint16_t(patch_data_entry.new_value_)); + break; + case 4: + xe::store_and_swap(address, uint32_t(patch_data_entry.new_value_)); + break; + case 8: + xe::store_and_swap(address, uint64_t(patch_data_entry.new_value_)); + break; + default: + XELOGE("Patcher: Unsupported patch allocation size - {}", + patch_data_entry.alloc_size_); + break; + } + // Restore previous protection + heap->Protect(patch_data_entry.memory_address_, + patch_data_entry.alloc_size_, old_address_protect); + + is_any_patch_applied_ = true; + } +} + +} // namespace patcher +} // namespace xe diff --git a/src/xenia/patcher/patcher.h b/src/xenia/patcher/patcher.h new file mode 100644 index 000000000..0f6e27328 --- /dev/null +++ b/src/xenia/patcher/patcher.h @@ -0,0 +1,37 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_PATCHER_H_ +#define XENIA_PATCHER_H_ + +#include "xenia/memory.h" +#include "xenia/patcher/patch_db.h" + +namespace xe { +namespace patcher { + +class Patcher { + public: + Patcher(const std::filesystem::path patches_root); + ~Patcher(); + + void ApplyPatch(Memory* memory, const PatchInfoEntry* patch); + void ApplyPatchesForTitle(Memory* memory, const uint32_t title_id, + const uint64_t hash); + + bool IsAnyPatchApplied() { return is_any_patch_applied_; } + + private: + PatchDB* patch_db; + bool is_any_patch_applied_; +}; + +} // namespace patcher +} // namespace xe +#endif From fc16e3dc4075bb09962236181acc76f26ee23865 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 4 Feb 2022 12:15:47 +0100 Subject: [PATCH 3/6] Support for patch types: - float - double - string - u16string - byte_array Plus some smaller changes --- src/xenia/base/string_util.h | 26 ++++++++++ src/xenia/kernel/user_module.cc | 2 +- src/xenia/patcher/patch_db.cc | 91 ++++++++++++++++++++------------- src/xenia/patcher/patch_db.h | 89 +++++++++++++++++++++++++------- src/xenia/patcher/patcher.cc | 27 +++------- 5 files changed, 161 insertions(+), 74 deletions(-) diff --git a/src/xenia/base/string_util.h b/src/xenia/base/string_util.h index 09575ff56..4cbbcc6b3 100644 --- a/src/xenia/base/string_util.h +++ b/src/xenia/base/string_util.h @@ -111,6 +111,32 @@ inline size_t copy_and_swap_maybe_truncating(char16_t* dest, return chars_copied; } +inline bool hex_string_to_array(std::vector& output_array, + const std::string_view value) { + output_array.reserve((value.size() + 1) / 2); + + size_t remaining_length = value.size(); + while (remaining_length > 0) { + uint8_t chars_to_read = remaining_length > 1 ? 2 : 1; + const char* substring_pointer = + value.data() + value.size() - remaining_length; + + uint8_t string_value = 0; + std::from_chars_result result = std::from_chars( + substring_pointer, substring_pointer + chars_to_read, string_value, 16); + + if (result.ec != std::errc() || + result.ptr != substring_pointer + chars_to_read) { + output_array.clear(); + return false; + } + + output_array.push_back(string_value); + remaining_length -= chars_to_read; + } + return true; +} + inline std::string to_hex_string(uint32_t value) { return fmt::format("{:08X}", value); } diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index 407922833..e9ed11e2b 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -141,7 +141,7 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { } } - XELOGI("Module hash: {:16X} for {}", hash_, name_); + XELOGI("Module hash: {:016X} for {}", hash_, name_); return LoadXexContinue(); } diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc index 9135e254c..ac3c19b0b 100644 --- a/src/xenia/patcher/patch_db.cc +++ b/src/xenia/patcher/patch_db.cc @@ -36,7 +36,7 @@ void PatchDB::LoadPatches() { const std::vector& patch_files = filesystem::ListFiles(patches_directory); const std::regex file_name_regex_match = - std::regex("^[A-Fa-f0-9]{8}.*\\.patch$"); + std::regex("^[A-Fa-f0-9]{8}.*\\.patch\\.toml$"); for (const xe::filesystem::FileInfo& patch_file : patch_files) { // Skip files that doesn't have only title_id as name and .patch as @@ -93,11 +93,13 @@ PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) { patch.is_enabled = is_enabled; // Iterate through all available data sizes - for (const auto& type : patch_data_types_size) { - auto patch_entries = ReadPatchData(type.first, patch_table_entry); + for (const auto& patch_data_type : patch_data_types_size) { + bool success = + ReadPatchData(patch.patch_data, patch_data_type, patch_table_entry); - for (const PatchDataEntry& patch_entry : *patch_entries) { - patch.patch_data.push_back(patch_entry); + if (!success) { + XELOGE("PatchDB: Cannot read patch {}", patch_name); + break; } } patchFile.patch_info.push_back(patch); @@ -105,32 +107,64 @@ PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) { return patchFile; } -std::vector* PatchDB::ReadPatchData( - const std::string size_type, +bool PatchDB::ReadPatchData( + std::vector& patch_data, + const std::pair data_type, const std::shared_ptr& patch_table) { - std::vector* patch_data = new std::vector(); - auto patch_data_tarr = patch_table->get_table_array(size_type); - + auto patch_data_tarr = patch_table->get_table_array(data_type.first); if (!patch_data_tarr) { - return patch_data; + return true; } for (const auto& patch_data_table : *patch_data_tarr) { uint32_t address = *patch_data_table->get_as("address"); - uint64_t value = *patch_data_table->get_as("value"); + size_t alloc_size = (size_t)data_type.second.size; - if (size_type == "f32") { - float fvalue = float(*patch_data_table->get_as("value")); - value = *(reinterpret_cast(&fvalue)); - } else if (size_type == "f64") { - double dvalue = *patch_data_table->get_as("value"); - value = *(reinterpret_cast(&dvalue)); + switch (data_type.second.type) { + case PatchDataType::f64: { + double val = *patch_data_table->get_as("value"); + uint64_t value = *reinterpret_cast(&val); + patch_data.push_back({address, PatchDataValue(alloc_size, value)}); + break; + } + case PatchDataType::f32: { + float value = float(*patch_data_table->get_as("value")); + patch_data.push_back({address, PatchDataValue(alloc_size, value)}); + break; + } + case PatchDataType::string: { + std::string value = *patch_data_table->get_as("value"); + patch_data.push_back({address, PatchDataValue(value)}); + break; + } + case PatchDataType::u16string: { + std::u16string value = + xe::to_utf16(*patch_data_table->get_as("value")); + patch_data.push_back({address, PatchDataValue(value)}); + break; + } + case PatchDataType::byte_array: { + std::vector data; + const std::string value = + *patch_data_table->get_as("value"); + + bool success = string_util::hex_string_to_array(data, value); + if (!success) { + XELOGE("PatchDB: Cannot load patch due to invalid data!"); + return false; + } + + patch_data.push_back({address, PatchDataValue(value.size() / 2, data)}); + break; + } + default: { + uint64_t value = *patch_data_table->get_as("value"); + patch_data.push_back({address, PatchDataValue(alloc_size, value)}); + break; + } } - - PatchDataEntry patchData = {GetAllocSize(size_type), address, value}; - patch_data->push_back(patchData); } - return patch_data; + return true; } std::vector PatchDB::GetTitlePatches(uint32_t title_id, @@ -147,18 +181,5 @@ std::vector PatchDB::GetTitlePatches(uint32_t title_id, return title_patches; } -uint8_t PatchDB::GetAllocSize(const std::string provided_size) { - uint8_t alloc_size = 0; - - auto itr = patch_data_types_size.find(provided_size); - if (itr == patch_data_types_size.cend()) { - XELOGW("PatchDB: Unsupported patch type!"); - return alloc_size; - } - - alloc_size = uint8_t(itr->second); - return alloc_size; -} - } // namespace patcher } // namespace xe diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h index 585336e7f..5d237d1d4 100644 --- a/src/xenia/patcher/patch_db.h +++ b/src/xenia/patcher/patch_db.h @@ -16,16 +16,46 @@ namespace xe { namespace patcher { -struct PatchDataEntry { - const uint8_t alloc_size_; - const uint32_t memory_address_; - const uint64_t new_value_; - PatchDataEntry(const uint8_t alloc_size, const uint32_t memory_address, - const uint64_t new_value) - : alloc_size_(alloc_size), - memory_address_(memory_address), - new_value_(new_value){}; +struct PatchDataValue { + const size_t alloc_size_; + const uint8_t* patch_data_ptr_; + + PatchDataValue(const size_t alloc_size, const uint64_t value) + : alloc_size_(alloc_size) { + patch_data_ptr_ = new uint8_t[alloc_size_]; + memcpy((void*)patch_data_ptr_, &value, alloc_size); + }; + + PatchDataValue(const size_t alloc_size, const float value) + : alloc_size_(alloc_size) { + patch_data_ptr_ = new uint8_t[alloc_size_]; + memcpy((void*)patch_data_ptr_, &value, alloc_size); + }; + + PatchDataValue(const size_t alloc_size, const std::vector value) + : alloc_size_(alloc_size) { + patch_data_ptr_ = new uint8_t[alloc_size_]; + memcpy((void*)patch_data_ptr_, value.data(), alloc_size); + }; + + PatchDataValue(const std::string value) : alloc_size_(value.size()) { + patch_data_ptr_ = new uint8_t[alloc_size_]; + memcpy((void*)patch_data_ptr_, value.c_str(), alloc_size_); + }; + + PatchDataValue(const std::u16string value) : alloc_size_(value.size() * 2) { + patch_data_ptr_ = new uint8_t[alloc_size_]; + memcpy((void*)patch_data_ptr_, value.c_str(), alloc_size_); + }; +}; + +struct PatchDataEntry { + const uint32_t memory_address_; + const PatchDataValue new_data_; + + PatchDataEntry(const uint32_t memory_address, const PatchDataValue new_data) + : memory_address_(memory_address), new_data_(new_data){}; }; struct PatchInfoEntry { @@ -44,6 +74,25 @@ struct PatchFileEntry { std::vector patch_info; }; +enum class PatchDataType { + be8, + be16, + be32, + be64, + f32, + f64, + string, + u16string, + byte_array +}; + +struct PatchData { + uint8_t size; + PatchDataType type; + + PatchData(uint8_t size_, PatchDataType type_) : size(size_), type(type_){}; +}; + class PatchDB { public: PatchDB(const std::filesystem::path patches_root); @@ -52,9 +101,9 @@ class PatchDB { void LoadPatches(); PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path); - std::vector* ReadPatchData( - const std::string size_type, - const std::shared_ptr& patch_table); + bool ReadPatchData(std::vector& patch_data, + const std::pair data_type, + const std::shared_ptr& patch_table); std::vector GetTitlePatches(uint32_t title_id, const uint64_t hash); @@ -64,12 +113,16 @@ class PatchDB { std::vector loaded_patches; std::filesystem::path patches_root_; - uint8_t GetAllocSize(const std::string provided_size); - - const std::map patch_data_types_size = { - {"f64", sizeof(uint64_t)}, {"f32", sizeof(uint32_t)}, - {"be64", sizeof(uint64_t)}, {"be32", sizeof(uint32_t)}, - {"be16", sizeof(uint16_t)}, {"be8", sizeof(uint8_t)}}; + const std::map patch_data_types_size = { + {"string", PatchData(0, PatchDataType::string)}, + {"u16string", PatchData(0, PatchDataType::u16string)}, + {"array", PatchData(0, PatchDataType::byte_array)}, + {"f64", PatchData(sizeof(uint64_t), PatchDataType::f64)}, + {"f32", PatchData(sizeof(uint32_t), PatchDataType::f32)}, + {"be64", PatchData(sizeof(uint64_t), PatchDataType::be64)}, + {"be32", PatchData(sizeof(uint32_t), PatchDataType::be32)}, + {"be16", PatchData(sizeof(uint16_t), PatchDataType::be16)}, + {"be8", PatchData(sizeof(uint8_t), PatchDataType::be8)}}; }; } // namespace patcher diff --git a/src/xenia/patcher/patcher.cc b/src/xenia/patcher/patcher.cc index 76bfadc65..b35e92534 100644 --- a/src/xenia/patcher/patcher.cc +++ b/src/xenia/patcher/patcher.cc @@ -48,30 +48,17 @@ void Patcher::ApplyPatch(Memory* memory, const PatchInfoEntry* patch) { heap->QueryProtect(patch_data_entry.memory_address_, &old_address_protect); heap->Protect(patch_data_entry.memory_address_, - patch_data_entry.alloc_size_, + (uint32_t)patch_data_entry.new_data_.alloc_size_, kMemoryProtectRead | kMemoryProtectWrite); - switch (patch_data_entry.alloc_size_) { - case 1: - xe::store_and_swap(address, uint8_t(patch_data_entry.new_value_)); - break; - case 2: - xe::store_and_swap(address, uint16_t(patch_data_entry.new_value_)); - break; - case 4: - xe::store_and_swap(address, uint32_t(patch_data_entry.new_value_)); - break; - case 8: - xe::store_and_swap(address, uint64_t(patch_data_entry.new_value_)); - break; - default: - XELOGE("Patcher: Unsupported patch allocation size - {}", - patch_data_entry.alloc_size_); - break; - } + xe::copy_and_swap(address, + (uint8_t*)patch_data_entry.new_data_.patch_data_ptr_, + patch_data_entry.new_data_.alloc_size_); + // Restore previous protection heap->Protect(patch_data_entry.memory_address_, - patch_data_entry.alloc_size_, old_address_protect); + (uint32_t)patch_data_entry.new_data_.alloc_size_, + old_address_protect); is_any_patch_applied_ = true; } From 585b208fc01623d189b11edb9e4351c754e41871 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 29 Apr 2022 09:41:45 +0200 Subject: [PATCH 4/6] Added support for multiple game hashes --- src/xenia/patcher/patch_db.cc | 34 +++++++++++++++++++++++++--------- src/xenia/patcher/patch_db.h | 6 ++++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc index ac3c19b0b..c1e1d0267 100644 --- a/src/xenia/patcher/patch_db.cc +++ b/src/xenia/patcher/patch_db.cc @@ -15,7 +15,7 @@ #include "xenia/patcher/patch_db.h" -DEFINE_bool(apply_patches, true, "Enables patching functionality", "General"); +DEFINE_bool(apply_patches, true, "Enables custom patching functionality", "General"); namespace xe { namespace patcher { @@ -71,11 +71,10 @@ PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) { auto title_name = patch_toml_fields->get_as("title_name"); auto title_id = patch_toml_fields->get_as("title_id"); - auto title_hash = patch_toml_fields->get_as("hash"); patchFile.title_id = strtoul((*title_id).c_str(), NULL, 16); - patchFile.hash = strtoull((*title_hash).c_str(), NULL, 16); patchFile.title_name = *title_name; + ReadHash(patchFile, patch_toml_fields); auto patch_table = patch_toml_fields->get_table_array("patch"); @@ -171,15 +170,32 @@ std::vector PatchDB::GetTitlePatches(uint32_t title_id, const uint64_t hash) { std::vector title_patches; - std::copy_if(loaded_patches.cbegin(), loaded_patches.cend(), - std::back_inserter(title_patches), - [=](const PatchFileEntry entry) { - return entry.title_id == title_id && - (!entry.hash || entry.hash == hash); - }); + std::copy_if( + loaded_patches.cbegin(), loaded_patches.cend(), + std::back_inserter(title_patches), [=](const PatchFileEntry entry) { + bool hash_exist = std::find(entry.hashes.cbegin(), entry.hashes.cend(), + hash) != entry.hashes.cend(); + + return entry.title_id == title_id && + (entry.hashes.empty() || hash_exist); + }); return title_patches; } +void PatchDB::ReadHash(PatchFileEntry &patchEntry, + std::shared_ptr patch_toml_fields) { + auto title_hashes = patch_toml_fields->get_array_of("hash"); + + for (const auto& hash : *title_hashes) { + patchEntry.hashes.push_back(strtoull(hash.c_str(), NULL, 16)); + } + + auto single_hash = patch_toml_fields->get_as("hash"); + if (single_hash) { + patchEntry.hashes.push_back(strtoull((*single_hash).c_str(), NULL, 16)); + } +} + } // namespace patcher } // namespace xe diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h index 5d237d1d4..c6adecf26 100644 --- a/src/xenia/patcher/patch_db.h +++ b/src/xenia/patcher/patch_db.h @@ -70,7 +70,7 @@ struct PatchInfoEntry { struct PatchFileEntry { uint32_t title_id; std::string title_name; - uint64_t hash = 0; + std::vector hashes; std::vector patch_info; }; @@ -123,8 +123,10 @@ class PatchDB { {"be32", PatchData(sizeof(uint32_t), PatchDataType::be32)}, {"be16", PatchData(sizeof(uint16_t), PatchDataType::be16)}, {"be8", PatchData(sizeof(uint8_t), PatchDataType::be8)}}; -}; + void ReadHash(PatchFileEntry &patchEntry, + std::shared_ptr patch_toml_fields); +}; } // namespace patcher } // namespace xe From d78fd19ab4c79b55276466dc6091409b0c708ad2 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Fri, 29 Apr 2022 20:33:21 +0200 Subject: [PATCH 5/6] Fixed incorrect hash generation + lint fixes --- src/xenia/cpu/xex_module.cc | 2 -- src/xenia/kernel/user_module.cc | 22 ++++++++++------------ src/xenia/kernel/user_module.h | 1 + src/xenia/patcher/patch_db.cc | 5 +++-- src/xenia/patcher/patch_db.h | 2 +- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/xenia/cpu/xex_module.cc b/src/xenia/cpu/xex_module.cc index 672d44666..b0b963467 100644 --- a/src/xenia/cpu/xex_module.cc +++ b/src/xenia/cpu/xex_module.cc @@ -930,8 +930,6 @@ bool XexModule::Load(const std::string_view name, const std::string_view path, name_ = name; path_ = path; - uint8_t* data = memory()->TranslateVirtual(base_address_); - // Load in the XEX basefile // We'll try using both XEX2 keys to see if any give a valid PE int result_code = ReadImage(xex_addr, xex_length, false); diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index e9ed11e2b..c12da3705 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -73,12 +73,6 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { // Load the module. result = LoadFromMemory(mmap->data(), mmap->size()); - if (XSUCCEEDED(result)) { - XXH3_state_t hash_state; - XXH3_64bits_reset(&hash_state); - XXH3_64bits_update(&hash_state, mmap->data(), mmap->size()); - hash_ = XXH3_64bits_digest(&hash_state); - } } else { std::vector buffer(fs_entry->size()); @@ -100,12 +94,6 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { // Load the module. result = LoadFromMemory(buffer.data(), bytes_read); - // Generate xex hash - XXH3_state_t hash_state; - XXH3_64bits_reset(&hash_state); - XXH3_64bits_update(&hash_state, buffer.data(), bytes_read); - hash_ = XXH3_64bits_digest(&hash_state); - // Close the file. file->Destroy(); } @@ -141,6 +129,8 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) { } } + CalculateHash(); + XELOGI("Module hash: {:016X} for {}", hash_, name_); return LoadXexContinue(); } @@ -818,5 +808,13 @@ void UserModule::Dump() { xe::logging::AppendLogLine(xe::LogLevel::Info, 'i', sb.to_string_view()); } +void UserModule::CalculateHash() { + uint8_t* base_adr = memory()->TranslateVirtual(xex_module()->base_address()); + + XXH3_state_t hash_state; + XXH3_64bits_reset(&hash_state); + XXH3_64bits_update(&hash_state, base_adr, xex_module()->image_size()); + hash_ = XXH3_64bits_digest(&hash_state); +} } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/user_module.h b/src/xenia/kernel/user_module.h index 7b949ce86..a11deba73 100644 --- a/src/xenia/kernel/user_module.h +++ b/src/xenia/kernel/user_module.h @@ -98,6 +98,7 @@ class UserModule : public XModule { private: X_STATUS LoadXexContinue(); + void CalculateHash(); std::string name_; std::string path_; diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc index c1e1d0267..9a830e226 100644 --- a/src/xenia/patcher/patch_db.cc +++ b/src/xenia/patcher/patch_db.cc @@ -15,7 +15,8 @@ #include "xenia/patcher/patch_db.h" -DEFINE_bool(apply_patches, true, "Enables custom patching functionality", "General"); +DEFINE_bool(apply_patches, true, "Enables custom patching functionality", + "General"); namespace xe { namespace patcher { @@ -183,7 +184,7 @@ std::vector PatchDB::GetTitlePatches(uint32_t title_id, return title_patches; } -void PatchDB::ReadHash(PatchFileEntry &patchEntry, +void PatchDB::ReadHash(PatchFileEntry& patchEntry, std::shared_ptr patch_toml_fields) { auto title_hashes = patch_toml_fields->get_array_of("hash"); diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h index c6adecf26..f4991cfb1 100644 --- a/src/xenia/patcher/patch_db.h +++ b/src/xenia/patcher/patch_db.h @@ -124,7 +124,7 @@ class PatchDB { {"be16", PatchData(sizeof(uint16_t), PatchDataType::be16)}, {"be8", PatchData(sizeof(uint8_t), PatchDataType::be8)}}; - void ReadHash(PatchFileEntry &patchEntry, + void ReadHash(PatchFileEntry& patchEntry, std::shared_ptr patch_toml_fields); }; } // namespace patcher From ccbb5a2ebf4e411d7c4b05feb4a3974fe4d7e75e Mon Sep 17 00:00:00 2001 From: Gliniak Date: Sat, 30 Apr 2022 11:45:22 +0200 Subject: [PATCH 6/6] Cleanup --- src/xenia/patcher/patch_db.cc | 4 +--- src/xenia/patcher/patch_db.h | 11 +++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/xenia/patcher/patch_db.cc b/src/xenia/patcher/patch_db.cc index 9a830e226..20f1e5831 100644 --- a/src/xenia/patcher/patch_db.cc +++ b/src/xenia/patcher/patch_db.cc @@ -36,8 +36,7 @@ void PatchDB::LoadPatches() { const std::filesystem::path patches_directory = patches_root_ / "patches"; const std::vector& patch_files = filesystem::ListFiles(patches_directory); - const std::regex file_name_regex_match = - std::regex("^[A-Fa-f0-9]{8}.*\\.patch\\.toml$"); + const std::regex file_name_regex_match = std::regex(patch_filename_regex); for (const xe::filesystem::FileInfo& patch_file : patch_files) { // Skip files that doesn't have only title_id as name and .patch as @@ -150,7 +149,6 @@ bool PatchDB::ReadPatchData( bool success = string_util::hex_string_to_array(data, value); if (!success) { - XELOGE("PatchDB: Cannot load patch due to invalid data!"); return false; } diff --git a/src/xenia/patcher/patch_db.h b/src/xenia/patcher/patch_db.h index f4991cfb1..e2baf4675 100644 --- a/src/xenia/patcher/patch_db.h +++ b/src/xenia/patcher/patch_db.h @@ -110,8 +110,11 @@ class PatchDB { std::vector& GetAllPatches() { return loaded_patches; } private: - std::vector loaded_patches; - std::filesystem::path patches_root_; + void ReadHash(PatchFileEntry& patchEntry, + std::shared_ptr patch_toml_fields); + + inline static const std::string patch_filename_regex = + "^[A-Fa-f0-9]{8}.*\\.patch\\.toml$"; const std::map patch_data_types_size = { {"string", PatchData(0, PatchDataType::string)}, @@ -124,8 +127,8 @@ class PatchDB { {"be16", PatchData(sizeof(uint16_t), PatchDataType::be16)}, {"be8", PatchData(sizeof(uint8_t), PatchDataType::be8)}}; - void ReadHash(PatchFileEntry& patchEntry, - std::shared_ptr patch_toml_fields); + std::vector loaded_patches; + std::filesystem::path patches_root_; }; } // namespace patcher } // namespace xe