Added handling of normal compression for patching xex files (#126)

* Added handling of normal compression for patching xex files

* Added normal compression handling to XenonAnalyse

* Swap calloc for unique_ptr, tidied up code layout
This commit is contained in:
The Spicy Chef 2025-04-12 11:05:53 +01:00 committed by GitHub
parent 0bfeaed44a
commit 49c5e3b4f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 2 deletions

View File

@ -5,6 +5,8 @@
#include <vector>
#include <unordered_map>
#include <aes.hpp>
#include <TinySHA1.hpp>
#include <xex_patcher.h>
#define STRINGIFY(X) #X
#define XE_EXPORT(MODULE, ORDINAL, NAME, TYPE) { (ORDINAL), "__imp__" STRINGIFY(NAME) }
@ -135,7 +137,7 @@ Image Xex2LoadImage(const uint8_t* data, size_t dataSize)
// Decompress image
if (fileFormatInfo != nullptr)
{
assert(fileFormatInfo->compressionType <= XEX_COMPRESSION_BASIC);
assert(fileFormatInfo->compressionType <= XEX_COMPRESSION_NORMAL);
std::unique_ptr<uint8_t[]> decryptedData;
const uint8_t* srcData = nullptr;
@ -192,6 +194,67 @@ Image Xex2LoadImage(const uint8_t* data, size_t dataSize)
destData += blocks[i].zeroSize;
}
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL)
{
result = std::make_unique<uint8_t[]>(imageSize);
auto* destData = result.get();
const Xex2CompressedBlockInfo* blocks = &((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->firstBlock;
const uint32_t headerSize = header->headerSize.get();
const uint32_t exeLength = dataSize - headerSize;
const uint8_t* exeBuffer = srcData;
auto compressBuffer = std::make_unique<uint8_t[]>(exeLength);
const uint8_t* p = NULL;
uint8_t* d = NULL;
sha1::SHA1 s;
p = exeBuffer;
d = compressBuffer.get();
uint8_t blockCalcedDigest[0x14];
while (blocks->blockSize)
{
const uint8_t* pNext = p + blocks->blockSize;
const auto* nextBlock = (const Xex2CompressedBlockInfo*)p;
s.reset();
s.processBytes(p, blocks->blockSize);
s.finalize(blockCalcedDigest);
if (memcmp(blockCalcedDigest, blocks->blockHash, 0x14) != 0)
return {};
p += 4;
p += 20;
while (true)
{
const size_t chunkSize = (p[0] << 8) | p[1];
p += 2;
if (!chunkSize)
break;
memcpy(d, p, chunkSize);
p += chunkSize;
d += chunkSize;
}
p = pNext;
blocks = nextBlock;
}
int resultCode = 0;
uint32_t uncompressedSize = security->imageSize;
uint8_t* buffer = destData;
resultCode = lzxDecompress(compressBuffer.get(), d - compressBuffer.get(), buffer, uncompressedSize, ((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->windowSize, nullptr, 0);
if (resultCode)
return {};
}
}
image.data = std::move(result);

View File

@ -403,7 +403,63 @@ XexPatcher::Result XexPatcher::apply(const uint8_t* xexBytes, size_t xexBytesSiz
memmove(outDataCursor, srcDataCursor, blocks[i].dataSize);
}
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL || fileFormatInfo->compressionType == XEX_COMPRESSION_DELTA)
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_NORMAL)
{
const Xex2CompressedBlockInfo* blocks = &((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->firstBlock;
const uint32_t exeLength = xexBytesSize - xexHeader->headerSize.get();
const uint8_t* exeBuffer = &outBytes[headerTargetSize];
auto compressBuffer = std::make_unique<uint8_t[]>(exeLength);
const uint8_t* p = NULL;
uint8_t* d = NULL;
sha1::SHA1 s;
p = exeBuffer;
d = compressBuffer.get();
uint8_t blockCalcedDigest[0x14];
while (blocks->blockSize)
{
const uint8_t* pNext = p + blocks->blockSize;
const auto* nextBlock = (const Xex2CompressedBlockInfo*)p;
s.reset();
s.processBytes(p, blocks->blockSize);
s.finalize(blockCalcedDigest);
if (memcmp(blockCalcedDigest, blocks->blockHash, 0x14) != 0)
return Result::PatchFailed;
p += 4;
p += 20;
while (true)
{
const size_t chunkSize = (p[0] << 8) | p[1];
p += 2;
if (!chunkSize)
break;
memcpy(d, p, chunkSize);
p += chunkSize;
d += chunkSize;
}
p = pNext;
blocks = nextBlock;
}
int resultCode = 0;
uint32_t uncompressedSize = originalSecurityInfo->imageSize;
uint8_t* buffer = outBytes.data() + newXexHeaderSize;
resultCode = lzxDecompress(compressBuffer.get(), d - compressBuffer.get(), buffer, uncompressedSize, ((const Xex2FileNormalCompressionInfo*)(fileFormatInfo + 1))->windowSize, nullptr, 0);
if (resultCode)
return Result::PatchFailed;
}
else if (fileFormatInfo->compressionType == XEX_COMPRESSION_DELTA)
{
return Result::XexFileUnsupported;
}

View File

@ -16,6 +16,8 @@
#include <span>
#include <vector>
extern int lzxDecompress(const void* lzxData, size_t lzxLength, void* dst, size_t dstLength, uint32_t windowSize, void* windowData, size_t windowDataLength);
struct XexPatcher
{
enum class Result {