#!/usr/bin/env python3
"""Race-window: dump TNG executable maps and hunt kill+SVC after launch."""
import re
import subprocess
import sys
import time
from pathlib import Path

ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
OUT = Path(__file__).resolve().parents[1] / "dumps" / "tng_runtime"
OUT.mkdir(parents=True, exist_ok=True)

# aarch64 movz x8/w8,#129/130/131 + svc #0
KILL_IMMS = [
    bytes.fromhex("281080d2"),
    bytes.fromhex("28108052"),
    bytes.fromhex("481080d2"),
    bytes.fromhex("48108052"),
    bytes.fromhex("681080d2"),
    bytes.fromhex("68108052"),
]
SVC0 = bytes.fromhex("010000d4")
EXIT_IMM = bytes.fromhex("c80b80d2")  # movz x8,#94 exit_group
EXIT2 = bytes.fromhex("ba0b80d2")  # movz x8,#93 exit


def adb(*args, check=False):
    r = subprocess.run([ADB, *args], capture_output=True)
    out = (r.stdout or b"") + (r.stderr or b"")
    if check and r.returncode != 0:
        raise RuntimeError(out.decode("utf-8", "ignore"))
    return r.returncode, out.decode("utf-8", "replace")


def su(cmd):
    return adb("shell", f"su -c '{cmd}'")


def main():
    adb("shell", "am", "force-stop", PKG)
    time.sleep(0.5)
    adb("shell", "monkey", "-p", PKG, "-c", "android.intent.category.LAUNCHER", "1")

    pid = None
    for _ in range(40):
        time.sleep(0.25)
        _, out = adb("shell", "pidof", PKG)
        toks = out.strip().split()
        if toks:
            pid = toks[0]
            break
    if not pid:
        print("FAIL: no pid")
        return 1
    print(f"pid={pid}")

    # pull maps
    _, maps = su(f"cat /proc/{pid}/maps")
    maps_path = OUT / f"maps_{pid}.txt"
    maps_path.write_text(maps, encoding="utf-8", errors="replace")
    print(f"maps -> {maps_path} lines={len(maps.splitlines())}")

    targets = []
    for line in maps.splitlines():
        if "r-xp" not in line and "r-x" not in line:
            # also rw-p with execute rarely; keep x
            if "x" not in line.split()[1] if len(line.split()) > 1 else "":
                continue
        if "tngdigital" in line or "libnative" in line or "ewallet" in line.lower():
            parts = line.split()
            rng = parts[0]
            start_s, end_s = rng.split("-")
            start, end = int(start_s, 16), int(end_s, 16)
            path = parts[-1] if len(parts) >= 6 else ""
            targets.append((start, end, path, line))

    print(f"target segments={len(targets)}")
    for start, end, path, line in targets[:12]:
        print(f"  {hex(start)}-{hex(end)} {path}")

    # dump via dd from /proc/pid/mem
    remote = f"/data/local/tmp/tng_rt_{pid}.bin"
    su(f"rm -f {remote}")
    total = 0
    for i, (start, end, path, _) in enumerate(targets):
        size = end - start
        if size <= 0 or size > 32 * 1024 * 1024:
            continue
        # append dump
        cmd = (
            f"dd if=/proc/{pid}/mem bs=4096 skip={start // 4096} "
            f"count={(size + 4095) // 4096} 2>/dev/null >> {remote}"
        )
        # dd skip is in blocks from file start — wrong for /proc/pid/mem!
        # Use busybox dd with seek on output and skip via python on device instead.
        cmd = (
            f"toybox dd if=/proc/{pid}/mem of={remote}.p{i} "
            f"bs=1 skip={start} count={size} 2>/dev/null"
        )
        code, _ = su(cmd)
        if code == 0:
            total += size
            print(f"  dumped p{i} size={size} from {path}")
        else:
            # fallback: python on device
            py = (
                f"python3 -c \"import sys;f=open('/proc/{pid}/mem','rb');"
                f"f.seek({start});d=f.read({size});open('{remote}.p{i}','wb').write(d)\""
            )
            code2, out2 = su(py)
            if code2 == 0:
                total += size
                print(f"  dumped p{i} via python size={size}")
            else:
                print(f"  FAIL dump p{i}: {out2[:120]}")

    # pull pieces and scan
    local_dir = OUT / f"mem_{pid}"
    local_dir.mkdir(exist_ok=True)
    kill_hits = 0
    exit_hits = 0
    for i, (start, end, path, _) in enumerate(targets):
        rem = f"{remote}.p{i}"
        loc = local_dir / f"seg_{i}_{start:x}.bin"
        code, _ = adb("shell", f"su -c 'test -f {rem} && echo OK'")
        if "OK" not in _:
            continue
        adb("pull", rem, str(loc))
        if not loc.exists():
            continue
        data = loc.read_bytes()
        for imm in KILL_IMMS:
            pos = 0
            while True:
                j = data.find(imm, pos)
                if j < 0:
                    break
                win = data[j : j + 36]
                if SVC0 in win:
                    kill_hits += 1
                    delta = win.find(SVC0)
                    print(f"KILL+SVC seg{i} file+0x{j:x} va=0x{start+j:x} delta={delta} path={path}")
                pos = j + 4
        for imm in (EXIT_IMM, EXIT2):
            pos = 0
            while True:
                j = data.find(imm, pos)
                if j < 0:
                    break
                win = data[j : j + 36]
                if SVC0 in win:
                    exit_hits += 1
                    if exit_hits <= 15:
                        print(f"EXIT+SVC seg{i} file+0x{j:x} va=0x{start+j:x} path={path}")
                pos = j + 4
        # also count raw svc
        print(f"  seg{i} svc0={data.count(SVC0)} size={len(data)}")

    print(f"DONE kill+svc={kill_hits} exit+svc={exit_hits} dumped_bytes~={total}")
    _, alive = adb("shell", "pidof", PKG)
    print(f"still alive? {alive.strip() or 'NO'}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
