From cd6fcb33bdcaff37c8c9d2083c7951e1d73ae9da Mon Sep 17 00:00:00 2001 From: "Skyth (Asilkan)" <19259897+blueskythlikesclouds@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:22:30 +0300 Subject: [PATCH] Port XEX patcher from Unleashed Recompiled. (#4) * Port XEX patcher from Unleashed Recompiled. * Fix compilation error on Linux. --- .gitmodules | 6 + XenonRecomp/main.cpp | 4 +- XenonRecomp/recompiler.cpp | 80 ++++- XenonRecomp/recompiler.h | 2 +- XenonRecomp/recompiler_config.cpp | 2 + XenonRecomp/recompiler_config.h | 2 + XenonUtils/CMakeLists.txt | 27 +- XenonUtils/image.cpp | 2 +- XenonUtils/memory_mapped_file.cpp | 169 ++++++++++ XenonUtils/memory_mapped_file.h | 32 ++ XenonUtils/xex.cpp | 89 ++++-- XenonUtils/xex.h | 293 +++++++++++------ XenonUtils/xex_patcher.cpp | 512 ++++++++++++++++++++++++++++++ XenonUtils/xex_patcher.h | 35 ++ thirdparty/TinySHA1/TinySHA1.hpp | 223 +++++++++++++ thirdparty/libmspack | 1 + thirdparty/tiny-AES-c | 1 + 17 files changed, 1336 insertions(+), 144 deletions(-) create mode 100644 XenonUtils/memory_mapped_file.cpp create mode 100644 XenonUtils/memory_mapped_file.h create mode 100644 XenonUtils/xex_patcher.cpp create mode 100644 XenonUtils/xex_patcher.h create mode 100644 thirdparty/TinySHA1/TinySHA1.hpp create mode 160000 thirdparty/libmspack create mode 160000 thirdparty/tiny-AES-c diff --git a/.gitmodules b/.gitmodules index 4b5a7b5..d12d6a2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "thirdparty/tomlplusplus"] path = thirdparty/tomlplusplus url = https://github.com/marzer/tomlplusplus.git +[submodule "thirdparty/libmspack"] + path = thirdparty/libmspack + url = https://github.com/kyz/libmspack +[submodule "thirdparty/tiny-AES-c"] + path = thirdparty/tiny-AES-c + url = https://github.com/kokke/tiny-AES-c.git diff --git a/XenonRecomp/main.cpp b/XenonRecomp/main.cpp index 0e79245..602d3db 100644 --- a/XenonRecomp/main.cpp +++ b/XenonRecomp/main.cpp @@ -14,7 +14,9 @@ int main(int argc, char* argv[]) if (std::filesystem::is_regular_file(path)) { Recompiler recompiler; - recompiler.LoadConfig(path); + if (!recompiler.LoadConfig(path)) + return -1; + recompiler.Analyse(); auto entry = recompiler.image.symbols.find(recompiler.image.entry_point); diff --git a/XenonRecomp/recompiler.cpp b/XenonRecomp/recompiler.cpp index 1a18cfb..a854fe7 100644 --- a/XenonRecomp/recompiler.cpp +++ b/XenonRecomp/recompiler.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "recompiler.h" +#include static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop) { @@ -9,12 +10,87 @@ static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop) return mstart <= mstop ? value : ~value; } -void Recompiler::LoadConfig(const std::string_view& configFilePath) +bool Recompiler::LoadConfig(const std::string_view& configFilePath) { config.Load(configFilePath); - const auto file = LoadFile((config.directoryPath + config.filePath).c_str()); + std::vector file; + if (!config.patchedFilePath.empty()) + file = LoadFile((config.directoryPath + config.patchedFilePath).c_str()); + + if (file.empty()) + { + file = LoadFile((config.directoryPath + config.filePath).c_str()); + + if (!config.patchFilePath.empty()) + { + const auto patchFile = LoadFile((config.directoryPath + config.patchFilePath).c_str()); + if (!patchFile.empty()) + { + std::vector outBytes; + auto result = XexPatcher::apply(file.data(), file.size(), patchFile.data(), patchFile.size(), outBytes, false); + if (result == XexPatcher::Result::Success) + { + std::exchange(file, outBytes); + + if (!config.patchedFilePath.empty()) + { + std::ofstream stream(config.directoryPath + config.patchedFilePath, std::ios::binary); + if (stream.good()) + { + stream.write(reinterpret_cast(file.data()), file.size()); + stream.close(); + } + } + } + else + { + fmt::print("ERROR: Unable to apply the patch file, "); + + switch (result) + { + case XexPatcher::Result::XexFileUnsupported: + fmt::println("XEX file unsupported"); + break; + + case XexPatcher::Result::XexFileInvalid: + fmt::println("XEX file invalid"); + break; + + case XexPatcher::Result::PatchFileInvalid: + fmt::println("patch file invalid"); + break; + + case XexPatcher::Result::PatchIncompatible: + fmt::println("patch file incompatible"); + break; + + case XexPatcher::Result::PatchFailed: + fmt::println("patch failed"); + break; + + case XexPatcher::Result::PatchUnsupported: + fmt::println("patch unsupported"); + break; + + default: + fmt::println("reason unknown"); + break; + } + + return false; + } + } + else + { + fmt::println("ERROR: Unable to load the patch file"); + return false; + } + } + } + image = Image::ParseImage(file.data(), file.size()); + return true; } void Recompiler::Analyse() diff --git a/XenonRecomp/recompiler.h b/XenonRecomp/recompiler.h index abb5498..8242db5 100644 --- a/XenonRecomp/recompiler.h +++ b/XenonRecomp/recompiler.h @@ -35,7 +35,7 @@ struct Recompiler size_t cppFileIndex = 0; RecompilerConfig config; - void LoadConfig(const std::string_view& configFilePath); + bool LoadConfig(const std::string_view& configFilePath); template void print(fmt::format_string fmt, Args&&... args) diff --git a/XenonRecomp/recompiler_config.cpp b/XenonRecomp/recompiler_config.cpp index e558f29..d746b68 100644 --- a/XenonRecomp/recompiler_config.cpp +++ b/XenonRecomp/recompiler_config.cpp @@ -13,6 +13,8 @@ void RecompilerConfig::Load(const std::string_view& configFilePath) { const auto& main = *mainPtr; filePath = main["file_path"].value_or(""); + patchFilePath = main["patch_file_path"].value_or(""); + patchedFilePath = main["patched_file_path"].value_or(""); outDirectoryPath = main["out_directory_path"].value_or(""); switchTableFilePath = main["switch_table_file_path"].value_or(""); diff --git a/XenonRecomp/recompiler_config.h b/XenonRecomp/recompiler_config.h index ee61ed9..534e503 100644 --- a/XenonRecomp/recompiler_config.h +++ b/XenonRecomp/recompiler_config.h @@ -26,6 +26,8 @@ struct RecompilerConfig { std::string directoryPath; std::string filePath; + std::string patchFilePath; + std::string patchedFilePath; std::string outDirectoryPath; std::string switchTableFilePath; std::unordered_map switchTables; diff --git a/XenonUtils/CMakeLists.txt b/XenonUtils/CMakeLists.txt index 8d4f3c7..b8ca66f 100644 --- a/XenonUtils/CMakeLists.txt +++ b/XenonUtils/CMakeLists.txt @@ -4,7 +4,28 @@ add_library(XenonUtils "disasm.cpp" "xex.cpp" "image.cpp" - "xdbf_wrapper.cpp") + "xdbf_wrapper.cpp" + "xex_patcher.cpp" + "memory_mapped_file.cpp" + "${THIRDPARTY_ROOT}/libmspack/libmspack/mspack/lzxd.c" + "${THIRDPARTY_ROOT}/tiny-AES-c/aes.c" +) -target_include_directories(XenonUtils PUBLIC .) -target_link_libraries(XenonUtils PUBLIC disasm) +target_compile_definitions(XenonUtils + PRIVATE + NOMINMAX +) + +target_include_directories(XenonUtils + PUBLIC + . + PRIVATE + "${THIRDPARTY_ROOT}/libmspack/libmspack/mspack" + "${THIRDPARTY_ROOT}/tiny-AES-c" + "${THIRDPARTY_ROOT}/TinySHA1" +) + +target_link_libraries(XenonUtils + PUBLIC + disasm +) diff --git a/XenonUtils/image.cpp b/XenonUtils/image.cpp index 4903639..39afe92 100644 --- a/XenonUtils/image.cpp +++ b/XenonUtils/image.cpp @@ -37,7 +37,7 @@ Image Image::ParseImage(const uint8_t* data, size_t size) } else if (data[0] == 'X' && data[1] == 'E' && data[2] == 'X' && data[3] == '2') { - return Xex2LoadImage(data); + return Xex2LoadImage(data, size); } return {}; diff --git a/XenonUtils/memory_mapped_file.cpp b/XenonUtils/memory_mapped_file.cpp new file mode 100644 index 0000000..ba3c5d8 --- /dev/null +++ b/XenonUtils/memory_mapped_file.cpp @@ -0,0 +1,169 @@ +#include "memory_mapped_file.h" + +#if !defined(_WIN32) +# include +# include +# include +# include +#endif + +MemoryMappedFile::MemoryMappedFile() +{ + // Default constructor. +} + +MemoryMappedFile::MemoryMappedFile(const std::filesystem::path &path) +{ + open(path); +} + +MemoryMappedFile::~MemoryMappedFile() +{ + close(); +} + +MemoryMappedFile::MemoryMappedFile(MemoryMappedFile &&other) +{ +#if defined(_WIN32) + fileHandle = other.fileHandle; + fileMappingHandle = other.fileMappingHandle; + fileView = other.fileView; + fileSize = other.fileSize; + + other.fileHandle = nullptr; + other.fileMappingHandle = nullptr; + other.fileView = nullptr; + other.fileSize.QuadPart = 0; +#else + fileHandle = other.fileHandle; + fileView = other.fileView; + fileSize = other.fileSize; + + other.fileHandle = -1; + other.fileView = MAP_FAILED; + other.fileSize = 0; +#endif +} + +bool MemoryMappedFile::open(const std::filesystem::path &path) +{ +#if defined(_WIN32) + fileHandle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (fileHandle == INVALID_HANDLE_VALUE) + { + fprintf(stderr, "CreateFileW failed with error %lu.\n", GetLastError()); + fileHandle = nullptr; + return false; + } + + if (!GetFileSizeEx(fileHandle, &fileSize)) + { + fprintf(stderr, "GetFileSizeEx failed with error %lu.\n", GetLastError()); + CloseHandle(fileHandle); + fileHandle = nullptr; + return false; + } + + fileMappingHandle = CreateFileMappingW(fileHandle, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (fileMappingHandle == nullptr) + { + fprintf(stderr, "CreateFileMappingW failed with error %lu.\n", GetLastError()); + CloseHandle(fileHandle); + fileHandle = nullptr; + return false; + } + + fileView = MapViewOfFile(fileMappingHandle, FILE_MAP_READ, 0, 0, 0); + if (fileView == nullptr) + { + fprintf(stderr, "MapViewOfFile failed with error %lu.\n", GetLastError()); + CloseHandle(fileMappingHandle); + CloseHandle(fileHandle); + fileMappingHandle = nullptr; + fileHandle = nullptr; + return false; + } + + return true; +#else + fileHandle = ::open(path.c_str(), O_RDONLY); + if (fileHandle == -1) + { + fprintf(stderr, "open for %s failed with error %s.\n", path.c_str(), strerror(errno)); + return false; + } + + fileSize = lseek(fileHandle, 0, SEEK_END); + if (fileSize == (off_t)(-1)) + { + fprintf(stderr, "lseek failed with error %s.\n", strerror(errno)); + ::close(fileHandle); + fileHandle = -1; + return false; + } + + fileView = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0); + if (fileView == MAP_FAILED) + { + fprintf(stderr, "mmap failed with error %s.\n", strerror(errno)); + ::close(fileHandle); + fileHandle = -1; + return false; + } + + return true; +#endif +} + +void MemoryMappedFile::close() +{ +#if defined(_WIN32) + if (fileView != nullptr) + { + UnmapViewOfFile(fileView); + } + + if (fileMappingHandle != nullptr) + { + CloseHandle(fileMappingHandle); + } + + if (fileHandle != nullptr) + { + CloseHandle(fileHandle); + } +#else + if (fileView != MAP_FAILED) + { + munmap(fileView, fileSize); + } + + if (fileHandle != -1) + { + ::close(fileHandle); + } +#endif +} + +bool MemoryMappedFile::isOpen() const +{ +#if defined(_WIN32) + return (fileView != nullptr); +#else + return (fileView != MAP_FAILED); +#endif +} + +uint8_t *MemoryMappedFile::data() const +{ + return reinterpret_cast(fileView); +} + +size_t MemoryMappedFile::size() const +{ +#if defined(_WIN32) + return fileSize.QuadPart; +#else + return static_cast(fileSize); +#endif +} diff --git a/XenonUtils/memory_mapped_file.h b/XenonUtils/memory_mapped_file.h new file mode 100644 index 0000000..a3de88b --- /dev/null +++ b/XenonUtils/memory_mapped_file.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#if defined(_WIN32) +# include +#else +# include +#endif + +struct MemoryMappedFile { +#if defined(_WIN32) + HANDLE fileHandle = nullptr; + HANDLE fileMappingHandle = nullptr; + LPVOID fileView = nullptr; + LARGE_INTEGER fileSize = {}; +#else + int fileHandle = -1; + void *fileView = MAP_FAILED; + off_t fileSize = 0; +#endif + + MemoryMappedFile(); + MemoryMappedFile(const std::filesystem::path &path); + MemoryMappedFile(MemoryMappedFile &&other); + ~MemoryMappedFile(); + bool open(const std::filesystem::path &path); + void close(); + bool isOpen() const; + uint8_t *data() const; + size_t size() const; +}; diff --git a/XenonUtils/xex.cpp b/XenonUtils/xex.cpp index 1b1cd4d..d1972c0 100644 --- a/XenonUtils/xex.cpp +++ b/XenonUtils/xex.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #define STRINGIFY(X) #X #define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) } @@ -121,58 +122,80 @@ std::unordered_map XboxKernelExports = #include "xbox/xboxkrnl_table.inc" }; -Image Xex2LoadImage(const uint8_t* data) +Image Xex2LoadImage(const uint8_t* data, size_t dataSize) { - auto* header = reinterpret_cast(data); - auto* security = reinterpret_cast(data + header->AddressOfSecurityInfo); - - const auto* compressionInfo = Xex2FindOptionalHeader(header, XEX_HEADER_FILE_FORMAT_INFO); + auto* header = reinterpret_cast(data); + auto* security = reinterpret_cast(data + header->securityOffset); + const auto* fileFormatInfo = reinterpret_cast(getOptHeaderPtr(data, XEX_HEADER_FILE_FORMAT_INFO)); Image image{}; std::unique_ptr result{}; - size_t imageSize = security->SizeOfImage; + size_t imageSize = security->imageSize; // Decompress image - if (compressionInfo != nullptr) + if (fileFormatInfo != nullptr) { - assert(compressionInfo->CompressionType <= XEX_COMPRESSION_BASIC); - assert(compressionInfo->EncryptionType == XEX_ENCRYPTION_NONE); + assert(fileFormatInfo->compressionType <= XEX_COMPRESSION_BASIC); - if (compressionInfo->CompressionType == XEX_COMPRESSION_NONE) + std::unique_ptr decryptedData; + const uint8_t* srcData = nullptr; + + if (fileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL) + { + constexpr uint32_t KeySize = 16; + AES_ctx aesContext; + + uint8_t decryptedKey[KeySize]; + memcpy(decryptedKey, security->aesKey, KeySize); + AES_init_ctx_iv(&aesContext, Xex2RetailKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decryptedKey, KeySize); + + decryptedData = std::make_unique(dataSize - header->headerSize); + memcpy(decryptedData.get(), data + header->headerSize, dataSize - header->headerSize); + AES_init_ctx_iv(&aesContext, decryptedKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decryptedData.get(), dataSize - header->headerSize); + + srcData = decryptedData.get(); + } + else + { + srcData = data + header->headerSize; + } + + if (fileFormatInfo->compressionType == XEX_COMPRESSION_NONE) { result = std::make_unique(imageSize); - memcpy(result.get(), data + header->SizeOfHeader, imageSize); + memcpy(result.get(), srcData, imageSize); } - else if (compressionInfo->CompressionType == XEX_COMPRESSION_BASIC) + else if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC) { - auto* blocks = reinterpret_cast(compressionInfo + 1); - const size_t numBlocks = (compressionInfo->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1; + auto* blocks = reinterpret_cast(fileFormatInfo + 1); + const size_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionInfo)) - 1; imageSize = 0; for (size_t i = 0; i < numBlocks; i++) { - imageSize += blocks[i].SizeOfData + blocks[i].SizeOfPadding; + imageSize += blocks[i].dataSize + blocks[i].zeroSize; } result = std::make_unique(imageSize); - auto* srcData = data + header->SizeOfHeader; auto* destData = result.get(); for (size_t i = 0; i < numBlocks; i++) { - memcpy(destData, srcData, blocks[i].SizeOfData); + memcpy(destData, srcData, blocks[i].dataSize); - srcData += blocks[i].SizeOfData; - destData += blocks[i].SizeOfData; + srcData += blocks[i].dataSize; + destData += blocks[i].dataSize; - memset(destData, 0, blocks[i].SizeOfPadding); - destData += blocks[i].SizeOfPadding; + memset(destData, 0, blocks[i].zeroSize); + destData += blocks[i].zeroSize; } } } image.data = std::move(result); - image.size = imageSize; + image.size = security->imageSize; // Map image const auto* dosHeader = reinterpret_cast(image.data.get()); @@ -198,22 +221,22 @@ Image Xex2LoadImage(const uint8_t* data) section.Misc.VirtualSize, flags, image.data.get() + section.VirtualAddress); } - auto* imports = Xex2FindOptionalHeader(header, XEX_HEADER_IMPORT_LIBRARIES); + auto* imports = reinterpret_cast(getOptHeaderPtr(data, XEX_HEADER_IMPORT_LIBRARIES)); if (imports != nullptr) { std::vector stringTable; auto* pStrTable = reinterpret_cast(imports + 1); - for (size_t i = 0; i < imports->NumImports; i++) + for (size_t i = 0; i < imports->numImports; i++) { stringTable.emplace_back(pStrTable); pStrTable += strlen(pStrTable) + 1; } - auto* library = (XEX_IMPORT_LIBRARY*)(((char*)imports) + sizeof(XEX_IMPORT_HEADER) + imports->SizeOfStringTable); + auto* library = (Xex2ImportLibrary*)(((char*)imports) + sizeof(Xex2ImportHeader) + imports->sizeOfStringTable); for (size_t i = 0; i < stringTable.size(); i++) { - auto* descriptors = (XEX_IMPORT_DESCRIPTOR*)(library + 1); + auto* descriptors = (Xex2ImportDescriptor*)(library + 1); static std::unordered_map DummyExports; const std::unordered_map* names = &DummyExports; @@ -226,25 +249,25 @@ Image Xex2LoadImage(const uint8_t* data) names = &XboxKernelExports; } - for (size_t im = 0; im < library->NumberOfImports; im++) + for (size_t im = 0; im < library->numberOfImports; im++) { - auto originalThunk = (XEX_THUNK_DATA*)image.Find(descriptors[im].FirstThunk); + auto originalThunk = (Xex2ThunkData*)image.Find(descriptors[im].firstThunk); auto originalData = originalThunk; - originalData->Data = ByteSwap(originalData->Data); + originalData->data = ByteSwap(originalData->data); - if (originalData->OriginalData.Type != 0) + if (originalData->originalData.type != 0) { uint32_t thunk[4] = { 0x00000060, 0x00000060, 0x00000060, 0x2000804E }; - auto name = names->find(originalData->OriginalData.Ordinal); + auto name = names->find(originalData->originalData.ordinal); if (name != names->end()) { - image.symbols.insert({ name->second, descriptors[im].FirstThunk, sizeof(thunk), Symbol_Function }); + image.symbols.insert({ name->second, descriptors[im].firstThunk, sizeof(thunk), Symbol_Function }); } memcpy(originalThunk, thunk, sizeof(thunk)); } } - library = (XEX_IMPORT_LIBRARY*)((char*)(library + 1) + library->NumberOfImports * sizeof(XEX_IMPORT_DESCRIPTOR)); + library = (Xex2ImportLibrary*)((char*)(library + 1) + library->numberOfImports * sizeof(Xex2ImportDescriptor)); } } diff --git a/XenonUtils/xex.h b/XenonUtils/xex.h index bdaa03c..9ab831e 100644 --- a/XenonUtils/xex.h +++ b/XenonUtils/xex.h @@ -2,18 +2,17 @@ #include #include "xbox.h" -#define XEX_COMPRESSION_NONE 0 -#define XEX_COMPRESSION_BASIC 1 +inline constexpr uint8_t Xex2RetailKey[16] = { 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91 }; +inline constexpr uint8_t AESBlankIV[16] = {}; -#define XEX_ENCRYPTION_NONE 0 - -enum _XEX_THUNK_TYPES +enum Xex2ModuleFlags { - XEX_THUNK_VARIABLE = 0, - XEX_THUNK_FUNCTION = 1, + XEX_MODULE_MODULE_PATCH = 0x10, + XEX_MODULE_PATCH_FULL = 0x20, + XEX_MODULE_PATCH_DELTA = 0x40, }; -enum _XEX_OPTIONAL_HEADER_TYPES +enum Xex2HeaderKeys { XEX_HEADER_RESOURCE_INFO = 0x000002FF, XEX_HEADER_FILE_FORMAT_INFO = 0x000003FF, @@ -47,118 +46,212 @@ enum _XEX_OPTIONAL_HEADER_TYPES XEX_HEADER_EXPORTS_BY_NAME = 0x00E10402, }; -typedef struct _XEX_FILE_FORMAT_INFO +enum Xex2EncryptionType { - be SizeOfHeader; - be EncryptionType; - be CompressionType; -} XEX_FILE_FORMAT_INFO; + XEX_ENCRYPTION_NONE = 0, + XEX_ENCRYPTION_NORMAL = 1, +}; -typedef struct _XEX_RESOURCE_INFO +enum Xex2CompressionType { - be SizeOfHeader; - uint8_t ResourceID[8]; - be Offset; - be SizeOfData; -} XEX_RESOURCE_INFO; + XEX_COMPRESSION_NONE = 0, + XEX_COMPRESSION_BASIC = 1, + XEX_COMPRESSION_NORMAL = 2, + XEX_COMPRESSION_DELTA = 3, +}; -typedef struct _XEX_BASIC_FILE_COMPRESSION_INFO +enum Xex2SectionType { - be SizeOfData; - be SizeOfPadding; -} XEX_BASIC_FILE_COMPRESSION_INFO; + XEX_SECTION_CODE = 1, + XEX_SECTION_DATA = 2, + XEX_SECTION_READONLY_DATA = 3, +}; -typedef struct _XEX_THUNK_DATA { +enum Xex2ThunkTypes +{ + XEX_THUNK_VARIABLE = 0, + XEX_THUNK_FUNCTION = 1, +}; + +struct Xex2OptHeader +{ + be key; + + union + { + be value; + be offset; + }; +}; + +struct Xex2Header +{ + be magic; + be moduleFlags; + be headerSize; + be reserved; + be securityOffset; + be headerCount; +}; + +struct Xex2PageDescriptor +{ + union + { + // Must be endian-swapped before reading the bitfield. + uint32_t beValue; + struct + { + uint32_t info : 4; + uint32_t pageCount : 28; + }; + }; + + char dataDigest[0x14]; +}; + +struct Xex2SecurityInfo +{ + be headerSize; + be imageSize; + char rsaSignature[0x100]; + be unknown; + be imageFlags; + be loadAddress; + char sectionDigest[0x14]; + be importTableCount; + char importTableDigest[0x14]; + char xgd2MediaId[0x10]; + char aesKey[0x10]; + be exportTable; + char headerDigest[0x14]; + be region; + be allowedMediaTypes; + be pageDescriptorCount; +}; + +struct Xex2DeltaPatch +{ + be oldAddress; + be newAddress; + be uncompressedLength; + be compressedLength; + char patchData[1]; +}; + +struct Xex2OptDeltaPatchDescriptor +{ + be size; + be targetVersionValue; + be sourceVersionValue; + uint8_t digestSource[0x14]; + uint8_t imageKeySource[0x10]; + be sizeOfTargetHeaders; + be deltaHeadersSourceOffset; + be deltaHeadersSourceSize; + be deltaHeadersTargetOffset; + be deltaImageSourceOffset; + be deltaImageSourceSize; + be deltaImageTargetOffset; + Xex2DeltaPatch info; +}; + +struct Xex2FileBasicCompressionBlock +{ + be dataSize; + be zeroSize; +}; + +struct Xex2FileBasicCompressionInfo +{ + Xex2FileBasicCompressionBlock firstBlock; +}; + +struct Xex2CompressedBlockInfo +{ + be blockSize; + uint8_t blockHash[20]; +}; + +struct Xex2FileNormalCompressionInfo +{ + be windowSize; + Xex2CompressedBlockInfo firstBlock; +}; + +struct Xex2OptFileFormatInfo +{ + be infoSize; + be encryptionType; + be compressionType; +}; + +struct Xex2ImportHeader +{ + be sizeOfHeader; + be sizeOfStringTable; + be numImports; +}; + +struct Xex2ImportLibrary +{ + be size; + char nextImportDigest[0x14]; + be id; + be version; + be minVersion; + be name; + be numberOfImports; +}; + +struct Xex2ImportDescriptor +{ + be firstThunk; // VA XEX_THUNK_DATA +}; + +struct Xex2ThunkData +{ union { struct { - uint16_t Ordinal : 16; - uint16_t Hint : 8; - uint16_t Type : 8; - } OriginalData; + uint16_t ordinal : 16; + uint16_t hint : 8; + uint16_t type : 8; + } originalData; - be Ordinal; - be Function; - be AddressOfData; + be ordinal; + be function; + be addressOfData; // For easier swapping - uint32_t Data; + uint32_t data; }; -} XEX_THUNK_DATA; +}; -typedef struct _XEX_IMPORT_HEADER { - be SizeOfHeader; - be SizeOfStringTable; - be NumImports; -} XEX_IMPORT_HEADER; - -typedef struct _XEX_IMPORT_LIBRARY { - be Size; - char NextImportDigest[0x14]; - be ID; - be Version; - be MinVersion; - be Name; - be NumberOfImports; -} XEX_IMPORT_LIBRARY; - -static_assert(sizeof(XEX_IMPORT_LIBRARY) == 0x28); - -typedef struct _XEX_IMPORT_DESCRIPTOR { - be FirstThunk; // VA XEX_THUNK_DATA -} XEX_IMPORT_DESCRIPTOR; - -typedef struct _XEX_OPTIONAL_HEADER +struct Xex2ResourceInfo { - be Type; - be Address; -} XEX_OPTIONAL_HEADER; + be sizeOfHeader; + uint8_t resourceID[8]; + be offset; + be sizeOfData; +}; -typedef struct _XEX2_SECURITY_INFO +inline const void* getOptHeaderPtr(const uint8_t* moduleBytes, uint32_t headerKey) { - be SizeOfHeader; - be SizeOfImage; - char RsaSignature[0x100]; - be Unknown108; - be ImageFlags; - be ImageBase; - char SectionDigest[0x14]; - be NumberOfImports; - char ImportsDigest[0x14]; - char Xgd2MediaID[0x10]; - char AesKey[0x10]; - be AddressOfExports; - char HeaderDigest[0x14]; - be Region; - be AllowedMediaTypes; - be NumberOfPageDescriptors; -} XEX2_SECURITY_INFO; - -typedef struct _XEX_HEADER -{ - char Signature[4]; - be Flags; - be SizeOfHeader; - char Reserved[4]; - be AddressOfSecurityInfo; - be NumberOfOptionalHeaders; -} XEX_HEADER; - -template -inline static const T* Xex2FindOptionalHeader(const void* base, const XEX_OPTIONAL_HEADER* headers, size_t n, _XEX_OPTIONAL_HEADER_TYPES type) -{ - for (size_t i = 0; i < n; i++) + const Xex2Header* xex2Header = (const Xex2Header*)(moduleBytes); + for (uint32_t i = 0; i < xex2Header->headerCount; i++) { - if (headers[i].Type == (uint32_t)type) + const Xex2OptHeader& optHeader = ((const Xex2OptHeader*)(xex2Header + 1))[i]; + if (optHeader.key == headerKey) { - if ((type & 0xFF) == 0) + if ((headerKey & 0xFF) == 0) { - return reinterpret_cast(&headers[i].Address); + return &optHeader.value; } else { - return reinterpret_cast(static_cast(base) + headers[i].Address); + return &moduleBytes[optHeader.offset]; } } } @@ -166,11 +259,5 @@ inline static const T* Xex2FindOptionalHeader(const void* base, const XEX_OPTION return nullptr; } -template -inline static const T* Xex2FindOptionalHeader(const XEX_HEADER* header, _XEX_OPTIONAL_HEADER_TYPES type) -{ - return Xex2FindOptionalHeader(header, (XEX_OPTIONAL_HEADER*)(header + 1), header->NumberOfOptionalHeaders, type); -} - struct Image; -Image Xex2LoadImage(const uint8_t* data); +Image Xex2LoadImage(const uint8_t* data, size_t dataSize); diff --git a/XenonUtils/xex_patcher.cpp b/XenonUtils/xex_patcher.cpp new file mode 100644 index 0000000..18aacda --- /dev/null +++ b/XenonUtils/xex_patcher.cpp @@ -0,0 +1,512 @@ +// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc + +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2023 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xex_patcher.h" +#include "xex.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "memory_mapped_file.h" + +struct mspack_memory_file +{ + mspack_system sys; + void *buffer; + size_t bufferSize; + size_t offset; +}; + +static mspack_memory_file *mspack_memory_open(mspack_system *sys, void *buffer, size_t bufferSize) +{ + assert(bufferSize < INT_MAX); + + if (bufferSize >= INT_MAX) + { + return nullptr; + } + + mspack_memory_file *memoryFile = (mspack_memory_file *)(std::calloc(1, sizeof(mspack_memory_file))); + if (memoryFile == nullptr) + { + return memoryFile; + } + + memoryFile->buffer = buffer; + memoryFile->bufferSize = bufferSize; + memoryFile->offset = 0; + return memoryFile; +} + +static void mspack_memory_close(mspack_memory_file *file) +{ + std::free(file); +} + +static int mspack_memory_read(mspack_file *file, void *buffer, int chars) +{ + mspack_memory_file *memoryFile = (mspack_memory_file *)(file); + const size_t remaining = memoryFile->bufferSize - memoryFile->offset; + const size_t total = std::min(size_t(chars), remaining); + std::memcpy(buffer, (uint8_t *)(memoryFile->buffer) + memoryFile->offset, total); + memoryFile->offset += total; + return int(total); +} + +static int mspack_memory_write(mspack_file *file, void *buffer, int chars) +{ + mspack_memory_file *memoryFile = (mspack_memory_file *)(file); + const size_t remaining = memoryFile->bufferSize - memoryFile->offset; + const size_t total = std::min(size_t(chars), remaining); + std::memcpy((uint8_t *)(memoryFile->buffer) + memoryFile->offset, buffer, total); + memoryFile->offset += total; + return int(total); +} + +static void *mspack_memory_alloc(mspack_system *sys, size_t chars) +{ + return std::calloc(chars, 1); +} + +static void mspack_memory_free(void *ptr) +{ + std::free(ptr); +} + +static void mspack_memory_copy(void *src, void *dest, size_t chars) +{ + std::memcpy(dest, src, chars); +} + +static mspack_system *mspack_memory_sys_create() +{ + auto sys = (mspack_system *)(std::calloc(1, sizeof(mspack_system))); + if (!sys) + { + return nullptr; + } + + sys->read = mspack_memory_read; + sys->write = mspack_memory_write; + sys->alloc = mspack_memory_alloc; + sys->free = mspack_memory_free; + sys->copy = mspack_memory_copy; + return sys; +} + +static void mspack_memory_sys_destroy(struct mspack_system *sys) +{ + free(sys); +} + +#if defined(_WIN32) +inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex) +{ + return _BitScanForward((unsigned long *)(outFirstSetIndex), v) != 0; +} + +inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex) +{ + return _BitScanForward64((unsigned long *)(outFirstSetIndex), v) != 0; +} + +#else +inline bool bitScanForward(uint32_t v, uint32_t *outFirstSetIndex) +{ + int i = ffs(v); + *outFirstSetIndex = i - 1; + return i != 0; +} + +inline bool bitScanForward(uint64_t v, uint32_t *outFirstSetIndex) +{ + int i = __builtin_ffsll(v); + *outFirstSetIndex = i - 1; + return i != 0; +} +#endif + +static int lzxDecompress(const void *lzxData, size_t lzxLength, void *dst, size_t dstLength, uint32_t windowSize, void *windowData, size_t windowDataLength) +{ + int resultCode = 1; + uint32_t windowBits; + if (!bitScanForward(windowSize, &windowBits)) { + return resultCode; + } + + mspack_system *sys = mspack_memory_sys_create(); + mspack_memory_file *lzxSrc = mspack_memory_open(sys, (void *)(lzxData), lzxLength); + mspack_memory_file *lzxDst = mspack_memory_open(sys, dst, dstLength); + lzxd_stream *lzxd = lzxd_init(sys, (mspack_file *)(lzxSrc), (mspack_file *)(lzxDst), windowBits, 0, 0x8000, dstLength, 0); + if (lzxd != nullptr) { + if (windowData != nullptr) { + size_t paddingLength = windowSize - windowDataLength; + std::memset(&lzxd->window[0], 0, paddingLength); + std::memcpy(&lzxd->window[paddingLength], windowData, windowDataLength); + lzxd->ref_data_size = windowSize; + } + + resultCode = lzxd_decompress(lzxd, dstLength); + lzxd_free(lzxd); + } + + if (lzxSrc) { + mspack_memory_close(lzxSrc); + } + + if (lzxDst) { + mspack_memory_close(lzxDst); + } + + if (sys) { + mspack_memory_sys_destroy(sys); + } + + return resultCode; +} + +static int lzxDeltaApplyPatch(const Xex2DeltaPatch *deltaPatch, uint32_t patchLength, uint32_t windowSize, uint8_t *dstData) +{ + const void *patchEnd = (const uint8_t *)(deltaPatch) + patchLength; + const Xex2DeltaPatch *curPatch = deltaPatch; + while (patchEnd > curPatch) + { + int patchSize = -4; + if (curPatch->compressedLength == 0 && curPatch->uncompressedLength == 0 && curPatch->newAddress == 0 && curPatch->oldAddress == 0) + { + // End of patch. + break; + } + + switch (curPatch->compressedLength) + { + case 0: + // Set the data to zeroes. + std::memset(&dstData[curPatch->newAddress], 0, curPatch->uncompressedLength); + break; + case 1: + // Move the data. + std::memcpy(&dstData[curPatch->newAddress], &dstData[curPatch->oldAddress], curPatch->uncompressedLength); + break; + default: + // Decompress the data into the destination. + patchSize = curPatch->compressedLength - 4; + int result = lzxDecompress(curPatch->patchData, curPatch->compressedLength, &dstData[curPatch->newAddress], curPatch->uncompressedLength, windowSize, &dstData[curPatch->oldAddress], curPatch->uncompressedLength); + if (result != 0) + { + return result; + } + + break; + } + + curPatch++; + curPatch = (const Xex2DeltaPatch *)((const uint8_t *)(curPatch) + patchSize); + } + + return 0; +} + +XexPatcher::Result XexPatcher::apply(const uint8_t* xexBytes, size_t xexBytesSize, const uint8_t* patchBytes, size_t patchBytesSize, std::vector &outBytes, bool skipData) +{ + // Validate headers. + static const char Xex2Magic[] = "XEX2"; + const Xex2Header *xexHeader = (const Xex2Header *)(xexBytes); + if (memcmp(xexBytes, Xex2Magic, 4) != 0) + { + return Result::XexFileInvalid; + } + + const Xex2Header *patchHeader = (const Xex2Header *)(patchBytes); + if (memcmp(patchBytes, Xex2Magic, 4) != 0) + { + return Result::PatchFileInvalid; + } + + if ((patchHeader->moduleFlags & (XEX_MODULE_MODULE_PATCH | XEX_MODULE_PATCH_DELTA | XEX_MODULE_PATCH_FULL)) == 0) + { + return Result::PatchFileInvalid; + } + + // Validate patch. + const Xex2OptDeltaPatchDescriptor *patchDescriptor = (const Xex2OptDeltaPatchDescriptor *)(getOptHeaderPtr(patchBytes, XEX_HEADER_DELTA_PATCH_DESCRIPTOR)); + if (patchDescriptor == nullptr) + { + return Result::PatchFileInvalid; + } + + const Xex2OptFileFormatInfo *patchFileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(patchBytes, XEX_HEADER_FILE_FORMAT_INFO)); + if (patchFileFormatInfo == nullptr) + { + return Result::PatchFileInvalid; + } + + if (patchFileFormatInfo->compressionType != XEX_COMPRESSION_DELTA) + { + return Result::PatchFileInvalid; + } + + if (patchDescriptor->deltaHeadersSourceOffset > xexHeader->headerSize) + { + return Result::PatchIncompatible; + } + + if (patchDescriptor->deltaHeadersSourceSize > (xexHeader->headerSize - patchDescriptor->deltaHeadersSourceOffset)) + { + return Result::PatchIncompatible; + } + + if (patchDescriptor->deltaHeadersTargetOffset > patchDescriptor->sizeOfTargetHeaders) + { + return Result::PatchIncompatible; + } + + uint32_t deltaTargetSize = patchDescriptor->sizeOfTargetHeaders - patchDescriptor->deltaHeadersTargetOffset; + if (patchDescriptor->deltaHeadersSourceSize > deltaTargetSize) + { + return Result::PatchIncompatible; + } + + // Apply patch. + uint32_t headerTargetSize = patchDescriptor->sizeOfTargetHeaders; + if (headerTargetSize == 0) + { + headerTargetSize = patchDescriptor->deltaHeadersTargetOffset + patchDescriptor->deltaHeadersSourceSize; + } + + // Create the bytes for the new XEX header. Copy over the existing data. + uint32_t newXexHeaderSize = std::max(headerTargetSize, xexHeader->headerSize.get()); + outBytes.resize(newXexHeaderSize); + memset(outBytes.data(), 0, newXexHeaderSize); + memcpy(outBytes.data(), xexBytes, headerTargetSize); + + Xex2Header *newXexHeader = (Xex2Header *)(outBytes.data()); + if (patchDescriptor->deltaHeadersSourceOffset > 0) + { + memcpy(&outBytes[patchDescriptor->deltaHeadersTargetOffset], &outBytes[patchDescriptor->deltaHeadersSourceOffset], patchDescriptor->deltaHeadersSourceSize); + } + + int resultCode = lzxDeltaApplyPatch(&patchDescriptor->info, patchDescriptor->size, ((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->windowSize, outBytes.data()); + if (resultCode != 0) + { + return Result::PatchFailed; + } + + // Make the header the specified size by the patch. + outBytes.resize(headerTargetSize); + newXexHeader = (Xex2Header *)(outBytes.data()); + + // Copy the rest of the data. + const Xex2SecurityInfo *newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]); + outBytes.resize(outBytes.size() + newSecurityInfo->imageSize); + memset(&outBytes[headerTargetSize], 0, outBytes.size() - headerTargetSize); + memcpy(&outBytes[headerTargetSize], &xexBytes[xexHeader->headerSize], xexBytesSize - xexHeader->headerSize); + newXexHeader = (Xex2Header *)(outBytes.data()); + newSecurityInfo = (const Xex2SecurityInfo *)(&outBytes[newXexHeader->securityOffset]); + + // Decrypt the keys and validate that the patch is compatible with the base file. + constexpr uint32_t KeySize = 16; + const Xex2SecurityInfo *originalSecurityInfo = (const Xex2SecurityInfo *)(&xexBytes[xexHeader->securityOffset]); + const Xex2SecurityInfo *patchSecurityInfo = (const Xex2SecurityInfo *)(&patchBytes[patchHeader->securityOffset]); + uint8_t decryptedOriginalKey[KeySize]; + uint8_t decryptedNewKey[KeySize]; + uint8_t decryptedPatchKey[KeySize]; + uint8_t decrpytedImageKeySource[KeySize]; + memcpy(decryptedOriginalKey, originalSecurityInfo->aesKey, KeySize); + memcpy(decryptedNewKey, newSecurityInfo->aesKey, KeySize); + memcpy(decryptedPatchKey, patchSecurityInfo->aesKey, KeySize); + memcpy(decrpytedImageKeySource, patchDescriptor->imageKeySource, KeySize); + + AES_ctx aesContext; + AES_init_ctx_iv(&aesContext, Xex2RetailKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decryptedOriginalKey, KeySize); + + AES_ctx_set_iv(&aesContext, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decryptedNewKey, KeySize); + + AES_init_ctx_iv(&aesContext, decryptedNewKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decryptedPatchKey, KeySize); + + AES_ctx_set_iv(&aesContext, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, decrpytedImageKeySource, KeySize); + + // Validate the patch's key matches the one from the original XEX. + if (memcmp(decrpytedImageKeySource, decryptedOriginalKey, KeySize) != 0) + { + return Result::PatchIncompatible; + } + + // Don't process the rest of the patch. + if (skipData) + { + return Result::Success; + } + + // Decrypt base XEX if necessary. + const Xex2OptFileFormatInfo *fileFormatInfo = (const Xex2OptFileFormatInfo *)(getOptHeaderPtr(xexBytes, XEX_HEADER_FILE_FORMAT_INFO)); + if (fileFormatInfo == nullptr) + { + return Result::XexFileInvalid; + } + + if (fileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL) + { + AES_init_ctx_iv(&aesContext, decryptedOriginalKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, &outBytes[headerTargetSize], xexBytesSize - xexHeader->headerSize); + } + else if (fileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE) + { + return Result::XexFileInvalid; + } + + // Decompress base XEX if necessary. + if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC) + { + const Xex2FileBasicCompressionBlock *blocks = &((const Xex2FileBasicCompressionInfo*)(fileFormatInfo + 1))->firstBlock; + int32_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionBlock)) - 1; + int32_t baseCompressedSize = 0; + int32_t baseImageSize = 0; + for (int32_t i = 0; i < numBlocks; i++) { + baseCompressedSize += blocks[i].dataSize; + baseImageSize += blocks[i].dataSize + blocks[i].zeroSize; + } + + if (outBytes.size() < (headerTargetSize + baseImageSize)) + { + return Result::XexFileInvalid; + } + + // Reverse iteration allows to perform this decompression in place. + uint8_t *srcDataCursor = outBytes.data() + headerTargetSize + baseCompressedSize; + uint8_t *outDataCursor = outBytes.data() + headerTargetSize + baseImageSize; + for (int32_t i = numBlocks - 1; i >= 0; i--) + { + outDataCursor -= blocks[i].zeroSize; + memset(outDataCursor, 0, blocks[i].zeroSize); + outDataCursor -= blocks[i].dataSize; + srcDataCursor -= blocks[i].dataSize; + memmove(outDataCursor, srcDataCursor, blocks[i].dataSize); + } + } + else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL || fileFormatInfo->compressionType == XEX_COMPRESSION_DELTA) + { + return Result::XexFileUnsupported; + } + else if (fileFormatInfo->compressionType != XEX_COMPRESSION_NONE) + { + return Result::XexFileInvalid; + } + + Xex2OptFileFormatInfo *newFileFormatInfo = (Xex2OptFileFormatInfo *)(getOptHeaderPtr(outBytes.data(), XEX_HEADER_FILE_FORMAT_INFO)); + if (newFileFormatInfo == nullptr) + { + return Result::PatchFailed; + } + + // Update the header to indicate no encryption or compression is used. + newFileFormatInfo->encryptionType = XEX_ENCRYPTION_NONE; + newFileFormatInfo->compressionType = XEX_COMPRESSION_NONE; + + // Copy and decrypt patch data if necessary. + std::vector patchData; + patchData.resize(patchBytesSize - patchHeader->headerSize); + memcpy(patchData.data(), &patchBytes[patchHeader->headerSize], patchData.size()); + + if (patchFileFormatInfo->encryptionType == XEX_ENCRYPTION_NORMAL) + { + AES_init_ctx_iv(&aesContext, decryptedPatchKey, AESBlankIV); + AES_CBC_decrypt_buffer(&aesContext, patchData.data(), patchData.size()); + } + else if (patchFileFormatInfo->encryptionType != XEX_ENCRYPTION_NONE) + { + return Result::PatchFileInvalid; + } + + const Xex2CompressedBlockInfo *currentBlock = &((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->firstBlock; + uint8_t *outExe = &outBytes[newXexHeader->headerSize]; + if (patchDescriptor->deltaImageSourceOffset > 0) + { + memcpy(&outExe[patchDescriptor->deltaImageTargetOffset], &outExe[patchDescriptor->deltaImageSourceOffset], patchDescriptor->deltaImageSourceSize); + } + + static const uint32_t DigestSize = 20; + uint8_t sha1Digest[DigestSize]; + sha1::SHA1 sha1Context; + uint8_t *patchDataCursor = patchData.data(); + while (currentBlock->blockSize > 0) + { + const Xex2CompressedBlockInfo *nextBlock = (const Xex2CompressedBlockInfo *)(patchDataCursor); + + // Hash and validate the block. + sha1Context.reset(); + sha1Context.processBytes(patchDataCursor, currentBlock->blockSize); + sha1Context.finalize(sha1Digest); + if (memcmp(sha1Digest, currentBlock->blockHash, DigestSize) != 0) + { + return Result::PatchFailed; + } + + patchDataCursor += 24; + + // Apply the block's patch data. + uint32_t blockDataSize = currentBlock->blockSize - 24; + if (lzxDeltaApplyPatch((const Xex2DeltaPatch *)(patchDataCursor), blockDataSize, ((const Xex2FileNormalCompressionInfo*)(patchFileFormatInfo + 1))->windowSize, outExe) != 0) + { + return Result::PatchFailed; + } + + patchDataCursor += blockDataSize; + currentBlock = nextBlock; + } + + return Result::Success; +} + +XexPatcher::Result XexPatcher::apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath) +{ + MemoryMappedFile baseXexFile(baseXexPath); + MemoryMappedFile patchFile(patchXexPath); + if (!baseXexFile.isOpen() || !patchFile.isOpen()) + { + return Result::FileOpenFailed; + } + + std::vector newXexBytes; + Result result = apply(baseXexFile.data(), baseXexFile.size(), patchFile.data(), patchFile.size(), newXexBytes, false); + if (result != Result::Success) + { + return result; + } + + std::ofstream newXexFile(newXexPath, std::ios::binary); + if (!newXexFile.is_open()) + { + return Result::FileOpenFailed; + } + + newXexFile.write((const char *)(newXexBytes.data()), newXexBytes.size()); + newXexFile.close(); + + if (newXexFile.bad()) + { + std::filesystem::remove(newXexPath); + return Result::FileWriteFailed; + } + + return Result::Success; +} diff --git a/XenonUtils/xex_patcher.h b/XenonUtils/xex_patcher.h new file mode 100644 index 0000000..b8c5728 --- /dev/null +++ b/XenonUtils/xex_patcher.h @@ -0,0 +1,35 @@ +// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/cpu/xex_module.cc + +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2023 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#pragma once + +#include +#include +#include +#include + +struct XexPatcher +{ + enum class Result { + Success, + FileOpenFailed, + FileWriteFailed, + XexFileUnsupported, + XexFileInvalid, + PatchFileInvalid, + PatchIncompatible, + PatchFailed, + PatchUnsupported + }; + + static Result apply(const uint8_t* xexBytes, size_t xexBytesSize, const uint8_t* patchBytes, size_t patchBytesSize, std::vector &outBytes, bool skipData); + static Result apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath); +}; diff --git a/thirdparty/TinySHA1/TinySHA1.hpp b/thirdparty/TinySHA1/TinySHA1.hpp new file mode 100644 index 0000000..2493e88 --- /dev/null +++ b/thirdparty/TinySHA1/TinySHA1.hpp @@ -0,0 +1,223 @@ +/* + * + * TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based + * on the implementation in boost::uuid::details. + * + * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 + * + * Copyright (c) 2012-22 SAURAV MOHAPATRA + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Taken from https://github.com/mohaps/TinySHA1 + * Modified for use by Xenia + */ +#ifndef _TINY_SHA1_HPP_ +#define _TINY_SHA1_HPP_ + +#include +#include +#include +#include + +namespace sha1 { +class SHA1 { + public: + typedef uint32_t digest32_t[5]; + typedef uint8_t digest8_t[20]; + inline static uint32_t LeftRotate(uint32_t value, size_t count) { + return (value << count) ^ (value >> (32 - count)); + } + SHA1() { reset(); } + virtual ~SHA1() {} + SHA1(const SHA1& s) { *this = s; } + const SHA1& operator=(const SHA1& s) { + memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t)); + memcpy(m_block, s.m_block, 64); + m_blockByteIndex = s.m_blockByteIndex; + m_byteCount = s.m_byteCount; + + return *this; + } + + SHA1& init(const uint32_t digest[5], const uint8_t block[64], + uint32_t count) { + std::memcpy(m_digest, digest, 20); + std::memcpy(m_block, block, count % 64); + m_byteCount = count; + m_blockByteIndex = count % 64; + + return *this; + } + + const uint32_t* getDigest() const { return m_digest; } + const uint8_t* getBlock() const { return m_block; } + size_t getBlockByteIndex() const { return m_blockByteIndex; } + size_t getByteCount() const { return m_byteCount; } + + SHA1& reset() { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + m_blockByteIndex = 0; + m_byteCount = 0; + return *this; + } + + SHA1& processByte(uint8_t octet) { + this->m_block[this->m_blockByteIndex++] = octet; + ++this->m_byteCount; + if (m_blockByteIndex == 64) { + this->m_blockByteIndex = 0; + processBlock(); + } + + return *this; + } + + SHA1& processBlock(const void* const start, const void* const end) { + const uint8_t* begin = static_cast(start); + const uint8_t* finish = static_cast(end); + while (begin != finish) { + processByte(*begin); + begin++; + } + return *this; + } + + SHA1& processBytes(const void* const data, size_t len) { + const uint8_t* block = static_cast(data); + processBlock(block, block + len); + return *this; + } + + const uint32_t* finalize(digest32_t digest) { + size_t bitCount = this->m_byteCount * 8; + processByte(0x80); + if (this->m_blockByteIndex > 56) { + while (m_blockByteIndex != 0) { + processByte(0); + } + while (m_blockByteIndex < 56) { + processByte(0); + } + } else { + while (m_blockByteIndex < 56) { + processByte(0); + } + } + processByte(0); + processByte(0); + processByte(0); + processByte(0); + processByte(static_cast((bitCount >> 24) & 0xFF)); + processByte(static_cast((bitCount >> 16) & 0xFF)); + processByte(static_cast((bitCount >> 8) & 0xFF)); + processByte(static_cast((bitCount)&0xFF)); + + memcpy(digest, m_digest, 5 * sizeof(uint32_t)); + return digest; + } + + const uint8_t* finalize(digest8_t digest) { + digest32_t d32; + finalize(d32); + size_t di = 0; + digest[di++] = ((d32[0] >> 24) & 0xFF); + digest[di++] = ((d32[0] >> 16) & 0xFF); + digest[di++] = ((d32[0] >> 8) & 0xFF); + digest[di++] = ((d32[0]) & 0xFF); + + digest[di++] = ((d32[1] >> 24) & 0xFF); + digest[di++] = ((d32[1] >> 16) & 0xFF); + digest[di++] = ((d32[1] >> 8) & 0xFF); + digest[di++] = ((d32[1]) & 0xFF); + + digest[di++] = ((d32[2] >> 24) & 0xFF); + digest[di++] = ((d32[2] >> 16) & 0xFF); + digest[di++] = ((d32[2] >> 8) & 0xFF); + digest[di++] = ((d32[2]) & 0xFF); + + digest[di++] = ((d32[3] >> 24) & 0xFF); + digest[di++] = ((d32[3] >> 16) & 0xFF); + digest[di++] = ((d32[3] >> 8) & 0xFF); + digest[di++] = ((d32[3]) & 0xFF); + + digest[di++] = ((d32[4] >> 24) & 0xFF); + digest[di++] = ((d32[4] >> 16) & 0xFF); + digest[di++] = ((d32[4] >> 8) & 0xFF); + digest[di++] = ((d32[4]) & 0xFF); + return digest; + } + + protected: + void processBlock() { + uint32_t w[80]; + for (size_t i = 0; i < 16; i++) { + w[i] = (m_block[i * 4 + 0] << 24); + w[i] |= (m_block[i * 4 + 1] << 16); + w[i] |= (m_block[i * 4 + 2] << 8); + w[i] |= (m_block[i * 4 + 3]); + } + for (size_t i = 16; i < 80; i++) { + w[i] = LeftRotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1); + } + + uint32_t a = m_digest[0]; + uint32_t b = m_digest[1]; + uint32_t c = m_digest[2]; + uint32_t d = m_digest[3]; + uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + uint32_t f = 0; + uint32_t k = 0; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i]; + e = d; + d = c; + c = LeftRotate(b, 30); + b = a; + a = temp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } + + private: + digest32_t m_digest; + uint8_t m_block[64]; + size_t m_blockByteIndex; + size_t m_byteCount; +}; +} +#endif diff --git a/thirdparty/libmspack b/thirdparty/libmspack new file mode 160000 index 0000000..3059077 --- /dev/null +++ b/thirdparty/libmspack @@ -0,0 +1 @@ +Subproject commit 305907723a4e7ab2018e58040059ffb5e77db837 diff --git a/thirdparty/tiny-AES-c b/thirdparty/tiny-AES-c new file mode 160000 index 0000000..2385675 --- /dev/null +++ b/thirdparty/tiny-AES-c @@ -0,0 +1 @@ +Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d