mirror of
https://github.com/hedge-dev/XenonRecomp.git
synced 2025-04-19 10:51:18 +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
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…
x
Reference in New Issue
Block a user