2024-01-25 00:04:39 +01:00
|
|
|
//=============================================================================//
|
|
|
|
//
|
|
|
|
// Purpose: buffered pak encoder
|
|
|
|
//
|
|
|
|
//=============================================================================//
|
2024-01-27 01:38:19 +01:00
|
|
|
#include "tier0/binstream.h"
|
2024-01-25 00:04:39 +01:00
|
|
|
#include "thirdparty/zstd/zstd.h"
|
|
|
|
#include "rtech/ipakfile.h"
|
2024-01-27 01:38:19 +01:00
|
|
|
#include "paktools.h"
|
2024-01-25 00:04:39 +01:00
|
|
|
#include "pakencode.h"
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// encodes the pak file from buffer, we can't do streamed compression as we
|
|
|
|
// need to know the actual decompress size ahead of time, else the runtime will
|
|
|
|
// fail as we wouldn't be able to parse the decompressed size from the frame
|
|
|
|
// header
|
|
|
|
//-----------------------------------------------------------------------------
|
2024-01-27 01:38:19 +01:00
|
|
|
bool Pak_BufferToBufferEncode(const uint8_t* const inBuf, const uint64_t inLen,
|
2024-01-25 00:04:39 +01:00
|
|
|
uint8_t* const outBuf, const uint64_t outLen, const int level)
|
|
|
|
{
|
|
|
|
// offset to the actual pak data, the main file header shouldn't be
|
|
|
|
// compressed
|
|
|
|
const size_t dataOffset = sizeof(PakFileHeader_t);
|
|
|
|
|
|
|
|
const size_t compressSize = ZSTD_compress(
|
|
|
|
outBuf + dataOffset,
|
|
|
|
outLen - dataOffset,
|
|
|
|
inBuf + dataOffset,
|
|
|
|
inLen - dataOffset,
|
|
|
|
level);
|
|
|
|
|
|
|
|
if (ZSTD_isError(compressSize))
|
2024-01-28 18:15:19 +01:00
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: compression failed! [%s]\n",
|
|
|
|
__FUNCTION__, ZSTD_getErrorName(compressSize));
|
|
|
|
|
2024-01-25 00:04:39 +01:00
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
|
|
|
PakFileHeader_t* const outHeader = reinterpret_cast<PakFileHeader_t* const>(outBuf);
|
|
|
|
|
|
|
|
// the compressed size includes the entire buffer, even the data we didn't
|
|
|
|
// compress like the file header
|
|
|
|
outHeader->compressedSize = compressSize + dataOffset;
|
|
|
|
|
|
|
|
// these flags are required for the game's runtime to decide whether or not to
|
|
|
|
// decompress the pak, and how; see Pak_ProcessPakFile() for more details
|
|
|
|
outHeader->flags |= PAK_HEADER_FLAGS_COMPRESSED;
|
2024-01-27 01:45:57 +01:00
|
|
|
outHeader->flags |= PAK_HEADER_FLAGS_ZSTREAM;
|
2024-01-25 00:04:39 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-01-27 01:38:19 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2024-01-28 18:15:19 +01:00
|
|
|
// encodes the pak file from file name
|
2024-01-27 01:38:19 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
2024-01-28 18:15:19 +01:00
|
|
|
bool Pak_EncodePakFile(const char* const inPakFile, const char* const outPakFile, const int level)
|
2024-01-27 01:38:19 +01:00
|
|
|
{
|
2024-01-28 18:15:19 +01:00
|
|
|
if (!Pak_CreateBasePath())
|
2024-01-27 01:38:19 +01:00
|
|
|
{
|
2024-01-28 18:15:19 +01:00
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to create output path for pak file '%s'!\n",
|
|
|
|
__FUNCTION__, outPakFile);
|
2024-01-27 01:38:19 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
2024-01-27 01:38:19 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
CIOStream inPakStream;
|
2024-01-27 01:38:19 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
if (!inPakStream.Open(inPakFile, CIOStream::READ | CIOStream::BINARY))
|
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for read!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
2024-01-27 01:38:19 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
return false;
|
2024-01-27 01:38:19 +01:00
|
|
|
}
|
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
CIOStream outPakStream;
|
2024-01-27 01:38:19 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
if (!outPakStream.Open(outPakFile, CIOStream::WRITE | CIOStream::BINARY))
|
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to open pak file '%s' for write!\n",
|
|
|
|
__FUNCTION__, outPakFile);
|
2024-01-25 00:04:39 +01:00
|
|
|
|
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
const size_t fileSize = inPakStream.GetSize();
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-27 01:38:19 +01:00
|
|
|
// file appears truncated
|
2024-01-25 00:04:39 +01:00
|
|
|
if (fileSize <= sizeof(PakFileHeader_t))
|
2024-01-28 18:15:19 +01:00
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: pak '%s' appears truncated!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
|
|
|
|
2024-01-25 00:04:39 +01:00
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
std::unique_ptr<uint8_t[]> inPakBufContainer(new uint8_t[fileSize]);
|
|
|
|
uint8_t* const inPakBuf = inPakBufContainer.get();
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
inPakStream.Read(inPakBuf, fileSize);
|
|
|
|
inPakStream.Close();
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-02-04 00:55:48 +01:00
|
|
|
const PakFileHeader_t* const inHeader = reinterpret_cast<PakFileHeader_t* const>(inPakBuf);
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-27 01:38:19 +01:00
|
|
|
if (inHeader->magic != PAK_HEADER_MAGIC || inHeader->version != PAK_HEADER_VERSION)
|
2024-01-28 18:15:19 +01:00
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: pak '%s' has incompatible or invalid header!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
|
|
|
|
2024-01-25 00:04:39 +01:00
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-27 01:38:19 +01:00
|
|
|
if (inHeader->IsCompressed())
|
2024-01-28 18:15:19 +01:00
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: pak '%s' is already compressed!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
|
|
|
|
2024-01-25 00:04:39 +01:00
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
if (inHeader->compressedSize != fileSize)
|
2024-01-27 01:38:19 +01:00
|
|
|
{
|
2024-01-28 18:15:19 +01:00
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: pak '%s' appears truncated or corrupt; compressed size: '%zu' expected: '%zu'!\n",
|
|
|
|
__FUNCTION__, inPakFile, fileSize, inHeader->compressedSize);
|
|
|
|
|
2024-01-27 01:38:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
// NOTE: if the paks this particular pak patches have different sizes than
|
|
|
|
// current sizes in the patch header, the runtime will crash!
|
|
|
|
if (inHeader->patchIndex && !Pak_UpdatePatchHeaders(inPakBuf, outPakFile))
|
|
|
|
{
|
|
|
|
Warning(eDLL_T::RTECH, "%s: pak '%s' is a patch pak, but the pak(s) it patches weren't found; patch headers not updated!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<uint8_t[]> outPakBufContainer(new uint8_t[inHeader->decompressedSize]);
|
|
|
|
uint8_t* const outPakBuf = outPakBufContainer.get();
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-01-28 18:15:19 +01:00
|
|
|
PakFileHeader_t* const outHeader = reinterpret_cast<PakFileHeader_t* const>(outPakBuf);
|
2024-01-27 01:38:19 +01:00
|
|
|
|
|
|
|
// copy the header over
|
|
|
|
*outHeader = *inHeader;
|
|
|
|
|
2024-01-25 00:04:39 +01:00
|
|
|
// encoding failed
|
2024-01-28 18:15:19 +01:00
|
|
|
if (!Pak_BufferToBufferEncode(inPakBuf, inHeader->compressedSize, outPakBuf, inHeader->decompressedSize, level))
|
|
|
|
{
|
|
|
|
Error(eDLL_T::RTECH, NO_ERROR, "%s: failed to compress pak file '%s'!\n",
|
|
|
|
__FUNCTION__, inPakFile);
|
2024-01-25 00:04:39 +01:00
|
|
|
|
|
|
|
return false;
|
2024-01-28 18:15:19 +01:00
|
|
|
}
|
2024-01-25 00:04:39 +01:00
|
|
|
|
2024-02-04 00:55:48 +01:00
|
|
|
const PakFileHeader_t* const outPakHeader = reinterpret_cast<PakFileHeader_t* const>(outPakBuf);
|
2024-01-28 18:15:19 +01:00
|
|
|
|
|
|
|
Pak_ShowHeaderDetails(outPakHeader);
|
2024-01-25 00:04:39 +01:00
|
|
|
|
|
|
|
// this will be true if the entire buffer has been written
|
2024-01-28 18:15:19 +01:00
|
|
|
outPakStream.Write(outPakBuf, outPakHeader->compressedSize);
|
|
|
|
|
|
|
|
Msg(eDLL_T::RTECH, "Compressed pak file to: '%s'\n", outPakFile);
|
2024-01-27 01:38:19 +01:00
|
|
|
return true;
|
2024-01-25 00:04:39 +01:00
|
|
|
}
|