Initial support for xex patching
This commit is contained in:
parent
88ddfe86db
commit
5a933156c0
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue