"use strict";
/*
 * TNG eWallet — 定位 TigerTally(libtiger_tally.so) 启动期 fread 阻塞。观察不改行为。
 *
 * 背景(ANR 栈): CaptchaInitializer → TigerTallyAPI.init → t.B.genericNt1(native)
 *   → libtiger_tally.so (mNYjyzyN23) → fread → __sread → read  永远读不到数据
 */
const TIGER_SO = "libtiger_tally.so";

const LIBC = Process.getModuleByName("libc.so");
const readlink = new NativeFunction(
    LIBC.findExportByName("readlink"), "long", ["pointer", "pointer", "ulong"]);

/* ---- Tiger 模块范围（热路径缓存，每 2s 刷新一次） ---- */
let tigerMod = null;
function refreshTiger() {
    const m = Process.findModuleByName(TIGER_SO);
    if (m) tigerMod = m;
    return !!tigerMod;
}
function inTiger(addr) {
    if (!addr) return false;
    if (!tigerMod) return false;
    return addr.compare(tigerMod.base) >= 0 && addr.compare(tigerMod.base.add(tigerMod.size)) < 0;
}
setInterval(() => { refreshTiger(); }, 2000);

function resolveFd(fd) {
    try {
        const link = Memory.allocUtf8String(`/proc/self/fd/${fd}`);
        const out = Memory.alloc(256);
        const n = readlink(link, out, 256);
        if (n > 0) return out.readUtf8String(Math.min(n, 255));
    } catch (e) { /* ignore */ }
    return "?";
}
function fdKind(fd) {
    const p = resolveFd(fd);
    if (p.indexOf("socket:") === 0) return "SOCKET " + p;
    if (p.indexOf("pipe:") === 0) return "PIPE " + p;
    if (p.indexOf("anon_inode:") === 0) return "ANON " + p;
    return p;
}
function threadName() {
    try { return Process.getCurrentThreadName(); } catch (e) { return "?"; }
}
function fmtAddr(a) { return a ? a.toString(16) : "?"; }

const stats = {}; // tid -> info
function bump(fd, kind, ret) {
    const tid = Process.getCurrentThreadId();
    let s = stats[tid];
    if (!s) { s = { name: threadName(), reads: 0, lastFd: fd, lastFdKind: kind, lastRet: ret }; stats[tid] = s; }
    s.name = threadName();
    s.reads++;
    s.lastFd = fd;
    s.lastFdKind = kind;
    s.lastRet = ret;
}

/* ---- fread: FILE* 第4参数; bionic __sFILE._file 偏移约 18 ---- */
Interceptor.attach(LIBC.findExportByName("fread"), {
    onEnter(args) {
        const caller = this.returnAddress;
        if (!inTiger(caller)) return;
        const fp = args[3];
        let fd = -1;
        for (const off of [18, 16, 24, 20]) {
            try { const v = fp.add(off).readU16(); if (v > 0 && v < 4096) { fd = v; break; } }
            catch (e) { /* try next */ }
        }
        const kind = fd > 0 ? fdKind(fd) : "?";
        bump(fd, kind, "pending");
        console.log(`[FREAD] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
    },
    onLeave(ret) {
        if (!inTiger(this.returnAddress)) return;
        bump(-1, "", ret.toInt32());
        console.log(`[FREAD-LEAVE] tid=${Process.getCurrentThreadId()} ret=${ret.toInt32()}`);
    }
});

/* ---- read: 只观察调用者位于 libtiger_tally.so 的 ---- */
Interceptor.attach(LIBC.findExportByName("read"), {
    onEnter(args) {
        const caller = this.returnAddress;
        if (!inTiger(caller)) return;
        const fd = args[0].toInt32();
        const kind = fdKind(fd);
        bump(fd, kind, "?");
        console.log(`[READ] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
    },
    onLeave(ret) {
        if (!inTiger(this.returnAddress)) return;
        const tid = Process.getCurrentThreadId();
        const s = stats[tid];
        const fd = s ? s.lastFd : -1;
        const r = ret.toInt32();
        if (s) s.lastRet = r;
        if (r < 0) console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r} (blocked/error)`);
        else if (r === 0) console.log(`[READ-EOF] tid=${tid} fd=${fd} ret=0 (EOF/closed)`);
        else console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r}`);
    }
});

/* ---- 周期 dump ---- */
function dumpThreads() {
    try {
        const dir = new File("/proc/self/task", "r");
        const entries = dir.list();
        dir.close();
        let relevant = [];
        for (const e of entries) {
            let nm = "?";
            try { const nf = new File(`/proc/self/task/${e}/comm`, "r"); nm = nf.readString().trim(); nf.close(); } catch (err) {}
            const s = stats[e] || null;
            const lower = nm.toLowerCase();
            if (lower.indexOf("location") >= 0 || lower.indexOf("tally") >= 0 || s) {
                let info = `tid=${e} "${nm}"`;
                if (s) info += ` tigerReads=${s.reads} lastFd=${s.lastFd} kind=${s.lastFdKind} lastRet=${s.lastRet}`;
                relevant.push(info);
            }
        }
        console.log(`[DUMP] tiger-fread threads: ${relevant.length ? relevant.join(" | ") : "(none)"}`);
    } catch (e) {
        console.log(`[DUMP] failed: ${e}`);
    }
}

setInterval(() => {
    dumpThreads();
    try {
        const dir = new File("/proc/self/fd", "r");
        const fds = dir.list();
        dir.close();
        let pipes = [], socks = [];
        for (const f of fds) {
            const kind = fdKind(parseInt(f, 10));
            if (kind.indexOf("PIPE") === 0) pipes.push(f + ":" + kind.split(" ").slice(1).join(" "));
            if (kind.indexOf("SOCKET") === 0) socks.push(f + ":" + kind.split(" ").slice(1).join(" "));
        }
        if (pipes.length) console.log(`[DUMP-FD] pipes: ${pipes.join(" | ")}`);
        if (socks.length) console.log(`[DUMP-FD] sockets: ${socks.join(" | ")}`);
    } catch (e) { /* ignore */ }
}, 3000);

console.log("[TIGER-FREAD] armed (observe-only)");
