From e2898802e8e57ae1ac1b24574d824928064f6450 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Sat, 27 Jan 2024 01:38:19 +0100 Subject: [PATCH] RTech: properly deal with patch headers when encoding pak files Patch header must be updated to accommodate the new pak file sizes the patch pak patches. --- src/rtech/pak/pakencode.cpp | 120 +++++++++++++++++++++++++++--------- src/rtech/pak/pakencode.h | 2 +- 2 files changed, 92 insertions(+), 30 deletions(-) diff --git a/src/rtech/pak/pakencode.cpp b/src/rtech/pak/pakencode.cpp index 102a2f05..c2707fb5 100644 --- a/src/rtech/pak/pakencode.cpp +++ b/src/rtech/pak/pakencode.cpp @@ -3,12 +3,11 @@ // Purpose: buffered pak encoder // //=============================================================================// +#include "tier0/binstream.h" #include "thirdparty/zstd/zstd.h" #include "rtech/ipakfile.h" +#include "paktools.h" #include "pakencode.h" -#include - -extern CFileSystem_Stdio* FileSystem(); //----------------------------------------------------------------------------- // encodes the pak file from buffer, we can't do streamed compression as we @@ -16,7 +15,7 @@ extern CFileSystem_Stdio* FileSystem(); // fail as we wouldn't be able to parse the decompressed size from the frame // header //----------------------------------------------------------------------------- -bool Pak_EncodePak(const uint8_t* const inBuf, const uint64_t inLen, +bool Pak_BufferToBufferEncode(const uint8_t* const inBuf, const uint64_t inLen, uint8_t* const outBuf, const uint64_t outLen, const int level) { // offset to the actual pak data, the main file header shouldn't be @@ -33,12 +32,8 @@ bool Pak_EncodePak(const uint8_t* const inBuf, const uint64_t inLen, if (ZSTD_isError(compressSize)) return false; - const PakFileHeader_t* const inHeader = reinterpret_cast(inBuf); PakFileHeader_t* const outHeader = reinterpret_cast(outBuf); - // copy the header over - *outHeader = *inHeader; - // the compressed size includes the entire buffer, even the data we didn't // compress like the file header outHeader->compressedSize = compressSize + dataOffset; @@ -51,60 +46,127 @@ bool Pak_EncodePak(const uint8_t* const inBuf, const uint64_t inLen, return true; } +//----------------------------------------------------------------------------- +// searches for pak patches and updates all referenced files in patch headers +//----------------------------------------------------------------------------- +bool Pak_UpdatePatchHeaders(uint8_t* const inBuf, const char* const outPakFile) +{ + // file name without extension and patch number ID + char baseFilePath[MAX_PATH]; + snprintf(baseFilePath, sizeof(baseFilePath), "%s", outPakFile); + + const char* const fileNameUnqualified = V_UnqualifiedFileName(baseFilePath); + + V_StripExtension(baseFilePath, baseFilePath, sizeof(baseFilePath)); + + // strip the patch id, but make sure we only do this on the file name + // and not the path + char* const patchIdentifier = strrchr(baseFilePath, '('); + + if (patchIdentifier && patchIdentifier > fileNameUnqualified) + *patchIdentifier = '\0'; + + // NOTE: we modify the in buffer as the patch headers belong to the + // compressed section + PakFileHeader_t* const inHeader = reinterpret_cast(inBuf); + + // update each patch header + for (uint16_t i = 0; i < inHeader->patchIndex; i++) + { + short patchNumber = Pak_GetPatchNumberForIndex(inHeader, i); + char patchFile[MAX_PATH]; + + // the first patch number does not have an identifier in its name + if (patchNumber == 0) + snprintf(patchFile, sizeof(patchFile), "%s.rpak", baseFilePath); + else + snprintf(patchFile, sizeof(patchFile), "%s(%02u).rpak", baseFilePath, patchNumber); + + CIOStream inPatch; + + // unable to open patch while there should be one, we must calculate + // new file sizes here, or else the runtime would fail to load them + if (!inPatch.Open(patchFile, CIOStream::READ | CIOStream::BINARY)) + return false; + + const size_t fileSize = inPatch.GetSize(); + + // pak appears truncated + if (fileSize <= sizeof(PakFileHeader_t)) + return false; + + DevMsg(eDLL_T::RTECH, "%s: updating patch header for pak '%s', new size = %zu\n", + __FUNCTION__, patchFile, fileSize); + + PakPatchFileHeader_t* const patchHeader = Pak_GetPatchFileHeader(inHeader, i); + patchHeader->m_sizeDisk = fileSize; + } + + return true; +} + //----------------------------------------------------------------------------- // encodes the pak file from file name //----------------------------------------------------------------------------- bool Pak_EncodePakFile(const char* const inPakFile, const char* const outPakFile, const int level) { - FileHandle_t inFileHandle = FileSystem()->Open(inPakFile, "rb", "GAME"); + CIOStream inFile; - // unable to open - if (!inFileHandle) + // failed to open + if (!inFile.Open(inPakFile, CIOStream::READ | CIOStream::BINARY)) return false; - const ssize_t fileSize = FileSystem()->Size(inFileHandle); + const ssize_t fileSize = inFile.GetSize(); - // corrupted/truncated + // file appears truncated if (fileSize <= sizeof(PakFileHeader_t)) - { - FileSystem()->Close(inFileHandle); return false; - } std::unique_ptr pakBuf(new uint8_t[fileSize]); uint8_t* const pPakBuf = pakBuf.get(); - FileSystem()->Read(pPakBuf, fileSize, inFileHandle); - FileSystem()->Close(inFileHandle); + inFile.Read(pPakBuf, fileSize); + inFile.Close(); - const PakFileHeader_t* inPakHeader = reinterpret_cast(pPakBuf); + const PakFileHeader_t* inHeader = reinterpret_cast(pPakBuf); // incompatible pak, or not a pak - if (inPakHeader->magic != PAK_HEADER_MAGIC || inPakHeader->version != PAK_HEADER_VERSION) + if (inHeader->magic != PAK_HEADER_MAGIC || inHeader->version != PAK_HEADER_VERSION) return false; // already compressed - if (inPakHeader->IsCompressed()) + if (inHeader->IsCompressed()) return false; - std::unique_ptr outBuf(new uint8_t[inPakHeader->decompressedSize]); + if (inHeader->patchIndex && !Pak_UpdatePatchHeaders(pPakBuf, outPakFile)) + { + // pak has patches but one or more weren't found; the headers need to + // be updated with new compression sizes since the runtime uses them to + // check for truncation + return false; + } + + std::unique_ptr outBuf(new uint8_t[inHeader->decompressedSize]); uint8_t* const pOutBuf = outBuf.get(); + PakFileHeader_t* const outHeader = reinterpret_cast(pOutBuf); + + // copy the header over + *outHeader = *inHeader; + // encoding failed - if (!Pak_EncodePak(pPakBuf, fileSize, pOutBuf, inPakHeader->decompressedSize, level)) + if (!Pak_BufferToBufferEncode(pPakBuf, fileSize, pOutBuf, inHeader->decompressedSize, level)) return false; - FileHandle_t outFileHandle = FileSystem()->Open(outPakFile, "wb", "GAME"); + CIOStream outFile; // failure to open output file - if (!outFileHandle) + if (!outFile.Open(outPakFile, CIOStream::WRITE | CIOStream::BINARY)) return false; const PakFileHeader_t* outPakHeader = reinterpret_cast(pOutBuf); // this will be true if the entire buffer has been written - const bool ret = FileSystem()->Write(pOutBuf, outPakHeader->compressedSize, outFileHandle) == (ssize_t)outPakHeader->compressedSize; - FileSystem()->Close(inFileHandle); - - return ret; + outFile.Write(pOutBuf, outPakHeader->compressedSize); + return true; } diff --git a/src/rtech/pak/pakencode.h b/src/rtech/pak/pakencode.h index 647e0320..4e567b89 100644 --- a/src/rtech/pak/pakencode.h +++ b/src/rtech/pak/pakencode.h @@ -1,7 +1,7 @@ #ifndef RTECH_PAKENCODE_H #define RTECH_PAKENCODE_H -bool Pak_EncodePak(const uint8_t* const inBuf, const uint64_t inLen, +bool Pak_BufferToBufferEncode(const uint8_t* const inBuf, const uint64_t inLen, uint8_t* const outBuf, const uint64_t outLen, const int level); bool Pak_EncodePakFile(const char* const inPakFile, const char* const outPakFile, const int level);