mirror of
				https://github.com/hedge-dev/XenonRecomp.git
				synced 2025-10-30 07:11:38 +00:00 
			
		
		
		
	Port XEX patcher from Unleashed Recompiled. (#4)
* Port XEX patcher from Unleashed Recompiled. * Fix compilation error on Linux.
This commit is contained in:
		
							parent
							
								
									0fc545a6e2
								
							
						
					
					
						commit
						cd6fcb33bd
					
				
					 17 changed files with 1336 additions and 144 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
										
									
									
										vendored
									
									
								
							|  | @ -7,3 +7,9 @@ | ||||||
| [submodule "thirdparty/tomlplusplus"] | [submodule "thirdparty/tomlplusplus"] | ||||||
| 	path = thirdparty/tomlplusplus | 	path = thirdparty/tomlplusplus | ||||||
| 	url = https://github.com/marzer/tomlplusplus.git | 	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 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,9 @@ int main(int argc, char* argv[]) | ||||||
|     if (std::filesystem::is_regular_file(path)) |     if (std::filesystem::is_regular_file(path)) | ||||||
|     { |     { | ||||||
|         Recompiler recompiler; |         Recompiler recompiler; | ||||||
|         recompiler.LoadConfig(path); |         if (!recompiler.LoadConfig(path)) | ||||||
|  |             return -1; | ||||||
|  | 
 | ||||||
|         recompiler.Analyse(); |         recompiler.Analyse(); | ||||||
| 
 | 
 | ||||||
|         auto entry = recompiler.image.symbols.find(recompiler.image.entry_point); |         auto entry = recompiler.image.symbols.find(recompiler.image.entry_point); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| #include "pch.h" | #include "pch.h" | ||||||
| #include "recompiler.h" | #include "recompiler.h" | ||||||
|  | #include <xex_patcher.h> | ||||||
| 
 | 
 | ||||||
| static uint64_t ComputeMask(uint32_t mstart, uint32_t mstop) | 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; |     return mstart <= mstop ? value : ~value; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Recompiler::LoadConfig(const std::string_view& configFilePath) | bool Recompiler::LoadConfig(const std::string_view& configFilePath) | ||||||
| { | { | ||||||
|     config.Load(configFilePath); |     config.Load(configFilePath); | ||||||
| 
 | 
 | ||||||
|     const auto file = LoadFile((config.directoryPath + config.filePath).c_str()); |     std::vector<uint8_t> 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<uint8_t> 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<const char*>(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()); |     image = Image::ParseImage(file.data(), file.size()); | ||||||
|  |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Recompiler::Analyse() | void Recompiler::Analyse() | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ struct Recompiler | ||||||
|     size_t cppFileIndex = 0; |     size_t cppFileIndex = 0; | ||||||
|     RecompilerConfig config; |     RecompilerConfig config; | ||||||
| 
 | 
 | ||||||
|     void LoadConfig(const std::string_view& configFilePath); |     bool LoadConfig(const std::string_view& configFilePath); | ||||||
| 
 | 
 | ||||||
|     template<class... Args> |     template<class... Args> | ||||||
|     void print(fmt::format_string<Args...> fmt, Args&&... args) |     void print(fmt::format_string<Args...> fmt, Args&&... args) | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ void RecompilerConfig::Load(const std::string_view& configFilePath) | ||||||
|     { |     { | ||||||
|         const auto& main = *mainPtr; |         const auto& main = *mainPtr; | ||||||
|         filePath = main["file_path"].value_or<std::string>(""); |         filePath = main["file_path"].value_or<std::string>(""); | ||||||
|  |         patchFilePath = main["patch_file_path"].value_or<std::string>(""); | ||||||
|  |         patchedFilePath = main["patched_file_path"].value_or<std::string>(""); | ||||||
|         outDirectoryPath = main["out_directory_path"].value_or<std::string>(""); |         outDirectoryPath = main["out_directory_path"].value_or<std::string>(""); | ||||||
|         switchTableFilePath = main["switch_table_file_path"].value_or<std::string>(""); |         switchTableFilePath = main["switch_table_file_path"].value_or<std::string>(""); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,8 @@ struct RecompilerConfig | ||||||
| { | { | ||||||
|     std::string directoryPath; |     std::string directoryPath; | ||||||
|     std::string filePath; |     std::string filePath; | ||||||
|  |     std::string patchFilePath; | ||||||
|  |     std::string patchedFilePath; | ||||||
|     std::string outDirectoryPath; |     std::string outDirectoryPath; | ||||||
|     std::string switchTableFilePath; |     std::string switchTableFilePath; | ||||||
|     std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables; |     std::unordered_map<uint32_t, RecompilerSwitchTable> switchTables; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,28 @@ add_library(XenonUtils | ||||||
|     "disasm.cpp"  |     "disasm.cpp"  | ||||||
|     "xex.cpp"  |     "xex.cpp"  | ||||||
|     "image.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_compile_definitions(XenonUtils | ||||||
| target_link_libraries(XenonUtils PUBLIC disasm) |     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 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | @ -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') |     else if (data[0] == 'X' && data[1] == 'E' && data[2] == 'X' && data[3] == '2') | ||||||
|     { |     { | ||||||
|         return Xex2LoadImage(data); |         return Xex2LoadImage(data, size); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return {}; |     return {}; | ||||||
|  |  | ||||||
							
								
								
									
										169
									
								
								XenonUtils/memory_mapped_file.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								XenonUtils/memory_mapped_file.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,169 @@ | ||||||
|  | #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 | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								XenonUtils/memory_mapped_file.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								XenonUtils/memory_mapped_file.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | ||||||
|  | #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; | ||||||
|  | }; | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| #include <cstring> | #include <cstring> | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <unordered_map> | #include <unordered_map> | ||||||
|  | #include <aes.hpp> | ||||||
| 
 | 
 | ||||||
| #define STRINGIFY(X) #X | #define STRINGIFY(X) #X | ||||||
| #define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) } | #define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) } | ||||||
|  | @ -121,58 +122,80 @@ std::unordered_map<size_t, const char*> XboxKernelExports = | ||||||
|     #include "xbox/xboxkrnl_table.inc" |     #include "xbox/xboxkrnl_table.inc" | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| Image Xex2LoadImage(const uint8_t* data) | Image Xex2LoadImage(const uint8_t* data, size_t dataSize) | ||||||
| { | { | ||||||
|     auto* header = reinterpret_cast<const XEX_HEADER*>(data); |     auto* header = reinterpret_cast<const Xex2Header*>(data); | ||||||
|     auto* security = reinterpret_cast<const XEX2_SECURITY_INFO*>(data + header->AddressOfSecurityInfo); |     auto* security = reinterpret_cast<const Xex2SecurityInfo*>(data + header->securityOffset); | ||||||
| 
 |     const auto* fileFormatInfo = reinterpret_cast<const Xex2OptFileFormatInfo*>(getOptHeaderPtr(data, XEX_HEADER_FILE_FORMAT_INFO)); | ||||||
|     const auto* compressionInfo = Xex2FindOptionalHeader<XEX_FILE_FORMAT_INFO>(header, XEX_HEADER_FILE_FORMAT_INFO); |  | ||||||
| 
 | 
 | ||||||
|     Image image{}; |     Image image{}; | ||||||
|     std::unique_ptr<uint8_t[]> result{}; |     std::unique_ptr<uint8_t[]> result{}; | ||||||
|     size_t imageSize = security->SizeOfImage; |     size_t imageSize = security->imageSize; | ||||||
| 
 | 
 | ||||||
|     // Decompress image
 |     // Decompress image
 | ||||||
|     if (compressionInfo != nullptr) |     if (fileFormatInfo != nullptr) | ||||||
|     { |     { | ||||||
|         assert(compressionInfo->CompressionType <= XEX_COMPRESSION_BASIC); |         assert(fileFormatInfo->compressionType <= XEX_COMPRESSION_BASIC); | ||||||
|         assert(compressionInfo->EncryptionType == XEX_ENCRYPTION_NONE); |  | ||||||
| 
 | 
 | ||||||
|         if (compressionInfo->CompressionType == XEX_COMPRESSION_NONE) |         std::unique_ptr<uint8_t[]> 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<uint8_t[]>(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<uint8_t[]>(imageSize); |             result = std::make_unique<uint8_t[]>(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<const XEX_BASIC_FILE_COMPRESSION_INFO*>(compressionInfo + 1); |             auto* blocks = reinterpret_cast<const Xex2FileBasicCompressionBlock*>(fileFormatInfo + 1); | ||||||
|             const size_t numBlocks = (compressionInfo->SizeOfHeader / sizeof(XEX_BASIC_FILE_COMPRESSION_INFO)) - 1; |             const size_t numBlocks = (fileFormatInfo->infoSize / sizeof(Xex2FileBasicCompressionInfo)) - 1; | ||||||
| 
 | 
 | ||||||
|             imageSize = 0; |             imageSize = 0; | ||||||
|             for (size_t i = 0; i < numBlocks; i++) |             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<uint8_t[]>(imageSize); |             result = std::make_unique<uint8_t[]>(imageSize); | ||||||
|             auto* srcData = data + header->SizeOfHeader; |  | ||||||
|             auto* destData = result.get(); |             auto* destData = result.get(); | ||||||
| 
 | 
 | ||||||
|             for (size_t i = 0; i < numBlocks; i++) |             for (size_t i = 0; i < numBlocks; i++) | ||||||
|             { |             { | ||||||
|                 memcpy(destData, srcData, blocks[i].SizeOfData); |                 memcpy(destData, srcData, blocks[i].dataSize); | ||||||
| 
 | 
 | ||||||
|                 srcData += blocks[i].SizeOfData; |                 srcData += blocks[i].dataSize; | ||||||
|                 destData += blocks[i].SizeOfData; |                 destData += blocks[i].dataSize; | ||||||
| 
 | 
 | ||||||
|                 memset(destData, 0, blocks[i].SizeOfPadding); |                 memset(destData, 0, blocks[i].zeroSize); | ||||||
|                 destData += blocks[i].SizeOfPadding; |                 destData += blocks[i].zeroSize; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     image.data = std::move(result); |     image.data = std::move(result); | ||||||
|     image.size = imageSize; |     image.size = security->imageSize; | ||||||
| 
 | 
 | ||||||
|     // Map image
 |     // Map image
 | ||||||
|     const auto* dosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(image.data.get()); |     const auto* dosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(image.data.get()); | ||||||
|  | @ -198,22 +221,22 @@ Image Xex2LoadImage(const uint8_t* data) | ||||||
|             section.Misc.VirtualSize, flags, image.data.get() + section.VirtualAddress); |             section.Misc.VirtualSize, flags, image.data.get() + section.VirtualAddress); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     auto* imports = Xex2FindOptionalHeader<XEX_IMPORT_HEADER>(header, XEX_HEADER_IMPORT_LIBRARIES); |     auto* imports = reinterpret_cast<const Xex2ImportHeader*>(getOptHeaderPtr(data, XEX_HEADER_IMPORT_LIBRARIES)); | ||||||
|     if (imports != nullptr) |     if (imports != nullptr) | ||||||
|     { |     { | ||||||
|         std::vector<std::string_view> stringTable; |         std::vector<std::string_view> stringTable; | ||||||
|         auto* pStrTable = reinterpret_cast<const char*>(imports + 1); |         auto* pStrTable = reinterpret_cast<const char*>(imports + 1); | ||||||
| 
 | 
 | ||||||
|         for (size_t i = 0; i < imports->NumImports; i++) |         for (size_t i = 0; i < imports->numImports; i++) | ||||||
|         { |         { | ||||||
|             stringTable.emplace_back(pStrTable); |             stringTable.emplace_back(pStrTable); | ||||||
|             pStrTable += strlen(pStrTable) + 1; |             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++) |         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<size_t, const char*> DummyExports; |             static std::unordered_map<size_t, const char*> DummyExports; | ||||||
|             const std::unordered_map<size_t, const char*>* names = &DummyExports; |             const std::unordered_map<size_t, const char*>* names = &DummyExports; | ||||||
| 
 | 
 | ||||||
|  | @ -226,25 +249,25 @@ Image Xex2LoadImage(const uint8_t* data) | ||||||
|                 names = &XboxKernelExports; |                 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; |                 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 }; |                     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()) |                     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)); |                     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)); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										293
									
								
								XenonUtils/xex.h
									
										
									
									
									
								
							
							
						
						
									
										293
									
								
								XenonUtils/xex.h
									
										
									
									
									
								
							|  | @ -2,18 +2,17 @@ | ||||||
| #include <memory> | #include <memory> | ||||||
| #include "xbox.h" | #include "xbox.h" | ||||||
| 
 | 
 | ||||||
| #define XEX_COMPRESSION_NONE 0 | inline constexpr uint8_t Xex2RetailKey[16] = { 0x20, 0xB1, 0x85, 0xA5, 0x9D, 0x28, 0xFD, 0xC3, 0x40, 0x58, 0x3F, 0xBB, 0x08, 0x96, 0xBF, 0x91 }; | ||||||
| #define XEX_COMPRESSION_BASIC 1 | inline constexpr uint8_t AESBlankIV[16] = {}; | ||||||
| 
 | 
 | ||||||
| #define XEX_ENCRYPTION_NONE 0 | enum Xex2ModuleFlags | ||||||
| 
 |  | ||||||
| enum _XEX_THUNK_TYPES |  | ||||||
| { | { | ||||||
|     XEX_THUNK_VARIABLE = 0, |     XEX_MODULE_MODULE_PATCH = 0x10, | ||||||
|     XEX_THUNK_FUNCTION = 1, |     XEX_MODULE_PATCH_FULL = 0x20, | ||||||
|  |     XEX_MODULE_PATCH_DELTA = 0x40, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| enum _XEX_OPTIONAL_HEADER_TYPES | enum Xex2HeaderKeys | ||||||
| { | { | ||||||
|     XEX_HEADER_RESOURCE_INFO = 0x000002FF, |     XEX_HEADER_RESOURCE_INFO = 0x000002FF, | ||||||
|     XEX_HEADER_FILE_FORMAT_INFO = 0x000003FF, |     XEX_HEADER_FILE_FORMAT_INFO = 0x000003FF, | ||||||
|  | @ -47,118 +46,212 @@ enum _XEX_OPTIONAL_HEADER_TYPES | ||||||
|     XEX_HEADER_EXPORTS_BY_NAME = 0x00E10402, |     XEX_HEADER_EXPORTS_BY_NAME = 0x00E10402, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX_FILE_FORMAT_INFO | enum Xex2EncryptionType | ||||||
| { | { | ||||||
|     be<uint32_t> SizeOfHeader; |     XEX_ENCRYPTION_NONE = 0, | ||||||
|     be<uint16_t> EncryptionType; |     XEX_ENCRYPTION_NORMAL = 1, | ||||||
|     be<uint16_t> CompressionType; | }; | ||||||
| } XEX_FILE_FORMAT_INFO; |  | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX_RESOURCE_INFO | enum Xex2CompressionType | ||||||
| { | { | ||||||
|     be<uint32_t> SizeOfHeader; |     XEX_COMPRESSION_NONE = 0, | ||||||
|     uint8_t ResourceID[8]; |     XEX_COMPRESSION_BASIC = 1, | ||||||
|     be<uint32_t> Offset; |     XEX_COMPRESSION_NORMAL = 2, | ||||||
|     be<uint32_t> SizeOfData; |     XEX_COMPRESSION_DELTA = 3, | ||||||
| } XEX_RESOURCE_INFO; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX_BASIC_FILE_COMPRESSION_INFO | enum Xex2SectionType | ||||||
| { | { | ||||||
|     be<uint32_t> SizeOfData; |     XEX_SECTION_CODE = 1, | ||||||
|     be<uint32_t> SizeOfPadding; |     XEX_SECTION_DATA = 2, | ||||||
| } XEX_BASIC_FILE_COMPRESSION_INFO; |     XEX_SECTION_READONLY_DATA = 3, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX_THUNK_DATA { | enum Xex2ThunkTypes | ||||||
|  | { | ||||||
|  |     XEX_THUNK_VARIABLE = 0, | ||||||
|  |     XEX_THUNK_FUNCTION = 1, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Xex2ImportHeader | ||||||
|  | { | ||||||
|  |     be<uint32_t> sizeOfHeader; | ||||||
|  |     be<uint32_t> sizeOfStringTable; | ||||||
|  |     be<uint32_t> numImports; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Xex2ImportLibrary  | ||||||
|  | { | ||||||
|  |     be<uint32_t> size; | ||||||
|  |     char nextImportDigest[0x14]; | ||||||
|  |     be<uint32_t> id; | ||||||
|  |     be<uint32_t> version; | ||||||
|  |     be<uint32_t> minVersion; | ||||||
|  |     be<uint16_t> name; | ||||||
|  |     be<uint16_t> numberOfImports; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Xex2ImportDescriptor  | ||||||
|  | { | ||||||
|  |     be<uint32_t> firstThunk; // VA XEX_THUNK_DATA
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Xex2ThunkData  | ||||||
|  | { | ||||||
|     union |     union | ||||||
|     { |     { | ||||||
|         struct |         struct | ||||||
|         { |         { | ||||||
|             uint16_t Ordinal : 16; |             uint16_t ordinal : 16; | ||||||
|             uint16_t Hint : 8; |             uint16_t hint : 8; | ||||||
|             uint16_t Type : 8; |             uint16_t type : 8; | ||||||
|         } OriginalData; |         } originalData; | ||||||
| 
 | 
 | ||||||
|         be<uint32_t> Ordinal; |         be<uint32_t> ordinal; | ||||||
|         be<uint32_t> Function; |         be<uint32_t> function; | ||||||
|         be<uint32_t> AddressOfData; |         be<uint32_t> addressOfData; | ||||||
| 
 | 
 | ||||||
|         // For easier swapping
 |         // For easier swapping
 | ||||||
|         uint32_t Data; |         uint32_t data; | ||||||
|     }; |     }; | ||||||
| } XEX_THUNK_DATA; | }; | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX_IMPORT_HEADER { | struct Xex2ResourceInfo | ||||||
|     be<uint32_t> SizeOfHeader; |  | ||||||
|     be<uint32_t> SizeOfStringTable; |  | ||||||
|     be<uint32_t> NumImports; |  | ||||||
| } XEX_IMPORT_HEADER; |  | ||||||
| 
 |  | ||||||
| typedef struct _XEX_IMPORT_LIBRARY { |  | ||||||
|     be<uint32_t> Size; |  | ||||||
|     char NextImportDigest[0x14]; |  | ||||||
|     be<uint32_t> ID; |  | ||||||
|     be<uint32_t> Version; |  | ||||||
|     be<uint32_t> MinVersion; |  | ||||||
|     be<uint16_t> Name; |  | ||||||
|     be<uint16_t> NumberOfImports; |  | ||||||
| } XEX_IMPORT_LIBRARY; |  | ||||||
| 
 |  | ||||||
| static_assert(sizeof(XEX_IMPORT_LIBRARY) == 0x28); |  | ||||||
| 
 |  | ||||||
| typedef struct _XEX_IMPORT_DESCRIPTOR { |  | ||||||
|     be<uint32_t> FirstThunk; // VA XEX_THUNK_DATA
 |  | ||||||
| } XEX_IMPORT_DESCRIPTOR; |  | ||||||
| 
 |  | ||||||
| typedef struct _XEX_OPTIONAL_HEADER |  | ||||||
| { | { | ||||||
|     be<uint32_t> Type; |     be<uint32_t> sizeOfHeader; | ||||||
|     be<uint32_t> Address; |     uint8_t resourceID[8]; | ||||||
| } XEX_OPTIONAL_HEADER; |     be<uint32_t> offset; | ||||||
|  |     be<uint32_t> sizeOfData; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| typedef struct _XEX2_SECURITY_INFO | inline const void* getOptHeaderPtr(const uint8_t* moduleBytes, uint32_t headerKey) | ||||||
| { | { | ||||||
|     be<uint32_t> SizeOfHeader; |     const Xex2Header* xex2Header = (const Xex2Header*)(moduleBytes); | ||||||
|     be<uint32_t> SizeOfImage; |     for (uint32_t i = 0; i < xex2Header->headerCount; i++) | ||||||
|     char RsaSignature[0x100]; |  | ||||||
|     be<uint32_t> Unknown108; |  | ||||||
|     be<uint32_t> ImageFlags; |  | ||||||
|     be<uint32_t> ImageBase; |  | ||||||
|     char SectionDigest[0x14]; |  | ||||||
|     be<uint32_t> NumberOfImports; |  | ||||||
|     char ImportsDigest[0x14]; |  | ||||||
|     char Xgd2MediaID[0x10]; |  | ||||||
|     char AesKey[0x10]; |  | ||||||
|     be<uint32_t> AddressOfExports; |  | ||||||
|     char HeaderDigest[0x14]; |  | ||||||
|     be<uint32_t> Region; |  | ||||||
|     be<uint32_t> AllowedMediaTypes; |  | ||||||
|     be<uint32_t> NumberOfPageDescriptors; |  | ||||||
| } XEX2_SECURITY_INFO; |  | ||||||
| 
 |  | ||||||
| typedef struct _XEX_HEADER |  | ||||||
| { |  | ||||||
|     char Signature[4]; |  | ||||||
|     be<uint32_t> Flags; |  | ||||||
|     be<uint32_t> SizeOfHeader; |  | ||||||
|     char Reserved[4]; |  | ||||||
|     be<uint32_t> AddressOfSecurityInfo; |  | ||||||
|     be<uint32_t> NumberOfOptionalHeaders; |  | ||||||
| } XEX_HEADER; |  | ||||||
| 
 |  | ||||||
| template<typename T> |  | ||||||
| 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++) |  | ||||||
|     { |     { | ||||||
|         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<const T*>(&headers[i].Address); |                 return &optHeader.value; | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|                 return reinterpret_cast<const T*>(static_cast<const char*>(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; |     return nullptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| template<typename T> |  | ||||||
| inline static const T* Xex2FindOptionalHeader(const XEX_HEADER* header, _XEX_OPTIONAL_HEADER_TYPES type) |  | ||||||
| { |  | ||||||
|     return Xex2FindOptionalHeader<T>(header, (XEX_OPTIONAL_HEADER*)(header + 1), header->NumberOfOptionalHeaders, type); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| struct Image; | struct Image; | ||||||
| Image Xex2LoadImage(const uint8_t* data); | Image Xex2LoadImage(const uint8_t* data, size_t dataSize); | ||||||
|  |  | ||||||
							
								
								
									
										512
									
								
								XenonUtils/xex_patcher.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										512
									
								
								XenonUtils/xex_patcher.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <bit> | ||||||
|  | #include <cassert> | ||||||
|  | #include <climits> | ||||||
|  | #include <fstream> | ||||||
|  | 
 | ||||||
|  | #include <aes.hpp> | ||||||
|  | #include <lzx.h> | ||||||
|  | #include <mspack.h> | ||||||
|  | #include <TinySHA1.hpp> | ||||||
|  | 
 | ||||||
|  | #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<uint8_t> &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<uint8_t> 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<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; | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								XenonUtils/xex_patcher.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								XenonUtils/xex_patcher.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <cstdint> | ||||||
|  | #include <filesystem> | ||||||
|  | #include <span> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | 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<uint8_t> &outBytes, bool skipData); | ||||||
|  |     static Result apply(const std::filesystem::path &baseXexPath, const std::filesystem::path &patchXexPath, const std::filesystem::path &newXexPath); | ||||||
|  | }; | ||||||
							
								
								
									
										223
									
								
								thirdparty/TinySHA1/TinySHA1.hpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								thirdparty/TinySHA1/TinySHA1.hpp
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -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 <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
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								thirdparty/libmspack
									
										
									
									
										vendored
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 305907723a4e7ab2018e58040059ffb5e77db837 | ||||||
							
								
								
									
										1
									
								
								thirdparty/tiny-AES-c
									
										
									
									
										vendored
									
									
										Submodule
									
								
							
							
						
						
									
										1
									
								
								thirdparty/tiny-AES-c
									
										
									
									
										vendored
									
									
										Submodule
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | Subproject commit 23856752fbd139da0b8ca6e471a13d5bcc99a08d | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Skyth (Asilkan)
						Skyth (Asilkan)