re: three ISL built-in names were wrong, including the most-used one

All re-read twice — the handler, and the thing it calls — because each had
been named from its shape rather than its effect.

* id 11 `yield` -> `end_coroutine`. 0x82272624 is li r11,1 ; li r3,3 ;
  stw r11,164(r31), and the dispatcher's r3==3 arm erases the thread from
  the active list and returns it to the free list. It destroys the thread.
  2945 sites game-wide, 372 in Stage 02 — the most-used built-in there was.
* id 5 `await_label` -> `kill_coroutine(label)`. sub_82273B08 kills the
  thread parked at the target pc, or itself if the target is its own pc.
  It waits for nothing.
* id 100 `push_trigger` -> `reset_phase_threads`. It clears the trigger
  container and then frees every thread whose pc differs from the caller's
  — the opposite of pushing a trigger. Corroborated by usage: its 12 Stage
  02 sites all sit in the phase terminator, next to timer_stop,
  clear_flag(-1) and MARK_LAST_PHASE.

One name recovered from the game's own text: opcode 992 prints
"RequestScriptMessage %s" at 0x820A5700, so id 64 is request_script_message
(2683 sites).

Return codes documented properly: 1 = restart the coroutine from its entry
(previously not recorded at all), 3 = terminate. And the blocking set was
wrong in two places — it is 102, 120, 137, 142, 143. Id 97 does NOT block;
its handler ends `b 0x822724F8`, so it always returns 0.

Unit-operand resolution settled from DATA over all 28 stages rather than by
reading 147 handlers: a slot qualifies only if every value is a valid
symtab-2 index, it takes >=15 distinct values, AND its maximum reaches most
of the table — that last clause is what discriminates, since every small
integer is trivially "in range". 31 built-ins at slot 4, 8 at slot 12, one
at slot 20. It also refutes set_flag's slot 0, whose maximum overruns the
table, and the resolver now declines rather than inventing a name.

New and unexplained: symtab-2 holds two types, 2 and 8, and built-ins 95 and
128 take type 8 at slot 12 in 100% of their sites.

A downstream inference is withdrawn with it: the note reading the live
trigger counter attributed it to "the script arming watches as it goes" via
built-in 100. The measurement stands; the attribution does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
Sylpheed RE agent
2026-08-25 22:09:05 +00:00
parent 723fc9b890
commit 1ba5d0a4a3
5 changed files with 261 additions and 56 deletions

View File

@@ -1,5 +1,7 @@
# Stage02.ssb — ISL disassembly artefacts
Generated by `tools/re-capture/isl_report.py calls`.
2846 call sites, 73 distinct built-ins
builtin 11 372 site(s)
builtin 69 255 site(s)
@@ -81,40 +83,47 @@
39 MARK_LAST_PHASE 8: 0x4dfc 0x52c8 0x60fc 0x19258 0x19724 0x2b584 0x2ba50 0x2c054
40 mark_not_last 4: 0x5a10 0x1a270 0x2c578 0x2d06c
## condition vocabulary actually used by Stage 02
11 yield 372
69 unit_state 255
1 start_coroutine 216
30 objective_marker 179
20 hp_pct_test 167
18 dist_lt 92
5 await_label 83
70 unit_alive 71
56 unit_relation 52
4 wait_s 34
26 damage_unit 33
3 move_order 20
124 timer_stop 15
120 wait_cmds_drained 14
8 set_flag 12
100 push_trigger 12
93 clear_flag 12
59 fade_sound 12
6 END_PHASE 12
9 read_freg 12
95 unit_hp_pct 9
115 named_event 9
39 MARK_LAST_PHASE 8
90 camera_at_route 8
17 wait_frames 6
10 random 6
123 timer_resume 5
40 mark_not_last 4
137 wait_units_ready 3
52 play_stream 3
14 play_bgm 3
62 FORCE_END_PHASE 3
127 timer_set 1
## named built-ins used, by traffic
11 end_coroutine 372
69 unit_state 255
1 start_coroutine 216
64 request_script_message 213
30 objective_marker 179
20 hp_pct_test 167
47 squadron_attack 137
18 dist_lt 92
5 kill_coroutine 83
70 unit_alive 71
56 unit_relation 52
4 wait_s 34
26 damage_unit 33
3 move_order 20
124 timer_stop 15
120 wait_cmds_drained 14
8 set_flag 12
100 reset_phase_threads 12
93 clear_flag 12
59 fade_sound 12
6 END_PHASE 12
9 read_freg 12
95 unit_hp_pct 9
115 named_event 9
48 squadron_escort 8
39 MARK_LAST_PHASE 8
90 camera_at_route 8
17 wait_frames 6
10 random 6
123 timer_resume 5
40 mark_not_last 4
137 wait_units_ready 3
52 play_stream 3
14 play_bgm 3
62 FORCE_END_PHASE 3
127 timer_set 1
## phase code bases
Each phase has its OWN base; the file header offset is not it.
0xe4 0x14aa8 0x24b4c
## disassembly into the first END_PHASE
resync from 0x5058
@@ -131,7 +140,7 @@ resync from 0x5058
0050CC: 02030C00 set.i len=12 k=02,03 00000010 00000000 local[16] = special[0]
0050D8: 01021001 set.f len=16 k=01,02 00000000 BFF00000 00000000 special[0] = imm -1
0050E8: 02030C01 set.f len=12 k=02,03 00000018 00000000 local[24] = special[0]
0050F4: 00000C13 call len=12 k=00,00 00000040 00000245 builtin64(0x42, 0x2, 0x1, 0x9, 0x1, -1)
0050F4: 00000C13 call len=12 k=00,00 00000040 00000245 request_script_message(0x42, 0x2, 0x1, 0x9, 0x1, -1)
005100: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
005108: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
005110: 01020C0A op10 len=12 k=01,02 00000000 00000000
@@ -153,13 +162,32 @@ resync from 0x5058
0051CC: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
0051D8: 00000C13 call len=12 k=00,00 00000004 0000024C wait_s(3)
0051E4: 00000C13 call len=12 k=00,00 00000006 0000024D END_PHASE
0051F0: 00000C13 call len=12 k=00,00 0000000B 0000024E yield
0051F0: 00000C13 call len=12 k=00,00 0000000B 0000024E end_coroutine
0051FC: 01030C00 set.i len=12 k=01,03 00000000 00000001 local[0] = imm 0x1
005208: 01030C00 set.i len=12 k=01,03 00000004 00000056 local[4] = imm 0x56
005214: 01021001 set.f len=16 k=01,02 00000000 00000000 00000000 special[0] = imm 0
005224: 02030C01 set.f len=12 k=02,03 00000008 00000000 local[8] = special[0]
005230: 00000C13 call len=12 k=00,00 00000014 00000252 hp_pct_test(0)
005230: 00000C13 call len=12 k=00,00 00000014 00000252 hp_pct_test(0x1, TCN004, 0)
00523C: 01020C0A op10 len=12 k=01,02 00000000 00000001
005248: 0000080E op14 len=8 k=00,00 0000575C
005250: 01020C00 set.i len=12 k=01,02 00000000 00000000 special[0] = imm 0x0
00525C: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
005268: 00000C13 call len=12 k=00,00 00000074 00000253 builtin116(0x0)
005274: 00000C13 call len=12 k=00,00 00000065 00000254 builtin101
005280: 00000C13 call len=12 k=00,00 00000064 00000255 reset_phase_threads
00528C: 00000C13 call len=12 k=00,00 0000007C 00000256 timer_stop
005298: 01020C00 set.i len=12 k=01,02 00000000 FFFFFFFF special[0] = imm 0xFFFFFFFF
0052A4: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
0052B0: 00000C13 call len=12 k=00,00 0000005D 00000257 clear_flag(0xFFFFFFFF)
0052BC: 00000C13 call len=12 k=00,00 00000076 00000258 builtin118
0052C8: 00000C13 call len=12 k=00,00 00000027 0000025A MARK_LAST_PHASE
0052D4: 00000C13 call len=12 k=00,00 00000052 0000025B builtin82
0052E0: 01021001 set.f len=16 k=01,02 00000000 40100000 00000000 special[0] = imm 4
0052F0: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
0052FC: 00000C13 call len=12 k=00,00 00000004 0000025C wait_s(4)
005308: 01020C00 set.i len=12 k=01,02 00000000 00000005 special[0] = imm 0x5
005314: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
005320: 00000C13 call len=12 k=00,00 0000000A 0000025E random(0x5)
00532C: 0000080C jmp len=8 k=00,00 00005648 -> code+0x5648 (file 0x566C)
005334: 01020C00 set.i len=12 k=01,02 00000000 00000043 special[0] = imm 0x43
005340: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]

View File

@@ -75,7 +75,8 @@ squadron lost anybody at all?"* — an escort-casualty test. **The game has no
| `is_engaged` | **S05 only**, 3 sites, all on `TCN004` |
**S18S23, the tutorials, are the outliers by a distance.** No flag/trigger
machinery at all — zero `read_freg`, `clear_flag`, `push_trigger`,
machinery at all — zero `read_freg`, `clear_flag`, `reset_phase_threads`
(built-in 100, formerly mis-named `push_trigger`),
`named_event`, `MARK_LAST_PHASE`, `FORCE_END_PHASE`, and none of the timer
family. One `set_flag` and one `END_PHASE` each. They are linear scripted
lessons, not missions.

View File

@@ -22,9 +22,28 @@ Arguments do **not** live in the instruction. Every handler starts
**packed operand blob**, which is what the `local[]` staging in
[isl-bytecode](isl-bytecode.md) fills.
Return codes: **0** continue, **2** yield (re-execute next frame), **3**
coroutine control. Five built-ins skip the pc advance on 2 and so genuinely
**block**: 97, 120, 137, 142, 143.
### ✅ Return codes — read off `ScriptPhase::Update` (`0x82263830`)
The dispatcher switches on `r3` exactly four ways:
| r3 | address | effect |
|---|---|---|
| **0** | `0x82263850` | continue to the next thread in the same frame |
| **1** | `0x82263878` | `[thread+4] = [thread+0]`**restart this coroutine from its entry** |
| **2** | `0x82263888` | `[thread+4] = saved pc` — resume next frame |
| **3** | `0x82263894` | `sub_8226EA20` erases the thread from the active list `[+216]`, then `sub_8226EAB8` returns it to the free list `[+204]`**terminate this coroutine** |
Two tails do the pc bookkeeping: `0x822724F8` is `li r3,0` then advance;
`0x822724FC` advances only, preserving `r3`. Advance is `pc += [insn+2]`, the
length byte — the same field [isl-bytecode](isl-bytecode.md) decodes. So **every
handler that ends `b 0x822724F0` returns 0**, and its only output is
`[phase+164]`/`[phase+176]`.
**CORRECTED — the blocking set was wrong in two places.** The blocking form is
`bctrl ; cmpwi r3,2 ; bne 0x822724FC`, and it appears at **102, 120, 137, 142,
143**. This file previously listed **97**, which does not block: its handler
`0x8227313C` ends `b 0x822724F8`, so it *always* returns 0. And **102** was
missing. All six handlers re-read to confirm.
## ✅ `ScriptPhase` state layout
@@ -65,7 +84,7 @@ two tables already parsed in [mission-script-ssb](mission-script-ssb.md).
| **132134** | player gauges | speed/boost ratios and a player byte |
| **73, 123127** | timer family | start / resume / stop / reset / read elapsed / read limit |
| **8 / 9 / 93** | `set_flag` / `read_freg` / `clear_flag` | latch a result into the 32-entry files |
| **100 / 115** | `push_trigger` / `named_event` | the engine→script edge |
| **100 / 115** | `reset_phase_threads` / `named_event` | ❌ 100 is **not** `push_trigger` — see below |
**The state machine is therefore:** a trigger fires a coroutine → the coroutine
tests one of the predicates → it latches the answer with `set_flag` → some later
@@ -80,9 +99,63 @@ Two spot-checks I ran against the disassembly rather than taking on trust:
a NULL object and state 1, then calls `823011B0` (initial, packed
`hi<<16|lo`) and `82301118` (current). Exactly as described.
## ❌ Three built-in names WITHDRAWN
Each re-read twice — the handler, and the thing it calls — because all three had
been named from their shape rather than their effect.
| id | was | **is** | evidence |
|---|---|---|---|
| **11** | `yield` | **`end_coroutine`** | `0x82272624` is `li r11,1 ; li r3,3 ; stw r11,164(r31)`. Return 3 **destroys the thread**. It is the single most-used built-in in the game — 2945 sites, 372 in Stage 02 alone — so this was the most load-bearing wrong name in the file. |
| **5** | `await_label` | **`kill_coroutine(label)`** | `sub_82273B08` computes `target = [phase+232] + blob[0]`; if that equals the **caller's own** pc it returns 3 (kill self), otherwise it finds the thread parked at `target` in `[phase+220]` and moves it to the free list. It does not wait for anything. |
| **100** | `push_trigger` | **`reset_phase_threads`** | the handler calls vtable slot 2 (clears the trigger container at `[phase+272]`) and then `sub_82273BE8`, which walks `[phase+220]` and frees **every thread whose pc differs from the caller's**. It drops queued triggers and terminates every *other* coroutine — the opposite of pushing one. |
One name is newly **recovered**, from the game's own text: interpreter opcode 992
prints `★RequestScriptMessage %s` (`0x820A5700`), so **id 64 is
`request_script_message`** — 2683 sites, and the second most-used built-in.
## ✅ Which operands are unit indices — settled from the data
`tools/re-capture/isl.py` resolved a symbol-table-2 name only for 11 built-ins,
at slot 4. The real set is much larger, and it was established by **measurement
over all 28 stages** rather than by reading 147 handlers:
> a slot qualifies only if every observed value is a valid symtab-2 index, it
> takes ≥15 distinct values, **and its maximum reaches most of the table**.
That last clause is what makes the test work. Symbol table 2 tops out at 122
entries, so a slot carrying something else overruns it; plain range-checking
cannot separate an index from a bool, because every small integer is in range.
* **unit index at slot 4** — 2, 3, 7, 12, 15, 16, 18, 19, 20, 24, 25, 26, 28,
29, 30, 47, 48, 56, 57, 58, 63, 69, 70, 79, 91, 92, 95, 105, 108, 128, 143
* **a second at slot 12** — 2, 18, 47, 48, 56, 79, 95, 128
* **a third at slot 20** — 128
Every one of these is 100.0 % in range across its call sites (the largest, id 20,
over 2930 of them).
The test also **refuted** a tempting entry: `set_flag`'s slot 0 passes the range
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.
**New, unexplained:** symbol table 2 holds **two types**, 2 (1160 entries
disc-wide) and 8 (249), and they are not interchangeable. Built-ins **95** and
**128** take a type-2 unit at slot 4 and, at slot 12, an operand that is type 8
in **100 %** of its 90 and 152 call sites. What separates the two classes is not
established.
## ✅ What Stage 02 actually uses — and it settles a standing question
Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`):
Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`, regenerated by
`tools/re-capture/isl_report.py calls`):
⚠️ The sibling artefact `data/isl-stage02-conditions.txt` **predates the name
corrections above** — it still prints `yield`, `await_label` and `push_trigger`,
and its operand rendering predates the staging fix. It has no committed
generator; reproducing it needs the coroutine entry points, which
`start_coroutine`'s operand carries and the tool does not yet follow.
| built-in | sites |
|---|---|
@@ -91,7 +164,7 @@ Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`):
| `dist_lt` | **92** |
| `unit_alive` | **71** |
| `unit_relation` | **52** |
| `set_flag` / `clear_flag` / `push_trigger` | 12 each |
| `set_flag` / `clear_flag` / `reset_phase_threads` | 12 each |
| `END_PHASE` / `MARK_LAST_PHASE` / `FORCE_END_PHASE` | 12 / 8 / 3 |
**Not used at all in Stage 02:** `squad_survival_pct`, `group_ratio_pct`,
@@ -285,9 +358,16 @@ Read from a running Stage 02 mission (`ScriptPhase 0xBE14DD80`, container at
```
**`+20` moves, 0 → 1 → 2**, while the phase ordinal stays 1. So it is a real
counter of **currently registered triggers** — the script arming watches as it
goes (Stage 02 has 12 `push_trigger` sites) — and it is readable live with no
debugger. That is the first direct view of *what the script is waiting for*.
counter of **currently registered triggers**, readable live with no debugger —
the first direct view of *what the script is waiting for*.
⚠️ The *measurement* stands; its attribution did not. This paragraph used to add
"the script arming watches as it goes (Stage 02 has 12 `push_trigger` sites)",
pointing at built-in 100. Built-in 100 is `reset_phase_threads` — it **clears**
the trigger container, it does not arm one. The 12 sites are real, but they are
12 places where Stage 02 *tears the trigger set down*, which is close to the
opposite reading. ❔ What actually arms a trigger is now open again; built-ins
19 and 25 both queue into `[phase+272]` and are the first place to look.
🟡 **`+12` is not a list head after all**, or not only that: it reads
`0x000A0009`, which is not a pointer. The `addi r31, r30, 12` in the push made

View File

@@ -78,18 +78,18 @@ KIND = {0: 'global', 1: 'imm', 2: 'special', 3: 'local'}
# guess. See docs/re/structures/isl-builtins.md.
BUILTIN = {
1: 'start_coroutine', 2: 'deploy_squadron', 3: 'move_order', 4: 'wait_s',
5: 'await_label', 6: 'END_PHASE', 8: 'set_flag', 9: 'read_freg',
10: 'random', 11: 'yield', 13: 'play_se', 14: 'play_bgm',
5: 'kill_coroutine', 6: 'END_PHASE', 8: 'set_flag', 9: 'read_freg',
10: 'random', 11: 'end_coroutine', 13: 'play_se', 14: 'play_bgm',
17: 'wait_frames', 18: 'dist_lt', 20: 'hp_pct_test', 24: 'squad_survival_pct',
26: 'damage_unit', 30: 'objective_marker', 31: 'objective_marker_at_route',
33: 'global_counter0', 34: 'global_counter1', 36: 'screen_fade',
39: 'MARK_LAST_PHASE', 40: 'mark_not_last', 43: 'play_voice',
45: 'play_voice_vol', 46: 'squadron_trace', 47: 'squadron_attack',
48: 'squadron_escort', 52: 'play_stream', 53: 'sound_busy', 54: 'stop_sound',
56: 'unit_relation', 59: 'fade_sound', 62: 'FORCE_END_PHASE',
56: 'unit_relation', 59: 'fade_sound', 62: 'FORCE_END_PHASE', 64: 'request_script_message',
69: 'unit_state', 70: 'unit_alive', 72: 'group_ratio_pct', 73: 'timer_start',
74: 'timer_limit', 88: 'camera_at', 90: 'camera_at_route',
93: 'clear_flag', 94: 'is_engaged', 95: 'unit_hp_pct', 100: 'push_trigger',
93: 'clear_flag', 94: 'is_engaged', 95: 'unit_hp_pct', 100: 'reset_phase_threads',
102: 'prompt_yes_no', 109: 'set_unit_flags', 115: 'named_event',
120: 'wait_cmds_drained', 123: 'timer_resume', 124: 'timer_stop',
125: 'timer_reset', 126: 'timer_elapsed', 127: 'timer_set',
@@ -135,10 +135,30 @@ def symbols(b, which):
return out
UNIT_ARG = {18, 20, 24, 26, 56, 69, 70, 94, 95, 105, 109} # unit idx at blob[4]
# Built-ins 71 and 72 carry a SECOND unit index at blob[12] (handler 0x8226E568
# indexes [phase+324] with both), so their second operand printed as a bare
# integer until this was noticed. 56 may be the same shape -- unverified.
# Built-ins whose operand blob carries a symbol-table-2 (unit) index, by slot.
#
# Derived from the DATA, not from reading 147 handlers: across all 28 stages a
# slot qualifies only if every observed value is a valid symtab-2 index, it takes
# >=15 distinct values, and its maximum reaches most of the table (symtab-2 tops
# out at 122 entries, so a non-index slot overruns). That last clause is what
# makes the test discriminating -- plain range-checking cannot separate an index
# from a bool, because every small integer is "in range".
#
# It also refutes one tempting entry: `set_flag`'s slot 0 passes the range and
# spread tests but its maximum EXCEEDS the table (flag indices run 0..31 against
# tables as small as 40), so it is excluded. Slots are only listed here when the
# ratio stayed below 1.0.
UNIT_ARG = {2, 3, 7, 12, 15, 16, 18, 19, 20, 24, 25, 26, 28, 29, 30, 47, 48,
56, 57, 58, 63, 69, 70, 79, 91, 92, 95, 105, 108, 128, 143}
UNIT_ARG2 = {2, 18, 47, 48, 56, 79, 95, 128} # a SECOND unit index at blob[12]
UNIT_ARG3 = {128} # and a third at blob[20]
UNIT_SLOTS = {4: UNIT_ARG, 12: UNIT_ARG2, 20: UNIT_ARG3}
# Symbol table 2 holds TWO entity types: type 2 (1160 entries disc-wide) and
# type 8 (249). They are not interchangeable -- built-ins 95 and 128 take a
# type-2 unit at slot 4 and, at slot 12, an operand that is type 8 in 100% of
# its 90 and 152 call sites respectively. What distinguishes the two classes is
# not yet established.
def dis(b, off, count=40, code_base=0x24, args=True, sym2=None):
@@ -203,7 +223,11 @@ def dis(b, off, count=40, code_base=0x24, args=True, sym2=None):
parts = []
for slot, v in sorted(staged.items()):
txt = ('0x%X' % v) if isinstance(v, int) else v
if (sym2 and slot == 4 and words[0] in UNIT_ARG
# Resolve only a slot that is declared a unit index AND
# whose value really is one -- a resolver that invents a
# name for a non-index is worse than one that prints the
# raw number.
if (sym2 and words[0] in UNIT_SLOTS.get(slot, ())
and isinstance(v, int) and v in sym2):
txt = sym2[v][1]
parts.append(txt)

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Regenerate the committed ISL artefacts under `docs/re/data/`.
isl_report.py <StageNN.ssb> calls -> the call-site census + a listing
The artefact was produced by an uncommitted one-off, so it drifted out of date
twice: once when operand staging was fixed (calls printed with too few
arguments) and once when three built-in names were corrected. Keeping the
generator in the tree is the point of this file.
`data/isl-stage02-conditions.txt` still has no generator here. A first attempt
is not committed because it printed most sites as bare `builtinN`: neither
`isl.resync` (it gives up far from a valid start) nor a naive linear decode from
the phase base reaches every call site, so producing that listing faithfully
needs the coroutine entry points, which `start_coroutine`'s operand carries and
this tool does not yet follow.
"""
import collections
import sys
import isl
def census(b):
cs = isl.call_sites(b)
return cs, collections.Counter(bid for _, bid, _ in cs)
def emit_calls(b, path):
cs, h = census(b)
s2 = isl.symbols(b, 2)
print('# %s — ISL disassembly artefacts' % path)
print()
print('Generated by `tools/re-capture/isl_report.py calls`.')
print()
print('%d call sites, %d distinct built-ins' % (len(cs), len(h)))
for bid, n in h.most_common():
print(' builtin %-4d %5d site(s)' % (bid, n))
print()
print('## phase-control sites')
for bid, nm in ((6, 'END_PHASE'), (62, 'FORCE_END_PHASE'),
(39, 'MARK_LAST_PHASE'), (40, 'mark_not_last')):
offs = [off for off, b2, _ in cs if b2 == bid]
print('%-3d %-18s %2d: %s'
% (bid, nm, len(offs), ' '.join('0x%x' % o for o in offs)))
print()
print('## named built-ins used, by traffic')
for bid, n in h.most_common():
nm = isl.BUILTIN.get(bid)
if nm:
print(' %-3d %-24s %4d' % (bid, nm, n))
print()
print('## phase code bases')
print('Each phase has its OWN base; the file header offset is not it.')
print(' ' + ' '.join('0x%x' % x for x in isl.phase_bases(b)))
print()
print('## disassembly into the first END_PHASE')
target = [off for off, bid, _ in cs if bid == 6][0]
start = isl.resync(b, target)
print('resync from 0x%X' % start)
for line in isl.dis(b, start, 64, code_base=0x24, sym2=s2):
print(line)
def main():
path = sys.argv[1]
b = isl.load(path)
name = path.replace('\\', '/').split('/')[-1]
{'calls': emit_calls}[sys.argv[2]](b, name)
if __name__ == '__main__':
main()