Merge remote-tracking branch 'GliniakRepo/patchingSystem' into canary_experimental
This commit is contained in:
@@ -34,15 +34,14 @@ void PatchDB::LoadPatches() {
|
||||
}
|
||||
|
||||
const std::filesystem::path patches_directory = patches_root_ / "patches";
|
||||
const std::vector<xe::filesystem::FileInfo>& patch_files =
|
||||
const std::vector<xe::filesystem::FileInfo> patch_files =
|
||||
filesystem::ListFiles(patches_directory);
|
||||
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
|
||||
// extension
|
||||
if (!std::regex_match(path_to_utf8(patch_file.name),
|
||||
file_name_regex_match)) {
|
||||
patch_filename_regex_)) {
|
||||
XELOGE("PatchDB: Skipped loading file {} due to incorrect filename",
|
||||
path_to_utf8(patch_file.name));
|
||||
continue;
|
||||
@@ -51,30 +50,30 @@ void PatchDB::LoadPatches() {
|
||||
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);
|
||||
loaded_patches_.push_back(loaded_title_patches);
|
||||
}
|
||||
}
|
||||
XELOGI("PatchDB: Loaded patches for {} titles", loaded_patches.size());
|
||||
XELOGI("PatchDB: Loaded patches for {} titles", loaded_patches_.size());
|
||||
}
|
||||
|
||||
PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) {
|
||||
PatchFileEntry patchFile;
|
||||
PatchFileEntry patch_file;
|
||||
std::shared_ptr<cpptoml::table> 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;
|
||||
patch_file.title_id = -1;
|
||||
return patch_file;
|
||||
};
|
||||
|
||||
auto title_name = patch_toml_fields->get_as<std::string>("title_name");
|
||||
auto title_id = patch_toml_fields->get_as<std::string>("title_id");
|
||||
|
||||
patchFile.title_id = strtoul((*title_id).c_str(), NULL, 16);
|
||||
patchFile.title_name = *title_name;
|
||||
ReadHash(patchFile, patch_toml_fields);
|
||||
patch_file.title_id = strtoul((*title_id).c_str(), NULL, 16);
|
||||
patch_file.title_name = *title_name;
|
||||
ReadHashes(patch_file, patch_toml_fields);
|
||||
|
||||
auto patch_table = patch_toml_fields->get_table_array("patch");
|
||||
|
||||
@@ -92,7 +91,7 @@ PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) {
|
||||
patch.is_enabled = is_enabled;
|
||||
|
||||
// Iterate through all available data sizes
|
||||
for (const auto& patch_data_type : patch_data_types_size) {
|
||||
for (const auto& patch_data_type : patch_data_types_size_) {
|
||||
bool success =
|
||||
ReadPatchData(patch.patch_data, patch_data_type, patch_table_entry);
|
||||
|
||||
@@ -101,9 +100,9 @@ PatchFileEntry PatchDB::ReadPatchFile(const std::filesystem::path& file_path) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
patchFile.patch_info.push_back(patch);
|
||||
patch_file.patch_info.push_back(patch);
|
||||
}
|
||||
return patchFile;
|
||||
return patch_file;
|
||||
}
|
||||
|
||||
bool PatchDB::ReadPatchData(
|
||||
@@ -120,77 +119,83 @@ bool PatchDB::ReadPatchData(
|
||||
size_t alloc_size = (size_t)data_type.second.size;
|
||||
|
||||
switch (data_type.second.type) {
|
||||
case PatchDataType::be8: {
|
||||
case PatchDataType::kBE8: {
|
||||
uint16_t value = *patch_data_table->get_as<uint8_t>("value");
|
||||
patch_data.push_back({address, PatchDataValue(alloc_size, value)});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::be16: {
|
||||
case PatchDataType::kBE16: {
|
||||
uint16_t value = *patch_data_table->get_as<uint16_t>("value");
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::be32: {
|
||||
case PatchDataType::kBE32: {
|
||||
uint32_t value = *patch_data_table->get_as<uint32_t>("value");
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::f64: {
|
||||
case PatchDataType::kBE64: {
|
||||
uint64_t value = *patch_data_table->get_as<uint64_t>("value");
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::kF64: {
|
||||
double val = *patch_data_table->get_as<double>("value");
|
||||
uint64_t value = *reinterpret_cast<uint64_t*>(&val);
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::f32: {
|
||||
case PatchDataType::kF32: {
|
||||
float value = float(*patch_data_table->get_as<double>("value"));
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::string: {
|
||||
case PatchDataType::kString: {
|
||||
std::string value = *patch_data_table->get_as<std::string>("value");
|
||||
patch_data.push_back({address, PatchDataValue(value)});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::u16string: {
|
||||
case PatchDataType::kU16String: {
|
||||
std::u16string value =
|
||||
xe::to_utf16(*patch_data_table->get_as<std::string>("value"));
|
||||
patch_data.push_back({address, PatchDataValue(value)});
|
||||
break;
|
||||
}
|
||||
case PatchDataType::byte_array: {
|
||||
case PatchDataType::kByteArray: {
|
||||
std::vector<uint8_t> data;
|
||||
const std::string value =
|
||||
*patch_data_table->get_as<std::string>("value");
|
||||
|
||||
bool success = string_util::hex_string_to_array(data, value);
|
||||
if (!success) {
|
||||
XELOGW("PatchDB: Cannot convert hex string to byte array! Skipping",
|
||||
address);
|
||||
return false;
|
||||
}
|
||||
|
||||
patch_data.push_back({address, PatchDataValue(value.size() / 2, data)});
|
||||
patch_data.push_back({address, PatchDataValue(data)});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
uint64_t value = *patch_data_table->get_as<uint64_t>("value");
|
||||
patch_data.push_back(
|
||||
{address, PatchDataValue(alloc_size, xe::byte_swap(value))});
|
||||
break;
|
||||
XELOGW("PatchDB: Unknown patch data type for address {:08X}! Skipping",
|
||||
address);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<PatchFileEntry> PatchDB::GetTitlePatches(uint32_t title_id,
|
||||
const uint64_t hash) {
|
||||
std::vector<PatchFileEntry> PatchDB::GetTitlePatches(
|
||||
const uint32_t title_id, const std::optional<uint64_t> hash) {
|
||||
std::vector<PatchFileEntry> title_patches;
|
||||
|
||||
std::copy_if(
|
||||
loaded_patches.cbegin(), loaded_patches.cend(),
|
||||
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();
|
||||
@@ -202,17 +207,17 @@ std::vector<PatchFileEntry> PatchDB::GetTitlePatches(uint32_t title_id,
|
||||
return title_patches;
|
||||
}
|
||||
|
||||
void PatchDB::ReadHash(PatchFileEntry& patchEntry,
|
||||
std::shared_ptr<cpptoml::table> patch_toml_fields) {
|
||||
void PatchDB::ReadHashes(PatchFileEntry& patch_entry,
|
||||
std::shared_ptr<cpptoml::table> patch_toml_fields) {
|
||||
auto title_hashes = patch_toml_fields->get_array_of<std::string>("hash");
|
||||
|
||||
for (const auto& hash : *title_hashes) {
|
||||
patchEntry.hashes.push_back(strtoull(hash.c_str(), NULL, 16));
|
||||
patch_entry.hashes.push_back(strtoull(hash.c_str(), NULL, 16));
|
||||
}
|
||||
|
||||
auto single_hash = patch_toml_fields->get_as<std::string>("hash");
|
||||
if (single_hash) {
|
||||
patchEntry.hashes.push_back(strtoull((*single_hash).c_str(), NULL, 16));
|
||||
patch_entry.hashes.push_back(strtoull((*single_hash).c_str(), NULL, 16));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#ifndef XENIA_PATCH_DB_H_
|
||||
#define XENIA_PATCH_DB_H_
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
|
||||
#include "third_party/cpptoml/include/cpptoml.h"
|
||||
|
||||
@@ -18,62 +21,37 @@ namespace xe {
|
||||
namespace patcher {
|
||||
|
||||
struct PatchDataValue {
|
||||
const size_t alloc_size_;
|
||||
const uint8_t* patch_data_ptr_;
|
||||
const size_t alloc_size;
|
||||
std::vector<uint8_t> patch_data;
|
||||
|
||||
PatchDataValue(const size_t alloc_size, const uint8_t value)
|
||||
: alloc_size_(alloc_size) {
|
||||
patch_data_ptr_ = new uint8_t[alloc_size_];
|
||||
memcpy((void*)patch_data_ptr_, &value, alloc_size);
|
||||
template <typename T>
|
||||
PatchDataValue(const size_t size, const T value) : alloc_size(size) {
|
||||
patch_data.resize(alloc_size);
|
||||
std::memcpy(patch_data.data(), &value, alloc_size);
|
||||
};
|
||||
|
||||
PatchDataValue(const size_t alloc_size, const uint16_t value)
|
||||
: alloc_size_(alloc_size) {
|
||||
patch_data_ptr_ = new uint8_t[alloc_size_];
|
||||
memcpy((void*)patch_data_ptr_, &value, alloc_size);
|
||||
PatchDataValue(const std::vector<uint8_t> value) : alloc_size(value.size()) {
|
||||
patch_data.resize(alloc_size);
|
||||
std::memcpy(patch_data.data(), value.data(), alloc_size);
|
||||
};
|
||||
|
||||
PatchDataValue(const size_t alloc_size, const uint32_t value)
|
||||
: alloc_size_(alloc_size) {
|
||||
patch_data_ptr_ = new uint8_t[alloc_size_];
|
||||
memcpy((void*)patch_data_ptr_, &value, alloc_size);
|
||||
PatchDataValue(const std::string value) : alloc_size(value.size()) {
|
||||
patch_data.resize(alloc_size);
|
||||
std::memcpy(patch_data.data(), value.c_str(), alloc_size);
|
||||
};
|
||||
|
||||
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<uint8_t> 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_);
|
||||
PatchDataValue(const std::u16string value) : alloc_size(value.size() * 2) {
|
||||
patch_data.resize(alloc_size);
|
||||
std::memcpy(patch_data.data(), value.c_str(), alloc_size);
|
||||
};
|
||||
};
|
||||
|
||||
struct PatchDataEntry {
|
||||
const uint32_t memory_address_;
|
||||
const PatchDataValue new_data_;
|
||||
const uint32_t address;
|
||||
const PatchDataValue data;
|
||||
|
||||
PatchDataEntry(const uint32_t memory_address, const PatchDataValue new_data)
|
||||
: memory_address_(memory_address), new_data_(new_data){};
|
||||
PatchDataEntry(const uint32_t memory_address, const PatchDataValue patch_data)
|
||||
: address(memory_address), data(patch_data){};
|
||||
};
|
||||
|
||||
struct PatchInfoEntry {
|
||||
@@ -93,15 +71,15 @@ struct PatchFileEntry {
|
||||
};
|
||||
|
||||
enum class PatchDataType {
|
||||
be8,
|
||||
be16,
|
||||
be32,
|
||||
be64,
|
||||
f32,
|
||||
f64,
|
||||
string,
|
||||
u16string,
|
||||
byte_array
|
||||
kBE8,
|
||||
kBE16,
|
||||
kBE32,
|
||||
kBE64,
|
||||
kF32,
|
||||
kF64,
|
||||
kString,
|
||||
kU16String,
|
||||
kByteArray
|
||||
};
|
||||
|
||||
struct PatchData {
|
||||
@@ -123,29 +101,29 @@ class PatchDB {
|
||||
const std::pair<std::string, PatchData> data_type,
|
||||
const std::shared_ptr<cpptoml::table>& patch_table);
|
||||
|
||||
std::vector<PatchFileEntry> GetTitlePatches(uint32_t title_id,
|
||||
const uint64_t hash);
|
||||
std::vector<PatchFileEntry>& GetAllPatches() { return loaded_patches; }
|
||||
std::vector<PatchFileEntry> GetTitlePatches(
|
||||
const uint32_t title_id, const std::optional<uint64_t> hash);
|
||||
std::vector<PatchFileEntry>& GetAllPatches() { return loaded_patches_; }
|
||||
|
||||
private:
|
||||
void ReadHash(PatchFileEntry& patchEntry,
|
||||
std::shared_ptr<cpptoml::table> patch_toml_fields);
|
||||
void ReadHashes(PatchFileEntry& patch_entry,
|
||||
std::shared_ptr<cpptoml::table> patch_toml_fields);
|
||||
|
||||
inline static const std::string patch_filename_regex =
|
||||
"^[A-Fa-f0-9]{8}.*\\.patch\\.toml$";
|
||||
inline static const std::regex patch_filename_regex_ =
|
||||
std::regex("^[A-Fa-f0-9]{8}.*\\.patch\\.toml$");
|
||||
|
||||
const std::map<std::string, PatchData> 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)}};
|
||||
const std::map<std::string, PatchData> patch_data_types_size_ = {
|
||||
{"string", PatchData(0, PatchDataType::kString)},
|
||||
{"u16string", PatchData(0, PatchDataType::kU16String)},
|
||||
{"array", PatchData(0, PatchDataType::kByteArray)},
|
||||
{"f64", PatchData(sizeof(uint64_t), PatchDataType::kF64)},
|
||||
{"f32", PatchData(sizeof(uint32_t), PatchDataType::kF32)},
|
||||
{"be64", PatchData(sizeof(uint64_t), PatchDataType::kBE64)},
|
||||
{"be32", PatchData(sizeof(uint32_t), PatchDataType::kBE32)},
|
||||
{"be16", PatchData(sizeof(uint16_t), PatchDataType::kBE16)},
|
||||
{"be8", PatchData(sizeof(uint8_t), PatchDataType::kBE8)}};
|
||||
|
||||
std::vector<PatchFileEntry> loaded_patches;
|
||||
std::vector<PatchFileEntry> loaded_patches_;
|
||||
std::filesystem::path patches_root_;
|
||||
};
|
||||
} // namespace patcher
|
||||
|
||||
@@ -6,23 +6,22 @@
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
#include "xenia/patcher/patcher.h"
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/patcher/patcher.h"
|
||||
|
||||
namespace xe {
|
||||
namespace patcher {
|
||||
|
||||
Patcher::Patcher(const std::filesystem::path patches_root) {
|
||||
is_any_patch_applied_ = false;
|
||||
patch_db = new PatchDB(patches_root);
|
||||
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);
|
||||
const std::optional<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) {
|
||||
@@ -39,25 +38,24 @@ void Patcher::ApplyPatchesForTitle(Memory* memory, const uint32_t title_id,
|
||||
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_);
|
||||
uint8_t* address = memory->TranslateVirtual(patch_data_entry.address);
|
||||
xe::BaseHeap* heap = memory->LookupHeap(patch_data_entry.address);
|
||||
if (!heap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
heap->QueryProtect(patch_data_entry.memory_address_, &old_address_protect);
|
||||
heap->QueryProtect(patch_data_entry.address, &old_address_protect);
|
||||
|
||||
heap->Protect(patch_data_entry.memory_address_,
|
||||
(uint32_t)patch_data_entry.new_data_.alloc_size_,
|
||||
heap->Protect(patch_data_entry.address,
|
||||
(uint32_t)patch_data_entry.data.alloc_size,
|
||||
kMemoryProtectRead | kMemoryProtectWrite);
|
||||
|
||||
|
||||
memcpy(address, patch_data_entry.new_data_.patch_data_ptr_,
|
||||
patch_data_entry.new_data_.alloc_size_);
|
||||
std::memcpy(address, patch_data_entry.data.patch_data.data(),
|
||||
patch_data_entry.data.alloc_size);
|
||||
|
||||
// Restore previous protection
|
||||
heap->Protect(patch_data_entry.memory_address_,
|
||||
(uint32_t)patch_data_entry.new_data_.alloc_size_,
|
||||
heap->Protect(patch_data_entry.address,
|
||||
(uint32_t)patch_data_entry.data.alloc_size,
|
||||
old_address_protect);
|
||||
|
||||
is_any_patch_applied_ = true;
|
||||
|
||||
@@ -19,16 +19,15 @@ 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);
|
||||
const std::optional<uint64_t> hash);
|
||||
|
||||
bool IsAnyPatchApplied() { return is_any_patch_applied_; }
|
||||
|
||||
private:
|
||||
PatchDB* patch_db;
|
||||
PatchDB* patch_db_;
|
||||
bool is_any_patch_applied_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user