#include <Windows.h>
#include <strsafe.h>
#include <shlwapi.h>
#include <bcrypt.h>

#pragma comment(lib, "bcrypt.lib")

#include "Json.h"
#include "Asar.h"

#define ASAR_BLOCK_SIZE     (4 * 1024 * 1024)   // 4MB blocks
#define SHA256_HASH_SIZE    32

// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
// Static Functions Forwarded Declarations

static BOOL CreateDirectoryRecursive(LPCSTR pszPath);
static BOOL ExtractFilesFromJson(PJSON_VALUE pFiles, LPCSTR pszPrefix, PFILE_ENTRY* ppEntries);
static PFILE_ENTRY BuildFileList(LPCSTR pszDirectory, PDWORD pdwTotalSize);
static PJSON_VALUE BuildJsonFromFileList(PFILE_ENTRY pEntries);

// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==

static BOOL CreateDirectoryRecursive(LPCSTR pszPath)
{
    CHAR    szPath[MAX_PATH]    = { 0 };
    LPSTR   pszSlash            = NULL;
    DWORD   dwError             = 0x00;

    if (!pszPath || lstrlenA(pszPath) == 0)
        return FALSE;

    if (FAILED(StringCbCopyA(szPath, sizeof(szPath), pszPath)))
        return FALSE;

    pszSlash = strchr(szPath, '\\');

    while (pszSlash)
    {
        *pszSlash = '\0';

        if (lstrlenA(szPath) > 0 && lstrcmpA(szPath, ".") != 0)
        {
            if (!CreateDirectoryA(szPath, NULL))
            {
                dwError = GetLastError();

                if (dwError != ERROR_ALREADY_EXISTS)
                {
                    printf("[!] CreateDirectoryA Failed With Error: %lu\n", GetLastError());
                    return FALSE;
                }
            }
        }

        *pszSlash   = '\\';
        pszSlash    = strchr(pszSlash + 1, '\\');
    }

    // Create Final Directory
    if (!CreateDirectoryA(szPath, NULL))
    {
        dwError = GetLastError();

        if (dwError != ERROR_ALREADY_EXISTS)
        {
            printf("[!] CreateDirectoryA Failed With Error: %lu\n", GetLastError());
            return FALSE;
        }
    }

    return TRUE;
}

static BOOL ExtractFilesFromJson(PJSON_VALUE pFiles, LPCSTR pszPrefix, PFILE_ENTRY* ppEntries)
{
     if (!pFiles || pFiles->type != JSON_TYPE_OBJECT || !ppEntries)
        return FALSE;

    PJSON_PAIR      pPair           = NULL;
    PJSON_VALUE     pValue          = NULL, 
                    pFilesObj       = NULL,
                    pOffset         = NULL, 
                    pSize           = NULL,
                    pExecutable     = NULL,
                    pIntegrity      = NULL,     
                    pBlocksArray    = NULL; 
    PFILE_ENTRY     pEntry          = NULL;
    PFILE_ENTRY     pDirEntry       = NULL;
    LPSTR           pszFullPath     = NULL,
                    pszOffset       = NULL,
                    pszSize         = NULL;
    SIZE_T          nPrefixLen      = 0x00,
                    nKeyLen         = 0x00,
                    nFullPathSize   = 0x00;
    DWORD           dwOffset        = 0x00,
                    dwSize          = 0x00;
    BOOL            bResult         = FALSE;

   
    pPair = pFiles->pObject;

    while (pPair)
    {
        pszFullPath     = NULL;
        pValue          = pPair->pValue;
        nPrefixLen      = pszPrefix ? lstrlenA(pszPrefix) : 0;
        nKeyLen         = lstrlenA(pPair->pszKey);
        nFullPathSize   = nPrefixLen + nKeyLen + 2;

        if (!(pszFullPath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nFullPathSize)))
        {
            printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
            goto _END_OF_FUNC;
        }

        if (pszPrefix && nPrefixLen > 0)
        {
            StringCbCopyA(pszFullPath, nFullPathSize, pszPrefix);
            StringCbCatA(pszFullPath, nFullPathSize, "\\");
        }

        StringCbCatA(pszFullPath, nFullPathSize, pPair->pszKey);

        pFilesObj = JsonGetObject(pValue, "files");

        // It's a directory
        if (pFilesObj && pFilesObj->type == JSON_TYPE_OBJECT)
        {
            if (!(pDirEntry = (PFILE_ENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FILE_ENTRY))))
            {
                printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
                goto _END_OF_FUNC;
            }

            pDirEntry->pszRelativePath  = pszFullPath;
            pDirEntry->bIsDirectory     = TRUE;
            pDirEntry->pNext            = *ppEntries;
            *ppEntries                  = pDirEntry;
            
            // Ownership Transferred
            pszFullPath                 = NULL; 

            // Recurse into directory
            if (!ExtractFilesFromJson(pFilesObj, pDirEntry->pszRelativePath, ppEntries))
                goto _END_OF_FUNC;

            pDirEntry = NULL;
        }
        // It's a file
        else
        {
            pOffset     = JsonGetObject(pValue, "offset");
            pSize       = JsonGetObject(pValue, "size");
            pExecutable = JsonGetObject(pValue, "executable");
            pIntegrity  = JsonGetObject(pValue, "integrity");

            if (pOffset && pSize)
            {
                dwOffset    = 0x00;
                dwSize      = 0x00;

                // Offset can be string ("1234") or number
                if (pOffset->type == JSON_TYPE_STRING)
                {
                    pszOffset = JsonGetString(pOffset);

                    if (pszOffset && *pszOffset)
                        dwOffset = (DWORD)_atoi64(pszOffset);
                }
                else if (pOffset->type == JSON_TYPE_NUMBER)
                {
                    dwOffset = (DWORD)JsonGetNumber(pOffset);
                }

                // Size is normally a number, but accept string too
                if (pSize->type == JSON_TYPE_NUMBER)
                {
                    dwSize = (DWORD)JsonGetNumber(pSize);
                }
                else if (pSize->type == JSON_TYPE_STRING)
                {
                    pszSize = JsonGetString(pSize);

                    if (pszSize && *pszSize)
                        dwSize = (DWORD)_atoi64(pszSize);
                }

                if (!(pEntry = (PFILE_ENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FILE_ENTRY))))
                {
                    printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
                    goto _END_OF_FUNC;
                }

                pEntry->pszRelativePath = pszFullPath;
                pEntry->dwOffset        = dwOffset;
                pEntry->dwSize          = dwSize;
                pEntry->bIsDirectory    = FALSE;
                pEntry->bIsExecutable   = FALSE;

                if (pExecutable)
                {
                    if (pExecutable->type == JSON_TYPE_BOOLEAN)
                        pEntry->bIsExecutable = pExecutable->bValue;
                    else if (pExecutable->type == JSON_TYPE_NUMBER)
                        pEntry->bIsExecutable = (JsonGetNumber(pExecutable) != 0);
                }

                // Parse integrity info (if present)
                if (pIntegrity && pIntegrity->type == JSON_TYPE_OBJECT)
                {
                    PJSON_VALUE pHashValue   = JsonGetObject(pIntegrity, "hash");
                    PJSON_VALUE pBlocksValue = JsonGetObject(pIntegrity, "blocks");

                    // Full-file hash
                    if (pHashValue && pHashValue->type == JSON_TYPE_STRING)
                    {
                        LPSTR  pszHashSrc = JsonGetString(pHashValue);
                        SIZE_T nHashLen   = pszHashSrc ? lstrlenA(pszHashSrc) : 0;

                        if (pszHashSrc && nHashLen > 0)
                        {
                            if ((pEntry->pszHash = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nHashLen + 1)))
                            {
                                StringCbCopyA(pEntry->pszHash, nHashLen + 1, pszHashSrc);
                            }
                        }
                    }

                    // Block hashes array
                    if (pBlocksValue && pBlocksValue->type == JSON_TYPE_ARRAY && pBlocksValue->pArray)
                    {
                        DWORD       dwBlockCount = 0x00;
                        PJSON_VALUE pElem        = pBlocksValue->pArray;

                        while (pElem) { dwBlockCount++; pElem = pElem->pNext; }

                        if (dwBlockCount > 0)
                        {
                            if ((pEntry->ppszBlocks = (LPSTR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBlockCount * sizeof(LPSTR))))
                            {
                                pEntry->dwBlockCount    = dwBlockCount;
                                pElem                   = pBlocksValue->pArray;

                                for (DWORD i = 0; i < dwBlockCount && pElem; i++)
                                {
                                    if (pElem->type == JSON_TYPE_STRING)
                                    {
                                        LPSTR  pszBlockSrc = JsonGetString(pElem);
                                        SIZE_T nBlockLen   = pszBlockSrc ? lstrlenA(pszBlockSrc) : 0;

                                        if (pszBlockSrc && nBlockLen > 0)
                                        {
                                            if ((pEntry->ppszBlocks[i] = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nBlockLen + 1)))
                                            {
                                                StringCbCopyA(pEntry->ppszBlocks[i], nBlockLen + 1, pszBlockSrc);
                                            }
                                        }
                                    }
                                    
                                    pElem = pElem->pNext;
                                }
                            }
                        }
                    }
                }

                pEntry->pNext   = *ppEntries;
                *ppEntries      = pEntry;

                // Ownership Transferred
                pszFullPath     = NULL;
                pEntry          = NULL;
            }
            else
            {
                HeapFree(GetProcessHeap(), 0, pszFullPath);
                pszFullPath = NULL;
            }
        }

        pPair = pPair->pNext;
    }

    bResult = TRUE;

_END_OF_FUNC:
    if (pszFullPath)
        HeapFree(GetProcessHeap(), 0, pszFullPath);

    return bResult;
}

static VOID FreeFileEntries(PFILE_ENTRY pEntries)
{
    while (pEntries)
    {
        PFILE_ENTRY pNext = pEntries->pNext;

        if (pEntries->pszRelativePath)
            HeapFree(GetProcessHeap(), 0, pEntries->pszRelativePath);

        if (pEntries->pszHash)
            HeapFree(GetProcessHeap(), 0, pEntries->pszHash);

        if (pEntries->ppszBlocks)
        {
            for (DWORD i = 0; i < pEntries->dwBlockCount; i++)
            {
                if (pEntries->ppszBlocks[i])
                    HeapFree(GetProcessHeap(), 0, pEntries->ppszBlocks[i]);
            }
            HeapFree(GetProcessHeap(), 0, pEntries->ppszBlocks);
        }

        HeapFree(GetProcessHeap(), 0, pEntries);
        pEntries = pNext;
    }
}

static PFILE_ENTRY BuildFileList(LPCSTR pszDirectory, PDWORD pdwTotalSize)
{
    if (!pszDirectory) return NULL;

    HANDLE              hFind                   = INVALID_HANDLE_VALUE;
    WIN32_FIND_DATAA    FindData                = { 0 };
    PFILE_ENTRY         pEntries                = NULL,
                        pSubEntries             = NULL,
                        pEntry                  = NULL,
                        pLast                   = NULL;
    CHAR                szSearchPath[MAX_PATH]  = { 0 };
    CHAR                szFullPath[MAX_PATH]    = { 0 };
    DWORD               dwTotalSize             = 0x00;
    SIZE_T              nPathLen                = 0x00;
    LPCSTR              pszExt                  = NULL;

    if (FAILED(StringCbPrintfA(szSearchPath, sizeof(szSearchPath), "%s\\*", pszDirectory)))
        return NULL;

    if ((hFind = FindFirstFileA(szSearchPath, &FindData)) == INVALID_HANDLE_VALUE)
        return NULL;

    do
    {
        if (lstrcmpA(FindData.cFileName, ".") == 0 || lstrcmpA(FindData.cFileName, "..") == 0)
            continue;

        if (FAILED(StringCbPrintfA(szFullPath, sizeof(szFullPath), "%s\\%s", pszDirectory, FindData.cFileName)))
            continue;

        if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            // Recurse into subdirectory
            pSubEntries = BuildFileList(szFullPath, &dwTotalSize);

            if (pSubEntries)
            {
                pLast = pSubEntries;

                while (pLast->pNext)
                    pLast = pLast->pNext;

                pLast->pNext    = pEntries;
                pEntries        = pSubEntries;
                pSubEntries     = NULL;
            }
        }
        else
        {
            // Add file entry
            if (!(pEntry = (PFILE_ENTRY)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(FILE_ENTRY))))
            {
                printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
                continue;
            }

            nPathLen = lstrlenA(szFullPath);

            if (!(pEntry->pszRelativePath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nPathLen + 1)))
            {
                printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
                HeapFree(GetProcessHeap(), 0, pEntry);
                pEntry = NULL;
                continue;
            }

            StringCbCopyA(pEntry->pszRelativePath, nPathLen + 1, szFullPath);

            pEntry->dwSize          = FindData.nFileSizeLow;
            pEntry->bIsDirectory    = FALSE;
            pEntry->dwOffset        = dwTotalSize;

            dwTotalSize += pEntry->dwSize;

            // Check if executable
            pszExt = PathFindExtensionA(FindData.cFileName);

            if (pszExt && (lstrcmpiA(pszExt, ".exe") == 0 ||
                           lstrcmpiA(pszExt, ".dll") == 0 ||
                           lstrcmpiA(pszExt, ".bat") == 0 ||
                           lstrcmpiA(pszExt, ".cmd") == 0)
            ) {
                pEntry->bIsExecutable = TRUE;
            }

            // Append to list
            if (!pEntries)
            {
                pEntries = pEntry;
            }
            else
            {
                pLast = pEntries;

                while (pLast->pNext)
                    pLast = pLast->pNext;

                pLast->pNext = pEntry;
            }

            pEntry = NULL;
        }

    } while (FindNextFileA(hFind, &FindData));

    if (hFind != INVALID_HANDLE_VALUE)
        FindClose(hFind);

    if (pdwTotalSize)
        *pdwTotalSize += dwTotalSize;

    return pEntries;
}

static PJSON_VALUE BuildJsonFromFileList(PFILE_ENTRY pEntries)
{
    PJSON_VALUE     pRoot           = NULL, pFiles          = NULL,
                    pCurrent        = NULL, pDir            = NULL,
                    pDirFiles       = NULL, pFileObj        = NULL;
    PFILE_ENTRY     pEntry          = NULL;
    LPSTR           pszCopy         = NULL,
                    pszToken        = NULL,
                    pszNext         = NULL,
                    pszContext      = NULL;
    SIZE_T          nPathLen        = 0x00;
    CHAR            szOffset[32]    = { 0 };

    if (!(pRoot = JsonCreateObject()))
        goto _END_OF_FUNC;

    if (!(pFiles = JsonCreateObject()))
        goto _END_OF_FUNC;

    JsonObjectAdd(pRoot, "files", pFiles);

    pEntry = pEntries;

    while (pEntry)
    {
        if (!pEntry->bIsDirectory && pEntry->pszRelativePath)
        {
            pCurrent    = pFiles;
            pszContext  = NULL;
            nPathLen    = lstrlenA(pEntry->pszRelativePath);

            if (!(pszCopy = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nPathLen + 1)))
            {
                printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
                pEntry = pEntry->pNext;
                continue;
            }

            StringCbCopyA(pszCopy, nPathLen + 1, pEntry->pszRelativePath);

            // Split path and create nested objects
            pszToken    = strtok_s(pszCopy, "\\", &pszContext);
            pszNext     = strtok_s(NULL, "\\", &pszContext);

            while (pszToken && pszNext)
            {
                pDir = JsonGetObject(pCurrent, pszToken);

                if (!pDir)
                {
                    if (!(pDir = JsonCreateObject()))
                        break;

                    if (!(pDirFiles = JsonCreateObject()))
                    {
                        JsonFree(pDir);
                        pDir = NULL;
                        break;
                    }

                    JsonObjectAdd(pDir, "files", pDirFiles);
                    JsonObjectAdd(pCurrent, pszToken, pDir);

                    // Ownership Transferred
                    pDirFiles = NULL; 
                }

                pCurrent    = JsonGetObject(pDir, "files");
                pszToken    = pszNext;
                pszNext     = strtok_s(NULL, "\\", &pszContext);

                pDir = NULL;
            }

            // Add file entry
            if (pszToken && pCurrent)
            {
                if ((pFileObj = JsonCreateObject()))
                {
                    // Size as number
                    JsonObjectAdd(pFileObj, "size", JsonCreateNumber(pEntry->dwSize));

                    // Offset as string (ASAR style)
                    StringCbPrintfA(szOffset, sizeof(szOffset), "%lu", (ULONG)pEntry->dwOffset);
                    JsonObjectAdd(pFileObj, "offset", JsonCreateString(szOffset));

                    // Executable as boolean
                    if (pEntry->bIsExecutable)
                        JsonObjectAdd(pFileObj, "executable", JsonCreateBoolean(TRUE));

                    // Integrity object
                    if (pEntry->pszHash)
                    {
                        PJSON_VALUE pIntegrity  = JsonCreateObject();
                        PJSON_VALUE pBlocks     = JsonCreateArray();

                        if (pIntegrity && pBlocks)
                        {
                            JsonObjectAdd(pIntegrity, "algorithm", JsonCreateString("SHA256"));
                            JsonObjectAdd(pIntegrity, "hash", JsonCreateString(pEntry->pszHash));
                            JsonObjectAdd(pIntegrity, "blockSize", JsonCreateNumber(ASAR_BLOCK_SIZE));

                            for (DWORD b = 0; b < pEntry->dwBlockCount; b++)
                            {
                                if (pEntry->ppszBlocks[b])
                                    JsonArrayAdd(pBlocks, JsonCreateString(pEntry->ppszBlocks[b]));
                            }

                            JsonObjectAdd(pIntegrity, "blocks", pBlocks);
                            JsonObjectAdd(pFileObj, "integrity", pIntegrity);
                        }
                        else
                        {
                            if (pIntegrity) JsonFree(pIntegrity);
                            if (pBlocks) JsonFree(pBlocks);
                        }
                    }


                    JsonObjectAdd(pCurrent, pszToken, pFileObj);

                    // Ownership Transferred
                    pFileObj = NULL;
                }
            }

            HeapFree(GetProcessHeap(), 0, pszCopy);
            pszCopy = NULL;
        }

        pEntry = pEntry->pNext;
    }

    return pRoot;

_END_OF_FUNC:
    if (pRoot)
        JsonFree(pRoot);
    else if (pFiles)
        JsonFree(pFiles);

    return NULL;
}

static LPSTR BytesToHex(LPBYTE pbData, DWORD dwBytesLength)
{
    static LPCSTR   pszHexChars     = "0123456789abcdef";
    LPSTR           pszOutput       = NULL;

    if (!(pszOutput = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (dwBytesLength * 2) + 1)))
        return NULL;

    for (int i = 0; i < dwBytesLength; i++)
    {
        pszOutput[i * 2]        = pszHexChars[(pbData[i] >> 4) & 0x0F];
        pszOutput[i * 2 + 1]    = pszHexChars[pbData[i] & 0x0F];
    }

    return pszOutput;
}

static LPSTR ComputeSha256Hex(LPBYTE pbData, DWORD dwLength)
{
    BCRYPT_ALG_HANDLE   hAlgorithm                  = NULL;
    BCRYPT_HASH_HANDLE  hHash                       = NULL;
    BYTE                rgHash[SHA256_HASH_SIZE]    = { 0 };
    LPSTR               pszHex                      = NULL;
    NTSTATUS            ntStatus                    = 0x00;

    if (!BCRYPT_SUCCESS((ntStatus = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_SHA256_ALGORITHM, NULL, 0))))
    {
        printf("[!] BCryptOpenAlgorithmProvider Failed With Error: 0x%0.8X \n", ntStatus);
        goto _END_OF_FUNC;
    }

    if (!BCRYPT_SUCCESS((ntStatus = BCryptCreateHash(hAlgorithm, &hHash, NULL, 0, NULL, 0, 0))))
    {
        printf("[!] BCryptCreateHash Failed With Error: 0x%0.8X \n", ntStatus);
        goto _END_OF_FUNC;
    }

    if (!BCRYPT_SUCCESS((ntStatus = BCryptHashData(hHash, pbData, dwLength, 0))))
    {
        printf("[!] BCryptHashData Failed With Error: 0x%0.8X \n", ntStatus);
        goto _END_OF_FUNC;
    }

    if (!BCRYPT_SUCCESS((ntStatus = BCryptFinishHash(hHash, rgHash, SHA256_HASH_SIZE, 0))))
    {
        printf("[!] BCryptFinishHash Failed With Error: 0x%0.8X \n", ntStatus);
        goto _END_OF_FUNC;
    }

    pszHex = BytesToHex(rgHash, SHA256_HASH_SIZE);

_END_OF_FUNC:
    if (hHash)
        BCryptDestroyHash(hHash);
    if (hAlgorithm)
        BCryptCloseAlgorithmProvider(hAlgorithm, 0);
    return pszHex;
}

static BOOL ComputeFileIntegrity(LPCSTR pszFilePath, PFILE_ENTRY pEntry)
{
    HANDLE              hFile           = INVALID_HANDLE_VALUE;
    LPBYTE              pbFileData      = NULL;
    DWORD               dwBytesRead     = 0x00,
                        dwBlockCount    = 0x00;
    BOOL                bResult         = FALSE;

    if ((hFile = CreateFileA(pszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
    {
        printf("[!] CreateFileA Failed With Error: %lu\n", GetLastError());
        return FALSE;
    }

    // Empty file: still gets one block with hash of empty data
    if (pEntry->dwSize == 0)
    {
        if (!(pEntry->pszHash = ComputeSha256Hex((LPBYTE)"", 0)))
            goto _END_OF_FUNC;

        pEntry->dwBlockCount = 1;

        if (!(pEntry->ppszBlocks = (LPSTR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(LPSTR))))
        {
            printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
            goto _END_OF_FUNC;
        }
        
        if (!(pEntry->ppszBlocks[0] = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lstrlenA(pEntry->pszHash) + 1)))
        {
            printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
            goto _END_OF_FUNC;
        }
        
        StringCbCopyA(pEntry->ppszBlocks[0], lstrlenA(pEntry->pszHash) + 1, pEntry->pszHash);
        
        bResult = TRUE;
        
        goto _END_OF_FUNC;
    }

    if (!(pbFileData = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, pEntry->dwSize)))
        goto _END_OF_FUNC;

    if (!ReadFile(hFile, pbFileData, pEntry->dwSize, &dwBytesRead, NULL) || dwBytesRead != pEntry->dwSize)
    {
        printf("[!] ReadFile Failed With Error: %lu\n[i] Read %ld Of %ld Bytes\n", GetLastError(), dwBytesRead, pEntry->dwSize);
        goto _END_OF_FUNC;
    }

    if (!(pEntry->pszHash = ComputeSha256Hex(pbFileData, pEntry->dwSize)))
        goto _END_OF_FUNC;

    dwBlockCount            = (pEntry->dwSize + ASAR_BLOCK_SIZE - 1) / ASAR_BLOCK_SIZE;
    pEntry->dwBlockCount    = dwBlockCount;

    if (!(pEntry->ppszBlocks = (LPSTR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBlockCount * sizeof(LPSTR))))
    {
        printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    for (int i = 0; i < dwBlockCount; i++)
    {
        DWORD dwBlockOffset = i * ASAR_BLOCK_SIZE;
        DWORD dwBlockSize   = min(ASAR_BLOCK_SIZE, pEntry->dwSize - dwBlockOffset);

        if (!(pEntry->ppszBlocks[i] = ComputeSha256Hex(pbFileData + dwBlockOffset, dwBlockSize)))
            goto _END_OF_FUNC;
    }

    bResult = TRUE;

_END_OF_FUNC:
    if (pbFileData)
        HeapFree(GetProcessHeap(), 0, pbFileData);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);
    return bResult;
}

// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==

BOOL UnpackAsarFile(IN LPCSTR pszAsarPath, IN LPCSTR pszOutputDir)
{
    if (!pszAsarPath || !pszOutputDir) return FALSE;
    
    BOOL            bResult                 = FALSE;
    HANDLE          hFile                   = INVALID_HANDLE_VALUE,
                    hOutFile                = INVALID_HANDLE_VALUE;
    LPBYTE          pbAsarData              = NULL,
                    pbHeaderPickle          = NULL,
                    pbJsonData              = NULL;
    LPSTR           pszJson                 = NULL;
    PJSON_VALUE     pJsonRoot               = NULL,
                    pFiles                  = NULL;
    PFILE_ENTRY     pEntries                = NULL,
                    pEntry                  = NULL;
    LARGE_INTEGER   liFileSize              = { 0 };
    DWORD           dwBytesRead             = 0x00,     dwBytesWritten      = 0x00,
                    dwSizePayload           = 0x00,     dwHeaderSize        = 0x00,
                    dwHeaderPayload         = 0x00,     dwJsonLen           = 0x00,
                    dwDataOffset            = 0x00,     dwFileCount         = 0x00,
                    dwDirCount              = 0x00,     dwError             = 0x00;
    CHAR            szOutputPath[MAX_PATH]  = { 0 },
                    szParentDir[MAX_PATH]   = { 0 };


    if ((hFile = CreateFileA(pszAsarPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
    {
        printf("[!] CreateFileA Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (!GetFileSizeEx(hFile, &liFileSize))
    {
        printf("[!] GetFileSizeEx Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (!(pbAsarData = (LPBYTE)VirtualAlloc(NULL, (SIZE_T)liFileSize.QuadPart, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)))
    {
        printf("[!] VirtualAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (!ReadFile(hFile, pbAsarData, (DWORD)liFileSize.QuadPart, &dwBytesRead, NULL) || dwBytesRead != (DWORD)liFileSize.QuadPart)
    {
        printf("[!] ReadFile Failed With Error: %lu\n[i] Read %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesRead, (DWORD)liFileSize.QuadPart);
        goto _END_OF_FUNC;
    }

    if (dwBytesRead < 8)
    {
        printf("[-] File Too Small To Contain A Valid ASAR Header\n");
        goto _END_OF_FUNC;
    }

    // First 8 bytes: size pickle
    dwSizePayload   = *(DWORD*)(pbAsarData + 0);  // Should be 4
    dwHeaderSize    = *(DWORD*)(pbAsarData + 4);  // Size of header pickle

    if (dwSizePayload != 4)
    {
        printf("[-] Invalid Size Pickle Payload: %lu (Expected 4)\n", dwSizePayload);
        goto _END_OF_FUNC;
    }

    // Header pickle starts at offset 8
    pbHeaderPickle  = pbAsarData + 8;
    dwHeaderPayload = *(DWORD*)(pbHeaderPickle + 0);
    dwJsonLen       = *(DWORD*)(pbHeaderPickle + 4);
    pbJsonData      = pbHeaderPickle + 8;

    // Extract JSON header
    if (!(pszJson = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwJsonLen + 1)))
    {
        printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    RtlCopyMemory(pszJson, pbJsonData, dwJsonLen);

    // Parse JSON
    if (!(pJsonRoot = JsonParse(pszJson)))
    {
        printf("[-] Failed To Parse JSON\n");
        goto _END_OF_FUNC;
    }

    // Get files object
    if (!(pFiles = JsonGetObject(pJsonRoot, "files")))
    {
        printf("[-] No Files Object in JSON\n");
        goto _END_OF_FUNC;
    }

    // Extract file entries
    if (!ExtractFilesFromJson(pFiles, NULL, &pEntries))
    {
        printf("[-] Failed To Extract File Entries\n");
        goto _END_OF_FUNC;
    }

    // Create output directory
    if (!CreateDirectoryA(pszOutputDir, NULL))
    {
        dwError = GetLastError();

        if (dwError != ERROR_ALREADY_EXISTS)
        {
            printf("[!] CreateDirectoryA Failed With Error: %lu\n", GetLastError());
            goto _END_OF_FUNC;
        }
    }

    // Extract files
    dwDataOffset    = 8 + dwHeaderSize;
    pEntry          = pEntries;

    while (pEntry)
    {
        if (FAILED(StringCbPrintfA(szOutputPath, sizeof(szOutputPath), "%s\\%s", pszOutputDir, pEntry->pszRelativePath)))
        {
            pEntry = pEntry->pNext;
            continue;
        }

        if (pEntry->bIsDirectory)
        {
#ifdef VERBOSE_OUTPUT
            printf("[v] Creating Directory: %s\n", pEntry->pszRelativePath);
#endif 
            CreateDirectoryRecursive(szOutputPath);
            dwDirCount++;
        }
        else
        {
#ifdef VERBOSE_OUTPUT
            printf("[v] Extracting: %s (%lu Bytes)\n", pEntry->pszRelativePath, pEntry->dwSize);
#endif 
            // Ensure parent directory exists
            StringCbCopyA(szParentDir, sizeof(szParentDir), szOutputPath);
            PathRemoveFileSpecA(szParentDir);
            CreateDirectoryRecursive(szParentDir);

            // Write file
            if ((hOutFile = CreateFileA(szOutputPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE)
            {
                if (pEntry->dwSize > 0)
                {
                    if (!WriteFile(hOutFile, pbAsarData + dwDataOffset + pEntry->dwOffset, pEntry->dwSize, &dwBytesWritten, NULL))
                    {
                        printf("[!] WriteFile Failed With Error: %lu\n[i] Wrote %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesWritten, pEntry->dwSize);
                    }
                }

                CloseHandle(hOutFile);
                hOutFile = INVALID_HANDLE_VALUE;

                dwFileCount++;
            }
            else
            {
                printf("[!] CreateFileA Failed To Create File '%s' With Error: %lu\n", szOutputPath, GetLastError());
            }
        }

        pEntry = pEntry->pNext;
    }

    printf("[+] Unpacking Completed Successfully!\n");
    printf("[i] Directories Created: %lu\n", dwDirCount);
    printf("[i] Files Extracted: %lu\n", dwFileCount);

    bResult = TRUE;

_END_OF_FUNC:
    FreeFileEntries(pEntries);
    if (pJsonRoot)
        JsonFree(pJsonRoot);
    if (pszJson)
        HeapFree(GetProcessHeap(), 0, pszJson);
    if (pbAsarData)
        VirtualFree(pbAsarData, 0, MEM_RELEASE);
    if (hOutFile != INVALID_HANDLE_VALUE)
        CloseHandle(hOutFile);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);
    return bResult;
}

BOOL PackAsarFile(IN LPCSTR pszInputDir, IN LPCSTR pszAsarPath)
{
    if (!pszInputDir || !pszAsarPath) return FALSE;

    BOOL            bResult                 = FALSE;
    HANDLE          hFile                   = INVALID_HANDLE_VALUE,
                    hInFile                 = INVALID_HANDLE_VALUE;
    LPBYTE          pbHeaderPickle          = NULL,
                    pbBuffer                = NULL;
    LPSTR           pszJson                 = NULL,
                    pszNew                  = NULL;
    PJSON_VALUE     pJsonRoot               = NULL;
    PFILE_ENTRY     pEntries                = NULL,
                    pEntry                  = NULL;
    DWORD           dwBytesWritten          = 0x00,     dwBytesRead         = 0x00,
                    dwTotalDataSize         = 0x00,     dwFileCount         = 0x00,
                    dwJsonLen               = 0x00,     dwPadding           = 0x00,
                    dwHeaderPayloadSize     = 0x00,     dwHeaderSize        = 0x00,
                    dwCurrentOffset         = 0x00;
    SIZE_T          nBaseLen                = 0x00,
                    nNewPathLen             = 0x00;
    CHAR            szFullPath[MAX_PATH]    = { 0 };
    BYTE            rgSizePickle[8]         = { 0 };

    // Build file list
    if (!(pEntries = BuildFileList(pszInputDir, &dwTotalDataSize)))
    {
        printf("[-] No Files Found In Directory\n");
        goto _END_OF_FUNC;
    }

    // Strip base directory from paths
    nBaseLen    = lstrlenA(pszInputDir) + 1;  // +1 for backslash
    pEntry      = pEntries;

    while (pEntry)
    {
        if (pEntry->pszRelativePath)
        {
            nNewPathLen = lstrlenA(pEntry->pszRelativePath + nBaseLen) + 1;

            if ((pszNew = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nNewPathLen)))
            {
                StringCbCopyA(pszNew, nNewPathLen, pEntry->pszRelativePath + nBaseLen);
                HeapFree(GetProcessHeap(), 0, pEntry->pszRelativePath);
                pEntry->pszRelativePath = pszNew;
                pszNew = NULL;
            }
        }

        if (!pEntry->bIsDirectory)
            dwFileCount++;

        pEntry = pEntry->pNext;
    }


    // Compute integrity hashes for all files
    pEntry = pEntries;

    while (pEntry)
    {
        if (!pEntry->bIsDirectory && pEntry->pszRelativePath)
        {
            if (SUCCEEDED(StringCbPrintfA(szFullPath, sizeof(szFullPath), "%s\\%s", pszInputDir, pEntry->pszRelativePath)))
            {
                if (!ComputeFileIntegrity(szFullPath, pEntry))
                {
                    printf("[!] Failed To Compute Integrity For: %s\n", pEntry->pszRelativePath);
                }
            }
        }

        pEntry = pEntry->pNext;
    }


    // Recalculate offsets in the order we'll write files
    dwCurrentOffset = 0x00;
    pEntry          = pEntries;

    while (pEntry)
    {
        if (!pEntry->bIsDirectory)
        {
            pEntry->dwOffset    = dwCurrentOffset;
            dwCurrentOffset     += pEntry->dwSize;
        }

        pEntry = pEntry->pNext;
    }

    dwTotalDataSize = dwCurrentOffset;

    printf("[i] Found %lu Files, Total Size: %lu Bytes\n", dwFileCount, dwTotalDataSize);

    // Build JSON structure
    if (!(pJsonRoot = BuildJsonFromFileList(pEntries)))
    {
        printf("[-] Failed To Build JSON Structure\n");
        goto _END_OF_FUNC;
    }

    // Convert to JSON string
    if (!(pszJson = JsonStringify(pJsonRoot, &dwJsonLen)))
    {
        printf("[-] Failed To Stringify JSON\n");
        goto _END_OF_FUNC;
    }

    // Create output ASAR file
    if ((hFile = CreateFileA(pszAsarPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
    {
        printf("[!] CreateFileA Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    // Calculate padding for 4-byte alignment
    dwPadding           = (4 - (dwJsonLen & 3)) & 3;
    dwHeaderPayloadSize = 4 + dwJsonLen + dwPadding;
    dwHeaderSize        = 4 + dwHeaderPayloadSize;

    // Build size pickle (first 8 bytes of file)
    *(DWORD*)(rgSizePickle + 0) = 4;            // Payload size (always 4)
    *(DWORD*)(rgSizePickle + 4) = dwHeaderSize; // Header pickle size

    // Build header pickle
    if (!(pbHeaderPickle = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwHeaderSize)))
    {
        printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    *(DWORD*)(pbHeaderPickle + 0) = dwHeaderPayloadSize;  // Payload size
    *(DWORD*)(pbHeaderPickle + 4) = dwJsonLen;            // JSON string length
    
    RtlCopyMemory(pbHeaderPickle + 8, pszJson, dwJsonLen);

    // Write the size pickle (8 bytes)
    if (!WriteFile(hFile, rgSizePickle, 8, &dwBytesWritten, NULL))
    {
        printf("[!] WriteFile Failed With Error: %lu\n[i] Wrote %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesWritten, 8);
        goto _END_OF_FUNC;
    }

    // Write the header pickle
    if (!WriteFile(hFile, pbHeaderPickle, dwHeaderSize, &dwBytesWritten, NULL))
    {
        printf("[!] WriteFile Failed With Error: %lu\n[i] Wrote %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesWritten, dwHeaderSize);
        goto _END_OF_FUNC;
    }

    // Write file contents
    pEntry = pEntries;

    while (pEntry)
    {
        if (!pEntry->bIsDirectory && pEntry->dwSize > 0)
        {
            if (FAILED(StringCbPrintfA(szFullPath, sizeof(szFullPath), "%s\\%s", pszInputDir, pEntry->pszRelativePath)))
            {
                pEntry = pEntry->pNext;
                continue;
            }

#ifdef VERBOSE_OUTPUT
            printf("[v] Packing: %s (Offset: %lu)\n", pEntry->pszRelativePath, pEntry->dwOffset);
#endif

            if ((hInFile = CreateFileA(szFullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE)
            {
                if ((pbBuffer = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pEntry->dwSize)))
                {
                    if (ReadFile(hInFile, pbBuffer, pEntry->dwSize, &dwBytesRead, NULL))
                    {
                        if (!WriteFile(hFile, pbBuffer, pEntry->dwSize, &dwBytesWritten, NULL))
                        {
                            printf("[!] WriteFile Failed With Error: %lu\n[i] Wrote %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesWritten, pEntry->dwSize);
                        }
                    }

                    HeapFree(GetProcessHeap(), 0, pbBuffer);
                    pbBuffer = NULL;
                }

                CloseHandle(hInFile);
                hInFile = INVALID_HANDLE_VALUE;
            }
            else
            {
                printf("[!] CreateFileA Failed To Open '%s' With Error: %lu\n", szFullPath, GetLastError());
            }
        }

        pEntry = pEntry->pNext;
    }

    printf("[+] Packing Completed Successfully!\n");
    printf("[i] Files Packed: %lu\n", dwFileCount);
    printf("[i] ASAR Size: %lu Bytes\n", 8 + dwHeaderSize + dwTotalDataSize);

    bResult = TRUE;

_END_OF_FUNC:
    FreeFileEntries(pEntries);
    if (pbHeaderPickle)
        HeapFree(GetProcessHeap(), 0, pbHeaderPickle);
    if (pJsonRoot)
        JsonFree(pJsonRoot);
    if (pszJson)
        HeapFree(GetProcessHeap(), 0, pszJson);
    if (pbBuffer)
        HeapFree(GetProcessHeap(), 0, pbBuffer);
    if (hInFile != INVALID_HANDLE_VALUE)
        CloseHandle(hInFile);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);
    return bResult;
}

BOOL VerifyAsarFile(IN LPCSTR pszAsarPath)
{
    if (!pszAsarPath) return FALSE;

    HANDLE          hFile                   = INVALID_HANDLE_VALUE;
    LPBYTE          pbAsarData              = NULL,
                    pbHeaderPickle          = NULL,
                    pbJsonData              = NULL;
    LPSTR           pszJson                 = NULL,
                    pszComputedHash         = NULL,
                    pszBlockHash            = NULL;
    PJSON_VALUE     pJsonRoot               = NULL,
                    pFiles                  = NULL;
    PFILE_ENTRY     pEntries                = NULL,
                    pEntry                  = NULL;
    LARGE_INTEGER   liFileSize              = { 0 };
    DWORD           dwBytesRead             = 0x00, dwSizePayload           = 0x00,
                    dwHeaderSize            = 0x00, dwJsonLen               = 0x00,
                    dwDataOffset            = 0x00, dwFileCount             = 0x00,
                    dwDirCount              = 0x00, dwFilesWithIntegrity    = 0x00,
                    dwFilesMissingIntegrity = 0x00, dwFilesHashOk           = 0x00,
                    dwFilesHashMismatch     = 0x00, dwFilesBlocksMismatch   = 0x00,
                    dwOffsetErrors          = 0x00;
    BOOL            bResult                 = FALSE;


    if ((hFile = CreateFileA(pszAsarPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
    {
        printf("[!] CreateFileA Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (!GetFileSizeEx(hFile, &liFileSize))
    {
        printf("[!] GetFileSizeEx Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (liFileSize.QuadPart < 8)
    {
        printf("[-] File Too Small To Contain A Valid ASAR Header\n");
        goto _END_OF_FUNC;
    }

    if (!(pbAsarData = (LPBYTE)VirtualAlloc(NULL, (SIZE_T)liFileSize.QuadPart, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)))
    {
        printf("[!] VirtualAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    if (!ReadFile(hFile, pbAsarData, (DWORD)liFileSize.QuadPart, &dwBytesRead, NULL) || dwBytesRead != (DWORD)liFileSize.QuadPart)
    {
        printf("[!] ReadFile Failed With Error: %lu\n[i] Read %ld Instead Of %ld Bytes\n", GetLastError(), dwBytesRead, (DWORD)liFileSize.QuadPart);
        goto _END_OF_FUNC;
    }

    dwSizePayload   = *(DWORD*)(pbAsarData + 0);
    dwHeaderSize    = *(DWORD*)(pbAsarData + 4);

    if (dwSizePayload != 4)
    {
        printf("[-] Invalid Size Pickle Payload: %lu (Expected 4)\n", dwSizePayload);
        goto _END_OF_FUNC;
    }

    if ((ULONGLONG)8 + dwHeaderSize > (ULONGLONG)liFileSize.QuadPart)
    {
        printf("[-] Header Size (%lu) Exceeds File Size\n", dwHeaderSize);
        goto _END_OF_FUNC;
    }

    pbHeaderPickle  = pbAsarData + 8;
    dwJsonLen       = *(DWORD*)(pbHeaderPickle + 4);
    pbJsonData      = pbHeaderPickle + 8;

    if ((ULONGLONG)8 + dwJsonLen > (ULONGLONG)dwHeaderSize)
    {
        printf("[-] JSON Length (%lu) Exceeds Header Size\n", dwJsonLen);
        goto _END_OF_FUNC;
    }

    if (!(pszJson = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwJsonLen + 1)))
    {
        printf("[!] HeapAlloc Failed With Error: %lu\n", GetLastError());
        goto _END_OF_FUNC;
    }

    RtlCopyMemory(pszJson, pbJsonData, dwJsonLen);

    if (!(pJsonRoot = JsonParse(pszJson)))
    {
        printf("[-] Failed To Parse JSON\n");
        goto _END_OF_FUNC;
    }

    if (!(pFiles = JsonGetObject(pJsonRoot, "files")))
    {
        printf("[-] No Files Object In JSON\n");
        goto _END_OF_FUNC;
    }

    if (!ExtractFilesFromJson(pFiles, NULL, &pEntries))
    {
        printf("[-] Failed To Extract File Entries\n");
        goto _END_OF_FUNC;
    }

    dwDataOffset = 8 + dwHeaderSize;
    pEntry       = pEntries;

    while (pEntry)
    {
        if (pEntry->bIsDirectory)
        {
            dwDirCount++;
            pEntry = pEntry->pNext;
            continue;
        }

        dwFileCount++;

        ULONGLONG   ullStart    = (ULONGLONG)dwDataOffset + pEntry->dwOffset;
        ULONGLONG   ullEnd      = ullStart + pEntry->dwSize;
        LPBYTE      pbFileData  = NULL;

        if (ullEnd > (ULONGLONG)liFileSize.QuadPart)
        {
            printf("[!] Invalid Offset/Size For: %s (Offset=%lu, Size=%lu)\n", pEntry->pszRelativePath, pEntry->dwOffset, pEntry->dwSize);
            dwOffsetErrors++;
            pEntry = pEntry->pNext;
            continue;
        }

        pbFileData      = pbAsarData + (SIZE_T)ullStart;
        pszComputedHash = ComputeSha256Hex(pbFileData, pEntry->dwSize);

        if (!pszComputedHash)
        {
            printf("[!] Failed To Compute SHA-256 For: %s\n", pEntry->pszRelativePath);
            dwOffsetErrors++;
            pEntry = pEntry->pNext;
            continue;
        }

        if (pEntry->pszHash && *pEntry->pszHash)
        {
            dwFilesWithIntegrity++;

            if (lstrcmpA(pEntry->pszHash, pszComputedHash) == 0)
            {
#ifdef VERBOSE_OUTPUT
                // printf("[v] Verified Hash Of: %s (%s)\n", pEntry->pszRelativePath, pEntry->pszHash);
                printf("[v] Verified Hash Of: %s\n", pEntry->pszRelativePath);
#endif
                dwFilesHashOk++;
            }
            else
            {
                printf("[!] Hash Mismatch: %s (Header=%s, Computed=%s)\n", pEntry->pszRelativePath, pEntry->pszHash, pszComputedHash);
                dwFilesHashMismatch++;
            }
        }
        else
        {
            dwFilesMissingIntegrity++;
        }

        if (pEntry->ppszBlocks && pEntry->dwBlockCount > 0)
        {
            BOOL  bBlocksOk        = TRUE;
            DWORD dwExpectedBlocks = pEntry->dwBlockCount;
            DWORD dwComputedBlocks = (pEntry->dwSize == 0) ? 1 :
                                     (pEntry->dwSize + ASAR_BLOCK_SIZE - 1) / ASAR_BLOCK_SIZE;

            if (dwExpectedBlocks != dwComputedBlocks)
            {
                printf("[!] Block Count Mismatch: %s (Header=%lu, Computed=%lu)\n", pEntry->pszRelativePath, dwExpectedBlocks, dwComputedBlocks);
                bBlocksOk = FALSE;
            }
            else
            {
                for (DWORD i = 0; i < dwExpectedBlocks; i++)
                {
                    DWORD dwBlockOffset = i * ASAR_BLOCK_SIZE;
                    DWORD dwBlockSize   = (pEntry->dwSize == 0) ? 0 :
                                          min(ASAR_BLOCK_SIZE, pEntry->dwSize - dwBlockOffset);

                    pszBlockHash = ComputeSha256Hex(pbFileData + dwBlockOffset, dwBlockSize);

                    if (!pszBlockHash)
                    {
                        printf("[!] Failed To Compute Block Hash: %s (Block %lu)\n", pEntry->pszRelativePath, i);
                        bBlocksOk = FALSE;
                        break;
                    }

                    if (!pEntry->ppszBlocks[i] || lstrcmpA(pEntry->ppszBlocks[i], pszBlockHash) != 0)
                    {
                        printf("[!] Block (%lu) Hash Mismatch: %s (Header=%s, Computed=%s)\n", i, pEntry->pszRelativePath, pEntry->ppszBlocks[i] ? pEntry->ppszBlocks[i] : "(null)", pszBlockHash);
                        bBlocksOk = FALSE;
                    }

                    HeapFree(GetProcessHeap(), 0, pszBlockHash);
                    pszBlockHash = NULL;
                }
            }

            if (!bBlocksOk) dwFilesBlocksMismatch++;
        }

        if (pszComputedHash)
        {
            HeapFree(GetProcessHeap(), 0, pszComputedHash);
            pszComputedHash = NULL;
        }

        pEntry = pEntry->pNext;
    }

    printf("[+] Verification Completed.\n");
    printf("[i] Directories:                   %lu\n", dwDirCount);
    printf("[i] Files:                         %lu\n", dwFileCount);
    printf("[i] Files With Integrity:          %lu\n", dwFilesWithIntegrity);
    printf("[i] Files Missing Integrity:       %lu\n", dwFilesMissingIntegrity);
    printf("[i] Files With Matching Hash:      %lu\n", dwFilesHashOk);
    printf("[i] Files With Hash Mismatch:      %lu\n", dwFilesHashMismatch);
    printf("[i] Files With Block Mismatch:     %lu\n", dwFilesBlocksMismatch);
    printf("[i] Files With Offset/Size Errors: %lu\n", dwOffsetErrors);


    bResult =   (dwFilesHashMismatch == 0 && dwFilesBlocksMismatch == 0);    // No hash or block mismatches
    bResult &=  (dwOffsetErrors == 0);                                       // No offset/size errors  
    bResult &=  (dwFilesWithIntegrity > 0 && dwFilesMissingIntegrity == 0);  // All files have integrity data
    bResult &=  (dwFilesWithIntegrity == dwFilesHashOk);                     // All files with integrity passed hash check

_END_OF_FUNC:
    if (pszBlockHash)
        HeapFree(GetProcessHeap(), 0, pszBlockHash);
    if (pszComputedHash)
        HeapFree(GetProcessHeap(), 0, pszComputedHash);
    FreeFileEntries(pEntries);
    if (pJsonRoot)
        JsonFree(pJsonRoot);
    if (pszJson)
        HeapFree(GetProcessHeap(), 0, pszJson);
    if (pbAsarData)
        VirtualFree(pbAsarData, 0, MEM_RELEASE);
    if (hFile != INVALID_HANDLE_VALUE)
        CloseHandle(hFile);

    return bResult;
}

// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
