/*
 * COFFLoader.c -- Complete, production-ready COFF/BOF loader
 * Step 08: The final polished loader combining all modules:
 *          COFF parsing, section loading, symbol resolution,
 *          relocation processing, entry point discovery, and
 *          output retrieval.
 *
 * Build (compile + link -- this is the LOADER, not a BOF):
 *   MinGW:  x86_64-w64-mingw32-gcc COFFLoader.c beacon_compatibility.c
 *                                   -o COFFLoader.exe -luser32 -ladvapi32
 *   MSVC:   cl.exe COFFLoader.c beacon_compatibility.c
 *                   kernel32.lib user32.lib advapi32.lib
 *
 * Usage:
 *   COFFLoader.exe <function_name> <coff_file> [hex_args]
 *
 *   function_name: entry point name in the BOF (typically "go")
 *   coff_file:     path to the compiled .o / .obj file
 *   hex_args:      optional hex-encoded argument buffer
 *
 * Examples:
 *   COFFLoader.exe go hello_bof.o
 *   COFFLoader.exe go args_bof.o 0c0000000800000068006f0073007400
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <windows.h>

#include "COFFLoader.h"
#include "beacon_compatibility.h"

/* ================================================================
 * Architecture-specific symbol prefix
 *
 * x64: __imp_FunctionName   (6 chars to skip)
 * x86: __imp__FunctionName  (7 chars -- extra underscore from C ABI)
 * ================================================================ */
#ifdef _WIN64
    #define PREPENDSYMBOLVALUE "__imp_"
    #define PREPENDSYMBOLVALUE_LEN 6
#else
    #define PREPENDSYMBOLVALUE "__imp__"
    #define PREPENDSYMBOLVALUE_LEN 7
#endif

/* ================================================================
 * hex_to_bytes -- Decode a hex string into a binary buffer
 *
 * Input:  "48656C6C6F" (ASCII hex pairs, no separators)
 * Output: {0x48, 0x65, 0x6C, 0x6C, 0x6F} ("Hello")
 *
 * Returns: allocated buffer (caller must free), or NULL on error
 * Sets *out_len to the number of decoded bytes.
 * ================================================================ */
static unsigned char* hex_to_bytes(const char* hexstr, int* out_len) {
    size_t hexlen = strlen(hexstr);
    if (hexlen % 2 != 0) {
        fprintf(stderr, "[-] Hex string has odd length: %zu\n", hexlen);
        return NULL;
    }

    size_t binlen = hexlen / 2;
    unsigned char* buf = (unsigned char*)malloc(binlen);
    if (!buf) {
        fprintf(stderr, "[-] malloc failed for hex decode buffer\n");
        return NULL;
    }

    for (size_t i = 0; i < binlen; i++) {
        unsigned int byte;
        if (sscanf(hexstr + i * 2, "%2x", &byte) != 1) {
            fprintf(stderr, "[-] Invalid hex at position %zu: '%.2s'\n",
                    i * 2, hexstr + i * 2);
            free(buf);
            return NULL;
        }
        buf[i] = (unsigned char)byte;
    }

    *out_len = (int)binlen;
    return buf;
}

/* ================================================================
 * get_symbol_name -- Retrieve the name of a symbol
 *
 * Handles both short names (<=8 chars, stored inline) and long
 * names (offset into the string table).
 * ================================================================ */
static const char* get_symbol_name(coff_sym_t* sym, char* string_table) {
    static char buf[9];
    if (sym->first.value[0] != 0) {
        /* Short name: may not be null-terminated if exactly 8 chars */
        memcpy(buf, sym->first.Name, 8);
        buf[8] = '\0';
        return buf;
    } else {
        /* Long name: offset into string table */
        return string_table + sym->first.value[1];
    }
}

/* ================================================================
 * process_symbol -- Resolve an external symbol name to an address
 *
 * Resolution order:
 *   1. Strip __imp_ prefix (if present)
 *   2. Check InternalFunctions table (Beacon API)
 *   3. Parse LIBRARY$Function format and use LoadLibraryA/GetProcAddress
 *
 * Returns the resolved function pointer, or NULL on failure.
 * ================================================================ */
static void* process_symbol(const char* symbolName) {
    /* Strip __imp_ / __imp__ prefix */
    const char* cleanName = symbolName;
    if (strncmp(symbolName, PREPENDSYMBOLVALUE, PREPENDSYMBOLVALUE_LEN) == 0) {
        cleanName = symbolName + PREPENDSYMBOLVALUE_LEN;
    }

    /* 1. Check InternalFunctions table (Beacon API functions) */
    for (int i = 0; i < 30; i++) {
        if (InternalFunctions[i][0] != NULL) {
            if (strcmp(cleanName, (char*)InternalFunctions[i][0]) == 0) {
                return (void*)InternalFunctions[i][1];
            }
        }
    }

    /* 2. Parse LIBRARY$Function format for DLL imports */
    char* dollar = strchr(cleanName, '$');
    if (dollar == NULL) {
        fprintf(stderr, "[-] Cannot resolve symbol (no $ separator): %s\n",
                symbolName);
        return NULL;
    }

    /* Extract library name */
    size_t liblen = dollar - cleanName;
    char libraryName[256];
    if (liblen >= sizeof(libraryName)) {
        fprintf(stderr, "[-] Library name too long: %s\n", symbolName);
        return NULL;
    }
    memcpy(libraryName, cleanName, liblen);
    libraryName[liblen] = '\0';

    /* Extract function name (everything after '$') */
    const char* functionName = dollar + 1;

    /* 3. Load the DLL and resolve the function */
    HMODULE hLib = LoadLibraryA(libraryName);
    if (hLib == NULL) {
        fprintf(stderr, "[-] LoadLibraryA(\"%s\") failed: %lu\n",
                libraryName, GetLastError());
        return NULL;
    }

    void* addr = (void*)GetProcAddress(hLib, functionName);
    if (addr == NULL) {
        fprintf(stderr, "[-] GetProcAddress(\"%s\", \"%s\") failed: %lu\n",
                libraryName, functionName, GetLastError());
        return NULL;
    }

    return addr;
}

/* ================================================================
 * RunCOFF -- Load and execute a COFF object file
 *
 * This is the core function that implements the complete loading
 * pipeline from Module 2 through Module 8:
 *
 *   1. Parse COFF header and validate
 *   2. Locate section/symbol/string tables
 *   3. Load sections into executable memory
 *   4. Allocate function pointer table (functionMapping)
 *   5. Populate InternalFunctions table
 *   6. Process relocations (symbol resolution + fixups)
 *   7. Find and call the entry point
 *   8. Retrieve output and clean up
 *
 * Returns: 0 on success, -1 on failure
 * ================================================================ */
int RunCOFF(const char* functionname, unsigned char* coff_data,
            uint32_t filesize, char* argumentdata, int argumentSize) {

    /* ----------------------------------------------------------
     * Step 1: Parse COFF header
     * ---------------------------------------------------------- */
    if (filesize < sizeof(coff_file_header_t)) {
        fprintf(stderr, "[-] File too small for COFF header\n");
        return -1;
    }

    coff_file_header_t* coff_header = (coff_file_header_t*)coff_data;

    /* Validate machine type */
#ifdef _WIN64
    if (coff_header->Machine != MACHINE_AMD64) {
        fprintf(stderr, "[-] Not an AMD64 COFF file (Machine=0x%04X)\n",
                coff_header->Machine);
        return -1;
    }
#else
    if (coff_header->Machine != MACHINE_I386) {
        fprintf(stderr, "[-] Not an i386 COFF file (Machine=0x%04X)\n",
                coff_header->Machine);
        return -1;
    }
#endif

    /* Validate: object files must have SizeOfOptionalHeader == 0 */
    if (coff_header->SizeOfOptionalHeader != 0) {
        fprintf(stderr, "[-] Not an object file (SizeOfOptionalHeader=%u)\n",
                coff_header->SizeOfOptionalHeader);
        return -1;
    }

    printf("[+] COFF: Machine=0x%04X, Sections=%u, Symbols=%u\n",
           coff_header->Machine,
           coff_header->NumberOfSections,
           coff_header->NumberOfSymbols);

    /* ----------------------------------------------------------
     * Step 2: Locate tables
     * ---------------------------------------------------------- */
    coff_sect_t* sections = (coff_sect_t*)(
        coff_data + sizeof(coff_file_header_t)
    );

    coff_sym_t* symbols = (coff_sym_t*)(
        coff_data + coff_header->PointerToSymbolTable
    );

    char* string_table = ((char*)symbols) +
        (coff_header->NumberOfSymbols * sizeof(coff_sym_t));

    /* ----------------------------------------------------------
     * Step 3: Load sections into executable memory
     *
     * Each section gets its own VirtualAlloc with RWX permissions.
     * sectionMapping[i] points to the loaded copy of section i.
     * ---------------------------------------------------------- */
    char** sectionMapping = (char**)calloc(
        coff_header->NumberOfSections, sizeof(char*)
    );
    if (!sectionMapping) {
        fprintf(stderr, "[-] calloc failed for sectionMapping\n");
        return -1;
    }

    for (int i = 0; i < coff_header->NumberOfSections; i++) {
        uint32_t rawSize = sections[i].SizeOfRawData;
        if (rawSize == 0) {
            sectionMapping[i] = NULL;
            continue;
        }

        sectionMapping[i] = (char*)VirtualAlloc(
            NULL, rawSize,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE
        );
        if (!sectionMapping[i]) {
            fprintf(stderr, "[-] VirtualAlloc failed for section %d (%u bytes)\n",
                    i, rawSize);
            /* Clean up previously allocated sections */
            for (int j = 0; j < i; j++) {
                if (sectionMapping[j])
                    VirtualFree(sectionMapping[j], 0, MEM_RELEASE);
            }
            free(sectionMapping);
            return -1;
        }

        /* Copy section data from the COFF file */
        memcpy(sectionMapping[i],
               coff_data + sections[i].PointerToRawData,
               rawSize);
    }

    /* ----------------------------------------------------------
     * Step 4: Allocate function pointer table (functionMapping)
     *
     * One 8-byte slot per relocation (worst case: every relocation
     * references a unique external function). This is the loader's
     * equivalent of the PE Import Address Table (IAT).
     * ---------------------------------------------------------- */
    int totalRelocations = 0;
    for (int i = 0; i < coff_header->NumberOfSections; i++) {
        totalRelocations += sections[i].NumberOfRelocations;
    }

    char* functionMapping = NULL;
    if (totalRelocations > 0) {
        functionMapping = (char*)VirtualAlloc(
            NULL,
            totalRelocations * sizeof(uint64_t),
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE
        );
        if (!functionMapping) {
            fprintf(stderr, "[-] VirtualAlloc failed for functionMapping\n");
            for (int i = 0; i < coff_header->NumberOfSections; i++) {
                if (sectionMapping[i])
                    VirtualFree(sectionMapping[i], 0, MEM_RELEASE);
            }
            free(sectionMapping);
            return -1;
        }
    }

    /* ----------------------------------------------------------
     * Step 5: Populate InternalFunctions[30] table
     *
     * Maps Beacon API function names to their compatibility-layer
     * implementations. process_symbol() searches this table when
     * resolving symbols that match Beacon API names.
     * ---------------------------------------------------------- */
    /* Data parsing (indices 0-4) */
    InternalFunctions[0][0]  = (unsigned char*)"BeaconDataParse";
    InternalFunctions[0][1]  = (unsigned char*)&BeaconDataParse;
    InternalFunctions[1][0]  = (unsigned char*)"BeaconDataInt";
    InternalFunctions[1][1]  = (unsigned char*)&BeaconDataInt;
    InternalFunctions[2][0]  = (unsigned char*)"BeaconDataShort";
    InternalFunctions[2][1]  = (unsigned char*)&BeaconDataShort;
    InternalFunctions[3][0]  = (unsigned char*)"BeaconDataLength";
    InternalFunctions[3][1]  = (unsigned char*)&BeaconDataLength;
    InternalFunctions[4][0]  = (unsigned char*)"BeaconDataExtract";
    InternalFunctions[4][1]  = (unsigned char*)&BeaconDataExtract;

    /* Format buffer (indices 5-11) */
    InternalFunctions[5][0]  = (unsigned char*)"BeaconFormatAlloc";
    InternalFunctions[5][1]  = (unsigned char*)&BeaconFormatAlloc;
    InternalFunctions[6][0]  = (unsigned char*)"BeaconFormatReset";
    InternalFunctions[6][1]  = (unsigned char*)&BeaconFormatReset;
    InternalFunctions[7][0]  = (unsigned char*)"BeaconFormatFree";
    InternalFunctions[7][1]  = (unsigned char*)&BeaconFormatFree;
    InternalFunctions[8][0]  = (unsigned char*)"BeaconFormatAppend";
    InternalFunctions[8][1]  = (unsigned char*)&BeaconFormatAppend;
    InternalFunctions[9][0]  = (unsigned char*)"BeaconFormatPrintf";
    InternalFunctions[9][1]  = (unsigned char*)&BeaconFormatPrintf;
    InternalFunctions[10][0] = (unsigned char*)"BeaconFormatToString";
    InternalFunctions[10][1] = (unsigned char*)&BeaconFormatToString;
    InternalFunctions[11][0] = (unsigned char*)"BeaconFormatInt";
    InternalFunctions[11][1] = (unsigned char*)&BeaconFormatInt;

    /* Output (indices 12-13) */
    InternalFunctions[12][0] = (unsigned char*)"BeaconPrintf";
    InternalFunctions[12][1] = (unsigned char*)&BeaconPrintf;
    InternalFunctions[13][0] = (unsigned char*)"BeaconOutput";
    InternalFunctions[13][1] = (unsigned char*)&BeaconOutput;

    /* Token / Process (indices 14-21) */
    InternalFunctions[14][0] = (unsigned char*)"BeaconUseToken";
    InternalFunctions[14][1] = (unsigned char*)&BeaconUseToken;
    InternalFunctions[15][0] = (unsigned char*)"BeaconRevertToken";
    InternalFunctions[15][1] = (unsigned char*)&BeaconRevertToken;
    InternalFunctions[16][0] = (unsigned char*)"BeaconIsAdmin";
    InternalFunctions[16][1] = (unsigned char*)&BeaconIsAdmin;
    InternalFunctions[17][0] = (unsigned char*)"BeaconGetSpawnTo";
    InternalFunctions[17][1] = (unsigned char*)&BeaconGetSpawnTo;
    InternalFunctions[18][0] = (unsigned char*)"BeaconSpawnTemporaryProcess";
    InternalFunctions[18][1] = (unsigned char*)&BeaconSpawnTemporaryProcess;
    InternalFunctions[19][0] = (unsigned char*)"BeaconInjectProcess";
    InternalFunctions[19][1] = (unsigned char*)&BeaconInjectProcess;
    InternalFunctions[20][0] = (unsigned char*)"BeaconInjectTemporaryProcess";
    InternalFunctions[20][1] = (unsigned char*)&BeaconInjectTemporaryProcess;
    InternalFunctions[21][0] = (unsigned char*)"BeaconCleanupProcess";
    InternalFunctions[21][1] = (unsigned char*)&BeaconCleanupProcess;

    /* Utility (indices 22-23) */
    InternalFunctions[22][0] = (unsigned char*)"toWideChar";
    InternalFunctions[22][1] = (unsigned char*)&toWideChar;
    InternalFunctions[23][0] = (unsigned char*)"BeaconGetOutputData";
    InternalFunctions[23][1] = (unsigned char*)&BeaconGetOutputData;

    /* Indices 24-29 reserved for custom extensions */

    /* ----------------------------------------------------------
     * Step 6: Process relocations (per section)
     *
     * For each relocation entry:
     *   a) Determine the fixup address (where to patch)
     *   b) Resolve the symbol (internal, Beacon API, or DLL import)
     *   c) Apply the architecture-specific fixup
     * ---------------------------------------------------------- */
    int functionMappingCount = 0;

    for (int secIdx = 0; secIdx < coff_header->NumberOfSections; secIdx++) {
        if (sections[secIdx].NumberOfRelocations == 0) continue;
        if (sectionMapping[secIdx] == NULL) continue;

        coff_reloc_t* relocs = (coff_reloc_t*)(
            coff_data + sections[secIdx].PointerToRelocations
        );

        for (int relIdx = 0; relIdx < sections[secIdx].NumberOfRelocations; relIdx++) {
            uint32_t symIdx = relocs[relIdx].SymbolTableIndex;
            const char* symName = get_symbol_name(&symbols[symIdx], string_table);

            /* Where to patch: base of loaded section + relocation offset */
            char* fixupAddress = sectionMapping[secIdx] + relocs[relIdx].VirtualAddress;

            /* Resolve the symbol to a target address */
            void* symbolAddress = NULL;

            if (symbols[symIdx].SectionNumber > 0) {
                /* ---- Internal symbol ----
                 * Defined within the COFF file. Address is the base of
                 * the symbol's section + its Value offset. */
                int targetSection = symbols[symIdx].SectionNumber - 1;
                if (sectionMapping[targetSection]) {
                    symbolAddress = sectionMapping[targetSection] + symbols[symIdx].Value;
                } else {
                    fprintf(stderr, "[-] Internal symbol '%s' references empty section %d\n",
                            symName, targetSection);
                    continue;
                }
            }
            else if (strncmp(symName, PREPENDSYMBOLVALUE, PREPENDSYMBOLVALUE_LEN) == 0) {
                /* ---- External __imp_ symbol ----
                 * Indirect reference (dllimport). The relocation points
                 * to a functionMapping SLOT that contains the real address.
                 * The CPU's CALL [rip+offset] dereferences this slot. */
                void* resolved = process_symbol(symName);
                if (resolved == NULL) {
                    fprintf(stderr, "[-] Failed to resolve: %s\n", symName);
                    continue;
                }

                /* Store the resolved address in the next functionMapping slot */
                symbolAddress = functionMapping + (functionMappingCount * sizeof(uint64_t));
                *(uint64_t*)symbolAddress = (uint64_t)(uintptr_t)resolved;
                functionMappingCount++;
            }
            else {
                /* ---- External symbol without __imp_ prefix ----
                 * Direct reference. Use the resolved address itself. */
                symbolAddress = process_symbol(symName);
                if (symbolAddress == NULL) {
                    fprintf(stderr, "[-] Failed to resolve: %s\n", symName);
                    continue;
                }
            }

            /* ---- Apply the relocation fixup ---- */
            uint16_t relocType = relocs[relIdx].Type;

            if (coff_header->Machine == MACHINE_AMD64) {
                switch (relocType) {

                case IMAGE_REL_AMD64_ADDR64:
                    /* 64-bit absolute address */
                    *(uint64_t*)fixupAddress = (uint64_t)(uintptr_t)symbolAddress;
                    break;

                case IMAGE_REL_AMD64_ADDR32NB:
                    /* 32-bit address relative to image base (no base) */
                    *(uint32_t*)fixupAddress = (uint32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        (uint64_t)(uintptr_t)sectionMapping[0]
                    );
                    break;

                case IMAGE_REL_AMD64_REL32:
                    /* 32-bit RIP-relative offset */
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4)
                    );
                    break;

                case IMAGE_REL_AMD64_REL32_1:
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4 + 1)
                    );
                    break;

                case IMAGE_REL_AMD64_REL32_2:
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4 + 2)
                    );
                    break;

                case IMAGE_REL_AMD64_REL32_3:
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4 + 3)
                    );
                    break;

                case IMAGE_REL_AMD64_REL32_4:
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4 + 4)
                    );
                    break;

                case IMAGE_REL_AMD64_REL32_5:
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint64_t)(uintptr_t)symbolAddress -
                        ((uint64_t)(uintptr_t)fixupAddress + 4 + 5)
                    );
                    break;

                default:
                    fprintf(stderr, "[!] Unknown AMD64 relocation type: 0x%04X\n",
                            relocType);
                    break;
                }
            }
            else if (coff_header->Machine == MACHINE_I386) {
                switch (relocType) {

                case IMAGE_REL_I386_DIR32:
                    /* 32-bit absolute address (additive) */
                    *(uint32_t*)fixupAddress += (uint32_t)(uintptr_t)symbolAddress;
                    break;

                case IMAGE_REL_I386_REL32:
                    /* 32-bit relative offset */
                    *(int32_t*)fixupAddress = (int32_t)(
                        (uint32_t)(uintptr_t)symbolAddress -
                        ((uint32_t)(uintptr_t)fixupAddress + 4)
                    );
                    break;

                default:
                    fprintf(stderr, "[!] Unknown i386 relocation type: 0x%04X\n",
                            relocType);
                    break;
                }
            }
        }
    }

    /* ----------------------------------------------------------
     * Step 7: Find and call the entry point
     *
     * Scan the symbol table for the requested function name.
     * On x64: exact match (e.g., "go")
     * On x86: prepend underscore (e.g., "_go")
     * ---------------------------------------------------------- */
    void* entryPoint = NULL;

    for (uint32_t i = 0; i < coff_header->NumberOfSymbols; i++) {
        const char* name = get_symbol_name(&symbols[i], string_table);

#ifdef _WIN64
        if (strcmp(name, functionname) == 0) {
#else
        char decorated[256];
        snprintf(decorated, sizeof(decorated), "_%s", functionname);
        if (strcmp(name, decorated) == 0) {
#endif
            if (symbols[i].SectionNumber > 0) {
                int secIdx = symbols[i].SectionNumber - 1;
                entryPoint = sectionMapping[secIdx] + symbols[i].Value;
            }
            break;
        }

        /* Skip auxiliary symbol entries */
        i += symbols[i].NumberOfAuxSymbols;
    }

    if (entryPoint == NULL) {
        fprintf(stderr, "[-] Entry point '%s' not found in symbol table\n",
                functionname);
        /* Clean up and return */
        for (int i = 0; i < coff_header->NumberOfSections; i++) {
            if (sectionMapping[i])
                VirtualFree(sectionMapping[i], 0, MEM_RELEASE);
        }
        if (functionMapping) VirtualFree(functionMapping, 0, MEM_RELEASE);
        free(sectionMapping);
        return -1;
    }

    printf("[+] Entry point '%s' found at %p\n", functionname, entryPoint);

    /* Cast and call the entry function */
    typedef void (*entry_fn)(char*, int);
    entry_fn entry = (entry_fn)entryPoint;

    printf("[+] Executing BOF...\n");
    printf("--- BOF Output ---\n");
    entry(argumentdata, argumentSize);
    printf("--- End BOF Output ---\n");

    /* ----------------------------------------------------------
     * Step 8: Retrieve output and clean up
     * ---------------------------------------------------------- */
    int outsize = 0;
    char* output = BeaconGetOutputData(&outsize);
    if (output && outsize > 0) {
        /* Output was already printed to console by BeaconPrintf.
         * Free the accumulated buffer. */
        free(output);
    }

    /* Free all loaded sections */
    for (int i = 0; i < coff_header->NumberOfSections; i++) {
        if (sectionMapping[i])
            VirtualFree(sectionMapping[i], 0, MEM_RELEASE);
    }

    /* Free the function pointer table */
    if (functionMapping)
        VirtualFree(functionMapping, 0, MEM_RELEASE);

    /* Free the section mapping array */
    free(sectionMapping);

    return 0;
}

/* ================================================================
 * main -- CLI entry point
 *
 * Usage: COFFLoader.exe <function_name> <coff_file> [hex_args]
 * ================================================================ */
int main(int argc, char* argv[]) {
    if (argc < 3) {
        fprintf(stderr, "COFFLoader -- Standalone COFF/BOF Loader\n");
        fprintf(stderr, "Usage: %s <function_name> <coff_file> [hex_args]\n\n", argv[0]);
        fprintf(stderr, "  function_name   Entry point name (typically 'go')\n");
        fprintf(stderr, "  coff_file        Path to compiled .o / .obj file\n");
        fprintf(stderr, "  hex_args         Optional hex-encoded argument buffer\n\n");
        fprintf(stderr, "Examples:\n");
        fprintf(stderr, "  %s go hello_bof.o\n", argv[0]);
        fprintf(stderr, "  %s go args_bof.o 0c000000080000006c6f63616c686f737400d2040000\n", argv[0]);
        return 1;
    }

    const char* functionname = argv[1];
    const char* coff_file    = argv[2];

    /* Read the COFF file into memory */
    FILE* f = fopen(coff_file, "rb");
    if (!f) {
        fprintf(stderr, "[-] Cannot open COFF file: %s\n", coff_file);
        return 1;
    }

    fseek(f, 0, SEEK_END);
    long filesize = ftell(f);
    fseek(f, 0, SEEK_SET);

    if (filesize <= 0) {
        fprintf(stderr, "[-] Invalid file size: %ld\n", filesize);
        fclose(f);
        return 1;
    }

    unsigned char* coff_data = (unsigned char*)malloc(filesize);
    if (!coff_data) {
        fprintf(stderr, "[-] malloc failed for COFF data (%ld bytes)\n", filesize);
        fclose(f);
        return 1;
    }

    size_t bytesRead = fread(coff_data, 1, filesize, f);
    fclose(f);

    if ((long)bytesRead != filesize) {
        fprintf(stderr, "[-] Short read: got %zu of %ld bytes\n", bytesRead, filesize);
        free(coff_data);
        return 1;
    }

    printf("[+] Loaded COFF file: %s (%ld bytes)\n", coff_file, filesize);

    /* Decode optional hex arguments */
    char* argumentdata = NULL;
    int   argumentSize = 0;

    if (argc >= 4) {
        unsigned char* decoded = hex_to_bytes(argv[3], &argumentSize);
        if (decoded == NULL) {
            fprintf(stderr, "[-] Failed to decode hex arguments\n");
            free(coff_data);
            return 1;
        }
        argumentdata = (char*)decoded;
        printf("[+] Decoded %d bytes of arguments from hex\n", argumentSize);
    }

    /* Run the COFF loader */
    int result = RunCOFF(functionname, coff_data, (uint32_t)filesize,
                         argumentdata, argumentSize);

    /* Clean up */
    if (argumentdata) free(argumentdata);
    free(coff_data);

    if (result == 0) {
        printf("[+] BOF execution completed successfully\n");
    } else {
        fprintf(stderr, "[-] BOF execution failed\n");
    }

    return (result == 0) ? 0 : 1;
}
