#!/usr/bin/env python3
"""Deeper TNG reverse: Exiting/Report, TigerTally API, kill-SVC in SO."""
from pathlib import Path
import re
import struct

ROOT = Path(__file__).resolve().parents[2]
APK_DIR = ROOT / "reverse" / "apks" / "tng"
SO = APK_DIR / "libtngdigital_ewallet.so"

def load_dexes():
    files = sorted(APK_DIR.glob("classes*.dex"))
    if not files:
        # try extracted under other layouts
        files = sorted((ROOT / "reverse" / "apks").rglob("tng*/classes*.dex"))
    return files

def near(data, needle, before=40, after=80):
    out = []
    for m in re.finditer(re.escape(needle), data):
        s = max(0, m.start() - before)
        e = min(len(data), m.end() + after)
        chunk = data[s:e]
        printable = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
        out.append(printable)
    return out

def main():
    dexes = load_dexes()
    print(f"dex count={len(dexes)}")
    all_data = b""
    for d in dexes:
        data = d.read_bytes()
        all_data += data
        hits = []
        for k in [b"Exiting:", b"Exiting", b"Report", b"W: 16", b"W:16",
                  b"TigerTallyAPI", b"ttInit", b"collect", b"killProcess",
                  b"SIGABRT", b"abort(", b"tgkill"]:
            c = data.count(k)
            if c:
                hits.append(f"{k.decode('latin1')}x{c}")
        if hits:
            print(f"{d.name}: {', '.join(hits)}")

    print("\n=== near Exiting ===")
    for s in near(all_data, b"Exiting")[:15]:
        print(" ", s)

    print("\n=== TigerTallyAPI method refs ===")
    for m in sorted(set(re.findall(rb"Lcom/aliyun/TigerTally/TigerTallyAPI;->[A-Za-z0-9_<>$]+", all_data))):
        print(" ", m.decode())

    print("\n=== TigerTally t/ classes ===")
    for m in sorted(set(re.findall(rb"Lcom/aliyun/TigerTally/[a-z]/[A-Za-z0-9_/$]*;", all_data))):
        print(" ", m.decode())

    print("\n=== xwwqazamx/bl method refs ===")
    for m in sorted(set(re.findall(rb"Lxwwqazamx/bl;->[A-Za-z0-9_<>$]+", all_data)))[:40]:
        print(" ", m.decode())

    print("\n=== Process.killProcess / Runtime.exit refs near Promon ===")
    for pat in [rb"Landroid/os/Process;->killProcess", rb"Ljava/lang/System;->exit",
                rb"Ljava/lang/Runtime;->exit", rb"Ljava/lang/Runtime;->halt"]:
        print(pat.decode(), "count=", len(re.findall(pat, all_data)))

    if SO.exists():
        so = SO.read_bytes()
        print(f"\n=== SO {SO.name} size={len(so)} ===")
        # movz x8,#129 = D2801028 LE
        patterns = {
            "movz_x8_129": bytes.fromhex("281080d2"),
            "movz_w8_129": bytes.fromhex("28108052"),
            "movz_x8_130": bytes.fromhex("481080d2"),
            "movz_x8_131": bytes.fromhex("681080d2"),
            "svc0": bytes.fromhex("010000d4"),
            "brk0": bytes.fromhex("000020d4"),
        }
        for name, pat in patterns.items():
            print(f"  {name}: {so.count(pat)}")

        # find movz kill + nearby svc within 32 bytes
        kill_imm = [bytes.fromhex(x) for x in ("281080d2", "28108052", "481080d2", "681080d2")]
        svc = bytes.fromhex("010000d4")
        found = 0
        for imm in kill_imm:
            start = 0
            while True:
                i = so.find(imm, start)
                if i < 0:
                    break
                window = so[i:i+36]
                if svc in window:
                    found += 1
                    if found <= 20:
                        off = window.find(svc)
                        print(f"  kill+svc @ file+0x{i:x} svc_delta={off}")
                start = i + 4
        print(f"  kill+svc pairs (packed): {found}")
    else:
        print(f"\nSO missing: {SO}")

if __name__ == "__main__":
    main()
