mirror of
				https://github.com/hedge-dev/UnleashedRecomp.git
				synced 2025-10-30 07:11:05 +00:00 
			
		
		
		
	Port XEX patcher to XenonRecomp. (#433)
* Port XEX patcher to XenonRecomp. * Update XenonRecomp submodule.
This commit is contained in:
		
							parent
							
								
									f123ec7083
								
							
						
					
					
						commit
						5ba4e927ab
					
				
					 18 changed files with 31 additions and 1193 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -7,9 +7,6 @@ | |||
| [submodule "tools/XenosRecomp"] | ||||
| 	path = tools/XenosRecomp | ||||
| 	url = https://github.com/hedge-dev/XenosRecomp.git | ||||
| [submodule "thirdparty/libmspack"] | ||||
| 	path = thirdparty/libmspack | ||||
| 	url = https://github.com/kyz/libmspack | ||||
| [submodule "UnleashedRecompResources"] | ||||
| 	path = UnleashedRecompResources | ||||
| 	url = https://github.com/hedge-dev/UnleashedRecompResources.git | ||||
|  | @ -40,9 +37,6 @@ | |||
| [submodule "thirdparty/concurrentqueue"] | ||||
| 	path = thirdparty/concurrentqueue | ||||
| 	url = https://github.com/cameron314/concurrentqueue.git | ||||
| [submodule "thirdparty/tiny-AES-c"] | ||||
| 	path = thirdparty/tiny-AES-c | ||||
| 	url = https://github.com/kokke/tiny-AES-c.git | ||||
| [submodule "thirdparty/magic_enum"] | ||||
| 	path = thirdparty/magic_enum | ||||
| 	url = https://github.com/Neargye/magic_enum.git | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| ``` | ||||
| git clone --recurse-submodules https://github.com/hedge-dev/UnleashedRecomp.git | ||||
| ``` | ||||
| 2. Decompress and decrypt `default.xex`, apply the title update patch (`default.xexp`), and place the resulting file in `./UnleashedRecompLib/private/`. | ||||
| 2. Place `default.xex` and `default.xexp` in `./UnleashedRecompLib/private/`. | ||||
| 3. Decompress `shader.ar` and place the resulting file in `./UnleashedRecompLib/private/`. | ||||
| 4. Open the repository directory in Visual Studio 2022 and wait for CMake generation to complete. If you don't plan to debug, switch to the `x64-Clang-Release` configuration. | ||||
| 5. Under Solution Explorer, right-click and choose "Switch to CMake Targets View". | ||||
|  |  | |||
|  | @ -165,10 +165,8 @@ set(UNLEASHED_RECOMP_UI_CXX_SOURCES | |||
| set(UNLEASHED_RECOMP_INSTALL_CXX_SOURCES | ||||
|     "install/installer.cpp" | ||||
|     "install/iso_file_system.cpp" | ||||
|     "install/memory_mapped_file.cpp" | ||||
|     "install/update_checker.cpp" | ||||
|     "install/xcontent_file_system.cpp" | ||||
|     "install/xex_patcher.cpp" | ||||
|     "install/hashes/apotos_shamar.cpp" | ||||
|     "install/hashes/chunnan.cpp" | ||||
|     "install/hashes/empire_city_adabat.cpp" | ||||
|  | @ -201,8 +199,6 @@ set(UNLEASHED_RECOMP_THIRDPARTY_SOURCES | |||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot/implot.cpp" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot/implot_demo.cpp" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot/implot_items.cpp" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/libmspack/libmspack/mspack/lzxd.c" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/tiny-AES-c/aes.c" | ||||
|     "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/thirdparty/smol-v/source/smolv.cpp" | ||||
| ) | ||||
| 
 | ||||
|  | @ -212,11 +208,8 @@ set(UNLEASHED_RECOMP_THIRDPARTY_INCLUDES | |||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/imgui" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/implot" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/json/include" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/libmspack/libmspack/mspack" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/magic_enum/include" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/stb" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/tiny-AES-c" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/TinySHA1" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/unordered_dense/include" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/volk" | ||||
|     "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/Vulkan-Headers/include" | ||||
|  |  | |||
|  | @ -598,5 +598,5 @@ XexPatcher::Result Installer::checkGameUpdateCompatibility(const std::filesystem | |||
|     } | ||||
| 
 | ||||
|     std::vector<uint8_t> patchedBytes; | ||||
|     return XexPatcher::apply(xexBytes, patchBytes, patchedBytes, true); | ||||
|     return XexPatcher::apply(xexBytes.data(), xexBytes.size(), patchBytes.data(), patchBytes.size(), patchedBytes, true); | ||||
| } | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ | |||
| #include <set> | ||||
| 
 | ||||
| #include "virtual_file_system.h" | ||||
| #include "xex_patcher.h" | ||||
| #include <xex_patcher.h> | ||||
| 
 | ||||
| enum class DLC { | ||||
|     Unknown, | ||||
|  |  | |||
|  | @ -16,7 +16,7 @@ | |||
| 
 | ||||
| #include "virtual_file_system.h" | ||||
| 
 | ||||
| #include "memory_mapped_file.h" | ||||
| #include <memory_mapped_file.h> | ||||
| 
 | ||||
| struct ISOFileSystem : VirtualFileSystem | ||||
| { | ||||
|  |  | |||
|  | @ -1,169 +0,0 @@ | |||
| #include "memory_mapped_file.h" | ||||
| 
 | ||||
| #if !defined(_WIN32) | ||||
| #   include <cstring> | ||||
| #   include <cstdio> | ||||
| #   include <fcntl.h> | ||||
| #   include <unistd.h> | ||||
| #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<uint8_t *>(fileView); | ||||
| } | ||||
| 
 | ||||
| size_t MemoryMappedFile::size() const | ||||
| { | ||||
| #if defined(_WIN32) | ||||
|     return fileSize.QuadPart; | ||||
| #else | ||||
|     return static_cast<size_t>(fileSize); | ||||
| #endif | ||||
| } | ||||
|  | @ -1,32 +0,0 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <filesystem> | ||||
| 
 | ||||
| #if defined(_WIN32) | ||||
| #   include <Windows.h> | ||||
| #else | ||||
| #   include <sys/mman.h> | ||||
| #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; | ||||
| }; | ||||
|  | @ -16,7 +16,7 @@ | |||
| 
 | ||||
| #include "virtual_file_system.h" | ||||
| 
 | ||||
| #include "memory_mapped_file.h" | ||||
| #include <memory_mapped_file.h> | ||||
| 
 | ||||
| enum class XContentVolumeType | ||||
| { | ||||
|  |  | |||
|  | @ -1,693 +0,0 @@ | |||
| // 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 <bit> | ||||
| #include <cassert> | ||||
| 
 | ||||
| #include <aes.hpp> | ||||
| #include <lzx.h> | ||||
| #include <mspack.h> | ||||
| #include <TinySHA1.hpp> | ||||
| 
 | ||||
| #include "memory_mapped_file.h" | ||||
| 
 | ||||
| enum Xex2ModuleFlags | ||||
| { | ||||
|     XEX_MODULE_MODULE_PATCH = 0x10, | ||||
|     XEX_MODULE_PATCH_FULL = 0x20, | ||||
|     XEX_MODULE_PATCH_DELTA = 0x40, | ||||
| }; | ||||
| 
 | ||||
| enum Xex2HeaderKeys | ||||
| { | ||||
|     XEX_HEADER_FILE_FORMAT_INFO = 0x3FF, | ||||
|     XEX_HEADER_DELTA_PATCH_DESCRIPTOR = 0x5FF, | ||||
| }; | ||||
| 
 | ||||
| enum Xex2EncryptionType | ||||
| { | ||||
|     XEX_ENCRYPTION_NONE = 0, | ||||
|     XEX_ENCRYPTION_NORMAL = 1, | ||||
| }; | ||||
| 
 | ||||
| enum Xex2CompressionType | ||||
| { | ||||
|     XEX_COMPRESSION_NONE = 0, | ||||
|     XEX_COMPRESSION_BASIC = 1, | ||||
|     XEX_COMPRESSION_NORMAL = 2, | ||||
|     XEX_COMPRESSION_DELTA = 3, | ||||
| }; | ||||
| 
 | ||||
| enum Xex2SectionType | ||||
| { | ||||
|     XEX_SECTION_CODE = 1, | ||||
|     XEX_SECTION_DATA = 2, | ||||
|     XEX_SECTION_READONLY_DATA = 3, | ||||
| }; | ||||
| 
 | ||||
| struct Xex2OptHeader | ||||
| { | ||||
|     be<uint32_t> key; | ||||
| 
 | ||||
|     union | ||||
|     { | ||||
|         be<uint32_t> value; | ||||
|         be<uint32_t> offset; | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2Header | ||||
| { | ||||
|     be<uint32_t> magic; | ||||
|     be<uint32_t> moduleFlags; | ||||
|     be<uint32_t> headerSize; | ||||
|     be<uint32_t> reserved; | ||||
|     be<uint32_t> securityOffset; | ||||
|     be<uint32_t> headerCount; | ||||
|     Xex2OptHeader headers[1]; | ||||
| }; | ||||
| 
 | ||||
| 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<uint32_t> headerSize; | ||||
|     be<uint32_t> imageSize; | ||||
|     char rsaSignature[0x100]; | ||||
|     be<uint32_t> unknown; | ||||
|     be<uint32_t> imageFlags; | ||||
|     be<uint32_t> loadAddress; | ||||
|     char sectionDigest[0x14]; | ||||
|     be<uint32_t> importTableCount; | ||||
|     char importTableDigest[0x14]; | ||||
|     char xgd2MediaId[0x10]; | ||||
|     char aesKey[0x10]; | ||||
|     be<uint32_t> exportTable; | ||||
|     char headerDigest[0x14]; | ||||
|     be<uint32_t> region; | ||||
|     be<uint32_t> allowedMediaTypes; | ||||
|     be<uint32_t> pageDescriptorCount; | ||||
|     Xex2PageDescriptor pageDescriptors[1]; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2DeltaPatch | ||||
| { | ||||
|     be<uint32_t> oldAddress; | ||||
|     be<uint32_t> newAddress; | ||||
|     be<uint16_t> uncompressedLength; | ||||
|     be<uint16_t> compressedLength; | ||||
|     char patchData[1]; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2OptDeltaPatchDescriptor | ||||
| { | ||||
|     be<uint32_t> size; | ||||
|     be<uint32_t> targetVersionValue; | ||||
|     be<uint32_t> sourceVersionValue; | ||||
|     uint8_t digestSource[0x14]; | ||||
|     uint8_t imageKeySource[0x10]; | ||||
|     be<uint32_t> sizeOfTargetHeaders; | ||||
|     be<uint32_t> deltaHeadersSourceOffset; | ||||
|     be<uint32_t> deltaHeadersSourceSize; | ||||
|     be<uint32_t> deltaHeadersTargetOffset; | ||||
|     be<uint32_t> deltaImageSourceOffset; | ||||
|     be<uint32_t> deltaImageSourceSize; | ||||
|     be<uint32_t> deltaImageTargetOffset; | ||||
|     Xex2DeltaPatch info; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2FileBasicCompressionBlock | ||||
| { | ||||
|     be<uint32_t> dataSize; | ||||
|     be<uint32_t> zeroSize; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2FileBasicCompressionInfo | ||||
| { | ||||
|     Xex2FileBasicCompressionBlock firstBlock; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2CompressedBlockInfo | ||||
| { | ||||
|     be<uint32_t> blockSize; | ||||
|     uint8_t blockHash[20]; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2FileNormalCompressionInfo | ||||
| { | ||||
|     be<uint32_t> windowSize; | ||||
|     Xex2CompressedBlockInfo firstBlock; | ||||
| }; | ||||
| 
 | ||||
| struct Xex2OptFileFormatInfo | ||||
| { | ||||
|     be<uint32_t> infoSize; | ||||
|     be<uint16_t> encryptionType; | ||||
|     be<uint16_t> compressionType; | ||||
|     union | ||||
|     { | ||||
|         Xex2FileBasicCompressionInfo basic; | ||||
|         Xex2FileNormalCompressionInfo normal; | ||||
|     } compressionInfo; | ||||
| }; | ||||
| 
 | ||||
| static const void *getOptHeaderPtr(std::span<const uint8_t> moduleBytes, uint32_t headerKey) | ||||
| { | ||||
|     if ((headerKey & 0xFF) == 0) | ||||
|     { | ||||
|         assert(false && "Wrong type of method for this key. Expected return value is a number."); | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     const Xex2Header *xex2Header = (const Xex2Header *)(moduleBytes.data()); | ||||
|     for (uint32_t i = 0; i < xex2Header->headerCount; i++) | ||||
|     { | ||||
|         const Xex2OptHeader &optHeader = xex2Header->headers[i]; | ||||
|         if (optHeader.key == headerKey) | ||||
|         { | ||||
|             if ((headerKey & 0xFF) == 1) | ||||
|             { | ||||
|                 return &optHeader.value; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 return &moduleBytes.data()[optHeader.offset]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| 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(std::span<const uint8_t> xexBytes, std::span<const uint8_t> patchBytes, std::vector<uint8_t> &outBytes, bool skipData) | ||||
| { | ||||
|     // Validate headers.
 | ||||
|     static const char Xex2Magic[] = "XEX2"; | ||||
|     const Xex2Header *xexHeader = (const Xex2Header *)(xexBytes.data()); | ||||
|     if (memcmp(xexBytes.data(), Xex2Magic, 4) != 0) | ||||
|     { | ||||
|         return Result::XexFileInvalid; | ||||
|     } | ||||
| 
 | ||||
|     const Xex2Header *patchHeader = (const Xex2Header *)(patchBytes.data()); | ||||
|     if (memcmp(patchBytes.data(), 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.data(), 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, patchFileFormatInfo->compressionInfo.normal.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], xexBytes.size() - 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.
 | ||||
|     static const uint32_t KeySize = 16; | ||||
|     static const uint8_t Xex2RetailKey[16] = { 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91 }; | ||||
|     static const uint8_t AESBlankIV[AES_BLOCKLEN] = {}; | ||||
|     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], xexBytes.size() - 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 = &fileFormatInfo->compressionInfo.basic.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, 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<uint8_t> patchData; | ||||
|     patchData.resize(patchBytes.size() - 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 = &patchFileFormatInfo->compressionInfo.normal.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, patchFileFormatInfo->compressionInfo.normal.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<uint8_t> 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; | ||||
| } | ||||
|  | @ -1,35 +0,0 @@ | |||
| // 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 <cstdint> | ||||
| #include <filesystem> | ||||
| #include <span> | ||||
| #include <vector> | ||||
| 
 | ||||
| struct XexPatcher | ||||
| { | ||||
|     enum class Result { | ||||
|         Success, | ||||
|         FileOpenFailed, | ||||
|         FileWriteFailed, | ||||
|         XexFileUnsupported, | ||||
|         XexFileInvalid, | ||||
|         PatchFileInvalid, | ||||
|         PatchIncompatible, | ||||
|         PatchFailed, | ||||
|         PatchUnsupported | ||||
|     }; | ||||
| 
 | ||||
|     static Result apply(std::span<const uint8_t> xexBytes, std::span<const uint8_t> patchBytes, std::vector<uint8_t> &outBytes, bool skipData); | ||||
|     static Result apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath); | ||||
| }; | ||||
|  | @ -106,33 +106,33 @@ uint32_t LdrLoadModule(const std::filesystem::path &path) | |||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     auto* xex = reinterpret_cast<XEX_HEADER*>(loadResult.data()); | ||||
|     auto security = reinterpret_cast<XEX2_SECURITY_INFO*>((char*)xex + xex->AddressOfSecurityInfo); | ||||
| 
 | ||||
|     auto format = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(xex, XEX_HEADER_FILE_FORMAT_INFO); | ||||
|     auto entry = *Xex2FindOptionalHeader<uint32_t>(xex, XEX_HEADER_ENTRY_POINT); | ||||
|     auto* header = reinterpret_cast<const Xex2Header*>(loadResult.data()); | ||||
|     auto* security = reinterpret_cast<const Xex2SecurityInfo*>(loadResult.data() + header->securityOffset); | ||||
|     const auto* fileFormatInfo = reinterpret_cast<const Xex2OptFileFormatInfo*>(getOptHeaderPtr(loadResult.data(), XEX_HEADER_FILE_FORMAT_INFO)); | ||||
|     auto entry = *reinterpret_cast<const uint32_t*>(getOptHeaderPtr(loadResult.data(), XEX_HEADER_ENTRY_POINT)); | ||||
|     ByteSwapInplace(entry); | ||||
| 
 | ||||
|     auto srcData = (char *)xex + xex->SizeOfHeader; | ||||
|     auto destData = (char *)g_memory.Translate(security->ImageBase); | ||||
|     if (format->CompressionType == 0) | ||||
|     auto srcData = loadResult.data() + header->headerSize; | ||||
|     auto destData = reinterpret_cast<uint8_t*>(g_memory.Translate(security->loadAddress)); | ||||
| 
 | ||||
|     if (fileFormatInfo->compressionType == XEX_COMPRESSION_NONE) | ||||
|     { | ||||
|         memcpy(destData, srcData, security->SizeOfImage); | ||||
|         memcpy(destData, srcData, security->imageSize); | ||||
|     } | ||||
|     else if (format->CompressionType == 1) | ||||
|     else if (fileFormatInfo->compressionType == XEX_COMPRESSION_BASIC) | ||||
|     { | ||||
|         auto numBlocks = (format->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1; | ||||
|         auto blocks = reinterpret_cast<const XEX_BASIC_FILE_COMPRESSION_INFO*>(format + 1); | ||||
|         auto* blocks = reinterpret_cast<const Xex2FileBasicCompressionBlock*>(fileFormatInfo + 1); | ||||
|         const size_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionInfo)) - 1; | ||||
| 
 | ||||
|         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; | ||||
|             memset(destData, 0, blocks[i].SizeOfPadding); | ||||
|             srcData += blocks[i].dataSize; | ||||
|             destData += blocks[i].dataSize; | ||||
| 
 | ||||
|             destData += blocks[i].SizeOfPadding; | ||||
|             memset(destData, 0, blocks[i].zeroSize); | ||||
|             destData += blocks[i].zeroSize; | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|  | @ -140,9 +140,9 @@ uint32_t LdrLoadModule(const std::filesystem::path &path) | |||
|         assert(false && "Unknown compression type."); | ||||
|     } | ||||
| 
 | ||||
|     auto res = Xex2FindOptionalHeader<XEX_RESOURCE_INFO>(xex, XEX_HEADER_RESOURCE_INFO); | ||||
|     auto res = reinterpret_cast<const Xex2ResourceInfo*>(getOptHeaderPtr(loadResult.data(), XEX_HEADER_RESOURCE_INFO)); | ||||
| 
 | ||||
|     g_xdbfWrapper = XDBFWrapper((uint8_t*)g_memory.Translate(res->Offset.get()), res->SizeOfData); | ||||
|     g_xdbfWrapper = XDBFWrapper((uint8_t*)g_memory.Translate(res->offset.get()), res->sizeOfData); | ||||
| 
 | ||||
|     return entry; | ||||
| } | ||||
|  |  | |||
|  | @ -28,7 +28,10 @@ endforeach() | |||
| add_custom_command( | ||||
|     OUTPUT ${UNLEASHED_RECOMP_PPC_RECOMPILED_SOURCES} | ||||
|     COMMAND $<TARGET_FILE:XenonRecomp> | ||||
|     DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex" "${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml" | ||||
|     DEPENDS  | ||||
|         "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex" | ||||
|         "${CMAKE_CURRENT_SOURCE_DIR}/private/default.xexp" | ||||
|         "${CMAKE_CURRENT_SOURCE_DIR}/config/SWA.toml" | ||||
| ) | ||||
| 
 | ||||
| set(XENOS_RECOMP_ROOT "${UNLEASHED_RECOMP_TOOLS_ROOT}/XenosRecomp/XenosRecomp") | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| [main] | ||||
| file_path = "../private/default.xex" | ||||
| patch_file_path = "../private/default.xexp" | ||||
| patched_file_path = "../private/default_patched.xex" | ||||
| out_directory_path = "../ppc" | ||||
| switch_table_file_path = "SWA_switch_tables.toml" | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										223
									
								
								thirdparty/TinySHA1/TinySHA1.hpp
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										223
									
								
								thirdparty/TinySHA1/TinySHA1.hpp
									
										
									
									
										vendored
									
									
								
							|  | @ -1,223 +0,0 @@ | |||
| /*
 | ||||
|  * | ||||
|  * 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 <mohaps@gmail.com> | ||||
|  * | ||||
|  * 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 <cstdio> | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| 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<const uint8_t*>(start); | ||||
|     const uint8_t* finish = static_cast<const uint8_t*>(end); | ||||
|     while (begin != finish) { | ||||
|       processByte(*begin); | ||||
|       begin++; | ||||
|     } | ||||
|     return *this; | ||||
|   } | ||||
| 
 | ||||
|   SHA1& processBytes(const void* const data, size_t len) { | ||||
|     const uint8_t* block = static_cast<const uint8_t*>(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<unsigned char>((bitCount >> 24) & 0xFF)); | ||||
|     processByte(static_cast<unsigned char>((bitCount >> 16) & 0xFF)); | ||||
|     processByte(static_cast<unsigned char>((bitCount >> 8) & 0xFF)); | ||||
|     processByte(static_cast<unsigned char>((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 | ||||
							
								
								
									
										1
									
								
								thirdparty/libmspack
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								thirdparty/libmspack
									
										
									
									
										vendored
									
									
								
							|  | @ -1 +0,0 @@ | |||
| Subproject commit 305907723a4e7ab2018e58040059ffb5e77db837 | ||||
							
								
								
									
										1
									
								
								thirdparty/tiny-AES-c
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								thirdparty/tiny-AES-c
									
										
									
									
										vendored
									
									
								
							|  | @ -1 +0,0 @@ | |||
| Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d | ||||
|  | @ -1 +1 @@ | |||
| Subproject commit 0fc545a6e25275d3a4c79685d32d2eb22fe98e98 | ||||
| Subproject commit cd6fcb33bdcaff37c8c9d2083c7951e1d73ae9da | ||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Skyth (Asilkan)
						Skyth (Asilkan)