From 1daf2d617137b4856cf8429b86705e1a9a7ed85d Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Wed, 26 Aug 2026 01:44:49 +0000 Subject: [PATCH] =?UTF-8?q?re:=20recover=20the=20ISL=20interpreter's=20com?= =?UTF-8?q?mand=20table=20=E2=80=94=2057=20opcodes,=20committed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/data/isl-command-table.txt | 69 ++++++++++++++++++++++++++++++ docs/re/structures/isl-builtins.md | 33 ++++++++++++++ tools/re-capture/isl_cmdtab.py | 52 ++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 docs/re/data/isl-command-table.txt create mode 100644 tools/re-capture/isl_cmdtab.py diff --git a/docs/re/data/isl-command-table.txt b/docs/re/data/isl-command-table.txt new file mode 100644 index 00000000..e7841fb3 --- /dev/null +++ b/docs/re/data/isl-command-table.txt @@ -0,0 +1,69 @@ +# The ISL interpreter's command table — opcode -> handler +# +# Recovered by symbolically executing `sub_822FE040`, which fills 1023 eight-byte +# slots at `table+32` with a default and then overwrites individual ones. Slot +# index = (N - 32) / 8 from each `std r9, N(r31)`. +# +# Regenerate with `tools/re-capture/isl_cmdtab.py`. +# +# `82391BA8` is `li r3,1 ; blr` — an accept-and-discard stub. The opcodes bound +# to it are dead in this build: 768, 769, 774, 775, 776, 791, 792, 793, 805. +# +populated slots: 57 + opcode 256 -> 822FE8D0 + opcode 512 -> 822FE8D8 + opcode 513 -> 822FE9D8 + opcode 514 -> 822FE9C0 + opcode 515 -> 822FE9C8 + opcode 516 -> 822FE9D0 + opcode 517 -> 822FE8E0 + opcode 518 -> 822FE8E8 + opcode 519 -> 822FE9A0 + opcode 520 -> 822FF4C8 + opcode 768 -> 82391BA8 + opcode 769 -> 82391BA8 + opcode 772 -> 822FEDE0 + opcode 774 -> 82391BA8 + opcode 775 -> 82391BA8 + opcode 776 -> 82391BA8 + opcode 784 -> 822FEE28 + opcode 785 -> 822FEF00 + opcode 786 -> 822FEE38 + opcode 787 -> 822FEE30 + opcode 788 -> 822FEFB8 + opcode 789 -> 822FF060 + opcode 790 -> 822FF110 + opcode 791 -> 82391BA8 + opcode 792 -> 82391BA8 + opcode 793 -> 82391BA8 + opcode 800 -> 822FF118 + opcode 801 -> 822FF120 + opcode 802 -> 822FF128 + opcode 803 -> 822FF130 + opcode 804 -> 822FF1B8 + opcode 805 -> 82391BA8 + opcode 806 -> 822FF218 + opcode 807 -> 822FF220 + opcode 808 -> 822FF138 + opcode 816 -> 822FECC8 + opcode 817 -> 822FED10 + opcode 896 -> 822FF228 + opcode 897 -> 822FF280 + opcode 992 -> 822FF2D8 + opcode 993 -> 822FEDB0 + opcode 994 -> 822FEDC8 + opcode 995 -> 822FF330 + opcode 996 -> 822FF3B8 + opcode 997 -> 822FF468 + opcode 998 -> 822FF4A8 + opcode 999 -> 822FF4C0 + opcode 1008 -> 822FE9A8 + opcode 1009 -> 822FEA80 + opcode 1010 -> 822FEB10 + opcode 1011 -> 822FEB40 + opcode 1012 -> 822FEBB0 + opcode 1013 -> 822FEBB8 + opcode 1014 -> 822FEBD0 + opcode 1015 -> 822FEC20 + opcode 1016 -> 822FEB70 + opcode 1017 -> 822FEB98 diff --git a/docs/re/structures/isl-builtins.md b/docs/re/structures/isl-builtins.md index fa2cc8b6..8f1f9fc0 100644 --- a/docs/re/structures/isl-builtins.md +++ b/docs/re/structures/isl-builtins.md @@ -140,6 +140,39 @@ and spread checks but its maximum *exceeds* the table — flag indices run 0..31 against symbol tables as small as 40 — so it is excluded, and the disassembler now declines to resolve it rather than printing an invented name. +### ✅ The interpreter's command table, recovered + +`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** — the full opcode → +handler map, committed as [`data/isl-command-table.txt`](../data/isl-command-table.txt) +and regenerable with `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 that post them do nothing. + +### ✅ Opcodes 800–802 send unit messages `0xED08 nn DE` + +Their table entries are thunks 8 bytes apart into handlers `0x823008C8`, +`0x823009B8`, `0x82300AA8` — each 60 instructions, and **differing in exactly two +words**: a descriptor offset and the message id. + +| opcode | built-in | unit message | +|---|---|---| +| 800 | 26 | **`0xED0802DE`** | +| 801 | **28** | **`0xED0803DE`** | +| 802 | **29**, 101 | **`0xED0804DE`** | + +That fixes the id format as `0xED08 nn DE`, and the ids already known from other +work fit it: opcode 514 → `00DE`, 803 → `07DE`, 999 → `0FDE`. + +🟡 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 one link further on. ❌ Not followed; 26/28/29 +remain unnamed. + ### 🟡 Built-ins 26 / 28 / 29 are one family — and `damage_unit` looks mis-named Method-diffing put the structure beyond doubt but did not reach the semantics. diff --git a/tools/re-capture/isl_cmdtab.py b/tools/re-capture/isl_cmdtab.py new file mode 100644 index 00000000..d272e7d9 --- /dev/null +++ b/tools/re-capture/isl_cmdtab.py @@ -0,0 +1,52 @@ +"""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 %s %s" % (s, '%08X'%hi if hi else 'None', '%08X'%lo if lo else ''))