//=============================================================================//
//
// 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->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->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->assets[currentIndex].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->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
    uint32_t v6; // eax
    __int64 v7; // rax
    char v8; // r13
    uint64_t bytesProcessed; // r12
    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
    uint64_t dataOffset; // rax
    PakDecoder_t* decodeContext; // rbp
    uint64_t decompressedSize; // rax
    uint64_t compressedSize; // rdx
    uint64_t qword1D0; // rcx
    __int64 v28; // rax
    unsigned int numBitsRemaining; // r8d
    int v35; // ecx
    int v39; // r10d
    int v40; // r9d
    uint64_t v42; // ecx
    unsigned int v43; // r8d
    uint64_t v44; // r12d
    char byteBC; // r15
    __int64 v46; // rbp
    __int64 v47; // r8
    unsigned __int64 v48; // rbp
    unsigned __int64 qword8; // rax
    __int64 patchCount; // rcx
    char c; // al
    char* it; // rcx
    char* i; // rdx
    int v56; // edi
    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 != 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)
        {
            pakHeader = &pak->memoryData.pakHeader;
            v16 = (unsigned __int64)fileStream->unsigned_intB4 << 19;
            v17 = fileStream->byteBF++ & PAK_MAX_ASYNC_STREAMED_LOAD_REQUESTS_MASK;

            if (v8 == 2)
            {
                v18 = v16 & fileStream->bufferMask;
                fileStream->bytesStreamed = bytesProcessed + v16;
                pakHeader = (PakFileHeader_t*)&fileStream->buffer[v18];
            }

            fileStream->descriptors[v17].dataOffset = v16 + sizeof(PakFileHeader_t);
            fileStream->descriptors[v17].compressedSize = v16 + pakHeader->compressedSize;
            fileStream->descriptors[v17].decompressedSize = pakHeader->decompressedSize;
            fileStream->descriptors[v17].isCompressed = pakHeader->IsCompressed();
        }
        goto LABEL_17;
    }
LABEL_18:
    byte1F8 = pak->byte1F8;
    if (byte1F8 != fileStream->byteBF)
    {
        byte1FD = pak->byte1FD;
        const bool useZStream = pak->GetHeader().flags & PAK_HEADER_FLAGS_ZSTREAM;

        do
        {
            v22 = &fileStream->descriptors[byte1F8 & PAK_MAX_ASYNC_STREAMED_LOAD_REQUESTS_MASK];

            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->decompBuffer,
                    PAK_DECODE_IN_RING_BUFFER_MASK, PAK_DECODE_OUT_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);
            }
            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());
            }

            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 = pak->inputBytePos >> 19;

        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)
                {
                    if (v48 < qword8)
                        qword8 = v48;

                    fileStream->gap14[v47] = v_FS_ReadAsyncFile(
                        fileStream->fileHandle,
                        v62 - fileStream->qword0,
                        qword8 - v62,
                        &fileStream->buffer[v62 & fileStream->bufferMask],
                        0i64,
                        0i64,
                        4);

                    fileStream->gap94[v47] = 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].compressedSize)
                    Error(eDLL_T::RTECH, EXIT_FAILURE, "File \"%s\" appears truncated; read size: %zu < expected size: %zu.\n",
                        pakPatchPath, numBytesToProcess, pak->memoryData.patchHeaders[patchCount].compressedSize);

                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].compressedSize;
            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->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->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->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->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)
        {
            FourCCString_t assetMagic;
            FourCCToString(assetMagic, asset->magic);

            DevWarning(eDLL_T::RTECH,
                "Unexpected asset version for \"%s\" (%.4s) asset with guid 0x%llX (asset %u in pakfile '%s'). Expected %u, found %u.\n",
                assetBinding->description,
                assetMagic,
                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->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;