chore: initialize age-reimpl repo

Reverse-engineering + open reimplementation workspace for Eushully's AGE/SYS4
engine (first target: Himegari). The repo root is age-reimpl/; the original game
install and the extracted ALF data are siblings outside the repo and are never
tracked. build/ (derived corpora) is gitignored and regenerated by the tools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-07-06 12:58:30 -04:00
commit aa3da00bfa
36 changed files with 9829 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
# Himegari opcode inference notes
Classification of the top ~20 unnamed opcodes (Phase 3.0, inference-only, 2026-07-06).
Machine-readable form: `tools/age_opcodes_himegari.py` (`INFERRED`). Evidence gathered with
`tools/opcode_context.py` over the 481-script `Output/DATA1` corpus (1,502,986 instructions).
Baseline: named opcodes = **72.62%** of instruction volume. Classifying the top 20 unnamed
ops covers to **~96.7%** cumulative. Kelebek labels (`u004xxxx`) are engine addresses from a
*later* AGE title — opcode number + argc are validated for Himegari (481/481 clean decode);
the meanings below are inferred and flagged by confidence + confirmation method.
## Buckets
### A. Statement/scope scaffolding — zero-arg, no operands, safe VM no-ops (HIGH)
The compiler brackets statements/blocks with operand-less markers. They carry no data and
sit at structural boundaries, so the VM v1 skips them; the Phase-4 dialogue harness confirms.
- **0x1f4 `stmt-begin`** (60,297; 4.01%) — opens scripts (first instr of ADDEN/ADDEXP/…), and
`stmt-end 0x1f5 → 0x1f4` pairs 25,899×. Successor is the statement's first real op (eq/mov/call).
- **0x1f5 `stmt-end`** (60,297; 4.01%) — equal count to 0x1f4; precedes `exit`/next statement.
`... → 0x1f5 → stmt-begin/lt/show-text`.
- **0x1d5 `cond-block`** (34,238; 2.28%) — **always** preceded by `jcc`; marks the fall-through
(condition-true) block entry. `jcc cond -1 label; 0x1d5; <body>`.
- **0x1bc `block-mark`** (26,865; 1.79%) — `jcc → 0x1bc → mov` (26,413×): another block boundary.
- **0x1bf `call-end`** (8,752; 0.58%) — `call → 0x1bf → stmt-end` (8,600×): end-of-call statement.
### B. Statement metadata with an id — tentative no-op, confirm via harness (MED/LOW)
Same scaffolding family but carrying an immediate; likely line numbers / statement descriptors.
Treated as no-ops in v1 but flagged `method=harness` because a wrong no-op here could drop state.
- **0x21b `line-id?`** (27,016; 1.80%) — `mov → 0x21b <imm> → stmt-end`. Arg is 0/1/small. Sits
near `savemesskip`/`loadmesskip` dev-comments (MENU/BUNKI) — **verify it isn't a message-control
toggle** before trusting the no-op.
- **0x1d2 `stmt-desc?`** (17,323; 1.15%) — always immediately after `stmt-begin 0x1f4`; 2 imm args
(e.g. `0x2 0x5`) then a `mov`. Reads like a per-statement descriptor.
- **0x258 `decl?`** (7,224; 0.48%) — runs in a chain right after script-entry `0x259`, enumerating
ids (`0x4 0x1`, `0x5 0x1`, `0x6 0x1`, …). Prologue declaration/registration table.
### C. Structural (HIGH)
- **0x71 `label-def`** (26,445; 1.76%) — its corpus count **exactly equals the T1 table size**, so
this is the instruction T1 (tag 0x71) indexes: a label/anchor definition. Arg is a small id.
Preceded by `end-text-line`/`call`. VM v1 = no-op; **revisit if menu/callback dispatch resolves
entries by this id** (T1 may be a named-entry-point table).
### D. Computational — exact behaviour via Unicorn/inference (MED/LOW)
- **0x215 `count?`** (5,675; 0.38%) — writes its first operand (a global) which is then tested
`gre …, 0` / `lt` and branched on. Signature (global,global). Looks like a count / search-returns
-index helper. Micro-exec candidate.
- **0x1a2 `resolve-handle?`** (17,585; 1.17%) — takes a `local-ptr` just filled by `lookup-array`,
then `create-texture` follows. Resolves a looked-up resource/handle. Borderline effectful → Frida.
### E. Effectful — draw / UI / audio / ADV; need Frida to confirm (see shortlist)
All in the 0x4200x421 graphics family or 0x41D audio family, clustered with named draw/audio ops.
Not semantically no-ops; VM must implement (or knowingly stub) them.
- **0x7a `text-param?`** (17,324; 1.15%) — `sub` computes a value, then `0x7a <imm> <computed> <imm>`,
then `show-text`. A text speed / auto-wait / window parameter for the following line. (The review
guessed "computational"; context says ADV-text-effectful.)
- **0x202 `draw-blit?`** (9,223) / **0x203 `draw?`** (9,149) — 45 coord/size args, preceded by
coordinate arithmetic, chained with `draw-texture`/`u00420950`. Blit/positioned-draw.
- **0x1f7 `ui-elem?`** (7,735) / **0x1fa `ui-clear?`** (6,126) — paired (`0x1f7 → 0x1fa` 5,014×);
create/begin a UI element then show/hide/clear it by id. From MENU: `0x1f7 0x11170 0xc80`, `0x1fa 0x49/0x4a/0x4b`.
- **0x217 / 0x218 / 0x21a `gfx-geom?`** (~5,000 each) — a chain each taking 4 global-ints
(`0x21a → 0x218`, `0x217 → 0x1ff`); rect/coordinate/geometry transforms in the draw library.
- **0x1ff `draw?`** (3,926) — 4 args then `call`; follows `0x217`.
- **0xb6 `snd-ctrl?`** (4,805) — 0x41D family, self-chains, near `play-sound-effect`/`0xb5`; sound
channel / volume / stop control.
### F. Input / UI hotspot branch — op 0x90 & 0x97 (A0-slice deep-dive, 2026-07-06 s2, HIGH)
Solved while investigating the A0 EMPTY scenes. Kelebek: `{0x90,"u0041BEB0",0x7} // args 5,6,7 =
code locations` (they left it "ukn"). Corpus evidence (`scratchpad/analyze_0x90.py` over all scripts):
- **op 0x90 = pointer/input hotspot hit-test branch.** Encoding `0x90 x y w h tgt_a tgt_b tgt_c`
(argc 7; targets `0xffffffff` = unused). Branches to one of 3 targets per interaction outcome and
**falls through to pc+1 when nothing matches** — design-confirmed: a 0x90 is 15 dwords, and the
instruction after the last one lands exactly on the fall-through statement (`0xd0+15 = 0xdf`).
- **Two forms, both only in one shared subroutine:**
- *Mode A* (1505 = 5×301): all-immediate, `w=h=20`. The five on-screen ADV buttons — identical in
all 301 scripts at `(x,y) = (684|706|728|750|772, 572)`, each setting one of `G[0x6c9..0x6cd]` to
1 (target a) / 0 (target b) / 0+run-action (target c). Reads as hover-enter / hover-leave / click.
- *Mode B* (903 = 3×301): local-int operands, `w=h=1`, only target c real — a keyed/degenerate
2-way input test (fall through, or jump to c). Operands are `0-1000`-based sentinels (key codes?).
- **Distribution is unanimous: every one of the 301 ADV scripts has EXACTLY 8 sites (5 A + 3 B).**
Zero scene-specific use — it is boilerplate ADV chrome (the system button row + input polling), not
gameplay branching.
- **VM handling:** headless (no cursor/input) ⇒ fall through ⇒ **our stub is already correct**; proven
safe by all 279 CLEAN dialogue scenes (which contain these same 8 sites). So 0x90 is NOT a headless
correctness hole. It must be modelled for real in **A2** (Godot input backend) as a live hotspot test;
confirm the exact target→interaction-state mapping via input capture / Frida then.
- **op 0x97** (`u0041C150`, argc 5: `v1 v2 1 1 <action-id>`, NO code targets) interleaves with 0x90 in
the same subroutine → companion *register-hotspot / set-widget-action* call (trailing imm = action id
0x0/0x7/0x8). Not control flow; part of the `0x90/0x91/0x92/0x95/0x97` widget cluster.
**Consequence for A0/A1:** the 12 EMPTY scenes are gated by **state + interactive input-wait loops**
(this chrome polls hotspots until a click), NOT by unmodelled 0x90 semantics. A1 can keep 0x90 as
fall-through with confidence; unlocking the EMPTY family is a Phase-A2/B task (seed state + input).
## Coverage after classification
See `build/opcode-coverage.md`. Roughly: named 72.6% + no-op scaffolding & label-def (A+C, high
conf) ≈ 14.4% + tentative-no-op metadata (B) ≈ 3.4% → **~90% handleable without live tools**;
the effectful/computational remainder (D+E) ≈ 67% is classified but needs Frida/Unicorn for
exact behaviour.
## Frida / Unicorn shortlist (feeds Phase 3.1/3.2)
- **Frida (effectful):** 0x7a (adv text param), 0x202/0x203/0x1f7/0x1fa/0x217/0x218/0x21a/0x1ff
(draw/UI), 0xb6 (audio), 0x1a2 (resource resolve). Also **Frida-confirm already-named effectful
ops the VM will rely on**: `play-voice 0xc4`, `play-bgm 0xbf`, `play-sound-effect 0xb4`,
`draw-texture 0x1fb`, `draw-string 0x204`, `create-texture 0x1f8`, `set-texture 0x1f9`.
- **Unicorn (computational):** 0x215 (count/search).
- **Harness (no live tools):** 0x21b / 0x1d2 / 0x258 — confirm no-op assumption by diffing the
VM's `show-text` sequence per scene against `build/text/dialogue.jsonl`.

View File

@@ -0,0 +1,638 @@
#include "Windows.h"
#include "age-shared.h"
#include <map>
#include <iostream>
// Keep this array ordered by op_code for binary search
static consteval auto make_defs() {
return std::to_array<Instruction_Definition>({
{0x1, "u004149C0", 0x0}, // error
{0x2, "exit", 0x0},
{0x3, "call-script", 0x1}, // call another script, param = SYSTEM4.bin index
{0x4, "u00417E30", 0x2},
{0x5, "ret", 0x0},
{0x6, "u00417E80", 0x2},
{0x7, "u00417F90", 0x1},
{0x8, "u00417FC0", 0x1},
{0x9, "exit-script", 0x0},
{0xA, "u00424170", 0x2},
{0xB, "u00418090", 0xB},
{0xC, "u004149E0", 0x0},
{0xD, "u004181A0", 0x4},
{0xE, "u00418200", 0xC},
{0xF, "u00418300", 0x1},
{0x10, "u00414A00", 0x4},
{0x11, "u00418330", 0x9},
{0x12, "u004183F0", 0x1},
{0x13, "u00418420", 0x4},
{0x14, "u00414A20", 0x0},
{0x15, "u00418490", 0x5},
{0x16, "u00418520", 0x2},
{0x17, "u00418560", 0x2},
{0x1E, "u004185B0", 0x8},
{0x1F, "u00418690", 0xC},
{0x20, "u004187C0", 0x6},
{0x21, "u00418860", 0x2},
{0x22, "u00418920", 0x2},
{0x23, "u004189D0", 0x2},
{0x24, "u00418A90", 0x2},
{0x25, "u00418B40", 0x3},
{0x26, "u00418C00", 0x4},
{0x27, "u00418CC0", 0x4},
{0x28, "u00418D90", 0x4},
{0x2A, "u00418E60", 0x4},
{0x2B, "u00418F30", 0x5},
{0x2C, "u00419010", 0x5},
{0x2D, "u004190A0", 0xC},
{0x2E, "u004194B0", 0x5},
{0x2F, "u004195A0", 0x4},
{0x30, "u00419670", 0x5},
{0x31, "u00419750", 0x4},
{0x32, "u004197C0", 0xA},
{0x33, "u00419900", 0x6},
{0x34, "u004199C0", 0xC},
{0x35, "u00419AF0", 0xB},
{0x36, "u00419C00", 0x3},
{0x37, "u00419C90", 0xB},
{0x38, "u00419DA0", 0xC},
{0x50, "add", 0x3}, // add. param1 = param2 + param3
{0x51, "sub", 0x3}, // sub. param1 = param2 - param3
{0x52, "mul", 0x3}, // mul. param1 = param2 * param3
{0x53, "div", 0x3}, // div. param1 = param2 / param3
{0x54, "mod", 0x3}, // mod. param1 = param2 % param3
{0x55, "mov", 0x2}, // mov. param1 = param2
{0x56, "and", 0x3}, // and. param1 = param2 & param3
{0x57, "or", 0x3}, // or. param1 = param2 | param3
{0x58, "sar", 0x3}, // sar. param1 = param2 >> param3
{0x59, "shl", 0x3}, // shl. param1 = param2 << param3
{0x5A, "eq", 0x3}, // eq. param1 = param2 == param3
{0x5B, "ne", 0x3}, // ne. param1 = param2 != param3
{0x5C, "lt", 0x3}, // lt. param1 = param2 < param3
{0x5D, "lte", 0x3}, // lte. param1 = param2 <= param3
{0x5E, "gr", 0x3}, // gr. param1 = param2 > param3
{0x5F, "gre", 0x3}, // gre. param1 = param2 >= param3
{0x60, "u0041A270", 0x2},
{0x61, "lookup-array", 0x3}, // lookup. param1 = param2[param3]
{0x62, "u0041A360", 0x3},
{0x63, "u00414A60", 0x2},
{0x64, "copy-local-array", 0x2},
{0x65, "u00414AA0", 0x2},
{0x66, "u00414AE0", 0x3},
{0x67, "u00414B20", 0x3},
{0x68, "u00414B60", 0x3},
{0x69, "u00414BA0", 0x3},
{0x6A, "u00414BE0", 0x3},
{0x6B, "u00414C20", 0x3},
{0x6C, "copy-to-global", 0x2}, // loop copy local value to global array, param1 = array start, param2 = count
{0x6D, "u00416960", 0x0},
{0x6E, "show-text", 0x2},
{0x6F, "end-text-line", 0x1},
{0x70, "u0041A750", 0x5},
{0x71, "u0041A7B0", 0x1},
{0x72, "wait-for-input", 0x1},
{0x73, "u0041AB30", 0xA},
{0x74, "u0041AC00", 0x1},
{0x75, "u0041AC30", 0x1},
{0x76, "u0041AC60", 0x1},
{0x77, "u0041ACB0", 0x1},
{0x78, "u0041AD00", 0x1},
{0x79, "u0041AD30", 0x3},
{0x7A, "u0041AD70", 0x3},
{0x7B, "u0041ADB0", 0x2}, // ukn, both args point to code locations
{0x7C, "u00416A90", 0x0},
{0x7D, "u0041AE00", 0x2},
{0x7E, "u0041AEA0", 0x1},
{0x7F, "u00414C60", 0x1},
{0x80, "u0041AF00", 0x1},
{0x81, "u0041AF30", 0x1},
{0x82, "u0041AF80", 0x5},
{0x83, "u00414C90", 0x3},
{0x84, "u0041AFE0", 0x1},
{0x85, "u00414CF0", 0x0},
{0x86, "u0041B210", 0x1},
{0x87, "u00414D10", 0x0},
{0x88, "u0041B290", 0x1},
{0x89, "u0041B2E0", 0x4},
{0x8A, "u0041B330", 0x6},
{0x8B, "u0041B3D0", 0x1},
{0x8C, "jmp", 0x1},
{0x8D, "u0041BCE0", 0x2},
{0x8E, "u0041BD60", 0x1},
{0x8F, "call", 0x1},
{0x90, "u0041BEB0", 0x7}, // ukn, args 5, 6 and 7 point to code locations
{0x91, "u0041BFB0", 0x1},
{0x92, "u0041C030", 0x2},
{0x93, "u00415040", 0x0},
{0x94, "u00415090", 0x0},
{0x95, "u0041C0C0", 0x2},
{0x96, "u004150C0", 0x0},
{0x97, "u0041C150", 0x5},
{0xA0, "jcc", 0x3},
{0xA1, "u00427C00", 0x0},
{0xA2, "u00427FD0", 0x2},
{0xA3, "u004244D0", 0x2},
{0xAA, "u0041C270", 0x2},
{0xAB, "u0041C330", 0x2},
{0xAC, "u0041C3E0", 0x9},
{0xAD, "u00415110", 0x0},
{0xAE, "u00415130", 0x0},
{0xAF, "u00415480", 0x0},
{0xB0, "u0041C530", 0x1},
{0xB1, "u0041C560", 0x1},
{0xB2, "u0041C590", 0x2},
{0xB3, "u004154B0", 0x0},
{0xB4, "play-sound-effect", 0x2}, // play a sound effect/ambient. param1 = file index, param2 = play mode?
{0xB5, "u0041D050", 0x1},
{0xB6, "u0041D080", 0x1},
{0xB7, "u0041D0E0", 0x1},
{0xB8, "u00415520", 0x0},
{0xB9, "u0041D140", 0x1},
{0xBA, "u0041D0B0", 0x1},
{0xBB, "u0041D250", 0x1},
{0xBC, "u0041D280", 0x1},
{0xBD, "u00415570", 0x1},
{0xBE, "u004155E0", 0x1},
{0xBF, "play-bgm", 0x1}, // param1 = bgm number
{0xC0, "u00415620", 0x1},
{0xC1, "u00415650", 0x0},
{0xC2, "u0041D2B0", 0x2},
{0xC3, "u0041D390", 0x1},
{0xC4, "play-voice", 0x1},
{0xC5, "u0041D4A0", 0x2},
{0xC6, "u0041D5D0", 0x2},
{0xC7, "u0041D760", 0x2},
{0xC8, "sleep", 0x1}, // param1 = sleep time?
{0xC9, "u00415770", 0x0},
{0xCA, "u004157A0", 0x0},
{0xCB, "u00415800", 0x1},
{0xCC, "mouse_callback", 0x2}, // sets mouse/keyboard callback location, param1 = id, param2 = offset (minus header, not multiplied by 4)
{0xCD, "get-input-type", 0x0}, // get input type, mouse, keyboard, pad etc
{0xCE, "u0041E0B0", 0x3},
{0xCF, "u00416D40", 0x0},
{0xD0, "u00415830", 0x1},
{0xD1, "u00415860", 0x0},
{0xD2, "u0041E110", 0x1},
{0xD3, "u00425960", 0x0},
{0xD4, "u004266F0", 0x4}, // seems to setup some kind of looping function calls. param1 = ukn, param2 = loop count, param3 = function location?, param4 = function location?
{0xD5, "u004262C0", 0x1},
{0xD6, "u004267D0", 0x6},
{0xD7, "u0041E1A0", 0x1},
{0xD8, "u0041E150", 0x2},
{0xD9, "u00415880", 0x0},
{0xDA, "u004158B0", 0x6},
{0xFA, "u00415940", 0x0},
{0xFB, "joy_callback", 0x2}, // sets code callback for joystick inputs. ID 0-4= left thumb up/down/left/right, 4 = X on Xbox controller etc. param1 = id, param2 = offset (minus header, not multiplied by 4)
{0xFC, "u004159F0", 0x0},
{0xFD, "u0041E2D0", 0x2},
{0xFE, "u0041E360", 0x1},
{0xFF, "u00415A10", 0x0},
{0x100, "u00415A60", 0x0},
{0x101, "u00415BF0", 0x0}, // joystick input?
{0x102, "u0041E3C0", 0x3},
{0x103, "u0041E4A0", 0x1},
{0x104, "u00415C50", 0x0},
{0x105, "u0041E4D0", 0x1},
{0x106, "u00415E40", 0x1},
{0x107, "u0041E500", 0x2},
{0x108, "u00415E70", 0x1},
{0x109, "u00415EC0", 0x2},
{0x10A, "u0041E540", 0x2},
{0x10B, "u0041E5A0", 0x2},
{0x10C, "u0041E5E0", 0x2},
{0x10D, "u00415F10", 0x1},
{0x10E, "u0041E650", 0x2},
{0x10F, "u0041E690", 0x1},
{0x12C, "lookup-array-2d", 0x5}, // 2d array lookup. param1 = param2[(param3 * param4) + param5]
{0x12D, "u0041E720", 0x7},
{0x12E, "u0041E940", 0x8},
{0x12F, "u0041ECB0", 0x4},
{0x130, "u00415F40", 0x1},
{0x131, "u00415F70", 0x1},
{0x132, "u0041EF00", 0x1},
{0x133, "u0041EFF0", 0x2},
{0x134, "u0041F050", 0x3},
{0x135, "bit-set", 0x2}, // bts, param1 = param1 OR param2
{0x136, "bit-reset", 0x2}, // btr, param1 = param1 NOR param2
{0x137, "u0041F1C0", 0x1},
{0x138, "u0041F2B0", 0x2},
{0x139, "u0041F310", 0x3},
{0x13A, "u0041F3A0", 0x6},
{0x13B, "u0041F440", 0x7},
{0x13C, "u0041F7E0", 0x1},
{0x13D, "u0041F840", 0x3},
{0x13E, "u0041F8D0", 0x2},
{0x13F, "check-bit", 0x3}, // param1 = param2 & (1 << param3). Neg, sbb, neg to get the result as a bool
{0x140, "u0041F9C0", 0x4},
{0x141, "u0041FAA0", 0x1},
{0x142, "u0041FB10", 0x1},
{0x143, "u00415FB0", 0x0},
{0x144, "u004259D0", 0x2},
{0x145, "u00416040", 0x1},
{0x146, "u0041FB40", 0x1},
{0x147, "u0041FB80", 0x6},
{0x148, "u004160A0", 0x1},
{0x149, "u0041FCE0", 0x1},
{0x14A, "u0041FD10", 0x7},
{0x14B, "u0041FF50", 0x1},
{0x14C, "set-agerc-export", 0x2}, // binds an agerc.dll export name to the given number
{0x14D, "call-agerc-export", 0x6}, // call the param1 agerc exported function
{0x190, "u0041C5E0", 0x2},
{0x191, "u0041A4A0", 0x2},
{0x192, "set-string", 0x2}, // u004252D0 : set-string. param1 = param2
{0x193, "concat", 0x3}, // u00425370 : concat. param1 = param2.concat(param3)
{0x194, "u00425480", 0x3},
{0x195, "u00425580", 0x3},
{0x196, "display-furigana", 0x3}, // u0041B400 : display-furigana. param1 = text, param2 = furigana
{0x197, "u0041B510", 0x1},
{0x198, "u0041B540", 0x3},
{0x199, "u00414D50", 0x0},
{0x19A, "u00414E50", 0x1},
{0x19B, "u00414E80", 0x0},
{0x19C, "u00414EC0", 0x0},
{0x19D, "u0041C680", 0x2},
{0x19E, "u0041C6E0", 0x2},
{0x19F, "u0041C860", 0x2},
{0x1A0, "u0041C9B0", 0x9},
{0x1A1, "u0041CB40", 0x2},
{0x1A2, "u00428010", 0x1},
{0x1A3, "string-lookup-set", 0x1}, // check the value given exists in save/current data and set. param1 = strings[param1]
{0x1A4, "u0041B580", 0x2},
{0x1A5, "set-font", 0x1}, // set-font
{0x1A6, "halve-strlen", 0x2}, // halve-strlen? param1 = param2.length() / 2 (rounded down)
{0x1A7, "comment", 0x1}, // Developer debug comment
{0x1A8, "dev_ukn", 0x0}, // Developer debug something, no function in-game
{0x1A9, "u00428090", 0x1},
{0x1AA, "u00425920", 0x1},
{0x1AB, "u0041CCA0", 0x2},
{0x1AC, "u0041CD80", 0x3},
{0x1AD, "u004154F0", 0x0},
{0x1AE, "u0041CED0", 0x3},
{0x1AF, "u004245C0", 0x3},
{0x1B0, "u0041A510", 0x3},
{0x1B1, "u0041B5C0", 0x1},
{0x1B2, "u00425790", 0x1}, // to string table?
{0x1B3, "u004257D0", 0x0},
{0x1B4, "u004237C0", 0x0},
{0x1B5, "u0041B5F0", 0x1},
{0x1B6, "u00414F60", 0x1},
{0x1B7, "u0041B640", 0x1},
{0x1B8, "u0041B670", 0x2},
{0x1B9, "u0041B710", 0x2},
{0x1BA, "u0041D850", 0x2},
{0x1BB, "u0041B7B0", 0x1},
{0x1BC, "u00415670", 0x0},
{0x1BD, "u0041D910", 0x1},
{0x1BE, "u0041D9D0", 0x2},
{0x1BF, "u004156C0", 0x0},
{0x1C0, "u0041DB70", 0x1},
{0x1C1, "u0041B820", 0x3},
{0x1C2, "u0041B860", 0x2},
{0x1C3, "u0041B8A0", 0x2},
{0x1C4, "u00415720", 0x1},
{0x1C5, "u00425800", 0x4},
{0x1C6, "u0041DD80", 0x2},
{0x1C7, "u00414F90", 0x1},
{0x1C8, "toString", 0x2}, // u00425680 : toString
{0x1C9, "u0041B8E0", 0x3},
{0x1CA, "u0041B9B0", 0x1},
{0x1CB, "u00414FD0", 0x1},
{0x1CC, "u00415010", 0x1},
{0x1CD, "u0041A560", 0x2},
{0x1CE, "u0041B9F0", 0x1},
{0x1CF, "u0041DA10", 0x1},
{0x1D0, "u0041BA80", 0x3},
{0x1D1, "u0041BAE0", 0x5},
{0x1D2, "u0041BB40", 0x2},
{0x1D3, "u0041BB90", 0x5},
{0x1D4, "u0041BC00", 0x4},
{0x1D5, "u00415700", 0x0},
{0x1D6, "u0041DA40", 0x2},
{0x1D7, "u0041DA80", 0x2},
{0x1D8, "u0041DAD0", 0x3},
{0x1D9, "u0041DB20", 0x2},
{0x1F4, "u004160D0", 0x0},
{0x1F5, "u00416120", 0x0},
{0x1F6, "u00416170", 0x0},
{0x1F7, "u00420270", 0x2},
{0x1F8, "create-texture", 0x4}, // create a new drawable rect. param1 = id, param2 = sizeX, param3 = sizeY, param4 = ukn
{0x1F9, "set-texture", 0x3}, // set a texture to a given ID. param1 = file index, param2 = id, param3 = ?
{0x1FA, "u00420480", 0x1},
{0x1FB, "draw-texture", 0x8}, // draw a texture. param1 = UI element id?, param2 = textureID, param3 = texX, param4 = texY, param5 = width, param6 = height, param7 = drawX, param8 = drawY
{0x1FC, "u004205F0", 0x1},
{0x1FD, "u00420620", 0x4},
{0x1FE, "u004206C0", 0x5},
{0x1FF, "u00420770", 0x4},
{0x200, "u00420800", 0x1},
{0x201, "u00416190", 0x1},
{0x202, "u00420880", 0x5},
{0x203, "u00420950", 0x4},
{0x204, "draw-string", 0x4}, // u00420A10 : place-string. param1 = id? param2 = x, param3 = y, param4 = string
{0x205, "u00420A60", 0x6},
{0x206, "u004161C0", 0x7},
{0x207, "u00420B00", 0x8},
{0x208, "u00420BF0", 0x3},
{0x209, "u00420C50", 0x5},
{0x20A, "u00420CE0", 0x1},
{0x20B, "u00420D50", 0x7},
{0x20C, "u00416200", 0x0},
{0x20D, "u00420E10", 0x1},
{0x20E, "u00416250", 0x0},
{0x20F, "u00420E40", 0x3},
{0x210, "u00420FF0", 0x1},
{0x211, "u00421060", 0x1},
{0x212, "u00421090", 0x2},
{0x213, "u004210D0", 0x3},
{0x214, "u00421120", 0x2},
{0x215, "u00421160", 0x2},
{0x216, "u004211A0", 0x2},
{0x217, "u004211E0", 0x4},
{0x218, "u00421270", 0x4},
{0x219, "u004212E0", 0x4},
{0x21A, "u00421370", 0x4},
{0x21B, "u004213E0", 0x1},
{0x21C, "u00416270", 0x0},
{0x21D, "u00421410", 0x2},
{0x21E, "u00421450", 0x6},
{0x21F, "u00421510", 0x7},
{0x220, "u004215D0", 0x6},
{0x221, "u00421670", 0x4},
{0x222, "u004216C0", 0x2},
{0x223, "u00421700", 0x8},
{0x224, "u00416290", 0x0},
{0x225, "u00421780", 0x2},
{0x226, "u004217D0", 0x5},
{0x227, "u00421880", 0x6},
{0x228, "u00421940", 0x5},
{0x229, "u004219E0", 0x5},
{0x22A, "u00421A90", 0x3},
{0x22B, "u00421B30", 0x4},
{0x22C, "u00421BD0", 0x3},
{0x22D, "u00421C60", 0x5},
{0x22E, "u00421D10", 0x6},
{0x22F, "u00421DD0", 0x5},
{0x230, "u00421E70", 0x1},
{0x231, "u00421EA0", 0x4},
{0x232, "u00421EF0", 0x4},
{0x233, "u00421FB0", 0x5},
{0x234, "u00422060", 0x5},
{0x235, "u00422100", 0x5},
{0x236, "u004221A0", 0x4},
{0x237, "u00422350", 0x2},
{0x238, "u00422390", 0x1},
{0x239, "u004223C0", 0x6},
{0x23A, "u00422420", 0x2},
{0x23B, "u00422460", 0x7},
{0x23C, "u004162B0", 0x0},
{0x23D, "u004162F0", 0x0},
{0x23E, "u004228C0", 0x2},
{0x23F, "u00422930", 0x2},
{0x240, "u004229A0", 0x4},
{0x241, "u00422B80", 0x5},
{0x242, "u00422D60", 0x2},
{0x243, "u00417070", 0x0},
{0x244, "u00416360", 0x0},
{0x245, "u00422DA0", 0x2},
{0x246, "u00422E10", 0x2},
{0x247, "u00416390", 0x1},
{0x248, "u00422E80", 0x1},
{0x249, "u00422EB0", 0x3},
{0x24A, "u004163C0", 0x3},
{0x24D, "u00422E90", 0xC},
{0x24E, "u00422EA0", 0x1},
{0x24F, "u00422ED0", 0xA},
{0x250, "u00422F60", 0xA},
{0x251, "u00422FF0", 0xC},
{0x252, "u00423000", 0x1},
{0x253, "u00423019", 0x2},
{0x254, "u00423049", 0x5},
{0x256, "u00423050", 0x5},
{0x257, "257", 0x5}, // Sankai no Yubiwa
{0x258, "u00422FE0", 0x2},
{0x259, "u00416410", 0x0},
{0x25A, "u00423120", 0x1},
{0x25B, "25B", 0x1}, // Kami no Rhapsody
{0x25C, "u00423122", 0x8},
{0x25D, "u00423123", 0x3},
{0x25E, "u00423124", 0x5},
{0x25F, "u00423125", 0x4},
{0x260, "u00423126", 0x4},
{0x261, "u00423127", 0x1},
{0x262, "262", 0x1}, // Amayui 2
{0x263, "263", 0x1}, // Amayui 2
{0x264, "264", 0x5}, // Hyakusen
{0x2BC, "u00423020", 0xB},
{0x2BD, "u00423100", 0x1},
{0x2BE, "u00423140", 0x1},
{0x2BF, "u00423180", 0x3},
{0x2C0, "u004231C0", 0x3},
{0x2C1, "u00425BC0", 0x1},
{0x2C2, "u00425CD0", 0x6},
{0x2C3, "u00423200", 0x2},
{0x2C4, "u00416450", 0x0}, // using as a way to test logging, originally u00416450
{0x2C5, "strlen", 0x2}, // u0042B5D0 : strlen. param1 = param2.length()
{0x2C6, "u0042B5E0", 0x2},
{0x2C7, "u0042B5F0", 0x4},
{0x2C8, "u0042B610", 0x4},
{0x2C9, "2C9", 0x3}, // Sankai no Yubiwa
{0x2CC, "2CC", 0x1}, // Sankai no Yubiwa
{0x2CD, "2CD", 0x1}, // Sankai no Yubiwa
{0x2CE, "u0042B616", 0x1},
{0x2CF, "u0042B617", 0x1},
{0x2D0, "u0042B940", 0x3},
{0x2D1, "u0042B950", 0x3},
{0x2D2, "u0042B960", 0x3},
{0x2D3, "u0042B970", 0x3},
{0x2D5, "u0042B990", 0x2},
{0x2D7, "u0042B9B0", 0x2},
{0x2D8, "set-array-to", 0x3}, // Set a given array to the given value x times. loop: param1[param3] = param2; param3++
{0x2D9, "u0042BA30", 0x2},
{0x2DA, "u004234E0", 0x8},
{0x2DB, "u004235C0", 0x1},
{0x2DC, "u0042BA80", 0x1},
{0x2DD, "u0042D880", 0x2},
{0x2DE, "u0042BAC0", 0x2},
{0x2DF, "u0042BAC1", 0x3},
{0x2E0, "u0042CE0F", 0x3},
{0x2E1, "u0042CE10", 0x3},
{0x2E2, "u0042CE11", 0x3},
{0x2E3, "u0042CE30", 0x3},
{0x2E4, "u0042CE31", 0x3},
{0x2E5, "u0042CE50", 0x1},
{0x2E6, "u0042CE60", 0x2},
{0x2E7, "u0042CE70", 0x2},
{0x2E8, "u0042CE80", 0x1},
{0x2E9, "u0042CE90", 0x1},
{0x2EA, "u0042CEA0", 0x1},
{0x2EB, "u0042CEB0", 0x1},
{0x2EC, "u0042CEC0", 0x2},
{0x2EE, "u0042CEC2", 0x1},
{0x2EF, "u0042CEC3", 0xB},
{0x2F0, "u0042CEC4", 0x9},
{0x2F1, "u0042CEC5", 0x7},
{0x2F2, "u0042CEC6", 0x6},
{0x2F3, "2F3", 0x6}, // La Dea
{0x2F4, "2F4", 0x3}, // La Dea
{0x2F5, "2F5", 0x4}, // La Dea
{0x2F6, "2F6", 0x1}, // La Dea
{0x2F7, "2F7", 0x1}, // La Dea
{0x2F8, "2F8", 0x2}, // La Dea
{0x2F9, "2F9", 0x7}, // La Dea
{0x2FA, "2FA", 0x1}, // La Dea
{0x2FB, "2FB", 0x1}, // La Dea
{0x2FC, "2FC", 0x5}, // Kami no Rhapsody
{0x2FD, "2FD", 0x6}, // Kami no Rhapsody
{0x2FE, "2FE", 0x1}, // Sankai no Yubiwa
{0x2FF, "2FF", 0x2}, // Sankai no Yubiwa
{0x300, "300", 0x3}, // Sankai no Yubiwa
{0x301, "301", 0x1}, // Sankai no Yubiwa
{0x302, "302", 0x2}, // Sankai no Yubiwa
{0x303, "303", 0x3}, // Sankai no Yubiwa
{0x304, "304", 0x0}, // Sankai no Yubiwa
{0x305, "305", 0x0}, // Sankai no Yubiwa
{0x306, "306", 0x1}, // Sankai no Yubiwa
{0x307, "307", 0x1}, // Sankai no Yubiwa
{0x308, "308", 0x1}, // Amayui Alchemy Meister
{0x30A, "30A", 0x2}, // Amayui Alchemy Meister
{0x30C, "30C", 0x1}, // Tenmei no Conquista
{0x320, "u0043AA20", 0xA},
{0x321, "u0043AA30", 0x3},
{0x322, "u0043AA40", 0x4},
{0x323, "u0043AA50", 0x5},
{0x324, "u0043AA60", 0x0},
{0x325, "u0043AA70", 0x2},
{0x326, "u0043AA80", 0x4},
{0x327, "u0043AA90", 0x1},
{0x328, "u0043AAA0", 0x3},
{0x329, "u0043AAB0", 0x2},
{0x32A, "32A", 0x1}, // Kami no Rhapsody
{0x32B, "u0043AAD0", 0x0},
{0x32C, "u0043AAE0", 0x6},
{0x32D, "u0043AAF0", 0x2},
{0x32E, "u0043AB10", 0xB},
{0x32F, "u0043AB11", 0x1},
{0x330, "u0043AB12", 0x2},
{0x332, "u0043AB14", 0x4},
{0x334, "u0043AB16", 0x1},
{0x335, "u0043AB17", 0x4},
{0x337, "u0043AB19", 0x4},
{0x33B, "u0043AB1D", 0x4},
{0x33D, "u0043AB1E", 0x3},
{0x33E, "u0043AB1F", 0x5},
{0x33F, "u0043AB20", 0x3},
{0x340, "340", 0x1}, // Sankai no Yubiwa
{0x341, "341", 0x2}, // Amayui Alchemy Meister
{0x342, "342", 0x1}, // Amayui Alchemy Meister
{0x344, "344", 0x2}, // Amayui Alchemy Meister
{0x345, "345", 0x3}, // Amayui Alchemy Meister
{0x349, "349", 0x4}, // Amayui Alchemy Meister
{0x34D, "34D", 0x6}, // Amayui Alchemy Meister
{0x34E, "34E", 0x4}, // Amayui Alchemy Meister
{0x352, "352", 0x3}, // Amayui Alchemy Meister
{0x353, "353", 0x2}, // Fuukan no Gransesta
{0x354, "354", 0x2}, // Fuukan no Gransesta
{0x358, "358", 0x5}, // Amayui 2
{0x35A, "35A", 0x5}, // Amayui 2
{0x35B, "35B", 0x2}, // Fuukan no Gransesta
{0x35C, "35C", 0x2}, // Fuukan no Gransesta
{0x35D, "35D", 0x3}, // Fuukan no Gransesta
{0x35F, "35F", 0x3}, // Fuukan no Gransesta
{0x360, "360", 0x3}, // Fuukan no Gransesta
{0x361, "361", 0x2}, // Fuukan no Gransesta
{0x363, "363", 0x3}, // Amayui 2
{0x364, "364", 0x3}, // Amayui 2
{0x384, "384", 0x3}, // Tenmei no Conquista
{0x386, "386", 0xB}, // Tenmei no Conquista
{0x387, "387", 0x8}, // Tenmei no Conquista
{0x388, "388", 0x3}, // Tenmei no Conquista
{0x389, "389", 0x6}, // Tenmei no Conquista
{0x38F, "38F", 0x6}, // Tenmei no Conquista
{0x390, "390", 0x7}, // Tenmei no Conquista
{0x391, "391", 0x2}, // Amayui 2
{0x392, "392", 0x1}, // Tenmei no Conquista
{0x393, "393", 0x6}, // Amayui 2
{0x396, "396", 0x5}, // Tenmei no Conquista
{0x398, "398", 0x3}, // Amayui 2
{0x399, "399", 0x7}, // Tenmei no Conquista
{0x39B, "39B", 0x5}, // Amayui 2
});
}
static constexpr auto definitions = make_defs();
const Instruction_Definition* instruction_for_op_code(u32 op_code, std::streamoff offset) {
size_t low = 0;
size_t high = definitions.size() - 1;
while (low <= high) {
const size_t mid = (low + high) >> 1;
const size_t mid_op_code = definitions[mid].op_code;
//std::cout << "op 0x" << std::hex << op_code << " --- mid 0x" << std::hex << mid << "\n";
if (mid_op_code < op_code) {
low = mid + 1;
} else if (mid_op_code > op_code) {
high = mid - 1;
} else {
//std::cout << "Found instruction 0x" << std::hex << mid_op_code << "\n";
return &definitions[mid];
}
}
fprintf(stderr, "Unknown instruction : 0x%x at 0x%llx\n", op_code, offset);
exit(-1);
return nullptr;
}
static std::map<const std::string_view, const Instruction_Definition*> str_memo;
const Instruction_Definition* instruction_for_label(const std::string_view label) {
const auto it = str_memo.find(label);
if (it != str_memo.end()) {
return it->second;
}
// We'll have to actually scan through here
const auto instr = std::ranges::find_if(definitions,
[&label](const Instruction_Definition& instr) { return instr.label == label; });
if (instr != definitions.end()) {
str_memo[instr->label] = &*instr;
return &*instr;
}
fprintf(stderr, "Unknown instruction : %s\n", label.data());
exit(-1);
return nullptr;
}
std::wstring cp_to_utf16(u32 code_page, const std::string& input) {
const auto in_size = MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0);
if (in_size == 0) {
return L"";
}
std::wstring utf16(in_size, L'\0');
if (in_size != MultiByteToWideChar(code_page, 0, input.data(), static_cast<int>(input.size()), utf16.data(), static_cast<int>(utf16.size()))) {
utf16.clear();
}
return utf16;
}
std::string utf16_to_cp(u32 code_page, const std::wstring& input) {
const auto in_size = WideCharToMultiByte(code_page, 0, input.data(), static_cast<int>(input.size()), nullptr, 0, nullptr, nullptr);
if (in_size == 0) {
return "";
}
std::string output(in_size, '\0');
if (in_size != WideCharToMultiByte(code_page, 0, input.data(), static_cast<int>(input.size()), output.data(), static_cast<int>(output.size()), nullptr, nullptr)) {
output.clear();
}
return output;
}

View File

@@ -0,0 +1,278 @@
#include "age-shared.h"
#include "disassembler.h"
#include <iostream>
Instruction parse_instruction(std::istream& fd, Header& header, const Instruction_Definition* def, std::streamoff offset, std::streamoff* data_array_end) {
std::vector<Argument> arguments;
arguments.reserve(def->argument_count);
for (u32 current{0}; current < def->argument_count; ++current) {
Argument& arg = arguments.emplace_back();
// reads only 2 uint32_t
fd >> arg;
// If this instruction is a 'String' or copy-array argument, we have to alter data_array_end accordingly.
if (arg.type == 2) {
// Strings are all located at the end of the data array, XORed with 0xFF, and separated by 0xFF.
std::streamoff string_offset = header.GetLength() + (static_cast<uint64_t>(arg.raw_data) << 2);
*data_array_end = std::min(*data_array_end, string_offset);
// mark current
std::streamoff cur_off = fd.tellg();
// jump to string offset
fd.seekg(string_offset, std::ios::beg);
// read the string, XOR'ing each character
std::string decoded;
decoded.reserve(32);
if (header.IsVer5()) {
std::wstring utf16_decoded{};
u16 character{};
fd.read((char*)&character, sizeof(character));
while (character != 0xFFFF) {
character ^= 0xFFFF;
// flip the bytes, we need little endian
//character = ((character & 0xFF00) >> 8) | ((character & 0xFF) << 8);
utf16_decoded += character;
fd.read((char*)&character, sizeof(character));
}
// convert it over to UTF8 for easier text editing
arg.decoded_stringv4 = utf16_to_cp(CP_UTF8, utf16_decoded);
} else {
// SJIS -> UTF8
u8 character{};
fd.read((char*)&character, sizeof(character));
while (character != 0xFF) {
character ^= 0xFF;
decoded += character;
fd.read((char*)&character, sizeof(character));
}
// convert it over to UTF8 for easier text editing
arg.decoded_stringv4 = utf16_to_cp(CP_UTF8, cp_to_utf16(CP_932, decoded));
}
// Go back to the instruction position
fd.seekg(cur_off, std::ios::beg);
} else if (def->op_code == 0x64 && current == 1) {
// This instruction actually references an array in the file's footer.
std::streamoff array_offset = header.GetLength() + (static_cast<std::int64_t>(arg.raw_data) << 2);
*data_array_end = std::min(*data_array_end, array_offset);
// mark current
std::streamoff cur_off = fd.tellg();
// Jump to array offset
fd.seekg(array_offset, std::ios::beg);
fd >> arg.data_array;
// Go back to the instruction position
fd.seekg(cur_off, std::ios::beg);
}
if (arg.type < 0 || (arg.type > 0xE && arg.type < 0x8003) || arg.type > 0x800B) {
std::streamoff cur_off = fd.tellg();
fprintf(stderr, "Pos : %llx -> Opcode : %x, argument %d\n", cur_off, def->op_code, current);
fprintf(stderr, "Unknown type : %x\n", arg.type);
fprintf(stderr, "Value : %x\n", arg.raw_data);
exit(-1);
}
}
return Instruction(def, arguments, offset);
}
std::string disassemble_header(Header& header) {
static constexpr std::string_view HEADER_PREFIX = "==Binary Information - do not edit==\n";
static constexpr std::string_view HEADER_SUFFIX = "====\n\n";
static constexpr std::string_view SIGNATURE_PREFIX = "signature = ";
static constexpr std::string_view VARS_PREFIX = "\nlocal_vars = { ";
static constexpr std::string_view VARS_SUFFIX = " }\n";
std::stringstream stream(std::stringstream::out);
auto binary_header{header.GetHeader()};
stream << HEADER_PREFIX;
stream << SIGNATURE_PREFIX;
stream.write((char*)binary_header.signature, sizeof(binary_header.signature));
stream << VARS_PREFIX;
stream << std::hex << binary_header.local_integer_1;
stream << ' ';
stream << std::hex << binary_header.local_floats;
stream << ' ';
stream << std::hex << binary_header.local_strings_1;
stream << ' ';
stream << std::hex << binary_header.local_integer_2;
stream << ' ';
stream << std::hex << binary_header.unknown_data;
stream << ' ';
stream << std::hex << binary_header.local_strings_2;
stream << VARS_SUFFIX;
stream << HEADER_SUFFIX;
stream.flush();
return stream.str();
}
const std::string get_type_label(u32 type) {
switch (type) {
case 0: return "";
// Not the best way to handle floats, but will do for now.
case 1: return "float";
case 2: return "";
case 3: return "global-int";
case 4: return "global-float";
case 5: return "global-string";
case 6: return "global-ptr";
case 8: return "global-string-ptr";
case 9: return "local-int";
case 0xA: return "local-float";
case 0xB: return "local-string";
case 0xC: return "local-ptr";
case 0xD: return "local-float-ptr";
case 0xE: return "local-string-ptr";
// In Sankai no Yubiwa onwards, why?
// Another type of int and string?
case 0x8003: return "0x8003";
case 0x8005: return "0x8005";
case 0x8009: return "0x8009";
case 0x800B: return "0x800B";
default: {
fprintf(stderr, "Unknown type value: %x\n", type);
exit(-1);
}
}
}
std::string disassemble_instruction(Header& header, const Instruction& instruction) {
std::stringstream stream(std::stringstream::out);
// Give the loc of where we are
//----------------
// stream << std::hex << (instruction->offset << 2) + HEADER_LENGTH << " (" << std::hex << (instruction->offset << 2) << ":" << std::hex << instruction->offset << "): ";
//----------------
stream << instruction.definition->label;
if (instruction.arguments.size() > 0) {
stream << ' ';
}
s32 x = 0;
for (const auto& argument : instruction.arguments) {
std::string type_label = get_type_label(argument.type);
if (type_label != "") {
// e.g. (global_int 17A)
stream << '(';
stream << type_label << ' ' << std::hex << argument.raw_data;
stream << ')';
} else if (argument.type == 2) {
// e.g. "this is a string"
stream << '"';
stream << argument.decoded_stringv4;
stream << '"';
} else if (instruction.definition->op_code == 0x64 && argument.type == 0) {
// e.g. [1 2 3 4 5 6]
stream << "[";
for (u32 i = 0; i < argument.data_array.length; i++) {
stream << std::hex << argument.data_array.data[i];
if (i < argument.data_array.length - 1) {
stream << ' ';
}
}
stream << "]";
} else if (is_control_flow(instruction)) {
// e.g. label_99C8
if (is_label_argument(instruction, x)) {
stream << "label_";
stream << std::right << std::setfill('0') << std::setw(8) <<
std::hex << header.GetLength() + (static_cast<uint64_t>(argument.raw_data) << 2);
} else {
stream << std::hex << argument.raw_data;
}
} else {
stream << std::hex << argument.raw_data;
}
if (x < instruction.arguments.size() - 1) {
stream << ' ';
}
x++;
}
stream << '\n';
stream.flush();
return stream.str();
}
std::stringstream write_script_file(Header& header, std::span<Instruction> instructions) {
// Find out which of our instructions are labels
std::unordered_set<u32> labels;
for (auto& instruction : instructions) {
if (is_control_flow(instruction)) {
s32 x{0};
for (auto& argument : instruction.arguments) {
if (is_label_argument(instruction, x)) {
labels.insert(argument.raw_data);
}
x++;
}
}
}
std::stringstream output(std::stringstream::in | std::stringstream::out | std::stringstream::binary);
output << disassemble_header(header);
for (auto& instruction : instructions) {
// If this instruction is referenced as a label, make it clear
if (labels.find(instruction.offset) != labels.end()) {
std::stringstream label_instr;
label_instr << "\nlabel_";
label_instr << std::right << std::setfill('0') << std::setw(8) <<
std::hex << header.GetLength() + (instruction.offset << 2);
label_instr << '\n';
output << std::move(label_instr.str());
}
output << disassemble_instruction(header, instruction);
}
output.flush();
return output;
}
std::stringstream disassemble(std::istream& fd) {
Header header(fd);
auto& binary_hdr{header.GetHeader()};
std::streamoff data_array_end = header.GetLength() + (static_cast<uint64_t>(std::min(std::min(binary_hdr.table_1_offset, binary_hdr.table_2_offset), binary_hdr.table_3_offset)) << 2);
std::streamoff strings_end = data_array_end;
std::vector<Instruction> instructions;
instructions.reserve(5'000);
while (fd.tellg() < data_array_end) {
std::streamoff offset = fd.tellg();
u32 op_code{};
fd.read((char*)&op_code, sizeof(op_code));
if (op_code == 0x0) {
fprintf(stderr, "Offset 0x%llX bad opcode : %X\n", offset, op_code);
exit(-1);
}
const Instruction_Definition* def = instruction_for_op_code(op_code, offset);
instructions.emplace_back(parse_instruction(fd, header, def, (offset - header.GetLength()) >> 2, &data_array_end));
}
return write_script_file(header, instructions);
}

1194
vm-map/opcode-leads.json Normal file

File diff suppressed because it is too large Load Diff

2023
vm-map/opcodes-himegari.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,140 @@
# Smallest scripts — full pseudo-listings (opcodes NOT yet decoded)
### INIT.BIN (64 bytes)
```
file INIT.BIN
body 1 dwords (4 bytes)
header F0=0x1 F1=1 F2=0x1 F3=0x1 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x00001) 1 dwords
table T1 off 0x00001 count 0 tag 0x71 purity 0/0
table T2 off 0x00001 count 0 tag 0x03 purity 0/0
table T3 off 0x00001 count 0 tag 0x8f purity 0/0
strings 0 inline, 0 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: 2
; ---- TABLES ----
T1 (count 0, off 0x00001):
T2 (count 0, off 0x00001):
T3 (count 0, off 0x00001):
; ---- STRINGS ----
(no inline strings)
```
### ADDILL.BIN (104 bytes)
```
file ADDILL.BIN
body 11 dwords (44 bytes)
header F0=0x1 F1=1 F2=0x1 F3=0x1 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x00009) 9 dwords
table T1 off 0x00009 count 0 tag 0x71 purity 0/0
table T2 off 0x00009 count 2 tag 0x03 purity 2/2
table T3 off 0x0000b count 0 tag 0x8f purity 0/0
strings 0 inline, 0 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: 1f4 3 0 329e 3 0 329d 1f5 2
; ---- TABLES ----
T1 (count 0, off 0x00009):
T2 (count 2, off 0x00009): 1 4
T3 (count 0, off 0x0000b):
; ---- STRINGS ----
(no inline strings)
```
### RTN_B002.BIN (160 bytes)
```
file RTN_B002.BIN
body 25 dwords (100 bytes)
header F0=0x1 F1=1 F2=0x1 F3=0x2 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x00019) 25 dwords
table T1 off 0x00019 count 0 tag 0x71 purity 0/0
table T2 off 0x00019 count 0 tag 0x03 purity 0/0
table T3 off 0x00019 count 0 tag 0x8f purity 0/0
strings 0 inline, 0 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: a0 3 cca45 0 ffffffff 0 18 61 c 0 3 52289 3 152616 55 c 0 0 b 55 3 eff77 0 1 2
; ---- TABLES ----
T1 (count 0, off 0x00019):
T2 (count 0, off 0x00019):
T3 (count 0, off 0x00019):
; ---- STRINGS ----
(no inline strings)
```
### RTN_B003.BIN (160 bytes)
```
file RTN_B003.BIN
body 25 dwords (100 bytes)
header F0=0x1 F1=1 F2=0x1 F3=0x2 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x00019) 25 dwords
table T1 off 0x00019 count 0 tag 0x71 purity 0/0
table T2 off 0x00019 count 0 tag 0x03 purity 0/0
table T3 off 0x00019 count 0 tag 0x8f purity 0/0
strings 0 inline, 0 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: a0 3 cca3a 0 ffffffff 0 18 61 c 0 3 52289 3 152616 55 c 0 0 0 55 3 eff77 0 1 2
; ---- TABLES ----
T1 (count 0, off 0x00019):
T2 (count 0, off 0x00019):
T3 (count 0, off 0x00019):
; ---- STRINGS ----
(no inline strings)
```
### DEBUGANIME.BIN (164 bytes)
```
file DEBUGANIME.BIN
body 26 dwords (104 bytes)
header F0=0x1 F1=1 F2=0x1 F3=0x1 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x0001a) 26 dwords
table T1 off 0x0001a count 0 tag 0x71 purity 0/0
table T2 off 0x0001a count 0 tag 0x03 purity 0/0
table T3 off 0x0001a count 0 tag 0x8f purity 0/0
strings 0 inline, 0 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: 55 3 15288c 0 8 55 3 15261a 0 1 55 3 15261b 0 0 55 3 15261c 0 1 55 3 15261d 0 32 2
; ---- TABLES ----
T1 (count 0, off 0x0001a):
T2 (count 0, off 0x0001a):
T3 (count 0, off 0x0001a):
; ---- STRINGS ----
(no inline strings)
```
### CALLBACK_LOST.BIN (176 bytes)
```
file CALLBACK_LOST.BIN
body 29 dwords (116 bytes)
header F0=0x3 F1=1 F2=0x1 F3=0x1 F4=1 F5=0x1 F6=0x1c
code [0x00000 .. 0x0001d) 29 dwords
table T1 off 0x0001d count 0 tag 0x71 purity 0/0
table T2 off 0x0001d count 0 tag 0x03 purity 0/0
table T3 off 0x0001d count 0 tag 0x8f purity 0/0
strings 1 inline, 1 references
; ---- CODE (chunked by T3 line-index; opcodes not yet decoded) ----
0x00000: 55 9 0 0 3314 1a7 2 str->0x00019 1a8 5a 9 1 3 62428 0 0 a0 9 1 0 ffffffff 0 18 20c 2 ; 'callback_lost'
0x00019: STR 'callback_lost'
; ---- TABLES ----
T1 (count 0, off 0x0001d):
T2 (count 0, off 0x0001d):
T3 (count 0, off 0x0001d):
; ---- STRINGS ----
[0x00019 @file 0x0000a0] (4dw) 'callback_lost'
```