r5sdk/r5dev/rtech/pak/pakparse.cpp
Kawe Mazidjatari e1c889719a RTech: properly implement ring buffer stream decode for Zstd decoder
The new decoder can now decode patched and custom rpaks files, all issues so far have been fixed. The code still needs some testing and bench marking to make sure
2024-04-05 17:56:48 +02:00

951 lines
35 KiB
C++

//=============================================================================//
//
// Purpose: pak file loading and unloading
//
//=============================================================================//
#include "rtech/ipakfile.h"
#include "rtech/async/asyncio.h"
#include "paktools.h"
#include "pakstate.h"
#include "pakpatch.h"
#include "pakalloc.h"
#include "pakparse.h"
#include "pakdecode.h"
#include "pakstream.h"
//-----------------------------------------------------------------------------
// resolve the target guid from lookuo table
//-----------------------------------------------------------------------------
static bool Pak_ResolveAssetDependency(const PakFile_t* const pak, PakGuid_t currentGuid,
const PakGuid_t targetGuid, int& currentIndex, const bool shouldCheckTwo)
{
while (true)
{
if (shouldCheckTwo && currentGuid == 2)
{
if (pak->memoryData.pakHeader.assetCount)
return false;
}
currentIndex++;
if (currentIndex >= PAK_MAX_ASSETS)
return false;
currentIndex &= PAK_MAX_ASSETS_MASK;
currentGuid = g_pPakGlobals->m_assets[currentIndex].guid;
if (currentGuid == targetGuid)
return true;
}
UNREACHABLE();
}
//-----------------------------------------------------------------------------
// resolve guid relations for asset
//-----------------------------------------------------------------------------
void Pak_ResolveAssetRelations(PakFile_t* const pak, const PakAsset_t* const asset)
{
PakPage_t* const pGuidDescriptors = &pak->memoryData.guidDescriptors[asset->dependenciesIndex];
volatile uint32_t* v5 = reinterpret_cast<volatile uint32_t*>(*(reinterpret_cast<uint64_t*>(g_pPakGlobals) + 0x17 * (pak->memoryData.pakId & PAK_MAX_HANDLES_MASK) + 0x160212));
if (pak_debugrelations->GetBool())
Msg(eDLL_T::RTECH, "Resolving relations for asset: '0x%-16llX', dependencies: %-4u; in pak '%s'\n",
asset->guid, asset->dependenciesCount, pak->memoryData.fileName);
for (uint32_t i = 0; i < asset->dependenciesCount; i++)
{
void** const pCurrentGuid = reinterpret_cast<void**>(pak->memoryData.memPageBuffers[pGuidDescriptors[i].index] + pGuidDescriptors[i].offset);
// get current guid
const PakGuid_t targetGuid = reinterpret_cast<uint64_t>(*pCurrentGuid);
// get asset index
int currentIndex = targetGuid & PAK_MAX_ASSETS_MASK;
const PakGuid_t currentGuid = g_pPakGlobals->m_assets[currentIndex].guid;
const int64_t v9 = 2i64 * InterlockedExchangeAdd(v5, 1u);
*reinterpret_cast<PakGuid_t*>(const_cast<uint32_t*>(&v5[2 * v9 + 2])) = targetGuid;
*reinterpret_cast<PakGuid_t*>(const_cast<uint32_t*>(&v5[2 * v9 + 4])) = asset->guid;
if (currentGuid != targetGuid)
{
// are we some special asset with the guid 2?
if (!Pak_ResolveAssetDependency(pak, currentGuid, targetGuid, currentIndex, true))
{
PakAsset_t* assetEntries = pak->memoryData.assetEntries;
uint64_t a = 0;
for (; assetEntries->guid != targetGuid; a++, assetEntries++)
{
if (a >= pak->memoryData.pakHeader.assetCount)
{
if (!Pak_ResolveAssetDependency(pak, currentGuid, targetGuid, currentIndex, false))
{
// the dependency couldn't be resolved, this state is irrecoverable;
// error out
Error(eDLL_T::RTECH, EXIT_FAILURE, "Failed to resolve asset dependency %u of %u\n"
"pak: '%s'\n"
"asset: '0x%llX'\n"
"target: '0x%llX'\n",
i, asset->dependenciesCount,
pak->memoryData.fileName,
asset->guid,
targetGuid);
}
break;
}
}
currentIndex = pak->memoryData.qword2E0[a];
}
}
// finally write the pointer to the guid entry
*pCurrentGuid = g_pPakGlobals->m_assets[currentIndex].m_head;
}
}
uint32_t Pak_ProcessRemainingPagePointers(PakFile_t* const pak)
{
uint32_t processedPointers = 0;
for (processedPointers = pak->numProcessedPointers; processedPointers < pak->GetPointerCount(); ++processedPointers)
{
PakPage_t* const curPage = &pak->memoryData.virtualPointers[processedPointers];
int curCount = curPage->index - pak->firstPageIdx;
if (curCount < 0)
curCount += pak->memoryData.pakHeader.memPageCount;
if (curCount >= pak->processedPageCount)
break;
PakPage_t* const ptr = reinterpret_cast<PakPage_t*>(pak->GetPointerForPageOffset(curPage));
ptr->ptr = pak->memoryData.memPageBuffers[ptr->index] + ptr->offset;
}
return processedPointers;
}
void __fastcall Rebuild_14043E030(PakFile_t* const pak)
{
__int64 numAssets; // rsi
PakAsset_t* pakAsset; // rdi
__int64 _numAssets; // r14
unsigned int assetBind; // ebp
__int64 v13; // rcx
__int64 qword2D8_low; // rdi
JobID_t qword2D8_high; // ebp
JobTypeID_t v16; // si
pak->numProcessedPointers = Pak_ProcessRemainingPagePointers(pak);
numAssets = (unsigned int)pak->processedAssetCount;
if ((_DWORD)numAssets != pak->memoryData.pakHeader.assetCount)
{
pakAsset = &pak->memoryData.assetEntries[numAssets];
_numAssets = (unsigned int)numAssets;
if ((int)pakAsset->pageEnd <= pak->processedPageCount)
{
while ((unsigned __int16)*word_167ED7BDE <= 0xC8u)
{
assetBind = pakAsset->HashTableIndexForAssetType();
pak->memoryData.qword2E0[_numAssets] = (int)sub_14043D3C0(pak, pakAsset);
_InterlockedIncrement16(word_167ED7BDE);
v13 = assetBind;
if (g_pPakGlobals->m_assetBindings[(unsigned __int64)assetBind].loadAssetFunc)
{
qword2D8_low = pak->memoryData.pakId;
qword2D8_high = pak->memoryData.unkJobID;
v16 = *((_BYTE*)&*g_pPakGlobals + v13 + 13207872);
JTGuts_AddJob(v16, qword2D8_high, (void*)qword2D8_low, (void*)_numAssets);
}
else
{
if (_InterlockedExchangeAdd16((volatile signed __int16*)&pakAsset->numRemainingDependencies, 0xFFFFu) == 1)
sub_14043D150(pak, pakAsset, (unsigned int)numAssets, assetBind);
_InterlockedDecrement16(word_167ED7BDE);
}
numAssets = (unsigned int)++pak->processedAssetCount;
if ((_DWORD)numAssets == pak->memoryData.pakHeader.assetCount)
{
JT_EndJobGroup(pak->memoryData.unkJobID);
return;
}
_numAssets = (unsigned int)numAssets;
pakAsset = &pak->memoryData.assetEntries[numAssets];
if (pakAsset->pageEnd > pak->processedPageCount)
return;
}
}
}
}
//-----------------------------------------------------------------------------
// load user-requested pak files on-demand
//-----------------------------------------------------------------------------
PakHandle_t Pak_LoadAsync(const char* const fileName, CAlignedMemAlloc* const allocator, const int nIdx, const bool bUnk)
{
PakHandle_t pakId = INVALID_PAK_HANDLE;
if (Pak_FileExists(fileName))
{
Msg(eDLL_T::RTECH, "Loading pak file: '%s'\n", fileName);
pakId = v_Pak_LoadAsync(fileName, allocator, nIdx, bUnk);
if (pakId == INVALID_PAK_HANDLE)
Error(eDLL_T::RTECH, NO_ERROR, "%s: Failed read '%s' results '%d'\n", __FUNCTION__, fileName, pakId);
}
else
{
Error(eDLL_T::RTECH, NO_ERROR, "%s: Failed; file '%s' doesn't exist\n", __FUNCTION__, fileName);
}
return pakId;
}
//-----------------------------------------------------------------------------
// unloads loaded pak files
//-----------------------------------------------------------------------------
void Pak_UnloadAsync(PakHandle_t handle)
{
const PakLoadedInfo_t* const pakInfo = Pak_GetPakInfo(handle);
if (pakInfo && pakInfo->fileName)
Msg(eDLL_T::RTECH, "Unloading pak file: '%s'\n", pakInfo->fileName);
v_Pak_UnloadAsync(handle);
}
#define CMD_INVALID -1
// only patch cmds 4,5,6 use this array to determine their data size
static const int s_patchCmdToBytesToProcess[] = { CMD_INVALID, CMD_INVALID, CMD_INVALID, CMD_INVALID, 3, 7, 6, 0 };
#undef CMD_INVALID
//----------------------------------------------------------------------------------
// loads and processes a pak file (handles decompression and patching)
// TODO: !!! FINISH REBUILD !!!
//----------------------------------------------------------------------------------
bool Pak_ProcessPakFile(PakFile_t* const pak)
{
PakFileHeader_t* pakHeader; // r8
PakFileStream_t* fileStream; // rsi
PakMemoryData_t* memoryData; // r14
__int64 dwordB8; // rcx
unsigned int v6; // eax // [was: int]
__int64 v7; // rax
char v8; // r13
//signed __int64 index_Maybe; // rdi
//char v10; // r15
//__int64 v11; // rdx
//const char* v12; // rbp
size_t bytesProcessed; // eax
char byteBF; // al
unsigned __int64 v16; // r9
unsigned __int8 v17; // cl
unsigned __int64 v18; // r8
uint8_t byte1F8; // al
uint8_t byte1FD; // cl
PakFileStream_t::Descriptor* v22; // rdi
size_t dataOffset; // rax
PakDecoder_t* decodeContext; // rbp
size_t decompressedSize; // rax
size_t compressedSize; // rdx
uint64_t qword1D0; // rcx
__int64 v28; // rax
unsigned int numBitsRemaining; // r8d
int v35; // ecx
int v39; // r10d
int v40; // r9d
unsigned int v42; // ecx
unsigned int v43; // r8d
unsigned int v44; // r12d
char byteBC; // r15
__int64 v46; // rbp
__int64 v47; // r8
unsigned __int64 v48; // rbp
unsigned __int64 qword8; // rax
__int64 v50; // rdi
char c; // al
char* it; // rcx
char* i; // rdx
int v56; // edi
unsigned int patchCount; // r15
unsigned __int64 v58; // rdx
char pakPatchPath[MAX_PATH]; // [rsp+40h] [rbp-148h] BYREF
unsigned __int64 v62; // [rsp+190h] [rbp+8h]
size_t numBytesToProcess; // [rsp+198h] [rbp+10h] BYREF
fileStream = &pak->fileStream;
memoryData = &pak->memoryData;
dwordB8 = (unsigned int)pak->fileStream.dwordB8;
if ((_DWORD)dwordB8)
v62 = dwordB8 << 19;
else
v62 = sizeof(PakFileHeader_t);
v6 = fileStream->unsigned_intB4;
if (v6 != (_DWORD)dwordB8)
{
while (1)
{
v7 = v6 & 0x1F;
v8 = fileStream->gap94[v7];
if (v8 != 1)
break;
LABEL_17:
v6 = fileStream->unsigned_intB4 + 1;
fileStream->unsigned_intB4 = v6;
if (v6 == fileStream->dwordB8)
goto LABEL_18;
}
const char* statusMsg = "(no reason)";
const uint8_t currentStatus = g_pakLoadApi->CheckAsyncRequest((unsigned char)fileStream->gap14[v7], &bytesProcessed, &statusMsg);
if (currentStatus == AsyncHandleStatus_t::FS_ASYNC_PENDING)
goto LABEL_18;
if (currentStatus == AsyncHandleStatus_t::FS_ASYNC_ERROR)
Error(eDLL_T::RTECH, EXIT_FAILURE, "Error reading pak file \"%s\" -- %s\n", pak->memoryData.fileName, statusMsg);
fileStream->bytesStreamed += bytesProcessed;
if (v8)
{
byteBF = fileStream->byteBF++;
pakHeader = &pak->memoryData.pakHeader;
v16 = (unsigned __int64)fileStream->unsigned_intB4 << 19;
if (v8 == 2)
{
v18 = v16 & fileStream->qword1C8;
fileStream->bytesStreamed = bytesProcessed + v16;
pakHeader = (PakFileHeader_t*)&fileStream->buffer[v18];
}
v17 = byteBF & 7;
fileStream->m_descriptors[v17].dataOffset = v16 + sizeof(PakFileHeader_t);
fileStream->m_descriptors[v17].compressedSize = v16 + pakHeader->compressedSize;
fileStream->m_descriptors[v17].decompressedSize = pakHeader->decompressedSize;
fileStream->m_descriptors[v17].isCompressed = pakHeader->IsCompressed();
}
goto LABEL_17;
}
LABEL_18:
byte1F8 = pak->byte1F8;
if (byte1F8 != fileStream->byteBF)
{
const bool useZStream = pak->GetHeader().flags & PAK_HEADER_FLAGS_ZSTREAM;
byte1FD = pak->byte1FD;
do
{
v22 = &fileStream->m_descriptors[byte1F8 & 7];
if (byte1FD)
{
pak->byte1FD = 0;
pak->inputBytePos = v22->dataOffset;
if (v22->isCompressed)
{
pak->isOffsetted_MAYBE = 0;
pak->isCompressed = 1;
dataOffset = sizeof(PakFileHeader_t);
}
else
{
pak->isOffsetted_MAYBE = 1;
pak->isCompressed = 0;
dataOffset = v22->dataOffset;
}
memoryData->processedPatchedDataSize = dataOffset;
if (!pak->isCompressed)
{
LABEL_35:
compressedSize = v22->compressedSize;
qword1D0 = compressedSize;
if (fileStream->bytesStreamed < compressedSize)
qword1D0 = fileStream->bytesStreamed;
goto LABEL_41;
}
decodeContext = &pak->pakDecoder;
decompressedSize = Pak_InitDecoder(&pak->pakDecoder, fileStream->buffer,
PAK_DECODE_IN_RING_BUFFER_MASK, v22->compressedSize - (v22->dataOffset - sizeof(PakFileHeader_t)),
v22->dataOffset - sizeof(PakFileHeader_t), sizeof(PakFileHeader_t), useZStream);
if (decompressedSize != v22->decompressedSize)
Error(eDLL_T::RTECH, EXIT_FAILURE,
"Error reading pak file \"%s\" -- decompressed size %zu doesn't match expected value %zu\n",
pak->memoryData.fileName,
decompressedSize,
pak->memoryData.pakHeader.decompressedSize);
pak->pakDecoder.outputBuf = pak->decompBuffer;
pak->pakDecoder.outputMask = PAK_DECODE_OUT_RING_BUFFER_MASK;
}
else
{
decodeContext = &pak->pakDecoder;
}
if (!pak->isCompressed)
goto LABEL_35;
qword1D0 = pak->pakDecoder.outBufBytePos;
if (qword1D0 != pak->pakDecoder.decompSize)
{
const bool didDecode = Pak_StreamToBufferDecode(decodeContext, fileStream->bytesStreamed, memoryData->processedPatchedDataSize + PAK_DECODE_OUT_RING_BUFFER_SIZE, useZStream);
qword1D0 = pak->pakDecoder.outBufBytePos;
pak->inputBytePos = pak->pakDecoder.inBufBytePos;
if (didDecode)
DevMsg(eDLL_T::RTECH, "%s: pak '%s' decoded successfully\n", __FUNCTION__, pak->GetName());
if (didDecode)
{
if (useZStream && decodeContext->zstreamContext)
{
ZSTD_freeDStream(decodeContext->zstreamContext);
decodeContext->zstreamContext = nullptr;
}
}
}
compressedSize = v22->compressedSize;
LABEL_41:
if (pak->inputBytePos != compressedSize || memoryData->processedPatchedDataSize != qword1D0)
goto LABEL_45;
byte1FD = 1;
byte1F8 = pak->byte1F8 + 1;
pak->byte1FD = 1;
pak->byte1F8 = byte1F8;
} while (byte1F8 != fileStream->byteBF);
}
qword1D0 = memoryData->processedPatchedDataSize;
LABEL_45:
v28 = memoryData->field_2A8;
numBytesToProcess = qword1D0 - memoryData->processedPatchedDataSize;
if (memoryData->patchSrcSize + v28)
{
do
{
// if there are no bytes left to process in this patch operation
if (!memoryData->numBytesToProcess_maybe)
{
RBitRead& bitbuf = memoryData->bitBuf;
numBitsRemaining = bitbuf.m_bitsRemaining; // number of "free" bits in the bitbuf (how many to fetch)
// fetch remaining bits
bitbuf.m_dataBuf |= *(_QWORD*)memoryData->patchData << (64 - (unsigned __int8)numBitsRemaining);
// advance patch data buffer by the number of bytes that have just been fetched
memoryData->patchData = &memoryData->patchData[numBitsRemaining >> 3];
// store the number of bits remaining to complete the data read
bitbuf.m_bitsRemaining = numBitsRemaining & 7; // number of bits above a whole byte
const unsigned __int8 index1 = static_cast<unsigned __int8>(bitbuf.ReadBits(6));
v35 = memoryData->PATCH_field_68[index1]; // number of bits to discard from bitbuf
__int8 cmd = memoryData->patchCommands[index1];
bitbuf.DiscardBits(v35);
// get the next patch function to execute
memoryData->patchFunc = g_pakPatchApi[cmd];
if (cmd <= 3u)
{
const unsigned __int8 index2 = static_cast<unsigned __int8>(bitbuf.ReadBits(8));
v39 = memoryData->PATCH_unk3[index2];
v40 = memoryData->PATCH_unk2[index2]; // number of stored bits for the data size
bitbuf.DiscardBits(v39);
memoryData->numBytesToProcess_maybe = (1ull << v40) + bitbuf.ReadBits(v40);
bitbuf.DiscardBits(v40);
}
else
{
memoryData->numBytesToProcess_maybe = s_patchCmdToBytesToProcess[cmd];
}
}
} while (pak->memoryData.patchFunc(pak, &numBytesToProcess) && memoryData->patchSrcSize + memoryData->field_2A8);
}
if (pak->isOffsetted_MAYBE)
pak->inputBytePos = memoryData->processedPatchedDataSize;
if (!fileStream->finishedLoadingPatches)
{
v42 = fileStream->unsigned_intB4;
v43 = fileStream->dwordB8;
if ((unsigned int)(pak->inputBytePos >> 19) < v42)
v42 = (unsigned int)pak->inputBytePos >> 19; // New cast added
v44 = v42 + 32;
if (v43 != v42 + 32)
{
while (1)
{
byteBC = fileStream->byteBC;
v46 = v43;
v47 = v43 & 0x1F;
v48 = (v46 + 1) << 19;
if (byteBC == 1)
break;
qword8 = fileStream->qword8;
if (v62 < qword8)
{
v50 = (unsigned int)v47;
if (v48 < qword8)
qword8 = v48;
fileStream->gap14[(unsigned int)v47] = v_FS_ReadAsyncFile(
fileStream->fileHandle,
v62 - fileStream->qword0,
qword8 - v62,
&fileStream->buffer[v62 & fileStream->qword1C8],
0i64,
0i64,
4);
fileStream->gap94[v50] = byteBC;
fileStream->byteBC = 0;
goto LABEL_65;
}
if (pak->patchCount >= pak->memoryData.pakHeader.patchIndex)
{
FS_CloseAsyncFile((short)fileStream->fileHandle);
fileStream->fileHandle = INVALID_PAK_HANDLE;
fileStream->qword0 = 0i64;
fileStream->finishedLoadingPatches = true;
return memoryData->patchSrcSize == 0;
}
if (!pak->dword14)
return memoryData->patchSrcSize == 0;
sprintf(pakPatchPath, PLATFORM_PAK_PATH"%s", pak->memoryData.fileName);
patchCount = pak->patchCount++;
// get path of next patch rpak to load
if (pak->memoryData.patchIndices[patchCount])
{
c = pakPatchPath[0];
it = pakPatchPath;
for (i = nullptr; c; ++it)
{
if (c == '.')
{
i = it;
}
else if (c == '\\' || c == '/')
{
i = nullptr;
}
c = it[1];
}
if (i)
it = i;
// replace extension '.rpak' with '(xx).rpak'
snprintf(it, &pakPatchPath[sizeof(pakPatchPath)] - it,
"(%02u).rpak", pak->memoryData.patchIndices[patchCount]);
}
v56 = FS_OpenAsyncFile(pakPatchPath, 5i64, &numBytesToProcess);
if (v56 == FS_ASYNC_FILE_INVALID)
Error(eDLL_T::RTECH, EXIT_FAILURE, "Couldn't open file \"%s\".\n", pakPatchPath);
if (numBytesToProcess < pak->memoryData.patchHeaders[patchCount].m_sizeDisk)
Error(eDLL_T::RTECH, EXIT_FAILURE, "File \"%s\" appears truncated; read size: %zu < expected size: %zu.\n",
pakPatchPath, numBytesToProcess, pak->memoryData.patchHeaders[patchCount].m_sizeDisk);
FS_CloseAsyncFile((short)fileStream->fileHandle);
v43 = fileStream->dwordB8;
fileStream->fileHandle = v56;
v58 = (unsigned __int64)((v43 + 7) & 0xFFFFFFF8) << 19;
fileStream->qword0 = v58;
fileStream->byteBC = (v43 == ((v43 + 7) & 0xFFFFFFF8)) + 1;
fileStream->qword8 = v58 + pak->memoryData.patchHeaders[patchCount].m_sizeDisk;
LABEL_84:
if (v43 == v44)
return memoryData->patchSrcSize == 0;
}
fileStream->gap14[v47] = -2;
fileStream->gap94[v47] = 1;
if ((((_BYTE)v47 + 1) & 7) == 0)
fileStream->byteBC = 2;
LABEL_65:
v43 = ++fileStream->dwordB8;
v62 = v48;
goto LABEL_84;
}
}
return memoryData->patchSrcSize == 0;
}
// sets patch variables for copying the next unprocessed page into the relevant segment buffer
// if this is a header page, fetch info from the next unprocessed asset and copy over the asset's header
bool SetupNextPageForPatching(PakLoadedInfo_t* a1, PakFile_t* pak)
{
Rebuild_14043E030(pak);
// numProcessedPointers has just been set in the above function call
pak->memoryData.numShiftedPointers = pak->numProcessedPointers;
if (pak->processedPageCount == pak->GetPageCount())
return false;
//break;
const uint32_t highestProcessedPageIdx = pak->processedPageCount + pak->firstPageIdx;
pak->processedPageCount++;
int v26 = highestProcessedPageIdx - pak->GetPageCount();
if (highestProcessedPageIdx < pak->GetPageCount())
v26 = highestProcessedPageIdx;
const PakPageHeader_t* const nextMemPageHeader = &pak->memoryData.pageHeaders[v26];
if ((pak->memoryData.segmentHeaders[nextMemPageHeader->segmentIdx].typeFlags & (SF_TEMP | SF_CPU)) != 0)
{
pak->memoryData.patchSrcSize = nextMemPageHeader->dataSize;
pak->memoryData.patchDstPtr = reinterpret_cast<char*>(pak->memoryData.memPageBuffers[v26]);
return true;
//continue;
}
// headers
PakAsset_t* pakAsset = pak->memoryData.ppAssetEntries[pak->memoryData.someAssetCount];
pak->memoryData.patchSrcSize = pakAsset->headerSize;
int assetTypeIdx = pakAsset->HashTableIndexForAssetType();
pak->memoryData.patchDstPtr = reinterpret_cast<char*>(a1->segmentBuffers[0]) + pak->memoryData.unkAssetTypeBindingSizes[assetTypeIdx];
pak->memoryData.unkAssetTypeBindingSizes[assetTypeIdx] += g_pPakGlobals->m_assetBindings[assetTypeIdx].nativeClassSize;
return true;
}
bool Pak_ProcessAssets(PakLoadedInfo_t* const a1)
{
PakFile_t* const pak = a1->pakFile;
while (pak->processedAssetCount != pak->GetAssetCount())
{
// TODO: invert condition and make the branch encompass the whole loop
if (!(pak->memoryData.patchSrcSize + pak->memoryData.field_2A8))
{
const bool res = SetupNextPageForPatching(a1, pak);
if (res)
continue;
else
break;
}
if (!Pak_ProcessPakFile(pak))
return false;
// processedPageCount must be greater than 0 here otherwise the page index will be negative and cause a crash
// if this happens, something probably went wrong with the patch data condition at the start of the loop, as that
// function call should increment processedPageCount if it succeeded
assert(pak->processedPageCount > 0);
const uint32_t pageCount = pak->GetPageCount();
const uint32_t v4 = (pak->firstPageIdx - 1) + pak->processedPageCount;
uint32_t shiftedPageIndex = v4;
if (v4 >= pageCount)
shiftedPageIndex -= pageCount;
// if "temp_" segment
if ((pak->memoryData.segmentHeaders[pak->memoryData.pageHeaders[shiftedPageIndex].segmentIdx].typeFlags & (SF_TEMP | SF_CPU)) != 0)
{
const bool res = SetupNextPageForPatching(a1, pak);
if (res)
continue;
else
break;
}
PakAsset_t* asset = pak->memoryData.ppAssetEntries[pak->memoryData.someAssetCount];
const uint32_t headPageOffset = asset->headPtr.offset;
char* v8 = pak->memoryData.patchDstPtr - asset->headerSize;
uint32_t newOffsetFromSegmentBufferToHeader = LODWORD(pak->memoryData.patchDstPtr)
- asset->headerSize
- LODWORD(a1->segmentBuffers[0]);
asset->headPtr.offset = newOffsetFromSegmentBufferToHeader;
uint32_t offsetSize = newOffsetFromSegmentBufferToHeader - headPageOffset;
for (uint32_t i = pak->memoryData.numShiftedPointers; i < pak->GetPointerCount(); pak->memoryData.numShiftedPointers = i)
{
PakPage_t* ptr = &pak->memoryData.virtualPointers[i];
ASSERT_PAKPTR_VALID(pak, ptr);
if (ptr->index != shiftedPageIndex)
break;
const uint32_t offsetToPointer = ptr->offset - headPageOffset;
if (offsetToPointer >= asset->headerSize)
break;
PakPage_t* pagePtr = reinterpret_cast<PakPage_t*>(v8 + offsetToPointer);
ASSERT_PAKPTR_VALID(pak, ptr);
ptr->offset += offsetSize;
if (pagePtr->index == shiftedPageIndex)
pagePtr->offset += offsetSize;
i = pak->memoryData.numShiftedPointers + 1;
}
for (uint32_t j = 0; j < asset->dependenciesCount; ++j)
{
PakPage_t* descriptor = &pak->memoryData.guidDescriptors[asset->dependenciesIndex + j];
if (descriptor->index == shiftedPageIndex)
descriptor->offset += offsetSize;
}
const uint32_t v16 = ++pak->memoryData.someAssetCount;
PakAsset_t* v17 = nullptr;
if (v16 < pak->GetAssetCount() && (v17 = pak->memoryData.ppAssetEntries[v16], v17->headPtr.index == shiftedPageIndex))
{
pak->memoryData.field_2A8 = v17->headPtr.offset - headPageOffset - asset->headerSize;
pak->memoryData.patchSrcSize = v17->headerSize;
const uint8_t assetTypeIdx = v17->HashTableIndexForAssetType();
pak->memoryData.patchDstPtr = reinterpret_cast<char*>(a1->segmentBuffers[0]) + pak->memoryData.unkAssetTypeBindingSizes[assetTypeIdx];
pak->memoryData.unkAssetTypeBindingSizes[assetTypeIdx] += g_pPakGlobals->m_assetBindings[assetTypeIdx].nativeClassSize;
}
else
{
bool res = SetupNextPageForPatching(a1, pak);
if (res)
continue;
else
break;
}
}
if (!JT_IsJobDone(pak->memoryData.unkJobID))
return false;
uint32_t i = 0;
PakAsset_t* pAsset = nullptr;
for (int j = pak->memoryData.pakId & PAK_MAX_HANDLES_MASK; i < pak->GetHeader().assetCount; a1->assetGuids[i - 1] = pAsset->guid)
{
pAsset = &pak->memoryData.assetEntries[i];
if (pAsset->numRemainingDependencies)
{
//printf("[%s] processing deps for %llX (%.4s)\n", pak->GetName(), pAsset->guid, (char*)&pAsset->magic);
Pak_ResolveAssetRelations(pak, pAsset);
const int v36 = pak->memoryData.qword2E0[i];
if (dword_167A40B3C[6 * g_pPakGlobals->m_assets[v36].unk_8] == j)
{
if (*qword_167ED7BC8)
{
uint64_t v38 = 0;
if ((*qword_167ED7BC8)->unk_0)
{
int* v39 = (*qword_167ED7BC8)->unk_array_9D410;
while (*v39 != v36)
{
++v38;
++v39;
if (v38 >= (*qword_167ED7BC8)->unk_0)
goto LABEL_41;
}
goto LABEL_42;
}
}
else
{
//printf("allocating thing\n");
*qword_167ED7BC8 = reinterpret_cast<UnknownPakStruct_t*>(AlignedMemAlloc()->Alloc(0x11D410, 8));
(*qword_167ED7BC8)->unk_0 = 0;
(*qword_167ED7BC8)->unk_4 = 0;
(*qword_167ED7BC8)->unk_8 = 0;
}
LABEL_41:
(*qword_167ED7BC8)->unk_array_9D410[(*qword_167ED7BC8)->unk_0] = v36;
++(*qword_167ED7BC8)->unk_0;
}
}
LABEL_42:
++i;
}
if (*qword_167ED7BC8)
sub_14043D870(a1, 0);
a1->status = EPakStatus::PAK_STATUS_LOADED;
return true;
}
void Pak_StubInvalidAssetBinds(PakFile_t* const pak, PakSegmentDescriptor_t* const desc)
{
for (uint32_t i = 0; i < pak->GetAssetCount(); ++i)
{
PakAsset_t* const asset = &pak->memoryData.assetEntries[i];
pak->memoryData.ppAssetEntries[i] = asset;
const uint8_t assetTypeIndex = asset->HashTableIndexForAssetType();
desc->assetTypeCount[assetTypeIndex]++;
PakAssetBinding_t* const assetBinding = &g_pPakGlobals->m_assetBindings[assetTypeIndex];
if (assetBinding->type == PakAssetBinding_t::NONE)
{
assetBinding->extension = asset->magic;
assetBinding->version = asset->version;
assetBinding->description = "<unknown>";
assetBinding->loadAssetFunc = nullptr;
assetBinding->unloadAssetFunc = nullptr;
assetBinding->replaceAssetFunc = nullptr;
assetBinding->allocator = AlignedMemAlloc();
assetBinding->headerSize = asset->headerSize;
assetBinding->nativeClassSize = asset->headerSize;
assetBinding->headerAlignment = pak->memoryData.pageHeaders[asset->headPtr.index].pageAlignment;
assetBinding->type = PakAssetBinding_t::STUB;
}
// this is dev only because it could spam a lot on older paks
// which isn't much help to the average user that can't rebuild other people's paks
if (asset->version != assetBinding->version)
{
DevWarning(eDLL_T::RTECH,
"Unexpected asset version for \"%s\" (%.4s) asset with guid 0x%llX (asset %i in pakfile '%s'). Expected %i, found %i.\n",
assetBinding->description,
reinterpret_cast<char*>(&asset->magic),
asset->guid,
i, pak->GetName(),
assetBinding->version, asset->version
);
}
}
}
bool Pak_StartLoadingPak(PakLoadedInfo_t* const loadedInfo)
{
PakFile_t* const pakFile = loadedInfo->pakFile;
if (pakFile->memoryData.patchSrcSize && !Pak_ProcessPakFile(pakFile))
return false;
PakSegmentDescriptor_t pakDescriptor = {};
Pak_StubInvalidAssetBinds(pakFile, &pakDescriptor);
const uint32_t numAssets = pakFile->GetAssetCount();
if (pakFile->memoryData.pakHeader.patchIndex)
pakFile->firstPageIdx = pakFile->memoryData.patchDataHeader->m_pageCount;
sub_140442740(pakFile->memoryData.ppAssetEntries, &pakFile->memoryData.ppAssetEntries[numAssets], numAssets, pakFile);
// pak must have no more than PAK_MAX_SEGMENTS segments as otherwise we will overrun the above "segmentSizes" array
// and write to arbitrary locations on the stack
if (pakFile->GetSegmentCount() > PAK_MAX_SEGMENTS)
{
Error(eDLL_T::RTECH, EXIT_FAILURE, "Too many segments in pakfile '%s'. Max %i, found %i.\n", pakFile->GetName(), PAK_MAX_SEGMENTS, pakFile->GetSegmentCount());
return false;
}
Pak_AlignSegmentHeaders(pakFile, &pakDescriptor);
Pak_AlignSegments(pakFile, &pakDescriptor);
// allocate segment buffers with predetermined alignments; pages will be
// copied into here
for (int8_t i = 0; i < PAK_SEGMENT_BUFFER_TYPES; ++i)
{
if (pakDescriptor.segmentSizeForType[i])
loadedInfo->segmentBuffers[i] = AlignedMemAlloc()->Alloc(pakDescriptor.segmentSizeForType[i], pakDescriptor.segmentAlignmentForType[i]);
}
Pak_CopyPagesToSegments(pakFile, loadedInfo, &pakDescriptor);
const PakFileHeader_t& pakHdr = pakFile->GetHeader();
if (*g_pUseAssetStreamingSystem)
{
Pak_LoadStreamingData(loadedInfo);
}
const __int64 v106 = pakHdr.descriptorCount + 2 * (pakHdr.patchIndex + pakHdr.assetCount + 4ull * pakHdr.assetCount + pakHdr.virtualSegmentCount);
const __int64 patchDestOffset = pakHdr.GetTotalHeaderSize() + 2 * (pakHdr.patchIndex + 6ull * pakHdr.memPageCount + 4 * v106);
pakFile->dword14 = 1;
PakMemoryData_t& memoryData = pakFile->memoryData;
memoryData.patchSrcSize = pakFile->memoryData.qword2D0 - patchDestOffset;
memoryData.patchDstPtr = (char*)&pakHdr + patchDestOffset;
loadedInfo->status = EPakStatus::PAK_STATUS_LOAD_PAKHDR;
return true;
}
void V_PakParse::Detour(const bool bAttach) const
{
DetourSetup(&v_Pak_LoadAsync, &Pak_LoadAsync, bAttach);
DetourSetup(&v_Pak_UnloadAsync, &Pak_UnloadAsync, bAttach);
DetourSetup(&v_Pak_StartLoadingPak, &Pak_StartLoadingPak, bAttach);
DetourSetup(&v_Pak_ProcessPakFile, &Pak_ProcessPakFile, bAttach);
DetourSetup(&v_Pak_ResolveAssetRelations, &Pak_ResolveAssetRelations, bAttach);
DetourSetup(&v_Pak_ProcessAssets, &Pak_ProcessAssets, bAttach);
DetourSetup(&sub_14043E030, &Rebuild_14043E030, bAttach);
}
// Symbols taken from R2 dll's.
PakLoadFuncs_t* g_pakLoadApi = nullptr;