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.
This commit is contained in:
Kawe Mazidjatari 2024-01-27 01:38:19 +01:00
parent 7f25ce7e42
commit e2898802e8
2 changed files with 92 additions and 30 deletions

View File

@ -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 <filesystem/filesystem.h>
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<const PakFileHeader_t* const>(inBuf);
PakFileHeader_t* const outHeader = reinterpret_cast<PakFileHeader_t* const>(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<PakFileHeader_t* const>(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<uint8_t[]> 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<PakFileHeader_t* const>(pPakBuf);
const PakFileHeader_t* inHeader = reinterpret_cast<PakFileHeader_t* const>(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<uint8_t[]> 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<uint8_t[]> outBuf(new uint8_t[inHeader->decompressedSize]);
uint8_t* const pOutBuf = outBuf.get();
PakFileHeader_t* const outHeader = reinterpret_cast<PakFileHeader_t* const>(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<PakFileHeader_t* const>(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;
}

View File

@ -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);