/*
 * COFFLoader.c -- COFF Loader with Relocation Processing
 * Step 06: Module 6 -- Relocation Processing
 *
 * This is the step where the loader becomes FUNCTIONAL.  Building on the
 * section loading and symbol resolution from step05, we now process
 * relocations -- patching live code in memory with correct addresses so
 * the BOF can actually execute.
 *
 * Flow:
 *   1. Read the .o file into memory
 *   2. Parse the COFF header, section table, symbol table, string table
 *   3. Allocate executable memory for each section (VirtualAlloc)
 *   4. Resolve every symbol (internal, external DLL, Beacon API)
 *   5. ** NEW ** Process relocations -- patch code with resolved addresses
 *   6. ** NEW ** Find the entry point ("go" / "_go") and CALL it
 *   7. Cleanup
 *
 * Compile (as a normal Windows program, NOT a BOF):
 *   MinGW:  x86_64-w64-mingw32-gcc COFFLoader.c beacon_compatibility.c
 *                                   -o COFFLoader.exe
 *
 * Usage:
 *   COFFLoader.exe go hello_bof.o
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "COFFLoader.h"
#include "beacon_compatibility.h"

/* ================================================================
 * Helper: get_symbol_name
 *
 * Symbols can have short names (up to 8 chars inline) or long names
 * stored in the string table.  If first.value[0] == 0 the name is
 * in the string table at offset first.value[1].
 * ================================================================ */
static const char* get_symbol_name(coff_sym_t* sym, char* stringTable) {
    static char buf[9];
    if (sym->first.value[0] != 0) {
        memcpy(buf, sym->first.Name, 8);
        buf[8] = '\0';
        return buf;
    }
    return stringTable + sym->first.value[1];
}

/* ================================================================
 * Helper: reloc_type_name
 *
 * Returns a human-readable name for a relocation type, used in
 * debug output so you can trace what the loader is doing.
 * ================================================================ */
static const char* reloc_type_name(uint16_t machine, uint16_t type) {
    if (machine == MACHINE_AMD64) {
        switch (type) {
            case IMAGE_REL_AMD64_ADDR64:   return "ADDR64";
            case IMAGE_REL_AMD64_ADDR32NB: return "ADDR32NB";
            case IMAGE_REL_AMD64_REL32:    return "REL32";
            case IMAGE_REL_AMD64_REL32_1:  return "REL32_1";
            case IMAGE_REL_AMD64_REL32_2:  return "REL32_2";
            case IMAGE_REL_AMD64_REL32_3:  return "REL32_3";
            case IMAGE_REL_AMD64_REL32_4:  return "REL32_4";
            case IMAGE_REL_AMD64_REL32_5:  return "REL32_5";
            default:                       return "???";
        }
    } else {
        switch (type) {
            case IMAGE_REL_I386_DIR32: return "DIR32";
            case IMAGE_REL_I386_REL32: return "REL32";
            default:                   return "???";
        }
    }
}

/* ================================================================
 * process_symbol -- Resolve a single external symbol
 *
 * External symbols fall into three categories:
 *
 *   1. __imp_LIBRARY$Function  -- a DLL import.
 *      Split on '$', LoadLibraryA the DLL, GetProcAddress the function.
 *
 *   2. __imp_BeaconXxx (or __imp_toWideChar) -- a Beacon API function.
 *      Look up in InternalFunctions table.
 *
 *   3. Bare external (no __imp_ prefix) -- try Beacon API table, then
 *      try as DLL$Function.
 *
 * Returns the resolved function pointer, or NULL on failure.
 * ================================================================ */
static void* process_symbol(const char* symbolName) {
    void* address = NULL;

    /* ---- Strip __imp_ prefix if present ---- */
    const char* name = symbolName;
    int is_imp = 0;
    if (strncmp(name, "__imp_", 6) == 0) {
        name += 6;
        is_imp = 1;
    }

    /* ---- Check InternalFunctions table (Beacon API) ---- */
    for (int i = 0; InternalFunctions[i][0] != NULL; i++) {
        if (strcmp((char*)InternalFunctions[i][0], name) == 0) {
            address = (void*)InternalFunctions[i][1];
            printf("  [symbol] %-40s -> Beacon API @ %p\n", symbolName, address);
            return address;
        }
    }

    /* ---- Try DLL import: look for LIBRARY$Function pattern ---- */
    char* dollar = strchr(name, '$');
    if (dollar != NULL) {
        /* Split into library name and function name */
        size_t libLen = dollar - name;
        char libName[256] = {0};
        if (libLen >= sizeof(libName)) libLen = sizeof(libName) - 1;
        memcpy(libName, name, libLen);
        libName[libLen] = '\0';

        const char* funcName = dollar + 1;

        HMODULE hMod = LoadLibraryA(libName);
        if (hMod) {
            address = (void*)GetProcAddress(hMod, funcName);
            if (address) {
                printf("  [symbol] %-40s -> %s!%s @ %p\n",
                       symbolName, libName, funcName, address);
            } else {
                printf("  [symbol] WARNING: GetProcAddress failed for %s in %s\n",
                       funcName, libName);
            }
        } else {
            printf("  [symbol] WARNING: LoadLibraryA failed for %s\n", libName);
        }
        return address;
    }

    printf("  [symbol] WARNING: unresolved symbol '%s'\n", symbolName);
    return NULL;
}

/* ================================================================
 * section_protection -- Map COFF characteristics to VirtualAlloc flags
 *
 * The COFF section flags tell us what kind of memory protection to
 * apply: execute, read, write, or combinations thereof.
 * ================================================================ */
static DWORD section_protection(uint32_t characteristics) {
    DWORD protect = PAGE_NOACCESS;

    int exec  = (characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
    int read  = (characteristics & IMAGE_SCN_MEM_READ)    != 0;
    int write = (characteristics & IMAGE_SCN_MEM_WRITE)   != 0;

    if (exec && read && write)  protect = PAGE_EXECUTE_READWRITE;
    else if (exec && read)      protect = PAGE_EXECUTE_READ;
    else if (exec && write)     protect = PAGE_EXECUTE_WRITECOPY;
    else if (exec)              protect = PAGE_EXECUTE;
    else if (read && write)     protect = PAGE_READWRITE;
    else if (read)              protect = PAGE_READONLY;
    else if (write)             protect = PAGE_WRITECOPY;

    return protect;
}

/* ================================================================
 * main -- The COFF loader entry point
 * ================================================================ */
int main(int argc, char* argv[]) {
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <entry_function> <coff_file.o> [args...]\n", argv[0]);
        fprintf(stderr, "Example: %s go hello_bof.o\n", argv[0]);
        return 1;
    }

    const char* entryName = argv[1];  /* typically "go" */
    const char* filename  = argv[2];

    printf("=== COFFLoader Step 06: Relocation Processing ===\n");
    printf("Entry function: %s\n", entryName);
    printf("COFF file:      %s\n\n", filename);

    /* ================================================================
     * PHASE 1: Read the COFF file into memory
     * ================================================================ */
    FILE* f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Error: cannot open '%s'\n", filename);
        return 1;
    }

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

    unsigned char* fileData = (unsigned char*)malloc(fileSize);
    if (!fileData) {
        fprintf(stderr, "Error: malloc failed (%ld bytes)\n", fileSize);
        fclose(f);
        return 1;
    }
    fread(fileData, 1, fileSize, f);
    fclose(f);

    printf("[+] Read %ld bytes from %s\n", fileSize, filename);

    /* ================================================================
     * PHASE 2: Parse COFF structures
     *
     * The layout is:
     *   [File Header] [Section Headers...] [Raw Data...] [Relocations...]
     *   [Symbol Table] [String Table]
     * ================================================================ */
    coff_file_header_t* header = (coff_file_header_t*)fileData;

    printf("[+] Machine: 0x%04X (%s)\n", header->Machine,
           header->Machine == MACHINE_AMD64 ? "AMD64" :
           header->Machine == MACHINE_I386  ? "i386"  : "unknown");
    printf("[+] Sections: %u, Symbols: %u\n\n",
           header->NumberOfSections, header->NumberOfSymbols);

    /* Section headers start right after the file header */
    coff_sect_t* sections = (coff_sect_t*)(fileData + sizeof(coff_file_header_t));

    /* Symbol table and string table */
    coff_sym_t* symbols     = (coff_sym_t*)(fileData + header->PointerToSymbolTable);
    char*       stringTable = (char*)symbols + (header->NumberOfSymbols * sizeof(coff_sym_t));

    /* ================================================================
     * PHASE 3: Load sections into executable memory
     *
     * Each section gets its own VirtualAlloc allocation.  We use
     * PAGE_EXECUTE_READWRITE initially so we can write relocations
     * into code sections; a production loader would tighten
     * permissions after patching.
     *
     * sectionMapping[i] = base address of section i in our process
     * ================================================================ */
    int numSections = header->NumberOfSections;
    char** sectionMapping = (char**)calloc(numSections, sizeof(char*));

    printf("--- Loading Sections ---\n");
    for (int i = 0; i < numSections; i++) {
        char name[9] = {0};
        memcpy(name, sections[i].Name, 8);

        uint32_t rawSize = sections[i].SizeOfRawData;

        /* Allocate at least 1 page even for BSS sections */
        uint32_t allocSize = rawSize > 0 ? rawSize : 4096;

        sectionMapping[i] = (char*)VirtualAlloc(
            NULL, allocSize,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_EXECUTE_READWRITE   /* RWX for patching; tighten later */
        );

        if (!sectionMapping[i]) {
            fprintf(stderr, "Error: VirtualAlloc failed for section %d (%s)\n", i, name);
            free(fileData);
            free(sectionMapping);
            return 1;
        }

        /* Copy raw data if the section has any (BSS sections have none) */
        if (rawSize > 0 && sections[i].PointerToRawData > 0) {
            memcpy(sectionMapping[i],
                   fileData + sections[i].PointerToRawData,
                   rawSize);
        } else {
            memset(sectionMapping[i], 0, allocSize);
        }

        printf("  [%d] %-8s  size=0x%04X  loaded @ %p\n",
               i, name, rawSize, sectionMapping[i]);
    }
    printf("\n");

    /* ================================================================
     * PHASE 4: Resolve external symbols
     *
     * Walk the symbol table.  For each EXTERNAL symbol with
     * SectionNumber == 0 (undefined), call process_symbol to resolve
     * it via LoadLibraryA/GetProcAddress or the Beacon API table.
     *
     * We also need a functionMapping array: for __imp_ symbols the
     * BOF code does an indirect call through a pointer.  We store
     * the resolved address in functionMapping[symbolIndex] and the
     * relocation will point to that slot.
     *
     * functionMapping is sized to hold one pointer per symbol.
     * ================================================================ */
    int numSymbols = header->NumberOfSymbols;
    void** functionMapping = (void**)calloc(numSymbols, sizeof(void*));

    printf("--- Resolving Symbols ---\n");
    for (int i = 0; i < numSymbols; i++) {
        if (symbols[i].StorageClass == IMAGE_SYM_CLASS_EXTERNAL &&
            symbols[i].SectionNumber == 0) {
            /* Undefined external -- needs resolution */
            const char* symName = get_symbol_name(&symbols[i], stringTable);
            void* addr = process_symbol(symName);
            functionMapping[i] = addr;
        }

        /* Skip auxiliary symbol entries */
        i += symbols[i].NumberOfAuxSymbols;
    }
    printf("\n");

    /* ================================================================
     * PHASE 5: Process Relocations  ** THIS IS THE NEW PART **
     *
     * Relocations are the heart of the COFF loader.  Each relocation
     * entry says: "at offset X in section S, patch in the address of
     * symbol Y using relocation type T."
     *
     * For each section that has relocations:
     *   For each relocation in that section:
     *     1. Compute fixupAddress = sectionMapping[s] + reloc.VirtualAddress
     *        This is where in our loaded memory we need to write.
     *
     *     2. Look up the target symbol by reloc.SymbolTableIndex.
     *
     *     3. Determine the symbol's address:
     *        - Internal symbol (SectionNumber > 0):
     *            address = sectionMapping[SectionNumber-1] + Value
     *        - External __imp_ symbol:
     *            The resolved pointer is in functionMapping[symIdx].
     *            But the code wants to do an indirect call, so it
     *            needs the ADDRESS OF the pointer slot, not the
     *            pointer value itself.
     *        - External non-__imp_ symbol:
     *            Use the resolved address directly from
     *            functionMapping[symIdx].
     *
     *     4. Apply the fixup based on relocation Type:
     *
     *        ADDR64 (0x0001):
     *          Write the full 64-bit absolute address.
     *          Formula: *(uint64_t*)fixup = symbolAddr
     *
     *        ADDR32NB (0x0003):
     *          Write a 32-bit RVA (relative virtual address).
     *          Formula: *(uint32_t*)fixup = (uint32_t)(symbolAddr - fixupAddr)
     *          Note: NB = "no base", i.e., image-base-relative.
     *          For in-memory loading we compute section-to-section offset.
     *
     *        REL32 (0x0004):
     *          Write a 32-bit PC-relative displacement.
     *          Formula: *(uint32_t*)fixup = symbolAddr - (fixupAddr + 4)
     *          The +4 accounts for the 4-byte fixup field itself:
     *          the CPU's PC has already advanced past it when executing.
     *
     *        REL32_1 through REL32_5 (0x0005 - 0x0009):
     *          Same as REL32 but with extra displacement bytes after
     *          the fixup field.  The CPU skips (4 + N) bytes total.
     *          Formula: *(uint32_t*)fixup = symbolAddr - (fixupAddr + 4 + N)
     *          where N = type - IMAGE_REL_AMD64_REL32 (1..5)
     *
     *        DIR32 (i386, 0x0006):
     *          Write a 32-bit absolute address (x86 only).
     *          Formula: *(uint32_t*)fixup += (uint32_t)(uintptr_t)symbolAddr
     *
     *        REL32 (i386, 0x0014):
     *          Write a 32-bit PC-relative displacement (x86).
     *          Formula: *(uint32_t*)fixup += symbolAddr - (fixupAddr + 4)
     * ================================================================ */
    printf("--- Processing Relocations ---\n");

    for (int s = 0; s < numSections; s++) {
        if (sections[s].NumberOfRelocations == 0) continue;

        char secName[9] = {0};
        memcpy(secName, sections[s].Name, 8);

        printf("  Section [%d] %s: %u relocations\n",
               s, secName, sections[s].NumberOfRelocations);

        /* Relocation entries for this section in the raw file data */
        coff_reloc_t* relocs = (coff_reloc_t*)(
            fileData + sections[s].PointerToRelocations);

        for (int r = 0; r < sections[s].NumberOfRelocations; r++) {
            /* ----------------------------------------------------------
             * Step 5a: Compute the fixup address
             *
             * This is the location in our loaded section memory that
             * we need to patch.  VirtualAddress is the offset from
             * the start of the section.
             * ---------------------------------------------------------- */
            char* fixupAddress = sectionMapping[s] + relocs[r].VirtualAddress;

            /* ----------------------------------------------------------
             * Step 5b: Look up the target symbol
             * ---------------------------------------------------------- */
            uint32_t symIdx = relocs[r].SymbolTableIndex;
            coff_sym_t* sym = &symbols[symIdx];
            const char* symName = get_symbol_name(sym, stringTable);

            /* ----------------------------------------------------------
             * Step 5c: Determine the symbol's address
             * ---------------------------------------------------------- */
            char* symbolAddress = NULL;

            if (sym->SectionNumber > 0) {
                /*
                 * INTERNAL symbol: defined in one of the COFF sections.
                 * SectionNumber is 1-based, so subtract 1 for the array.
                 * Value is the offset within that section.
                 */
                int secIdx = sym->SectionNumber - 1;
                symbolAddress = sectionMapping[secIdx] + sym->Value;
            }
            else if (strncmp(symName, "__imp_", 6) == 0) {
                /*
                 * EXTERNAL __imp_ symbol (DLL import or Beacon API).
                 *
                 * The BOF code does: call qword ptr [rip+offset]
                 * This is an INDIRECT call -- the code reads a pointer
                 * from memory, then jumps to that pointer's value.
                 *
                 * So the relocation must point to the SLOT that holds
                 * the function pointer, not the function itself.
                 * functionMapping[symIdx] already holds the pointer value;
                 * we give the relocation the address OF that slot.
                 */
                symbolAddress = (char*)&functionMapping[symIdx];
            }
            else {
                /*
                 * EXTERNAL non-__imp_ symbol (direct call).
                 * Use the resolved address directly.
                 */
                symbolAddress = (char*)functionMapping[symIdx];
            }

            if (symbolAddress == NULL && sym->SectionNumber == 0) {
                printf("    [!] WARNING: NULL address for symbol '%s' "
                       "(reloc %d in section %d)\n", symName, r, s);
            }

            /* ----------------------------------------------------------
             * Step 5d: Apply the fixup based on relocation Type
             *
             * Each relocation type has a specific formula for computing
             * the value to write at the fixup location.
             * ---------------------------------------------------------- */
            uint16_t relocType = relocs[r].Type;

            printf("    reloc[%2d] offset=0x%04X sym=%-30s type=%-8s",
                   r, relocs[r].VirtualAddress, symName,
                   reloc_type_name(header->Machine, relocType));

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

                    case IMAGE_REL_AMD64_ADDR64: {
                        /*
                         * ADDR64: 64-bit absolute address.
                         *
                         * Write the full virtual address of the symbol.
                         * Used for data references, vtable entries, etc.
                         *
                         * Formula: *(int64_t*)fixup = symbolAddr
                         */
                        int64_t addr64 = (int64_t)symbolAddress;
                        memcpy(fixupAddress, &addr64, sizeof(addr64));
                        printf(" -> abs64 %p", symbolAddress);
                        break;
                    }

                    case IMAGE_REL_AMD64_ADDR32NB: {
                        /*
                         * ADDR32NB: 32-bit address, no base (RVA-style).
                         *
                         * NB means "no base" -- relative to the image base.
                         * For in-memory loaded sections we compute the
                         * offset from the fixup location to the symbol.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - fixupAddr
                         */
                        int32_t rva = (int32_t)(symbolAddress - fixupAddress);
                        memcpy(fixupAddress, &rva, sizeof(rva));
                        printf(" -> rva32 %+d", rva);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32: {
                        /*
                         * REL32: 32-bit PC-relative displacement.
                         *
                         * The most common relocation for x64 code.  Used for
                         * CALL and LEA instructions.  The CPU adds this value
                         * to (RIP + 4) to get the target address, where the
                         * +4 accounts for the 4-byte displacement field that
                         * the instruction pointer has already moved past.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 4));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32 %+d", rel);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32_1: {
                        /*
                         * REL32_1: PC-relative with 1 byte extra displacement.
                         *
                         * Same as REL32 but the instruction has 1 additional
                         * byte after the 4-byte displacement field.  So the
                         * CPU skips 5 bytes total instead of 4.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4 + 1)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 5));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32+1 %+d", rel);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32_2: {
                        /*
                         * REL32_2: PC-relative with 2 bytes extra displacement.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4 + 2)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 6));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32+2 %+d", rel);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32_3: {
                        /*
                         * REL32_3: PC-relative with 3 bytes extra displacement.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4 + 3)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 7));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32+3 %+d", rel);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32_4: {
                        /*
                         * REL32_4: PC-relative with 4 bytes extra displacement.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4 + 4)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 8));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32+4 %+d", rel);
                        break;
                    }

                    case IMAGE_REL_AMD64_REL32_5: {
                        /*
                         * REL32_5: PC-relative with 5 bytes extra displacement.
                         *
                         * Formula: *(int32_t*)fixup = symbolAddr - (fixupAddr + 4 + 5)
                         */
                        int32_t rel = (int32_t)(symbolAddress - (fixupAddress + 9));
                        memcpy(fixupAddress, &rel, sizeof(rel));
                        printf(" -> rel32+5 %+d", rel);
                        break;
                    }

                    default:
                        printf(" -> WARNING: unknown AMD64 relocation type 0x%04X",
                               relocType);
                        break;
                }
            } else {
                switch (relocType) {

                    case IMAGE_REL_I386_DIR32: {
                        /*
                         * DIR32 (x86 only): 32-bit absolute address.
                         *
                         * The fixup location already contains an addend
                         * (usually 0).  We ADD the symbol's absolute address.
                         *
                         * Formula: *(uint32_t*)fixup += (uint32_t)symbolAddr
                         */
                        uint32_t existing;
                        memcpy(&existing, fixupAddress, sizeof(existing));
                        existing += (uint32_t)(uintptr_t)symbolAddress;
                        memcpy(fixupAddress, &existing, sizeof(existing));
                        printf(" -> dir32 0x%08X", existing);
                        break;
                    }

                    case IMAGE_REL_I386_REL32: {
                        /*
                         * REL32 (x86 only): 32-bit PC-relative displacement.
                         *
                         * Same concept as AMD64 REL32 but for 32-bit code.
                         * The existing value is an addend.
                         *
                         * Formula: *(uint32_t*)fixup += symbolAddr - (fixupAddr + 4)
                         */
                        uint32_t existing;
                        memcpy(&existing, fixupAddress, sizeof(existing));
                        existing += (uint32_t)(
                            (uintptr_t)symbolAddress - ((uintptr_t)fixupAddress + 4));
                        memcpy(fixupAddress, &existing, sizeof(existing));
                        printf(" -> i386_rel32 0x%08X", existing);
                        break;
                    }

                    default:
                        printf(" -> WARNING: unknown i386 relocation type 0x%04X",
                               relocType);
                        break;
                }
            }

            printf("\n");
        }
    }
    printf("\n");

    /* ================================================================
     * PHASE 6: Find and call the entry point
     *
     * Search the symbol table for the entry function name (typically
     * "go" or "_go").  MinGW on some platforms prepends an underscore,
     * so we check both.
     *
     * The entry function has the signature:
     *   void go(char* args, int len);
     *
     * We cast the resolved address to this function pointer type
     * and call it.  This is where the BOF actually EXECUTES.
     * ================================================================ */
    typedef void (*entry_fn)(char*, int);
    entry_fn entryPoint = NULL;

    printf("--- Finding Entry Point '%s' ---\n", entryName);

    /* Build the underscore-prefixed variant */
    char altName[256];
    snprintf(altName, sizeof(altName), "_%s", entryName);

    for (int i = 0; i < numSymbols; i++) {
        const char* symName = get_symbol_name(&symbols[i], stringTable);

        if ((strcmp(symName, entryName) == 0 || strcmp(symName, altName) == 0) &&
            symbols[i].SectionNumber > 0) {
            int secIdx = symbols[i].SectionNumber - 1;
            entryPoint = (entry_fn)(sectionMapping[secIdx] + symbols[i].Value);
            printf("  Found '%s' in section %d at offset 0x%X\n",
                   symName, symbols[i].SectionNumber, symbols[i].Value);
            printf("  Entry address: %p\n\n", (void*)entryPoint);
            break;
        }

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

    if (entryPoint == NULL) {
        fprintf(stderr, "Error: entry point '%s' not found in symbol table\n",
                entryName);
        /* Cleanup before exit */
        for (int i = 0; i < numSections; i++) {
            if (sectionMapping[i]) VirtualFree(sectionMapping[i], 0, MEM_RELEASE);
        }
        free(sectionMapping);
        free(functionMapping);
        free(fileData);
        return 1;
    }

    /* ---- CALL THE BOF ---- */
    printf("=== Executing BOF ===\n");
    entryPoint(NULL, 0);
    printf("\n=== BOF Execution Complete ===\n");

    /* ================================================================
     * PHASE 7: Cleanup
     *
     * Free all allocated memory: section mappings, function pointer
     * array, and the raw file data.
     * ================================================================ */
    for (int i = 0; i < numSections; i++) {
        if (sectionMapping[i]) {
            VirtualFree(sectionMapping[i], 0, MEM_RELEASE);
        }
    }
    free(sectionMapping);
    free(functionMapping);
    free(fileData);

    printf("=== Done ===\n");
    return 0;
}
