/** ****************************************************************************** * 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/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 custom 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); 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), patch_filename_regex_)) { 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 patch_file; 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)); patch_file.title_id = -1; return patch_file; }; auto title_name = patch_toml_fields->get_as("title_name"); auto title_id = patch_toml_fields->get_as("title_id"); 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"); 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& patch_data_type : patch_data_types_size_) { bool success = ReadPatchData(patch.patch_data, patch_data_type, patch_table_entry); if (!success) { XELOGE("PatchDB: Cannot read patch {}", patch_name); break; } } patch_file.patch_info.push_back(patch); } return patch_file; } bool PatchDB::ReadPatchData( std::vector& patch_data, const std::pair data_type, const std::shared_ptr& patch_table) { auto patch_data_tarr = patch_table->get_table_array(data_type.first); if (!patch_data_tarr) { return true; } for (const auto& patch_data_table : *patch_data_tarr) { uint32_t address = *patch_data_table->get_as("address"); size_t alloc_size = (size_t)data_type.second.size; switch (data_type.second.type) { case PatchDataType::kBE8: { uint16_t value = *patch_data_table->get_as("value"); patch_data.push_back({address, PatchDataValue(alloc_size, value)}); break; } case PatchDataType::kBE16: { uint16_t value = *patch_data_table->get_as("value"); patch_data.push_back( {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); break; } case PatchDataType::kBE32: { uint32_t value = *patch_data_table->get_as("value"); patch_data.push_back( {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); break; } case PatchDataType::kBE64: { uint64_t value = *patch_data_table->get_as("value"); patch_data.push_back( {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); break; } case PatchDataType::kF64: { double val = *patch_data_table->get_as("value"); uint64_t value = *reinterpret_cast(&val); patch_data.push_back( {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); break; } case PatchDataType::kF32: { float value = float(*patch_data_table->get_as("value")); patch_data.push_back( {address, PatchDataValue(alloc_size, xe::byte_swap(value))}); break; } case PatchDataType::kString: { std::string value = *patch_data_table->get_as("value"); patch_data.push_back({address, PatchDataValue(value)}); break; } case PatchDataType::kU16String: { std::u16string value = xe::to_utf16(*patch_data_table->get_as("value")); patch_data.push_back({address, PatchDataValue(value)}); break; } case PatchDataType::kByteArray: { 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) { XELOGW("PatchDB: Cannot convert hex string to byte array! Skipping", address); return false; } patch_data.push_back({address, PatchDataValue(data)}); break; } default: { XELOGW("PatchDB: Unknown patch data type for address {:08X}! Skipping", address); return false; } } } return true; } std::vector PatchDB::GetTitlePatches( const uint32_t title_id, const std::optional hash) { std::vector title_patches; 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::ReadHashes(PatchFileEntry& patch_entry, std::shared_ptr patch_toml_fields) { auto title_hashes = patch_toml_fields->get_array_of("hash"); for (const auto& hash : *title_hashes) { patch_entry.hashes.push_back(strtoull(hash.c_str(), NULL, 16)); } auto single_hash = patch_toml_fields->get_as("hash"); if (single_hash) { patch_entry.hashes.push_back(strtoull((*single_hash).c_str(), NULL, 16)); } } } // namespace patcher } // namespace xe