#!/bin/bash
# ============================================================
# 无 Root 全自动部署脚本
# 一键完成：检测设备 → 注入 Gadget → 安装 → 连通 → 控制微信
# ============================================================
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SDK_DIR="$(dirname "$SCRIPT_DIR")"
GADGET_SO="$SCRIPT_DIR/frida-gadget-17.8.1-android-arm64.so"
KEYSTORE="$SCRIPT_DIR/debug.keystore"
WECHAT_PKG="com.tencent.mm"
GADGET_PORT=27042

GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
NC='\033[0m'
log()  { echo -e "${GREEN}[✓]${NC} $1"; }
warn() { echo -e "${YELLOW}[!]${NC} $1"; }
err()  { echo -e "${RED}[✗]${NC} $1"; exit 1; }
info() { echo -e "${CYAN}[→]${NC} $1"; }

# ─────────────────────────────────────
# Phase 1: 等待设备
# ─────────────────────────────────────
info "Phase 1: 等待 ADB 设备连接..."
SERIAL=""
for i in $(seq 1 60); do
    DEV=$(adb devices 2>/dev/null | grep -w "device" | head -1 | awk '{print $1}')
    if [ -n "$DEV" ]; then
        SERIAL="$DEV"
        break
    fi
    printf "."
    sleep 2
done
echo ""
[ -z "$SERIAL" ] && err "等待 120 秒仍未检测到设备，请检查 USB 连接和 USB 调试"
log "设备已连接: $SERIAL"
ADB="adb -s $SERIAL"

# 设备基本信息
MODEL=$($ADB shell getprop ro.product.model 2>/dev/null | tr -d '\r')
ANDROID=$($ADB shell getprop ro.build.version.release 2>/dev/null | tr -d '\r')
log "型号: $MODEL | Android: $ANDROID"

# ─────────────────────────────────────
# Phase 2: 检查微信
# ─────────────────────────────────────
info "Phase 2: 检查微信状态..."
WX_VER=$($ADB shell dumpsys package $WECHAT_PKG 2>/dev/null | grep versionName | head -1 | awk -F= '{print $2}' | tr -d '\r ')
[ -z "$WX_VER" ] && err "微信未安装"
log "微信版本: $WX_VER"

# 检查是否已经是注入版
ALREADY_INJECTED=$($ADB shell "run-as $WECHAT_PKG ls lib/arm64/libfrida-gadget.so 2>/dev/null" 2>/dev/null | grep -c "gadget" || echo "0")
if [ "$ALREADY_INJECTED" != "0" ]; then
    log "微信已包含 Frida Gadget，跳过注入步骤"
    SKIP_INJECT=1
else
    SKIP_INJECT=0
fi

# ─────────────────────────────────────
# Phase 3: 注入 Frida Gadget（如需）
# ─────────────────────────────────────
if [ "$SKIP_INJECT" = "0" ]; then
    info "Phase 3: 提取并注入微信 APK..."

    WORK_DIR="$SCRIPT_DIR/gadget_work"
    rm -rf "$WORK_DIR"
    mkdir -p "$WORK_DIR"

    # 提取 APK
    APK_PATH=$($ADB shell pm path $WECHAT_PKG 2>/dev/null | head -1 | sed 's/package://' | tr -d '\r\n')
    log "微信 APK: $APK_PATH"

    info "拉取 APK（可能需要几分钟）..."
    $ADB pull "$APK_PATH" "$WORK_DIR/wechat_original.apk" 2>&1 | tail -1
    APK_SIZE=$(ls -lh "$WORK_DIR/wechat_original.apk" | awk '{print $5}')
    log "APK 大小: $APK_SIZE"

    # 反编译（只反编译资源，不反编译代码以加快速度）
    info "反编译 APK..."
    apktool d -f -s -o "$WORK_DIR/wechat_dec" "$WORK_DIR/wechat_original.apk" 2>&1 | tail -2

    # 注入 gadget
    LIB_DIR="$WORK_DIR/wechat_dec/lib/arm64-v8a"
    mkdir -p "$LIB_DIR"
    cp "$GADGET_SO" "$LIB_DIR/libfrida-gadget.so"
    log "frida-gadget.so 已注入到 lib/arm64-v8a/"

    # Gadget 配置（listen 模式，自动 resume）
    cat > "$LIB_DIR/libfrida-gadget.config.so" << 'EOF'
{
  "interaction": {
    "type": "listen",
    "address": "0.0.0.0",
    "port": 27042,
    "on_port_conflict": "pick-next",
    "on_load": "resume"
  }
}
EOF
    log "Gadget 配置: listen 模式, 端口 $GADGET_PORT"

    # 修改 AndroidManifest
    MANIFEST="$WORK_DIR/wechat_dec/AndroidManifest.xml"
    sed -i.bak 's/android:extractNativeLibs="false"/android:extractNativeLibs="true"/g' "$MANIFEST" 2>/dev/null || true
    log "AndroidManifest: extractNativeLibs=true"

    # 在 smali 中注入 loadLibrary
    info "注入 System.loadLibrary(\"frida-gadget\")..."
    # 查找 Application 类
    APP_CLASS=$(grep -oP 'android:name="\K[^"]+(?=.*application)' "$MANIFEST" 2>/dev/null | head -1 || true)
    [ -z "$APP_CLASS" ] && APP_CLASS="com.tencent.mm.app.MMApplication"
    SMALI_REL=$(echo "$APP_CLASS" | sed 's/\./\//g')

    SMALI_FILE=""
    for d in "$WORK_DIR/wechat_dec"/smali*; do
        [ -f "$d/${SMALI_REL}.smali" ] && SMALI_FILE="$d/${SMALI_REL}.smali" && break
    done

    if [ -n "$SMALI_FILE" ]; then
        python3 << PYEOF
import re, sys

path = "$SMALI_FILE"
with open(path, 'r') as f:
    code = f.read()

load = '    const-string v0, "frida-gadget"\n    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\n'

# 注入到 static initializer
clinit = re.search(r'(\.method\s+.*?static\s+.*?<clinit>.*?\n.*?\.locals\s+\d+\n)', code, re.DOTALL)
if clinit:
    pos = clinit.end()
    code = code[:pos] + load + code[pos:]
    with open(path, 'w') as f:
        f.write(code)
    print("[✓] 注入到 <clinit>")
    sys.exit(0)

# 注入到 attachBaseContext 或 onCreate
for method_name in ['attachBaseContext', 'onCreate']:
    pat = rf'(\.method\s+.*?{method_name}\(.*?\n(?:.*?\n)*?    \.locals\s+\d+\n)'
    m = re.search(pat, code)
    if m:
        pos = m.end()
        code = code[:pos] + load + code[pos:]
        with open(path, 'w') as f:
            f.write(code)
        print(f"[✓] 注入到 {method_name}")
        sys.exit(0)

print("[!] 未找到合适注入点，gadget 将通过 native lib 自动加载")
PYEOF
    else
        warn "未找到 Application smali: $APP_CLASS"
    fi

    # 重新打包
    info "重新打包 APK..."
    apktool b -o "$WORK_DIR/wechat_unsigned.apk" "$WORK_DIR/wechat_dec" 2>&1 | tail -2

    # 签名
    info "签名 APK..."
    jarsigner -sigalg SHA256withRSA -digestalg SHA-256 \
        -keystore "$KEYSTORE" -storepass android -keypass android \
        "$WORK_DIR/wechat_unsigned.apk" sdkkey 2>&1 | tail -2
    cp "$WORK_DIR/wechat_unsigned.apk" "$WORK_DIR/wechat_patched.apk"
    PATCHED_SIZE=$(ls -lh "$WORK_DIR/wechat_patched.apk" | awk '{print $5}')
    log "签名完成: $PATCHED_SIZE"

    # 备份微信数据（重要！）
    info "备份微信数据..."
    $ADB shell "pm clear $WECHAT_PKG 2>/dev/null" || true
    warn "注意：重新安装会清除微信登录状态，需要重新登录"

    # 卸载 + 安装
    info "卸载原版微信..."
    $ADB shell pm uninstall $WECHAT_PKG 2>&1 || true
    info "安装注入版微信（可能需要几分钟）..."
    $ADB install "$WORK_DIR/wechat_patched.apk" 2>&1 | tail -1
    log "微信安装完成"
fi

# ─────────────────────────────────────
# Phase 4: 启动微信 + 端口转发
# ─────────────────────────────────────
info "Phase 4: 启动微信..."
$ADB shell am force-stop $WECHAT_PKG 2>/dev/null || true
sleep 1
$ADB shell am start -n $WECHAT_PKG/.ui.LauncherUI 2>&1 | tail -1
log "微信已启动，等待 Gadget 初始化..."
sleep 8

info "设置端口转发..."
$ADB forward tcp:$GADGET_PORT tcp:$GADGET_PORT 2>&1
log "端口转发: localhost:$GADGET_PORT → device:$GADGET_PORT"

# ─────────────────────────────────────
# Phase 5: 验证 Frida 连接
# ─────────────────────────────────────
info "Phase 5: 验证 Frida Gadget 连接..."
sleep 3

python3 << 'PYEOF'
import frida
import time
import sys

host = "127.0.0.1:27042"
max_retries = 5

for attempt in range(max_retries):
    try:
        mgr = frida.get_device_manager()
        device = mgr.add_remote_device(host)
        session = device.attach("Gadget")
        print(f"[✓] Frida Gadget 连接成功（尝试 {attempt+1}）")

        # 加载 Hook 脚本测试
        script = session.create_script('rpc.exports = { ping: function() { return "pong_from_gadget"; } };')
        script.load()
        result = script.exports_sync.ping()
        print(f"[✓] RPC 测试: ping → {result}")
        script.unload()
        session.detach()
        print("[✓] Frida Gadget 工作正常！")
        sys.exit(0)
    except Exception as e:
        print(f"[!] 尝试 {attempt+1}/{max_retries}: {e}")
        time.sleep(3)

print("[✗] Frida Gadget 连接失败")
sys.exit(1)
PYEOF

FRIDA_OK=$?

# ─────────────────────────────────────
# Phase 6: 加载微信 Hook 脚本
# ─────────────────────────────────────
if [ "$FRIDA_OK" = "0" ]; then
    info "Phase 6: 加载微信 Hook 脚本..."
    python3 << PYEOF
import frida, json, sys, os

host = "127.0.0.1:$GADGET_PORT"
script_path = "$SDK_DIR/agent/hook/wechat_hook_v2.js"

if not os.path.exists(script_path):
    print(f"[!] Hook 脚本不存在: {script_path}")
    sys.exit(1)

with open(script_path, 'r') as f:
    source = f.read()

mgr = frida.get_device_manager()
device = mgr.add_remote_device(host)
session = device.attach("Gadget")

script = session.create_script(source)

def on_message(message, data):
    if message['type'] == 'send':
        payload = message.get('payload', {})
        if payload.get('type') == 'hook_event':
            print(f"  事件: {payload.get('event_type', '?')} | {json.dumps(payload.get('data', {}), ensure_ascii=False)[:100]}")
        elif payload.get('type') == 'log':
            print(f"  日志: [{payload.get('tag','')}] {payload.get('message','')}")
    elif message['type'] == 'error':
        print(f"  错误: {message.get('description','')}")

script.on('message', on_message)
script.load()

rpc = script.exports_sync

try:
    pong = rpc.ping()
    print(f"[✓] Hook 脚本已加载，ping={pong}")
except Exception as e:
    print(f"[!] ping 失败: {e}")

try:
    info = rpc.get_process_info({})
    print(f"[✓] 微信进程: {json.dumps(info, ensure_ascii=False)}")
except Exception as e:
    print(f"[!] get_process_info: {e}")

try:
    status = rpc.get_hook_status({})
    print(f"[✓] Hook 状态: {json.dumps(status, ensure_ascii=False)}")
except Exception as e:
    print(f"[!] get_hook_status: {e}")

print("")
print("=" * 50)
print("[✓] 全部完成！微信 Hook 通道已连通")
print("=" * 50)
print(f"  Frida Gadget:  {host}")
print(f"  Hook 脚本:     {os.path.basename(script_path)}")
print(f"  连接模式:      无 Root (Gadget)")
print(f"  SDK 服务端:    http://localhost:8899")
print("=" * 50)

script.unload()
session.detach()
PYEOF
else
    warn "Frida 连接未成功，微信可能需要登录后再试"
fi

echo ""
log "部署完成！"
