/*
 * coff_inspector.c -- COFF binary structure dumper
 * Step 02: A standalone tool that reads a .o/.obj file and prints
 *          every COFF structure: file header, sections, symbols,
 *          string table, and relocations.
 *
 * This tool helps you SEE the binary format described in Module 2.
 * Compile it as a normal program (WITH linking -- this is NOT a BOF):
 *
 *   MinGW:  x86_64-w64-mingw32-gcc coff_inspector.c -o coff_inspector.exe
 *   MSVC:   cl.exe coff_inspector.c /Fe:coff_inspector.exe
 *   Linux:  gcc coff_inspector.c -o coff_inspector
 *
 * Usage:
 *   coff_inspector.exe hello_bof.o
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "COFFLoader.h"

/* ---------- helpers ---------- */

static const char* machine_name(uint16_t machine) {
    switch (machine) {
        case MACHINE_AMD64: return "AMD64 (x64)";
        case MACHINE_I386:  return "i386 (x86)";
        default:            return "Unknown";
    }
}

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 "???";
        }
    }
}

static const char* storage_class_name(uint8_t cls) {
    switch (cls) {
        case IMAGE_SYM_CLASS_EXTERNAL: return "EXTERNAL";
        case IMAGE_SYM_CLASS_STATIC:   return "STATIC";
        case IMAGE_SYM_CLASS_LABEL:    return "LABEL";
        case IMAGE_SYM_CLASS_FUNCTION: return "FUNCTION";
        default:                       return "other";
    }
}

/* Retrieve the symbol name, handling short and long names */
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];
    }
}

/* Print section characteristics flags */
static void print_characteristics(uint32_t ch) {
    if (ch & IMAGE_SCN_CNT_CODE)               printf(" CODE");
    if (ch & IMAGE_SCN_CNT_INITIALIZED_DATA)   printf(" INIT_DATA");
    if (ch & IMAGE_SCN_CNT_UNINITIALIZED_DATA) printf(" UNINIT_DATA");
    if (ch & IMAGE_SCN_MEM_EXECUTE)            printf(" EXEC");
    if (ch & IMAGE_SCN_MEM_READ)               printf(" READ");
    if (ch & IMAGE_SCN_MEM_WRITE)              printf(" WRITE");
    if (ch & IMAGE_SCN_MEM_DISCARDABLE)        printf(" DISCARD");
}

/* ---------- main ---------- */

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <coff_file.o>\n", argv[0]);
        return 1;
    }

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

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

    unsigned char* data = (unsigned char*)malloc(filesize);
    if (!data) {
        fprintf(stderr, "Error: malloc failed\n");
        fclose(f);
        return 1;
    }
    fread(data, 1, filesize, f);
    fclose(f);

    printf("=== COFF Inspector ===\n");
    printf("File: %s (%ld bytes)\n\n", argv[1], filesize);

    /* ---- File Header ---- */
    coff_file_header_t* hdr = (coff_file_header_t*)data;

    printf("--- COFF File Header (20 bytes at offset 0x00) ---\n");
    printf("  Machine:              0x%04X  (%s)\n", hdr->Machine, machine_name(hdr->Machine));
    printf("  NumberOfSections:     %u\n", hdr->NumberOfSections);
    printf("  TimeDateStamp:        0x%08X\n", hdr->TimeDateStamp);
    printf("  PointerToSymbolTable: 0x%08X\n", hdr->PointerToSymbolTable);
    printf("  NumberOfSymbols:      %u\n", hdr->NumberOfSymbols);
    printf("  SizeOfOptionalHeader: %u\n", hdr->SizeOfOptionalHeader);
    printf("  Characteristics:      0x%04X\n\n", hdr->Characteristics);

    /* ---- Section Table ---- */
    coff_sect_t* sections = (coff_sect_t*)(data + sizeof(coff_file_header_t));

    printf("--- Section Table (%u sections) ---\n", hdr->NumberOfSections);
    for (int i = 0; i < hdr->NumberOfSections; i++) {
        char name[9] = {0};
        memcpy(name, sections[i].Name, 8);

        printf("  [%d] %-8s  RawSize=0x%04X  RawPtr=0x%04X  "
               "Relocs=%u  Chars=0x%08X",
               i, name,
               sections[i].SizeOfRawData,
               sections[i].PointerToRawData,
               sections[i].NumberOfRelocations,
               sections[i].Characteristics);
        print_characteristics(sections[i].Characteristics);
        printf("\n");
    }
    printf("\n");

    /* ---- Symbol Table ---- */
    coff_sym_t* symbols = (coff_sym_t*)(data + hdr->PointerToSymbolTable);
    char* string_table  = ((char*)symbols) + (hdr->NumberOfSymbols * sizeof(coff_sym_t));
    uint32_t strtab_size = *(uint32_t*)string_table;

    printf("--- Symbol Table (%u entries, string table size=%u) ---\n",
           hdr->NumberOfSymbols, strtab_size);

    for (uint32_t i = 0; i < hdr->NumberOfSymbols; i++) {
        const char* name = get_symbol_name(&symbols[i], string_table);
        printf("  [%2u] %-40s  Sec=%2d  Val=0x%04X  Class=%-8s  Type=0x%04X",
               i, name,
               (int16_t)symbols[i].SectionNumber,
               symbols[i].Value,
               storage_class_name(symbols[i].StorageClass),
               symbols[i].Type);

        if (symbols[i].SectionNumber == 0 && symbols[i].StorageClass == IMAGE_SYM_CLASS_EXTERNAL)
            printf("  <-- UNRESOLVED EXTERNAL");
        else if (symbols[i].SectionNumber > 0 && symbols[i].StorageClass == IMAGE_SYM_CLASS_EXTERNAL)
            printf("  <-- DEFINED EXTERNAL");

        printf("\n");

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

    /* ---- Relocations (per section) ---- */
    printf("--- Relocations ---\n");
    for (int s = 0; s < hdr->NumberOfSections; s++) {
        if (sections[s].NumberOfRelocations == 0) continue;

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

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

        coff_reloc_t* relocs = (coff_reloc_t*)(data + sections[s].PointerToRelocations);
        for (int r = 0; r < sections[s].NumberOfRelocations; r++) {
            const char* sym_name = get_symbol_name(
                &symbols[relocs[r].SymbolTableIndex], string_table);
            printf("    Offset=0x%04X  SymIdx=%2u (%s)  Type=0x%04X (%s)\n",
                   relocs[r].VirtualAddress,
                   relocs[r].SymbolTableIndex,
                   sym_name,
                   relocs[r].Type,
                   reloc_type_name(hdr->Machine, relocs[r].Type));
        }
    }

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