#!/usr/bin/env python3
"""
GDB startup script for WeChat message hooking.
Run with:
  gdb -x this_script.py --args ./WeChatLinux.AppImage --no-sandbox --disable-gpu

GDB disables ASLR by default, so wechat base = 0x555555554000
"""

import gdb
import os
import time

# Breakpoint RVA from Ajax's blog (4.1.0.16, confirmed valid for 4.1.7)
BP_RVA = 0x4994BEB
# Fixed base when GDB starts process (ASLR disabled)
FIXED_BASE = 0x555555554000

LOG_FILE = "/home/hmo/projects/AgentsMeeting/gateway/linux/logs/wechat_messages.log"


def log(msg):
    with open(LOG_FILE, "a") as f:
        f.write(f"{msg}\n")
    gdb.write(f"{msg}\n")


class WechatMessageBreakpoint(gdb.Breakpoint):
    """Breakpoint that fires on each incoming WeChat message."""

    def __init__(self, address):
        super().__init__(f"*{address}")
        self.suppress = True

    def stop(self):
        try:
            msg_ptr = int(gdb.parse_and_eval("$rsi"))
            if msg_ptr == 0:
                return False

            raw_type = gdb.selected_inferior().read_memory(msg_ptr + 0x14, 4)
            msg_type = int.from_bytes(raw_type, byteorder='little', signed=True)

            raw_svrid = gdb.selected_inferior().read_memory(msg_ptr + 0x50, 8)
            svrid = int.from_bytes(raw_svrid, byteorder='little')

            raw_holder = gdb.selected_inferior().read_memory(msg_ptr + 0x20, 8)
            holder = int.from_bytes(raw_holder, byteorder='little', signed=False)
            if holder == 0:
                return False

            raw_inner = gdb.selected_inferior().read_memory(holder + 0x8, 8)
            inner = int.from_bytes(raw_inner, byteorder='little', signed=False)
            if inner == 0:
                return False

            # Read content length from inner + 0x10
            raw_len = gdb.selected_inferior().read_memory(inner + 0x10, 4)
            content_len = int.from_bytes(raw_len, byteorder='little', signed=False)
            if content_len <= 0 or content_len > 100000:
                return False

            # Read content pointer from inner + 0x0
            raw_cp = gdb.selected_inferior().read_memory(inner + 0x0, 8)
            content_ptr = int.from_bytes(raw_cp, byteorder='little', signed=False)
            if content_ptr == 0:
                return False

            raw_content = gdb.selected_inferior().read_memory(content_ptr, min(content_len * 2, 100000))
            try:
                content = raw_content.tobytes()[:content_len * 2].decode('utf-16le', errors='replace')
            except:
                content = str(raw_content)

            # Try to read sender info from msg_ptr + 0x38 (talker wxid)
            try:
                raw_talker = gdb.selected_inferior().read_memory(msg_ptr + 0x38, 8)
                talker_ptr = int.from_bytes(raw_talker, byteorder='little', signed=False)
                if talker_ptr:
                    talker_data = gdb.selected_inferior().read_memory(talker_ptr, 64)
                    talker = talker_data.tobytes().split(b'\x00')[0].decode('utf-8', errors='replace')
                else:
                    talker = "unknown"
            except:
                talker = "unknown"

            info = f"[WECHAT_MSG] type={msg_type} svrid={hex(svrid)} talker={talker}"
            log(info)
            log(f"[WECHAT_MSG] content: {content[:300]}")

            # Forward to Hermes
            try:
                import urllib.request
                import json
                payload = json.dumps({
                    "model": "nova-4",
                    "messages": [
                        {"role": "user", "content": f"[WeChat from {talker}] {content[:500]}"}
                    ]
                }).encode()
                req = urllib.request.Request(
                    "http://192.168.1.246:8642/v1/chat/completions",
                    data=payload,
                    headers={"Content-Type": "application/json", "Authorization": "Bearer hermes123"},
                    method="POST"
                )
                urllib.request.urlopen(req, timeout=2)
            except:
                pass

        except Exception as e:
            log(f"[WECHAT_MSG] ERROR: {e}")

        return False


class AutoHookWechat(gdb.Command):
    """Auto-hook WeChat messages on startup."""

    def __init__(self):
        super().__init__("auto-hook-wechat", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        bp_addr = FIXED_BASE + BP_RVA
        WechatMessageBreakpoint(bp_addr)
        log(f"[WECHAT_MSG] Breakpoint set at 0x{bp_addr:x}")


class OnStart(gdb.Breakpoint):
    """Breakpoint on _start to set up hooks after process loads."""

    def __init__(self):
        super().__init__("_start")

    def stop(self):
        gdb.execute("auto-hook-wechat")
        return True  # Stop so user can continue


# Configure GDB
gdb.execute("set pagination off")
gdb.execute("set confirm off")
gdb.execute("handle SIG33 pass nostop noprint")
gdb.execute("set follow-fork-mode child")

# Register our commands
AutoHookWechat()

# Set breakpoint at _start so we hook after process loads
OnStart()

log("[WECHAT_MSG] GDB startup script loaded. Type 'run' to start WeChat.")
