#!/usr/bin/env python3
"""Sylpheed static-analysis helper over DuckDB `sylpheed.db`.

Hides the gotchas: DECIMAL bounds (DuckDB rejects 0x literals), read-only connect,
and the fact that the engine vtable / rdata is NOT in the DB (read it from guest
memory with `xenia-rs exec ... --dump-addr=0x<va>` instead).

Usage:
  zq.py dis  <lo_hex> <hi_hex>      # disassemble [lo,hi)
  zq.py fn   <pc_hex>               # function containing pc (address,name,end)
  zq.py xref <target_hex>           # xrefs whose target == addr (callers)
  zq.py callers <vtable_off_dec>    # call-sites of vtable slot at byte offset N
                                    #   (finds `lwz r11, N(r11)` + reports the fn)
  zq.py grep <substr>              # instructions whose operands LIKE %substr%
  zq.py find <word_hex>            # instructions whose raw word == value (e.g. a ptr)
"""
import duckdb, sys

DB = '/home/fabi/RE - Project Sylpheed/xenia-rs/sylpheed.db'
c = duckdb.connect(DB, read_only=True)
H = lambda x: '0x%08x' % x


def _fn(pc):
    r = c.execute('SELECT address,name,end_address FROM functions WHERE address<=? AND end_address>? '
                  'ORDER BY address DESC LIMIT 1', [pc, pc]).fetchall()
    return f'{r[0][1]}({H(r[0][0])})' if r else '?'


def main():
    if len(sys.argv) < 2:
        print(__doc__); return
    cmd = sys.argv[1]
    if cmd == 'dis':
        lo, hi = int(sys.argv[2], 16), int(sys.argv[3], 16)
        for a, m, o in c.execute('SELECT address,mnemonic,operands FROM instructions '
                                 'WHERE address>=? AND address<? ORDER BY address', [lo, hi]).fetchall():
            print(H(a), m, o)
    elif cmd == 'fn':
        print(_fn(int(sys.argv[2], 16)))
    elif cmd == 'xref':
        t = int(sys.argv[2], 16)
        for s, k, i, sf in c.execute('SELECT source,kind,instruction,source_func FROM xrefs '
                                     'WHERE target=? ORDER BY source', [t]).fetchall():
            print(H(s), k, 'in', _fn(s), ':', i)
    elif cmd == 'callers':
        off = int(sys.argv[2])  # decimal byte offset, e.g. 196 for vtable[49]
        pat = f'r11, {off}(r11)'
        for (a,) in c.execute("SELECT address FROM instructions WHERE mnemonic='lwz' AND operands=? "
                              'ORDER BY address', [pat]).fetchall():
            print(H(a), 'in', _fn(a))
    elif cmd == 'grep':
        sub = sys.argv[2]
        for a, m, o in c.execute("SELECT address,mnemonic,operands FROM instructions "
                                 "WHERE operands LIKE ? ORDER BY address", [f'%{sub}%']).fetchall():
            print(H(a), m, o, '  in', _fn(a))
    elif cmd == 'find':
        w = int(sys.argv[2], 16)
        for (a,) in c.execute('SELECT address FROM instructions WHERE raw=? ORDER BY address', [w]).fetchall():
            print(H(a))
    else:
        print(__doc__)


if __name__ == '__main__':
    main()
