Initial support for xex patching

This commit is contained in:
Gliniak 2020-10-09 21:02:32 +02:00
parent 88ddfe86db
commit 5a933156c0
7 changed files with 91 additions and 67 deletions

View File

@ -551,10 +551,10 @@ void EmulatorWindow::UpdateTitle() {
")"; ")";
} }
auto patching_system = emulator()->patching_system(); patcher::PatchingSystem* patching_system = emulator()->patching_system();
if (patching_system) { if (patching_system) {
auto title_patched = auto title_patched =
patching_system->isAnyPatchApplied() ? " [Patches Applied]" : ""; patching_system->IsAnyPatchApplied() ? " [Patches Applied]" : "";
title += title_patched; title += title_patched;
} }
window_->set_title(title); window_->set_title(title);

View File

@ -767,7 +767,8 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
} }
} }
patching_system_->applyPatchesForTitle(kernel_state()->memory(), module->title_id()); patching_system_->ApplyPatchesForTitle(
kernel_state()->memory(), module->title_id(), module->hash());
} }
} }

View File

@ -17,6 +17,7 @@
#include "xenia/base/exception_handler.h" #include "xenia/base/exception_handler.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/memory.h" #include "xenia/memory.h"
#include "xenia/patcher/patcher.h"
#include "xenia/vfs/virtual_file_system.h" #include "xenia/vfs/virtual_file_system.h"
#include "xenia/patcher/patcher.h" #include "xenia/patcher/patcher.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"

View File

@ -13,6 +13,7 @@
#include "xenia/base/byte_stream.h" #include "xenia/base/byte_stream.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/xxhash.h"
#include "xenia/cpu/elf_module.h" #include "xenia/cpu/elf_module.h"
#include "xenia/cpu/processor.h" #include "xenia/cpu/processor.h"
#include "xenia/cpu/xex_module.h" #include "xenia/cpu/xex_module.h"
@ -75,6 +76,12 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
// Load the module. // Load the module.
result = LoadFromMemory(mmap->data(), mmap->size()); 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 { } else {
std::vector<uint8_t> buffer(fs_entry->size()); std::vector<uint8_t> buffer(fs_entry->size());
@ -96,6 +103,12 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
// Load the module. // Load the module.
result = LoadFromMemory(buffer.data(), bytes_read); 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. // Close the file.
file->Destroy(); file->Destroy();
} }
@ -105,6 +118,8 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
return result; return result;
} }
XELOGI("Module hash: {:08X} for {}", hash_, name_);
if (cvars::xex_apply_patches) { if (cvars::xex_apply_patches) {
// Search for xexp patch file // Search for xexp patch file
auto module_path = fs_entry->path(); auto module_path = fs_entry->path();

View File

@ -38,6 +38,7 @@ class UserModule : public XModule {
const std::string& path() const override { return path_; } const std::string& path() const override { return path_; }
const std::string& name() const override { return name_; } const std::string& name() const override { return name_; }
const uint64_t hash() const { return hash_; }
enum ModuleFormat { enum ModuleFormat {
kModuleFormatUndefined = 0, kModuleFormatUndefined = 0,
@ -100,6 +101,7 @@ class UserModule : public XModule {
std::string name_; std::string name_;
std::string path_; std::string path_;
uint64_t hash_;
uint32_t guest_xex_header_ = 0; uint32_t guest_xex_header_ = 0;
ModuleFormat module_format_ = kModuleFormatUndefined; ModuleFormat module_format_ = kModuleFormatUndefined;

View File

@ -2,12 +2,10 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2016 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
#include <regex>
#include "xenia/patcher/patcher.h" #include "xenia/patcher/patcher.h"
#include "xenia/base/cvar.h" #include "xenia/base/cvar.h"
@ -20,24 +18,25 @@ namespace xe {
namespace patcher { namespace patcher {
PatchingSystem::PatchingSystem() { PatchingSystem::PatchingSystem() {
isAnyPatchApplied_ = false; is_any_patch_applied_ = false;
loadPatches(); LoadPatches();
} }
PatchingSystem::~PatchingSystem() {} PatchingSystem::~PatchingSystem() {}
void PatchingSystem::loadPatches() { void PatchingSystem::LoadPatches() {
if (!cvars::apply_patches) { if (!cvars::apply_patches) {
return; return;
} }
std::filesystem::path patches_directory = const std::filesystem::path patches_directory =
filesystem::GetExecutableFolder() / "patches"; filesystem::GetExecutableFolder() / "patches";
auto patch_files = filesystem::ListFiles(patches_directory); const std::vector<xe::filesystem::FileInfo>& patch_files =
std::regex file_name_regex_match = std::regex("^[A-Fa-f0-9]{8}\\.patch$"); filesystem::ListFiles(patches_directory);
std::regex file_name_regex_match = std::regex("^[A-Fa-f0-9]{8}.*\\.patch$");
for (const auto patch_file : patch_files) { for (const xe::filesystem::FileInfo& patch_file : patch_files) {
// Skip files that doesn't have only title_id as name and .patch as // Skip files that doesn't have only title_id as name and .patch as
// extension // extension
if (!std::regex_match(patch_file.name.u8string(), file_name_regex_match)) { if (!std::regex_match(patch_file.name.u8string(), file_name_regex_match)) {
@ -46,40 +45,44 @@ void PatchingSystem::loadPatches() {
patch_file.name.u8string()); patch_file.name.u8string());
continue; continue;
} }
// Load every patch from directory
auto loaded_file_patches = readPatchFile(patch_file.path / patch_file.name); const PatchFileEntry loaded_title_patches =
loaded_patch_files.push_back(loaded_file_patches); ReadPatchFile(patch_file.path / patch_file.name);
if (loaded_title_patches.title_id != -1) {
loaded_patch_files.push_back(loaded_title_patches);
}
} }
XELOGI("Patching System: Loaded patches for {} titles", XELOGI("Patching System: Loaded patches for {} titles",
loaded_patch_files.size()); loaded_patch_files.size());
} }
void PatchingSystem::applyPatchesForTitle(Memory* memory, void PatchingSystem::ApplyPatchesForTitle(Memory* memory,
const uint32_t title_id) { const uint32_t title_id,
for (const auto patchFile : loaded_patch_files) { const uint64_t hash) {
for (const PatchFileEntry& patchFile : loaded_patch_files) {
if (patchFile.title_id != title_id) { if (patchFile.title_id != title_id) {
continue; continue;
} }
if (patchFile.hash != 0 && patchFile.hash != hash) {
continue;
}
auto title_patches = patchFile.patch_info; for (const PatchInfoEntry& patchEntry : patchFile.patch_info) {
for (const auto patchEntry : title_patches) {
if (!patchEntry.is_enabled) { if (!patchEntry.is_enabled) {
continue; continue;
} }
XELOGE("Applying patch for: {}({:08X}) - {}", patchFile.title_name, XELOGE("Applying patch for: {}({:08X}) - {}", patchFile.title_name,
patchFile.title_id, patchEntry.patch_name); patchFile.title_id, patchEntry.patch_name);
applyPatch(memory, &patchEntry); ApplyPatch(memory, &patchEntry);
} }
} }
} }
void PatchingSystem::applyPatch(Memory* memory, const patchInfoEntry* patch) { void PatchingSystem::ApplyPatch(Memory* memory, const PatchInfoEntry* patch) {
for (const auto patch_data_entry : patch->patch_data) { for (const PatchDataEntry& patch_data_entry : patch->patch_data) {
uint32_t old_address_protect = 0; uint32_t old_address_protect = 0;
auto address = memory->TranslateVirtual(patch_data_entry.memory_address_); auto address = memory->TranslateVirtual(patch_data_entry.memory_address_);
auto heap = memory->LookupHeap(patch_data_entry.memory_address_); auto heap = memory->LookupHeap(patch_data_entry.memory_address_);
if (!heap) { if (!heap) {
continue; continue;
} }
@ -111,35 +114,37 @@ void PatchingSystem::applyPatch(Memory* memory, const patchInfoEntry* patch) {
heap->Protect(patch_data_entry.memory_address_, heap->Protect(patch_data_entry.memory_address_,
patch_data_entry.alloc_size_, old_address_protect); patch_data_entry.alloc_size_, old_address_protect);
if (!isAnyPatchApplied_) { if (!is_any_patch_applied_) {
isAnyPatchApplied_ = true; is_any_patch_applied_ = true;
} }
} }
} }
patchFileEntry PatchingSystem::readPatchFile( PatchFileEntry PatchingSystem::ReadPatchFile(
const std::filesystem::path& file_path) { const std::filesystem::path& file_path) {
patchFileEntry patchFile; PatchFileEntry patchFile;
std::shared_ptr<cpptoml::table> patch_toml_fields; std::shared_ptr<cpptoml::table> patch_toml_fields;
try { try {
patch_toml_fields = cpptoml::parse_file(file_path.u8string()); patch_toml_fields = cpptoml::parse_file(file_path.u8string());
} catch (...) { } catch (...) {
XELOGE("Cannot load patch file: {}", file_path.u8string()); XELOGE("Cannot load patch file: {}", file_path.u8string());
patchFile.title_id = -1;
return patchFile; return patchFile;
}; };
auto title_name = patch_toml_fields->get_as<std::string>("title_name"); auto title_name = patch_toml_fields->get_as<std::string>("title_name");
auto title_id = patch_toml_fields->get_as<std::string>("title_id"); auto title_id = patch_toml_fields->get_as<std::string>("title_id");
auto title_hash = patch_toml_fields->get_as<std::string>("hash");
patchFile.title_id = strtol((*title_id).c_str(), NULL, 16); patchFile.title_id = strtoul((*title_id).c_str(), NULL, 16);
patchFile.hash = strtoull((*title_hash).c_str(), NULL, 16);
patchFile.title_name = *title_name; patchFile.title_name = *title_name;
auto tarr = patch_toml_fields->get_table_array("patch"); auto tarr = patch_toml_fields->get_table_array("patch");
for (auto table : *tarr) { for (auto table : *tarr) {
patchInfoEntry patch = patchInfoEntry(); PatchInfoEntry patch = PatchInfoEntry();
auto patch_name = *table->get_as<std::string>("name"); auto patch_name = *table->get_as<std::string>("name");
auto patch_desc = *table->get_as<std::string>("desc"); auto patch_desc = *table->get_as<std::string>("desc");
auto patch_author = *table->get_as<std::string>("author"); auto patch_author = *table->get_as<std::string>("author");
@ -155,9 +160,9 @@ patchFileEntry PatchingSystem::readPatchFile(
// Iterate through all available data sizes // Iterate through all available data sizes
for (const std::string& type : data_types) { for (const std::string& type : data_types) {
auto entries = readPatchData(type, table); auto entries = ReadPatchData(type, table);
for (const auto entry : *entries) { for (const PatchDataEntry& entry : *entries) {
patch.patch_data.push_back(entry); patch.patch_data.push_back(entry);
} }
} }
@ -166,10 +171,10 @@ patchFileEntry PatchingSystem::readPatchFile(
return patchFile; return patchFile;
} }
std::vector<patchDataEntry>* PatchingSystem::readPatchData( std::vector<PatchDataEntry>* PatchingSystem::ReadPatchData(
const std::string size_type, const std::string size_type,
const std::shared_ptr<cpptoml::table>& patch_table) { const std::shared_ptr<cpptoml::table>& patch_table) {
std::vector<patchDataEntry>* patch_data = new std::vector<patchDataEntry>(); std::vector<PatchDataEntry>* patch_data = new std::vector<PatchDataEntry>();
auto patch_data_tarr = patch_table->get_table_array(size_type); auto patch_data_tarr = patch_table->get_table_array(size_type);
if (!patch_data_tarr) { if (!patch_data_tarr) {
@ -181,29 +186,28 @@ std::vector<patchDataEntry>* PatchingSystem::readPatchData(
// Todo: How to handle different sizes // Todo: How to handle different sizes
auto value = patch_data_table->get_as<std::uint64_t>("value"); auto value = patch_data_table->get_as<std::uint64_t>("value");
patchDataEntry patchData = {getAllocSize(size_type), *address, *value}; PatchDataEntry patchData = {GetAllocSize(size_type), *address, *value};
patch_data->push_back(patchData); patch_data->push_back(patchData);
} }
return patch_data; return patch_data;
} }
// TODO(Gliniak): Somehow resolve this mess. Maybe by template? uint8_t PatchingSystem::GetAllocSize(const std::string provided_size) {
uint8_t PatchingSystem::getAllocSize(const std::string provided_size) {
uint8_t alloc_size = sizeof(uint32_t); uint8_t alloc_size = sizeof(uint32_t);
if (provided_size == "be64") { if (provided_size == "be64") {
alloc_size = sizeof(uint64_t); alloc_size = sizeof(uint64_t);
} }
if (provided_size == "be32") { else if (provided_size == "be32") {
alloc_size = sizeof(uint32_t); alloc_size = sizeof(uint32_t);
} }
if (provided_size == "be16") { else if (provided_size == "be16") {
alloc_size = sizeof(uint16_t); alloc_size = sizeof(uint16_t);
} }
if (provided_size == "be8") { else if (provided_size == "be8") {
alloc_size = sizeof(uint8_t); alloc_size = sizeof(uint8_t);
} }
return alloc_size; return alloc_size;

View File

@ -2,7 +2,7 @@
****************************************************************************** ******************************************************************************
* Xenia : Xbox 360 Emulator Research Project * * Xenia : Xbox 360 Emulator Research Project *
****************************************************************************** ******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. * * Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. * * Released under the BSD license - see LICENSE in the root for more details. *
****************************************************************************** ******************************************************************************
*/ */
@ -11,68 +11,69 @@
#define XENIA_PATCHER_H_ #define XENIA_PATCHER_H_
#include <map> #include <map>
#include <regex>
#include <vector> #include <vector>
#include "third_party/cpptoml/include/cpptoml.h" #include "third_party/cpptoml/include/cpptoml.h"
#include "third_party/xxhash/xxhash.h"
#include "xenia/memory.h" #include "xenia/memory.h"
struct patchDataEntry { namespace xe {
namespace patcher {
struct PatchDataEntry {
const uint8_t alloc_size_; const uint8_t alloc_size_;
const uint32_t memory_address_; const uint32_t memory_address_;
const uint64_t new_value_; const uint64_t new_value_;
patchDataEntry(const uint8_t alloc_size, const uint32_t memory_address, PatchDataEntry(const uint8_t alloc_size, const uint32_t memory_address,
const uint64_t new_value) const uint64_t new_value)
: alloc_size_(alloc_size), : alloc_size_(alloc_size),
memory_address_(memory_address), memory_address_(memory_address),
new_value_(new_value){}; new_value_(new_value){};
}; };
struct patchInfoEntry { struct PatchInfoEntry {
uint32_t id; uint32_t id;
//XXH64_state_t hash_state; // Unsupported. For now.
std::string patch_name; std::string patch_name;
std::string patch_desc; std::string patch_desc;
std::string patch_author; std::string patch_author;
std::vector<patchDataEntry> patch_data; std::vector<PatchDataEntry> patch_data;
bool is_enabled; bool is_enabled;
}; };
struct patchFileEntry { struct PatchFileEntry {
uint32_t title_id; uint32_t title_id;
std::string title_name; std::string title_name;
std::vector<patchInfoEntry> patch_info; uint64_t hash;
std::vector<PatchInfoEntry> patch_info;
}; };
namespace xe {
namespace patcher {
class PatchingSystem { class PatchingSystem {
public: public:
PatchingSystem(); PatchingSystem();
~PatchingSystem(); ~PatchingSystem();
void loadPatches(); void LoadPatches();
void applyPatch(Memory* memory, const patchInfoEntry* patch); void ApplyPatch(Memory* memory, const PatchInfoEntry* patch);
void applyPatchesForTitle(Memory* memory, const uint32_t title_id); void ApplyPatchesForTitle(Memory* memory, const uint32_t title_id,
const uint64_t hash);
patchFileEntry readPatchFile(const std::filesystem::path& file_path); PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path);
std::vector<patchDataEntry>* readPatchData( std::vector<PatchDataEntry>* ReadPatchData(
const std::string size_type, const std::string size_type,
const std::shared_ptr<cpptoml::table>& patch_table); const std::shared_ptr<cpptoml::table>& patch_table);
std::vector<patchFileEntry> getAllPatches() { return loaded_patch_files; } std::vector<PatchFileEntry>& GetAllPatches() { return loaded_patch_files; }
bool isAnyPatchApplied() { return isAnyPatchApplied_; } bool IsAnyPatchApplied() { return is_any_patch_applied_; }
private: private:
std::vector<patchFileEntry> loaded_patch_files; std::vector<PatchFileEntry> loaded_patch_files;
bool isAnyPatchApplied_; bool is_any_patch_applied_;
uint8_t getAllocSize(const std::string provided_size); uint8_t GetAllocSize(const std::string provided_size);
}; };
} // namespace patcher } // namespace patcher