/*
 * TNG eWallet — Zygisk companion.
 *
 * stable: PLT + ABRT/SIGTRAP swallow + exit_group seccomp@400ms.
 * Promon worker SIGSEGV (libtngdigital_ewallet.so null deref): LR-return skip (cap N).
 * pc==lr 循环 SEGV 也 skip；libc++abi __cxa_guard_acquire → SIGABRT 吞掉。
 */
#include <android/log.h>
#include <dlfcn.h>
#include <errno.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <pthread.h>
#include <signal.h>
#include <stddef.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <ucontext.h>
#include <unistd.h>

#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstring>

#include "zygisk.hpp"

#define SC_RET_ALLOW        0x7fff0000U
#define SC_RET_ERRNO_EPERM  (0x00050000U | 1U)

#define LOG_TAG "TngExitGuard"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

static constexpr const char *kTargetPkg = "my.com.tngdigital.ewallet";
static constexpr const char *kPromonSo = "libtngdigital_ewallet.so";
static bool g_enabled = false;
static zygisk::Api *g_api = nullptr;
static std::atomic<int> g_cxx_plt{0};
static std::atomic<int> g_seccomp_ok{0};
static std::atomic<int> g_stack_chk{0};
static std::atomic<int> g_promon_segv{0};
static std::atomic<uintptr_t> g_promon_start{0};
static std::atomic<uintptr_t> g_promon_end{0};
static std::atomic<pid_t> g_main_tid{0};
static std::atomic<int> g_abrt_swallow{0};
static std::atomic<pid_t> g_abrt_last_tid{0};
static std::atomic<uintptr_t> g_abrt_last_pc{0};
static std::atomic<int> g_abrt_streak{0};
/* 隔离进程 :goacqowmmt 会循环 SEGV；主进程 worker 也狂刷。cap 后 freeze 该线程 */
static constexpr int kMaxPromonSegvSkip = 200;
static constexpr int kMaxAbrtStreak = 3;
static std::atomic<int> g_soft_sig_logged{0};

static void freeze_forever() {
    for (;;) pause();
}

static void refresh_promon_so_range() {
    FILE *fp = fopen("/proc/self/maps", "r");
    if (!fp) return;
    char line[1024];
    uintptr_t start = 0, end = 0;
    while (fgets(line, sizeof(line), fp)) {
        unsigned long s = 0, e = 0;
        char path[512] = {};
        int n = sscanf(line, "%lx-%lx %*s %*s %*s %*s %511[^\n]", &s, &e, path);
        if (n < 3) continue;
        char *p = path;
        while (*p == ' ') ++p;
        if (strstr(p, kPromonSo) == nullptr) continue;
        if (start == 0 || s < start) start = s;
        if (e > end) end = e;
    }
    fclose(fp);
    if (start != 0 && end > start) {
        g_promon_start.store(start);
        g_promon_end.store(end);
    }
}

static bool pc_in_promon_so(uintptr_t pc) {
    uintptr_t start = g_promon_start.load();
    uintptr_t end = g_promon_end.load();
    return start != 0 && pc >= start && pc < end;
}

static void promon_segv_handler(int sig, siginfo_t *info, void *ctx) {
    (void)sig;
    (void)info;
    ucontext_t *uc = reinterpret_cast<ucontext_t *>(ctx);
#if defined(__aarch64__)
    uintptr_t pc = uc->uc_mcontext.pc;
    uintptr_t lr = uc->uc_mcontext.regs[30];
#else
    uintptr_t pc = 0;
    uintptr_t lr = 0;
#endif
    refresh_promon_so_range();
    /* Promon 典型 pc==lr 自旋 null deref；maps 尚未刷新时也按此 skip */
    if (pc != 0 && pc == lr) {
        int n = ++g_promon_segv;
        if (n <= 3 || n % 50 == 0) {
            LOGI("promon pc==lr SIGSEGV tid=%d pc=%lx n=%d — skip to pc+4",
                 (int)gettid(), (unsigned long)pc, n);
        }
        uc->uc_mcontext.pc = pc + 4;
        return;
    }
    if (pc_in_promon_so(pc) || pc_in_promon_so(lr)) {
        int n = ++g_promon_segv;
        uintptr_t target = lr;
        if (target == 0 || target == pc) {
            target = pc + 4;
        }
        if (kMaxPromonSegvSkip <= 0 || n <= kMaxPromonSegvSkip) {
            if (n <= 3 || n % 50 == 0) {
                LOGI("promon SIGSEGV tid=%d pc=%lx lr=%lx n=%d — skip to %lx",
                     (int)gettid(), (unsigned long)pc, (unsigned long)lr, n,
                     (unsigned long)target);
            }
            uc->uc_mcontext.pc = target;
            return;
        }
        LOGI("promon SIGSEGV tid=%d n=%d — cap hit, freeze", (int)gettid(), n);
        freeze_forever();
    }
    /* 1.9.10：Login 后出现 Promon so 外 SEGV → 旧逻辑 re-raise 直接闪退 */
    {
        int n = ++g_promon_segv;
        pid_t tid = gettid();
        if (n <= 5 || n % 50 == 0) {
            LOGI("non-promon SIGSEGV tid=%d pc=%lx lr=%lx n=%d — pc+4",
                 (int)tid, (unsigned long)pc, (unsigned long)lr, n);
        }
        if (n > 200 && tid != g_main_tid.load()) {
            LOGI("non-promon SEGV storm tid=%d — freeze", (int)tid);
            freeze_forever();
        }
        if (pc != 0) {
            uc->uc_mcontext.pc = pc + 4;
            return;
        }
    }
    freeze_forever();
}

/**
 * ABRT/TRAP：一律 pc+4。跳远距 LR 会弄坏主线程 Looper（闪退观感）。
 * 工作线程同 PC 连 abort 超限 → freeze；主线程始终 pc+4。
 */
static void fatal_skip_handler(int sig, siginfo_t *info, void *ctx) {
    (void)info;
    ucontext_t *uc = reinterpret_cast<ucontext_t *>(ctx);
#if defined(__aarch64__)
    uintptr_t pc = uc->uc_mcontext.pc;
    uintptr_t lr = uc->uc_mcontext.regs[30];
    pid_t tid = gettid();

    if (sig == SIGABRT) {
        int streak = 1;
        if (g_abrt_last_tid.load() == tid && g_abrt_last_pc.load() == pc) {
            streak = g_abrt_streak.fetch_add(1) + 1;
        } else {
            g_abrt_last_tid.store(tid);
            g_abrt_last_pc.store(pc);
            g_abrt_streak.store(1);
        }
        if (streak > kMaxAbrtStreak && tid != g_main_tid.load()) {
            LOGI("ABRT streak cap tid=%d pc=%lx n=%d — freeze worker", (int)tid,
                 (unsigned long)pc, streak);
            freeze_forever();
        }
        int n = ++g_abrt_swallow;
        /* 主线程：abort 后 _exit 被 seccomp 拦 → abort 内部死循环（还复位 handler）→
         * 主线程永久卡死 → 黑屏/ANR。主线程 ABRT 时跳回 lr（Looper pollOnce）恢复；
         * 仅当 lr 距 pc 远（不在 abort 内部）才跳，否则仍在 abort epilogue 内跳 LR 会再 abort。 */
        uintptr_t target = pc + 4;
        if (tid == g_main_tid.load() && lr != 0 && pc != 0
                && (lr < pc - 0x1000 || lr > pc + 0x1000)) {
            target = lr;
        }
        if (n <= 5 || n % 50 == 0) {
            LOGI("ABRT tid=%d pc=%lx lr=%lx streak=%d -> %lx", (int)tid,
                 (unsigned long)pc, (unsigned long)lr, streak,
                 (unsigned long)target);
        }
        uc->uc_mcontext.pc = target;
        return;
    }

    if (sig == SIGTRAP) {
        int n = ++g_abrt_swallow;
        if (n <= 5 || n % 50 == 0) {
            LOGI("TRAP pc+4 tid=%d pc=%lx", (int)tid, (unsigned long)pc);
        }
        uc->uc_mcontext.pc = pc != 0 ? pc + 4 : lr;
        return;
    }
#endif
    freeze_forever();
}

static void install_fatal_skip_handlers() {
    struct sigaction sa {};
    sa.sa_sigaction = fatal_skip_handler;
    sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGABRT, &sa, nullptr);
    sigaction(SIGTRAP, &sa, nullptr);
    LOGI("fatal skip handlers (ABRT/TRAP always pc+4)");
}

static void install_promon_segv_handler() {
    refresh_promon_so_range();
    struct sigaction sa {};
    sa.sa_sigaction = promon_segv_handler;
    sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGSEGV, &sa, nullptr) != 0) {
        LOGE("SIGSEGV handler install failed errno=%d", errno);
        return;
    }
    LOGI("promon SIGSEGV handler armed range=%lx-%lx",
         (unsigned long)g_promon_start.load(),
         (unsigned long)g_promon_end.load());
}

static void install_soft_signals() {
    struct sigaction sa {};
    sa.sa_sigaction = fatal_skip_handler;
    sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGABRT, &sa, nullptr);
    sigaction(SIGTRAP, &sa, nullptr);
    if (g_soft_sig_logged.fetch_add(1) == 0) {
        LOGI("soft signals (ABRT/TRAP always pc+4)");
    }
}

#if defined(__aarch64__)
#define _BPFI(code, jt, jf, k) \
    ((struct sock_filter){(unsigned short)(code), (jt), (jf), (unsigned int)(k)})

static int install_seccomp_exit_group_only() {
    /* exit_group 拦 + 精准拦 Promon 自杀 kill(SIGABRT)（SI_USER）。
     * 只拦 kill()（nr=129）：Promon 自杀是 kill(pid,SIGABRT) → si_code=SI_USER。
     * 不拦 tgkill/tkill：ART 的 abort() 用 tgkill(self) → si_code=SI_TKILL，
     * 拦它会让 libart 状态错乱 → 进程崩（之前验证）。 */
    struct sock_filter filter[] = {
        // 0: arch
        _BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, arch)),
        _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_AARCH64),
        _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ALLOW),                        // 2: not aarch64
        _BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, nr)),
        _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, __NR_exit_group),           // 4: exit_group? true→5
        _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ERRNO_EPERM),                  // 5: EPERM exit_group
        _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 3, __NR_kill),                 // 6: kill? true→7
        _BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, args) + 8),  // 7: args[1]=sig
        _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, SIGABRT),                   // 8: sig==SIGABRT? true→9
        _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ERRNO_EPERM),                  // 9: EPERM kill ABRT
        _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ALLOW),                        // 10: allow
    };
    struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
    };
    prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
    long rc = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
                      SECCOMP_FILTER_FLAG_TSYNC, &prog);
    if (rc != 0) {
        rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
        if (rc != 0) {
            LOGE("seccomp failed errno=%d", errno);
            return -1;
        }
        LOGI("seccomp exit_group+kill-ABRT via prctl");
    } else {
        LOGI("seccomp exit_group+kill-ABRT via TSYNC");
    }
    g_seccomp_ok.store(1);
    return 0;
}
#else
static int install_seccomp_exit_group_only() { return -1; }
#endif

using exit_fn = void (*)(int);
using kill_fn = int (*)(pid_t, int);
using tgkill_fn = int (*)(int, int, int);
using raise_fn = int (*)(int);
using pthread_kill_fn = int (*)(pthread_t, int);
using cxa_guard_acquire_fn = int (*)(void *);
using cxa_guard_abort_fn = void (*)();
using dlopen_fn = void *(*)(const char *, int);
using android_dlopen_ext_fn = void *(*)(const char *, int, const void *);
using sphal_load_fn = void *(*)(const char *, int);
using open_passthrough_hal_fn = void *(*)(const char *, const char *, int);

static cxa_guard_acquire_fn orig_cxa_guard_acquire = nullptr;
static cxa_guard_abort_fn orig_cxa_guard_abort = nullptr;
static dlopen_fn orig_dlopen = nullptr;
static android_dlopen_ext_fn orig_android_dlopen_ext = nullptr;
static sphal_load_fn orig_sphal_load = nullptr;
static open_passthrough_hal_fn orig_open_passthrough_hal = nullptr;
static void *g_libandroid_handle = nullptr;
static void *g_mapper_pixel_handle = nullptr;

#ifndef RTLD_NOW
#define RTLD_NOW 2
#endif
#ifndef RTLD_GLOBAL
#define RTLD_GLOBAL 0x100
#endif
#ifndef RTLD_NOLOAD
#define RTLD_NOLOAD 0x4
#endif
#ifndef RTLD_DEFAULT
#define RTLD_DEFAULT reinterpret_cast<void *>(static_cast<uintptr_t>(-1))
#endif

static exit_fn orig_exit = nullptr;
static exit_fn orig__exit = nullptr;
static void (*orig_abort)() = nullptr;
static void (*orig_stack_chk_fail)() = nullptr;
static kill_fn orig_kill = nullptr;
static tgkill_fn orig_tgkill = nullptr;
static raise_fn orig_raise = nullptr;
static pthread_kill_fn orig_pthread_kill = nullptr;

static bool deadly(int sig) {
    return sig == SIGKILL || sig == SIGABRT || sig == SIGTERM ||
           sig == SIGTRAP || sig == SIGILL ||
           sig == 9 || sig == 6 || sig == 5 || sig == 4 || sig == 15;
}

static void hooked_exit(int code) { LOGI("blocked exit(%d)", code); }
static void hooked__exit(int code) { LOGI("blocked _exit(%d)", code); }
static void hooked_abort() {
    LOGI("blocked abort() tid=%d", (int)gettid());
}
static void hooked_stack_chk_fail() {
    int n = ++g_stack_chk;
    LOGI("blocked __stack_chk_fail tid=%d n=%d", (int)gettid(), n);
}
static int hooked_raise(int sig) {
    if (deadly(sig)) {
        LOGI("blocked raise(%d)", sig);
        return 0;
    }
    return orig_raise ? orig_raise(sig) : -1;
}
static int hooked_kill(pid_t pid, int sig) {
    if (deadly(sig)) {
        LOGI("blocked kill(%d,%d)", (int)pid, sig);
        return 0;
    }
    return orig_kill ? orig_kill(pid, sig) : -1;
}
static int hooked_tgkill(int tgid, int tid, int sig) {
    if (deadly(sig)) {
        LOGI("blocked tgkill(%d,%d,%d)", tgid, tid, sig);
        return 0;
    }
    return orig_tgkill ? orig_tgkill(tgid, tid, sig) : -1;
}
static int hooked_pthread_kill(pthread_t thread, int sig) {
    if (deadly(sig)) {
        LOGI("blocked pthread_kill(sig=%d)", sig);
        return 0;
    }
    return orig_pthread_kill ? orig_pthread_kill(thread, sig) : -1;
}

/** 仅打断递归初始化；系统 libc++ 静态 ctor（含 gralloc）必须真实执行。 */
static thread_local void *tl_cxa_guard = nullptr;
static thread_local int tl_cxa_depth = 0;

static int hooked_cxa_guard_acquire(void *guard) {
    if (guard != nullptr && tl_cxa_guard == guard) {
        LOGI("cxa_guard recursive skip tid=%d", (int)gettid());
        return 1;
    }
    if (!orig_cxa_guard_acquire) {
        return 1;
    }
    void *prev = tl_cxa_guard;
    tl_cxa_guard = guard;
    ++tl_cxa_depth;
    int r = orig_cxa_guard_acquire(guard);
    --tl_cxa_depth;
    tl_cxa_guard = prev;
    return r;
}
static void hooked_cxa_guard_abort() {
    LOGI("blocked __cxa_guard_abort tid=%d depth=%d", (int)gettid(), tl_cxa_depth);
}

static void *try_dlopen_noload(const char *name) {
    if (!orig_dlopen || name == nullptr) {
        return nullptr;
    }
    void *h = orig_dlopen(name, RTLD_NOW | RTLD_NOLOAD);
    if (h != nullptr) {
        return h;
    }
    const char *base = strrchr(name, '/');
    if (base != nullptr && base[1] != '\0') {
        h = orig_dlopen(base + 1, RTLD_NOW | RTLD_NOLOAD);
    }
    return h;
}

/** 校验 dlopen 句柄：拒绝空/明显毒化指针，并用 dlsym 探活。 */
static bool libandroid_handle_ok(void *h) {
    if (h == nullptr) return false;
    uintptr_t p = reinterpret_cast<uintptr_t>(h);
    // 用户态典型映射；排除明显垃圾（如 0x...c5c5 / 高熵毒化）
    if (p < 0x10000UL) return false;
    if ((p & 0xffffUL) == 0xc5c5UL) return false;
    void *sym = dlsym(h, "ANativeWindow_fromSurface");
    if (sym == nullptr) {
        sym = dlsym(h, "AAssetManager_fromJava");
    }
    return sym != nullptr;
}

static void preload_hwui_libs() {
    if (!orig_dlopen) {
        // PLT 尚未拿到 orig 时，用 libc 直调
        orig_dlopen = reinterpret_cast<dlopen_fn>(dlsym(RTLD_DEFAULT, "dlopen"));
    }
    if (!orig_sphal_load) {
        orig_sphal_load = reinterpret_cast<sphal_load_fn>(
                dlsym(RTLD_DEFAULT, "android_load_sphal_library"));
    }
    if (g_libandroid_handle != nullptr && !libandroid_handle_ok(g_libandroid_handle)) {
        LOGI("drop invalid cached libandroid %p", g_libandroid_handle);
        g_libandroid_handle = nullptr;
    }
    if (g_libandroid_handle == nullptr && orig_dlopen) {
        static const char *kPaths[] = {
                "libandroid.so",
                "/system/lib64/libandroid.so",
                "/apex/com.android.runtime/lib64/libandroid.so",
        };
        for (const char *path : kPaths) {
            void *h = orig_dlopen(path, RTLD_NOW | RTLD_GLOBAL);
            if (h == nullptr) {
                h = try_dlopen_noload(path);
            }
            if (libandroid_handle_ok(h)) {
                g_libandroid_handle = h;
                LOGI("preload libandroid ok path=%s -> %p", path, h);
                break;
            }
            if (h != nullptr) {
                LOGI("preload libandroid reject path=%s -> %p", path, h);
            }
        }
        if (g_libandroid_handle == nullptr) {
            LOGI("preload libandroid FAILED");
        }
    }
    if (g_mapper_pixel_handle == nullptr) {
        if (orig_sphal_load) {
            g_mapper_pixel_handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW);
            LOGI("preload mapper via sphal -> %p", g_mapper_pixel_handle);
        }
        if (g_mapper_pixel_handle == nullptr && orig_dlopen) {
            g_mapper_pixel_handle = orig_dlopen(
                    "/vendor/lib64/hw/mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL);
            LOGI("preload mapper via path -> %p", g_mapper_pixel_handle);
        }
    }
}

static void *hooked_dlopen(const char *name, int flags) {
    void *handle = orig_dlopen ? orig_dlopen(name, flags) : nullptr;
    if (name == nullptr) {
        return handle;
    }
    if (handle != nullptr) {
        // 命名空间下偶发返回毒化非空句柄，HWUI 随后 FATAL
        if (strstr(name, "libandroid.so") != nullptr && !libandroid_handle_ok(handle)) {
            LOGI("dlopen got bad handle %p for %s — recover", handle, name);
            handle = nullptr;
        } else {
            if (strstr(name, "libandroid.so") != nullptr) {
                g_libandroid_handle = handle;
            }
            return handle;
        }
    }
    // 已映射库：命名空间下按名 dlopen 会失败，RTLD_NOLOAD 可取回句柄
    handle = try_dlopen_noload(name);
    if (handle != nullptr) {
        if (strstr(name, "libandroid.so") == nullptr || libandroid_handle_ok(handle)) {
            LOGI("dlopen NOLOAD hit name=%s -> %p", name, handle);
            if (strstr(name, "libandroid.so") != nullptr) {
                g_libandroid_handle = handle;
            }
            return handle;
        }
    }
    if (strstr(name, "libandroid.so") != nullptr) {
        if (g_libandroid_handle != nullptr && !libandroid_handle_ok(g_libandroid_handle)) {
            LOGI("drop bad cached libandroid %p", g_libandroid_handle);
            g_libandroid_handle = nullptr;
        }
        if (g_libandroid_handle != nullptr) {
            LOGI("dlopen return cached libandroid %p (from %s)",
                 g_libandroid_handle, name);
            return g_libandroid_handle;
        }
        static const char *kAndroidPaths[] = {
            "/system/lib64/libandroid.so",
            "libandroid.so",
        };
        LOGI("dlopen miss name=%s flags=0x%x tid=%d — try fallback",
             name, flags, (int)gettid());
        for (const char *path : kAndroidPaths) {
            handle = try_dlopen_noload(path);
            if (handle == nullptr && orig_dlopen) {
                handle = orig_dlopen(path, flags | RTLD_GLOBAL);
            }
            if (libandroid_handle_ok(handle)) {
                g_libandroid_handle = handle;
                LOGI("dlopen fallback %s -> %p (from %s)", path, handle, name);
                return handle;
            }
            if (handle != nullptr) {
                LOGI("dlopen fallback reject %s -> %p", path, handle);
            }
        }
        LOGI("dlopen fallback failed name=%s tid=%d", name, (int)gettid());
        return nullptr;
    }
    if (strstr(name, "mapper.pixel") != nullptr
            || strstr(name, "mapper.") != nullptr) {
        if (g_mapper_pixel_handle != nullptr) {
            LOGI("dlopen return cached mapper %p (from %s)",
                 g_mapper_pixel_handle, name);
            return g_mapper_pixel_handle;
        }
        if (orig_sphal_load) {
            handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW);
            if (handle != nullptr) {
                g_mapper_pixel_handle = handle;
                LOGI("dlopen sphal mapper -> %p (from %s)", handle, name);
                return handle;
            }
        }
        if (orig_dlopen) {
            handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so",
                                 flags | RTLD_GLOBAL);
            if (handle != nullptr) {
                g_mapper_pixel_handle = handle;
                LOGI("dlopen path mapper -> %p (from %s)", handle, name);
                return handle;
            }
        }
        LOGI("dlopen mapper failed name=%s tid=%d", name, (int)gettid());
    }
    return handle;
}

static void *hooked_android_dlopen_ext(const char *name, int flags, const void *extinfo) {
    void *handle = orig_android_dlopen_ext
            ? orig_android_dlopen_ext(name, flags, extinfo)
            : nullptr;
    if (handle != nullptr || name == nullptr) {
        return handle;
    }
    if (strstr(name, "libandroid.so") != nullptr
            || strstr(name, "mapper") != nullptr) {
        LOGI("android_dlopen_ext miss name=%s — try dlopen fallback", name);
        return hooked_dlopen(name, flags);
    }
    return handle;
}

static bool find_lib_match(const char *suffix, const char *contains,
                           dev_t *dev, ino_t *ino) {
    FILE *fp = fopen("/proc/self/maps", "r");
    if (!fp) return false;
    char line[1024];
    bool ok = false;
    while (fgets(line, sizeof(line), fp)) {
        uintptr_t start = 0, end = 0;
        char perms[8] = {};
        unsigned long long offset = 0;
        char deststr[32] = {};
        unsigned long inode = 0;
        char path[512] = {};
        int n = sscanf(line, "%lx-%lx %7s %llx %31s %lu %511[^\n]",
                       &start, &end, perms, &offset, deststr, &inode, path);
        if (n < 7 || inode == 0) continue;
        char *p = path;
        while (*p == ' ') ++p;
        bool match = false;
        if (suffix != nullptr) {
            size_t plen = strlen(p);
            size_t slen = strlen(suffix);
            match = plen >= slen && strcmp(p + plen - slen, suffix) == 0;
        } else if (contains != nullptr) {
            match = strstr(p, contains) != nullptr;
        }
        if (!match) continue;
        unsigned maj = 0, min = 0;
        if (sscanf(deststr, "%x:%x", &maj, &min) != 2) continue;
        *dev = makedev(maj, min);
        *ino = inode;
        ok = true;
        break;
    }
    fclose(fp);
    return ok;
}

static bool find_lib_by_suffix(const char *suffix, dev_t *dev, ino_t *ino) {
    return find_lib_match(suffix, nullptr, dev, ino);
}

static bool find_libc(dev_t *dev, ino_t *ino) {
    return find_lib_by_suffix("libc.so", dev, ino);
}

/** 收集 maps 里所有匹配后缀的已加载库（去重）。Zygisk commit 前必须覆盖全部副本，
 *  否则 libc++ 多副本（/system、/vendor、/apex）时只 hook 一份，调用点仍走原生实现。 */
struct lib_devino {
    dev_t dev;
    ino_t ino;
};

static int find_all_lib_by_suffix(const char *suffix, lib_devino *out, int max) {
    FILE *fp = fopen("/proc/self/maps", "r");
    if (!fp) return 0;
    char line[1024];
    int n = 0;
    while (fgets(line, sizeof(line), fp)) {
        uintptr_t start = 0, end = 0;
        char perms[8] = {};
        unsigned long long offset = 0;
        char deststr[32] = {};
        unsigned long inode = 0;
        char path[512] = {};
        int got = sscanf(line, "%lx-%lx %7s %llx %31s %lu %511[^\n]",
                         &start, &end, perms, &offset, deststr, &inode, path);
        if (got < 7 || inode == 0) continue;
        char *p = path;
        while (*p == ' ') ++p;
        size_t plen = strlen(p);
        size_t slen = strlen(suffix);
        if (plen < slen || strcmp(p + plen - slen, suffix) != 0) continue;
        unsigned maj = 0, min = 0;
        if (sscanf(deststr, "%x:%x", &maj, &min) != 2) continue;
        dev_t d = makedev(maj, min);
        ino_t in = inode;
        bool dup = false;
        for (int i = 0; i < n; i++) {
            if (out[i].dev == d && out[i].ino == in) {
                dup = true;
                break;
            }
        }
        if (dup) continue;
        if (n < max) {
            out[n].dev = d;
            out[n].ino = in;
            n++;
        }
    }
    fclose(fp);
    return n;
}

static void register_plt(zygisk::Api *api, dev_t dev, ino_t ino,
                         const char *sym, void *hook, void **orig) {
    if (!api || dev == 0 || ino == 0) return;
    api->pltHookRegister(dev, ino, sym, hook, orig);
}

static void *hooked_sphal_load(const char *name, int flags) {
    void *handle = orig_sphal_load ? orig_sphal_load(name, flags) : nullptr;
    if (handle != nullptr || name == nullptr) {
        return handle;
    }
    LOGI("sphal miss name=%s flags=0x%x — try cache/path", name, flags);
    if (strstr(name, "mapper") != nullptr) {
        if (g_mapper_pixel_handle != nullptr) {
            LOGI("sphal return cached mapper %p", g_mapper_pixel_handle);
            return g_mapper_pixel_handle;
        }
        if (orig_dlopen) {
            handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so",
                                 RTLD_NOW | RTLD_GLOBAL);
            if (handle != nullptr) {
                g_mapper_pixel_handle = handle;
                LOGI("sphal path mapper -> %p", handle);
                return handle;
            }
            handle = orig_dlopen("mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL);
            if (handle != nullptr) {
                g_mapper_pixel_handle = handle;
                LOGI("sphal name mapper -> %p", handle);
                return handle;
            }
        }
    }
    return nullptr;
}

static void *hooked_open_passthrough_hal(const char *interface, const char *instance,
                                         int dlopen_flags) {
    void *handle = orig_open_passthrough_hal
            ? orig_open_passthrough_hal(interface, instance, dlopen_flags)
            : nullptr;
    if (handle != nullptr) {
        return handle;
    }
    LOGI("passthroughHal miss iface=%s inst=%s — try mapper path",
         interface ? interface : "?", instance ? instance : "?");
    if ((interface && strstr(interface, "mapper") != nullptr)
            || (instance && strstr(instance, "pixel") != nullptr)) {
        if (g_mapper_pixel_handle != nullptr) {
            return g_mapper_pixel_handle;
        }
        if (orig_sphal_load) {
            handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW);
        }
        if (handle == nullptr && orig_dlopen) {
            handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so",
                                 RTLD_NOW | RTLD_GLOBAL);
        }
        if (handle != nullptr) {
            g_mapper_pixel_handle = handle;
            LOGI("passthroughHal mapper recovered -> %p", handle);
        }
    }
    return handle;
}

static void register_dlopen_on_lib(zygisk::Api *api, const char *suffix) {
    dev_t dev = 0;
    ino_t ino = 0;
    if (!find_lib_by_suffix(suffix, &dev, &ino)) {
        return;
    }
    // 必须 hook 调用方 PLT（libhwui/libui），只 hook libc 拦不到 HWUI 的 dlopen
    register_plt(api, dev, ino, "dlopen",
                 (void *)hooked_dlopen, (void **)&orig_dlopen);
    register_plt(api, dev, ino, "android_dlopen_ext",
                 (void *)hooked_android_dlopen_ext, (void **)&orig_android_dlopen_ext);
    register_plt(api, dev, ino, "android_load_sphal_library",
                 (void *)hooked_sphal_load, (void **)&orig_sphal_load);
    register_plt(api, dev, ino, "AServiceManager_openDeclaredPassthroughHal",
                 (void *)hooked_open_passthrough_hal,
                 (void **)&orig_open_passthrough_hal);
    LOGI("dlopen PLT on %s", suffix);
}

/** 注册所有已加载 libc++ 副本的 __cxa_guard_acquire/abort。返回注册的副本数。 */
static int register_cxx_guard_hooks(zygisk::Api *api) {
    if (!api) return 0;
    lib_devino libs[8];
    int n = find_all_lib_by_suffix("libc++.so", libs, 8);
    if (n == 0) {
        n = find_all_lib_by_suffix("libc++_shared.so", libs, 8);
    }
    for (int i = 0; i < n; i++) {
        register_plt(api, libs[i].dev, libs[i].ino, "__cxa_guard_acquire",
                     (void *)hooked_cxa_guard_acquire, (void **)&orig_cxa_guard_acquire);
        register_plt(api, libs[i].dev, libs[i].ino, "__cxa_guard_abort",
                     (void *)hooked_cxa_guard_abort, (void **)&orig_cxa_guard_abort);
    }
    if (n > 0) {
        LOGI("cxx guard target libc++ copies=%d", n);
    }
    return n;
}

static void install_plt(zygisk::Api *api) {
    if (!api) return;
    dev_t dev = 0;
    ino_t ino = 0;
    if (find_libc(&dev, &ino)) {
        register_plt(api, dev, ino, "exit", (void *)hooked_exit, (void **)&orig_exit);
        register_plt(api, dev, ino, "_exit", (void *)hooked__exit, (void **)&orig__exit);
        register_plt(api, dev, ino, "abort", (void *)hooked_abort, (void **)&orig_abort);
        register_plt(api, dev, ino, "__stack_chk_fail",
                     (void *)hooked_stack_chk_fail, (void **)&orig_stack_chk_fail);
        register_plt(api, dev, ino, "raise", (void *)hooked_raise, (void **)&orig_raise);
        register_plt(api, dev, ino, "kill", (void *)hooked_kill, (void **)&orig_kill);
        register_plt(api, dev, ino, "tgkill", (void *)hooked_tgkill, (void **)&orig_tgkill);
        register_plt(api, dev, ino, "pthread_kill",
                     (void *)hooked_pthread_kill, (void **)&orig_pthread_kill);
        register_plt(api, dev, ino, "dlopen", (void *)hooked_dlopen, (void **)&orig_dlopen);
        register_plt(api, dev, ino, "android_dlopen_ext",
                     (void *)hooked_android_dlopen_ext, (void **)&orig_android_dlopen_ext);
    }
    // HWUI / libui 直接 PLT→linker，必须单独挂
    register_dlopen_on_lib(api, "libhwui.so");
    register_dlopen_on_lib(api, "libui.so");
    register_dlopen_on_lib(api, "libandroid_runtime.so");
    register_dlopen_on_lib(api, "libbinder_ndk.so");
    register_dlopen_on_lib(api, "libvndksupport.so");
    // libc++ cxa guard 必须在首次 commit 前注册：Zygisk pltHookCommit 二次调用会失败，
    // 导致 __cxa_guard_acquire 递归 abort 保护从未生效（主线程反复 SIGABRT → 黑屏）。
    int cxx = register_cxx_guard_hooks(api);
    bool ok = api->pltHookCommit();
    LOGI("PLT commit=%d cxx_guard_copies=%d", ok ? 1 : 0, cxx);
    if (ok && cxx > 0) {
        g_cxx_plt.store(1);
    }
    // commit 后立刻预加载，抢在 Promon/命名空间收紧之前拿到句柄
    preload_hwui_libs();
}

static void try_install_cxx_guard_plt() {
    if (g_cxx_plt.load() || !g_api) return;
    dev_t dev = 0;
    ino_t ino = 0;
    bool any = false;
    // libc++ 副本已由首次 PLT commit 覆盖；此处仅补 libtngdigital_ewallet.so 自身 PLT。
    if (find_lib_by_suffix("libtngdigital_ewallet.so", &dev, &ino)) {
        register_plt(g_api, dev, ino, "__cxa_guard_acquire",
                     (void *)hooked_cxa_guard_acquire, (void **)&orig_cxa_guard_acquire);
        register_plt(g_api, dev, ino, "__cxa_guard_abort",
                     (void *)hooked_cxa_guard_abort, (void **)&orig_cxa_guard_abort);
        any = true;
        LOGI("cxx guard target libtngdigital_ewallet");
    }
    if (!any) return;
    bool ok = g_api->pltHookCommit();
    LOGI("cxx guard commit=%d", ok ? 1 : 0);
    if (ok) {
        g_cxx_plt.store(1);
        LOGI("PLT cxx guards committed (orig acquire + recursive skip)");
    }
}

static void *phase_thread(void *) {
    install_promon_segv_handler();
    /* Promon 用 SVC exit_group 绕过 PLT；必须 seccomp。延迟 400ms 避开最早的 fork/getprop。 */
    usleep(400 * 1000);
    if (install_seccomp_exit_group_only() == 0) {
        LOGI("seccomp exit_group armed @400ms");
    } else {
        LOGE("seccomp install failed");
    }
    for (int i = 0; i < 40; i++) {
        usleep(1000 * 1000);
        install_soft_signals();
        install_promon_segv_handler();
        try_install_cxx_guard_plt();
        if (i % 5 == 0) refresh_promon_so_range();
    }
    LOGI("phase done seccomp=%d stack_chk=%d promon_segv=%d",
         g_seccomp_ok.load(), g_stack_chk.load(), g_promon_segv.load());
    return nullptr;
}

static void install_all(zygisk::Api *api) {
    g_api = api;
    g_main_tid.store(gettid());
    LOGI("install pid=%d main_tid=%d (PLT+cxx-guard+ABRT-pc+4+SEGV-skip+seccomp@400ms)",
         getpid(), (int)g_main_tid.load());
    install_fatal_skip_handlers();
    install_soft_signals();
    install_plt(api);
    pthread_t th;
    if (pthread_create(&th, nullptr, phase_thread, nullptr) == 0) {
        pthread_detach(th);
    }
    LOGI("ready");
}

class TngExitGuardModule : public zygisk::ModuleBase {
public:
    void onLoad(zygisk::Api *api, JNIEnv *env) override {
        this->api = api;
        this->env = env;
    }

    void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
        const char *nice = nullptr;
        if (args->nice_name) {
            nice = env->GetStringUTFChars(args->nice_name, nullptr);
        }
        bool match = nice && (
                std::strncmp(nice, kTargetPkg, std::strlen(kTargetPkg)) == 0);
        if (nice) env->ReleaseStringUTFChars(args->nice_name, nice);
        g_enabled = match;
        if (!match) {
            api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY);
            return;
        }
        LOGI("preAppSpecialize matched");
    }

    void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
        (void)args;
        if (!g_enabled) return;
        install_all(api);
    }

private:
    zygisk::Api *api = nullptr;
    JNIEnv *env = nullptr;
};

REGISTER_ZYGISK_MODULE(TngExitGuardModule)
