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) {
auto title_patched =
patching_system->isAnyPatchApplied() ? " [Patches Applied]" : "";
patching_system->IsAnyPatchApplied() ? " [Patches Applied]" : "";
title += title_patched;
}
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/kernel/kernel_state.h"
#include "xenia/memory.h"
#include "xenia/patcher/patcher.h"
#include "xenia/vfs/virtual_file_system.h"
#include "xenia/patcher/patcher.h"
#include "xenia/xbox.h"

View File

@ -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"
@ -75,6 +76,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<uint8_t> buffer(fs_entry->size());
@ -96,6 +103,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();
}
@ -105,6 +118,8 @@ X_STATUS UserModule::LoadFromFile(const std::string_view path) {
return result;
}
XELOGI("Module hash: {:08X} for {}", hash_, name_);
if (cvars::xex_apply_patches) {
// Search for xexp patch file
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& name() const override { return name_; }
const 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_;
uint32_t guest_xex_header_ = 0;
ModuleFormat module_format_ = kModuleFormatUndefined;

View File

@ -2,12 +2,10 @@
******************************************************************************
* 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. *
******************************************************************************
*/
#include <regex>
#include "xenia/patcher/patcher.h"
#include "xenia/base/cvar.h"
@ -20,24 +18,25 @@ namespace xe {
namespace patcher {
PatchingSystem::PatchingSystem() {
isAnyPatchApplied_ = false;
loadPatches();
is_any_patch_applied_ = false;
LoadPatches();
}
PatchingSystem::~PatchingSystem() {}
void PatchingSystem::loadPatches() {
void PatchingSystem::LoadPatches() {
if (!cvars::apply_patches) {
return;
}
std::filesystem::path patches_directory =
const std::filesystem::path patches_directory =
filesystem::GetExecutableFolder() / "patches";
auto patch_files = filesystem::ListFiles(patches_directory);
std::regex file_name_regex_match = std::regex("^[A-Fa-f0-9]{8}\\.patch$");
const std::vector<xe::filesystem::FileInfo>& patch_files =
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
// extension
if (!std::regex_match(patch_file.name.u8string(), file_name_regex_match)) {
@ -46,40 +45,44 @@ void PatchingSystem::loadPatches() {
patch_file.name.u8string());
continue;
}
// Load every patch from directory
auto loaded_file_patches = readPatchFile(patch_file.path / patch_file.name);
loaded_patch_files.push_back(loaded_file_patches);
const PatchFileEntry loaded_title_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",
loaded_patch_files.size());
}
void PatchingSystem::applyPatchesForTitle(Memory* memory,
const uint32_t title_id) {
for (const auto patchFile : loaded_patch_files) {
void PatchingSystem::ApplyPatchesForTitle(Memory* memory,
const uint32_t title_id,
const uint64_t hash) {
for (const PatchFileEntry& patchFile : loaded_patch_files) {
if (patchFile.title_id != title_id) {
continue;
}
if (patchFile.hash != 0 && patchFile.hash != hash) {
continue;
}
auto title_patches = patchFile.patch_info;
for (const auto patchEntry : title_patches) {
for (const PatchInfoEntry& patchEntry : patchFile.patch_info) {
if (!patchEntry.is_enabled) {
continue;
}
XELOGE("Applying patch for: {}({:08X}) - {}", patchFile.title_name,
patchFile.title_id, patchEntry.patch_name);
applyPatch(memory, &patchEntry);
ApplyPatch(memory, &patchEntry);
}
}
}
void PatchingSystem::applyPatch(Memory* memory, const patchInfoEntry* patch) {
for (const auto patch_data_entry : patch->patch_data) {
void PatchingSystem::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;
}
@ -111,35 +114,37 @@ void PatchingSystem::applyPatch(Memory* memory, const patchInfoEntry* patch) {
heap->Protect(patch_data_entry.memory_address_,
patch_data_entry.alloc_size_, old_address_protect);
if (!isAnyPatchApplied_) {
isAnyPatchApplied_ = true;
if (!is_any_patch_applied_) {
is_any_patch_applied_ = true;
}
}
}
patchFileEntry PatchingSystem::readPatchFile(
PatchFileEntry PatchingSystem::ReadPatchFile(
const std::filesystem::path& file_path) {
patchFileEntry patchFile;
PatchFileEntry patchFile;
std::shared_ptr<cpptoml::table> patch_toml_fields;
try {
patch_toml_fields = cpptoml::parse_file(file_path.u8string());
} catch (...) {
XELOGE("Cannot load patch file: {}", file_path.u8string());
patchFile.title_id = -1;
return patchFile;
};
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_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;
auto tarr = patch_toml_fields->get_table_array("patch");
for (auto table : *tarr) {
patchInfoEntry patch = patchInfoEntry();
PatchInfoEntry patch = PatchInfoEntry();
auto patch_name = *table->get_as<std::string>("name");
auto patch_desc = *table->get_as<std::string>("desc");
auto patch_author = *table->get_as<std::string>("author");
@ -155,9 +160,9 @@ patchFileEntry PatchingSystem::readPatchFile(
// Iterate through all available data sizes
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);
}
}
@ -166,10 +171,10 @@ patchFileEntry PatchingSystem::readPatchFile(
return patchFile;
}
std::vector<patchDataEntry>* PatchingSystem::readPatchData(
std::vector<PatchDataEntry>* PatchingSystem::ReadPatchData(
const std::string size_type,
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);
if (!patch_data_tarr) {
@ -181,33 +186,32 @@ std::vector<patchDataEntry>* PatchingSystem::readPatchData(
// Todo: How to handle different sizes
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);
}
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);
if (provided_size == "be64") {
alloc_size = sizeof(uint64_t);
}
if (provided_size == "be32") {
else if (provided_size == "be32") {
alloc_size = sizeof(uint32_t);
}
if (provided_size == "be16") {
else if (provided_size == "be16") {
alloc_size = sizeof(uint16_t);
}
if (provided_size == "be8") {
else if (provided_size == "be8") {
alloc_size = sizeof(uint8_t);
}
return alloc_size;
}
} // namespace patcher
} // namespace xe
} // namespace xe

View File

@ -2,7 +2,7 @@
******************************************************************************
* 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. *
******************************************************************************
*/
@ -11,70 +11,71 @@
#define XENIA_PATCHER_H_
#include <map>
#include <regex>
#include <vector>
#include "third_party/cpptoml/include/cpptoml.h"
#include "third_party/xxhash/xxhash.h"
#include "xenia/memory.h"
struct patchDataEntry {
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,
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 {
struct PatchInfoEntry {
uint32_t id;
//XXH64_state_t hash_state; // Unsupported. For now.
std::string patch_name;
std::string patch_desc;
std::string patch_author;
std::vector<patchDataEntry> patch_data;
std::vector<PatchDataEntry> patch_data;
bool is_enabled;
};
struct patchFileEntry {
struct PatchFileEntry {
uint32_t title_id;
std::string title_name;
std::vector<patchInfoEntry> patch_info;
uint64_t hash;
std::vector<PatchInfoEntry> patch_info;
};
namespace xe {
namespace patcher {
class PatchingSystem {
public:
PatchingSystem();
~PatchingSystem();
void loadPatches();
void applyPatch(Memory* memory, const patchInfoEntry* patch);
void applyPatchesForTitle(Memory* memory, const uint32_t title_id);
void LoadPatches();
void ApplyPatch(Memory* memory, const PatchInfoEntry* patch);
void ApplyPatchesForTitle(Memory* memory, const uint32_t title_id,
const uint64_t hash);
patchFileEntry readPatchFile(const std::filesystem::path& file_path);
std::vector<patchDataEntry>* readPatchData(
PatchFileEntry ReadPatchFile(const std::filesystem::path& file_path);
std::vector<PatchDataEntry>* ReadPatchData(
const std::string size_type,
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:
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 xe
#endif
#endif