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
Syplheed-Reborn/tools/re-capture/isl_cmdtab.py
Sylpheed RE agent 1707b6b74a re: recover the ISL interpreter's command table — 57 opcodes, committed
sub_822FE040 fills 1023 eight-byte slots at table+32 with a default and then
overwrites individual ones; slot = (N - 32) / 8 from each std r9, N(r31).
Symbolically executing it yields 57 populated slots, matching the count the
corpus recorded, now with the full opcode -> handler map committed as
docs/re/data/isl-command-table.txt and regenerable from
tools/re-capture/isl_cmdtab.py.

Nine opcodes point at 0x82391BA8, which is `li r3,1 ; blr` -- accept and
discard. 768, 769, 774, 775, 776, 791, 792, 793 and 805 are dead in this
build, which is why the built-ins posting them do nothing.

Opcodes 800-802's entries are thunks 8 bytes apart into 60-instruction
handlers that differ in exactly two words: a descriptor offset and a unit
message id.

  800  builtin 26        0xED0802DE
  801  builtin 28        0xED0803DE
  802  builtin 29, 101   0xED0804DE

That fixes the id format as 0xED08 nn DE, and the ids known from other work
fit it: opcode 514 -> 00DE, 803 -> 07DE, 999 -> 0FDE.

Stopped one link short of the semantics, and saying so: the pump's arm for
0xED0802DE does not apply an effect. It walks the unit's child list at
[unit+320]/[unit+324] and REBROADCASTS to each child as 0xED0902DE. So 0xED08
is the to-unit family and 0xED09 the to-child one, and the terminal effect is
further on. 26/28/29 remain unnamed.

The command table is the reusable part -- it answers "what does this opcode
reach" for every future built-in question, not just this family.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-26 01:44:49 +00:00

53 lines
2.0 KiB
Python

"""Recover the ISL interpreter's 1023-slot command table from sub_822FE040.
The builder fills every slot with a default, then overwrites individual slots.
Each write is `stw rX, off(r1)` (a two-word stack pair) followed by
`ld r9, off(r1)` and `std r9, N(r31)`, where slot = (N - 32) / 8.
"""
import duckdb, re, sys
con=duckdb.connect('/work/xenia-rs/sylpheed.db', read_only=True)
rows=con.execute("select address, coalesce(ext_disasm,disasm) from instructions "
"where address>=? and address<? order by address",
[0x822FE040,0x822FE618]).fetchall()
gpr={}; stack={}; slots={}
lis=re.compile(r"lis\s+r(\d+), 0x([0-9A-Fa-f]+)")
addi=re.compile(r"addi\s+r(\d+), r(\d+), (-?\d+)")
subi=re.compile(r"subi\s+r(\d+), r(\d+), (-?\d+)")
li=re.compile(r"li\s+r(\d+), (-?\d+)")
stw=re.compile(r"stw\s+r(\d+), (-?\d+)\(r1\)")
ld=re.compile(r"ld\s+r(\d+), (-?\d+)\(r1\)")
std=re.compile(r"std\s+r(\d+), (-?\d+)\(r31\)")
for a,d in rows:
m=lis.match(d)
if m: gpr[int(m.group(1))]=int(m.group(2),16)<<16; continue
m=addi.match(d)
if m:
b=gpr.get(int(m.group(2)))
gpr[int(m.group(1))]=None if b is None else (b+int(m.group(3)))&0xFFFFFFFF
continue
m=subi.match(d)
if m:
b=gpr.get(int(m.group(2)))
gpr[int(m.group(1))]=None if b is None else (b-int(m.group(3)))&0xFFFFFFFF
continue
m=li.match(d)
if m: gpr[int(m.group(1))]=int(m.group(2))&0xFFFFFFFF; continue
m=stw.match(d)
if m: stack[int(m.group(2))]=gpr.get(int(m.group(1))); continue
m=ld.match(d)
if m:
off=int(m.group(2))
gpr[int(m.group(1))]=(stack.get(off), stack.get(off+4))
continue
m=std.match(d)
if m:
v=gpr.get(int(m.group(1)))
n=int(m.group(2))
if (n-32)%8==0 and isinstance(v,tuple):
slots[(n-32)//8]=v
continue
print("populated slots: %d" % len(slots))
for s in sorted(slots):
hi,lo=slots[s]
print(" opcode %-5d -> %s %s" % (s, '%08X'%hi if hi else 'None', '%08X'%lo if lo else ''))