# -*- coding: utf-8 -*-
"""Dump JNI/native methods for sdkutils crypto + register-related classes."""
import re
import subprocess
import zipfile
from pathlib import Path
from typing import List

APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
OUT = Path(__file__).resolve().parent.parent / "output" / "crypto_jni_targets.txt"

CLASS_PATTERNS = [
    rb"Lcom/shopee/bke/lib/jni/[^;]{1,80};",
    rb"Lcom/shopee/shpssdkbank/wvvvuwwu;",
    rb"Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;",
    rb"Lcom/shopee/shpssdkbank/uwuvuvvww/[^;]{1,40};",
]

EXTRA_KEYWORDS = (
    b"NativeEncrypt",
    b"CharacterCrypto",
    b"SecurityMain",
    b"encrypt",
    b"decrypt",
    b"register",
)


def dump_class(out: str, target: str) -> List[str]:
    lines: list[str] = []
    cap = False
    for line in out.splitlines():
        if f"Class descriptor  : '{target}'" in line:
            cap = True
        elif cap and line.startswith("  Class descriptor") and target not in line:
            break
        if cap:
            lines.append(line)
    return lines


def main() -> None:
    with zipfile.ZipFile(str(APK)) as zf:
        data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))

    classes: set[str] = set()
    for pat in CLASS_PATTERNS:
        for m in re.finditer(pat, data):
            classes.add(m.group().decode()[1:-1].replace("/", "."))

    for kw in EXTRA_KEYWORDS:
        for m in re.finditer(rb"Lcom/[^;]{0,120};", data):
            s = m.group()
            if kw in s or kw in data[data.find(s) : data.find(s) + 4000]:
                classes.add(s.decode()[1:-1].replace("/", "."))

    # log tags -> classes from crash log
    for tag in [
        "com.shopee.bke.lib.jni.utils.f",  # SoUtils
        "com.shopee.bke.lib.jni.uwuwuwuw",
        "com.shopee.bke.lib.jni.uvuvuvuv",
        "com.shopee.bke.lib.jni.uvwwwwuv",
    ]:
        classes.add(tag)

    native_entries: List[str] = []
    with zipfile.ZipFile(str(APK)) as zf:
        for dex_name in zf.namelist():
            if not dex_name.endswith(".dex"):
                continue
            dex = zf.read(dex_name)
            hits = [c for c in classes if c.replace(".", "/") in dex.decode("latin1", errors="ignore")]
            if not hits:
                continue
            tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_crypto_jni.dex"
            tmp.write_bytes(dex)
            out = subprocess.check_output(
                [str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
            )
            for cls in sorted(hits):
                block = dump_class(out, "L" + cls.replace(".", "/") + ";")
                if not block:
                    continue
                native_entries.append(f"\n=== {dex_name} {cls} ===")
                for line in block:
                    if any(
                        k in line
                        for k in ("NATIVE", "name          :", "type          :", "loadLibrary")
                    ):
                        native_entries.append(line)

    text = "\n".join(native_entries)
    OUT.write_text(text, encoding="utf-8")
    print(text)
    print(f"\nwritten {OUT}")


if __name__ == "__main__":
    main()
