This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
xenia-rs/zq.py
MechaCat02 869790bab9 fix(tools): self-locate DB / cwd instead of the stale renamed path
zq.py and sylph-run.sh hard-coded '/home/fabi/RE - Project Sylpheed/...'
(the dashed dir was renamed 'RE Project Sylpheed'), so both were broken.
Resolve relative to the script directory now; zq.py honours $SYLPHEED_DB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 17:04:28 +02:00

71 lines
3.1 KiB
Python
Executable File

#!/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, os
# Resolve the DB next to this script so the tool survives the tree being renamed
# (the old hard-coded '/home/fabi/RE - Project Sylpheed/...' path went stale when
# the dir became 'RE Project Sylpheed'). Override with $SYLPHEED_DB.
DB = os.environ.get('SYLPHEED_DB',
os.path.join(os.path.dirname(os.path.abspath(__file__)), '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()