#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <setjmp.h>
#include <signal.h>

/* ---------------------------------------------------------------------------
 * ic_hook.c -- LD_PRELOAD interposer that turns the ionCube 8.5 loader into a
 * SAFE disassembler with NO gdb and NO reimplementation of the deserialiser.
 *
 * It preempts the loader's re-threaded executor `internal_execute_ex`.  By the
 * time that runs, ic_execute_ex_hook has already called rjY() to build the full
 * zend_op[] for the op_array, but NOT ONE opcode has executed.  We decrypt each
 * opline handler statically (they are stored XOR-encrypted, decrypted per-op at
 * dispatch), print machine-readable rows, and _exit() -- the target program
 * never runs.
 *
 *   handler[i] = enc ^ sxtw32(key_table[i] * 0x01010101)
 *   key_table  = (*(base+0x257080) -> +160)[ *(op_array+8) ]
 *
 * Output (stderr, tab-separated) is consumed by icdis/dynamic.py.
 * ------------------------------------------------------------------------- */

static uint64_t g_base;

static inline uint64_t rd64(uint64_t a){ return *(volatile uint64_t *)a; }
static inline uint32_t rd32(uint64_t a){ return *(volatile uint32_t *)a; }
static inline uint8_t  rd8 (uint64_t a){ return *(volatile uint8_t  *)a; }

/* longjmp guard for speculative reads: a bad address aborts just that read */
static sigjmp_buf g_jb;
static volatile int g_guard;
static void on_segv(int sig){ (void)sig; if (g_guard) siglongjmp(g_jb, 1); _exit(0); }

/* copy a zend_string's bytes into out[] (NUL-terminated; controls -> space) */
static void read_str(uint64_t zstr, char *out, size_t outsz)
{
    out[0] = 0;
    if (!zstr) return;
    uint64_t len = rd64(zstr + 0x10);
    if (len >= outsz) len = outsz - 1;
    for (uint64_t k = 0; k < len; k++) {
        uint8_t c = rd8(zstr + 0x18 + k);
        out[k] = (c == '\t' || c == '\n' || c == '\r') ? ' ' : c;
    }
    out[len] = 0;
}

/* best-effort resolve a CONST operand: zval at (opline + (int32)val) */
static void resolve(uint64_t opline, uint32_t val, char *out, size_t outsz)
{
    out[0] = 0;
    g_guard = 1;
    if (sigsetjmp(g_jb, 1)) { g_guard = 0; out[0] = 0; return; }

    uint64_t z = opline + (int64_t)(int32_t)val;   /* opline-relative, signed */
    uint8_t t = rd8(z + 8);                         /* zval.u1.type */
    uint64_t v = rd64(z);                           /* zval.value   */
    switch (t) {
    case 1:                                          /* IS_NULL  */
        snprintf(out, outsz, "null");
        break;
    case 2: case 3:                                  /* IS_FALSE / IS_TRUE */
        snprintf(out, outsz, "bool:%d", t == 3);
        break;
    case 4:                                          /* IS_LONG   */
        snprintf(out, outsz, "int:%lld", (long long)v);
        break;
    case 5: {                                        /* IS_DOUBLE */
        double d; memcpy(&d, &v, 8);
        snprintf(out, outsz, "float:%g", d);
        break;
    }
    case 6: case 7: case 8: {                        /* string variants */
        char s[220];
        read_str(v, s, sizeof s);
        snprintf(out, outsz, "str:%s", s);
        break;
    }
    }
    g_guard = 0;
}

void internal_execute_ex(void *execute_data)
{
    signal(SIGSEGV, on_segv);
    signal(SIGBUS, on_segv);
    Dl_info info;
    void *real = dlsym(RTLD_NEXT, "internal_execute_ex");
    g_base = (dladdr(real, &info)) ? (uint64_t)info.dli_fbase : 0;

    uint64_t ed = (uint64_t)execute_data;
    uint64_t func = rd64(ed + 0x18);
    uint64_t opcodes = rd64(func + 0x68);
    uint32_t last = rd32(func + 0x60);
    uint64_t oa = rd64(func + 0xe8);
    uint32_t key_index = rd32(oa + 8);

    uint64_t kt = 0;
    if (key_index != 0xffffffff && g_base) {
        uint64_t g = rd64(g_base + 0x257080);
        uint64_t ktbase = rd64(g + 160);
        kt = rd64(ktbase + (uint64_t)key_index * 8);
    }

    char file[256];
    read_str(rd64(func + 0xa8), file, sizeof file);
    fprintf(stderr, "IC\tfile\t%s\tlast\t%u\n", file[0] ? file : "?", last);

    /* CV (compiled-variable) names: op_array->last_var @0x5c, ->vars @0x80
       (PHP 8.5; anchored to last@0x60 / opcodes@0x68). */
    uint32_t last_var = rd32(func + 0x5c);
    uint64_t vars = rd64(func + 0x80);
    for (uint32_t i = 0; i < last_var && i < 4096; i++) {
        char nm[128];
        read_str(rd64(vars + (uint64_t)i * 8), nm, sizeof nm);
        fprintf(stderr, "CV\t%u\t%s\n", i, nm[0] ? nm : "?");
    }

    for (uint32_t i = 0; i < last; i++) {
        uint64_t op = opcodes + (uint64_t)i * 0x20;
        uint64_t enc = rd64(op);
        uint64_t h = enc;
        if (kt) {
            uint8_t k = rd8(kt + i);
            int32_t x = (int32_t)(k * 0x01010101u);   /* sxtw32 of the key word */
            h = enc ^ (uint64_t)(int64_t)x;
        }
        uint64_t hrel = h - g_base;
        uint32_t o1 = rd32(op + 0x08), o2 = rd32(op + 0x0c), res = rd32(op + 0x10);
        uint32_t ext = rd32(op + 0x14), line = rd32(op + 0x18);
        char l1[256], l2[256];
        resolve(op, o1, l1, sizeof l1);
        resolve(op, o2, l2, sizeof l2);
        fprintf(stderr, "OP\t%u\t%#lx\t%#x\t%#x\t%#x\t%#x\t%u\t%s\t%s\n",
                i, hrel, o1, o2, res, ext, line,
                l1[0] ? l1 : "-", l2[0] ? l2 : "-");
    }
    fflush(stderr);
    _exit(0);
}
