From 862b74ecbc7c8ccfa92a71fb9361de3412a3f2b0 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 29 Jul 2026 12:57:55 -0400 Subject: [PATCH] Investigate INPUTNAME string opcodes --- docs/engine-re.md | 52 +++++++++++++++++++++----- docs/opcode-reference.md | 41 +++++++++++++------- docs/phase-b-framework.md | 21 +++++++++++ tools/age_opcodes_himegari.py | 2 +- vm-map/opcodes.toml | 70 +++++++++++++++++++---------------- 5 files changed, 130 insertions(+), 56 deletions(-) diff --git a/docs/engine-re.md b/docs/engine-re.md index 4387633..fa1383b 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -611,16 +611,16 @@ handle array** (native entry-state a cold single-scene harness skips), NOT a sto table). So the intended lifecycle is **enter `aba5c=1` → intro loop fills slots → `aba5c→0` → content uses slots.** Cold we skip straight to content with an empty slot table → all layers collapse to slot 0 → grey. -**The loop iterator `op 0x140` is a native video-service call — not statically reproducible.** Handler = +**The loop iterator `op 0x140` is a native AGERC service call.** Handler = **`0x4299c0`** (dispatch `ctx[0x9b74c]=0x4299c0`; created+typed `EngineCtx*`+annotated; Kelebek `u0041F9C0` is VA-drift). It records the generic 9-dword instruction length, copies operand-2/3 strings (`"LABEL"`, `"J"`) + operand-4 int, calls -**`(*DAT_005c6018)(8, ctx[0x54fe8], &{str,str,int})`**, and writes the returned PC-like value back to operand 1 -(SC0000: `G[0x6be]`). `DAT_005c6018` is a **runtime-resolved function pointer** (all 6 xrefs are READs, no -static writer) — the engine's **native video / transition / timing service**: `FUN_00405740` (a screen- -transition/fade routine full of DirectDraw-layer calls) calls the *same* pointer with `cmd_id=3` and branches -on its return (`1`/`2` = transition progress). It is the **same class as the DirectDraw workers this project -deliberately does not model.** ⇒ faithfully emulating `0x140` = emulating the native video service = out of -static scope, permanently. +**`(*g_pfnAgercHostCallback)(8, ctx[0x54fe8], &{str,str,int})`**, and writes the returned PC-like value back +to operand 1 (SC0000: `G[0x6be]`). The callback has no static writer in the unpacked AGE image because the +loaded resource DLL supplies it. A read-only live probe now resolves it to `AGERC.DLL+0x1050`; the imported +`/support/AGERC.DLL` image names that thunk `agerc_host_callback_export` and its worker +`agerc_dispatch_host_command@0x100025f0`. Command 8 opens AGERC dialog resource 8. The same callback's +command 3 services `FUN_00405740` screen transitions. This closes the callback's ownership and makes a +future `0x140` dive statically possible, but does not by itself identify command 8's LABEL iterator result. **The two companion ops (confirmed):** - **`0x7b` (`FUN_0041ebf0`)** — *save yield handlers*: writes op1→`ctx[0x6da88+idx*4]`, op2→`ctx[0x6db28+idx*4]` @@ -638,8 +638,8 @@ completion so content plays — we reproduce the *observable effect*, not the vi byte-identical across all ~136 ADV scenes, so it generalizes with zero per-scene work. **Payoff beyond the slot fix:** the same `0x7b`/`0x7c` + handler machinery is the ADV frame loop, so a clean host model becomes the seam for the interactive-ADV backlog (`0x90` hotspots / EMPTY scenes) and likely fixes the stuck magic -circle (scene-phase cleanup). **Permanently out of static scope:** the real intro-transition *timing/pacing* -inside the loop (it lives in the native video service) — we approximate it host-side, as everywhere else. +circle (scene-phase cleanup). The real intro-transition *timing/pacing* inside AGERC's command-8 service +remains intentionally unmodeled; the port approximates it host-side, as it does other native transitions. **Host model implemented (2026-07-09).** `VirtualMachine` recognizes only the ADV `"LABEL","J"` form (138 corpus scripts; all have the same terminal-check shape), synthesizes `G[0xaba5c]=1` on top-level scene entry, forces one setup-body pass even if `G[0x6be]` is stale, then returns the terminal immediate @@ -2859,6 +2859,38 @@ A focused regression covers same-track restart, zero aliasing, loop/one-shot sel stop/clear, fade-to-zero release, and restart after stop; the threaded self-test exercises both loop modes and real stop/release. +**INPUTNAME native editor and CP932 string helpers (mapped 2026-07-29).** The three remaining +INPUTNAME-only gaps are one implementation slice, but have two ownership layers: + +- `op_0x144_edit_fullwidth_string_dialog@0x42bdd0` is the engine wrapper for AGERC host command 10. + Its ABI is `(result_inout, initial_text)`. AGE resolves both operands into separate 1024-byte buffers, + suspends normal display ownership, calls `g_pfnAgercHostCallback(10,owner_hwnd,&{result,initial,0})`, + writes both returned buffers through their string lvalues, and restores display ownership. +- A read-only Frida probe of the pristine running game resolved `g_pfnAgercHostCallback` + (`AGE+0x1c6018`) to `AGERC.DLL+0x1050`. The companion DLL is now imported and annotated at + `/support/AGERC.DLL`: `agerc_host_callback_export@0x10001050` forwards to + `agerc_dispatch_host_command@0x100025f0`, whose command-10 branch opens dialog resource 10 with + `agerc_fullwidth_text_dialog_proc@0x10002140`. +- The dialog initializes edit control 1006 from the second buffer. Accept reads no more than 255 bytes, + rejects a result longer than 16 CP932 bytes with `字数オーバーです`, and walks it in two-byte cells through + `agerc_reject_non_dbcs_character@0x10001b50`; any half-width or invalid cell produces + `半角文字は使用できません`. A valid accept replaces the first buffer. Cancel closes without changing it. + The dispatcher copies both buffers back, so operand 1 is the result/inout value and operand 2 is the + initial edit text. +- `op_0x2c6_cp932_character_length@0x42a6d0` sets the C locale to `japanese`, calls MSVC `_mbstrlen` on + operand 2, and writes the multibyte-character count to operand 1. +- `op_0x2c8_cp932_substring@0x42c420` copies its source into a 256-byte local buffer, counts Japanese + multibyte characters, and selects `[start,start+count)` while walking `_mbbtype` lead/trail pairs + together. Native clamping replaces the requested end with the full character length when + `start+count < 1` or exceeds that length. + +The release script's flow is now fully grounded. INPUTNAME's keyboard button assembles its current +character cells, copies that text into both `0x144` operands, then calls `0x2c6` and loops +`0x2c8(name,index,1)` to repopulate its eight cells. The pure helpers can use the VM's existing +`NativeStringCodePage` encoder. The modal op can follow the existing diagnostic-dialog ownership pattern: +the VM worker blocks while Godot's main thread presents a LineEdit dialog, validates the CP932 byte limit +and full-width-only rule, and signals the worker on accept or cancel. No save/profile format is involved. + `0xc2` is BGM rather than SFX: `op_0xc2_bgm_fade@0x4204c0` sets run-state `0x200`, arms the service timer, and calls `bgm_fade_arm@0x464830`. `bgm_fade_tick@0x464960` linearly interpolates current to target percent; durations at least 1000 ms take 100 steps, shorter durations take 10, and target zero releases the source. diff --git a/docs/opcode-reference.md b/docs/opcode-reference.md index 74c41cf..b6f5aec 100644 --- a/docs/opcode-reference.md +++ b/docs/opcode-reference.md @@ -418,6 +418,20 @@ Native applies strlen to the NUL-terminated engine byte string and shifts the by This is raw strlen(bytes), not a .NET UTF-16 character count. BUNKI compares all option/title byte lengths, adds four bytes of padding, and converts the result to pixels; skipping the opcode leaves its local maximum at zero, forcing the minimum-width menu and shifting every primary label right. The C# VM shares the configurable native-string byte counter used by op 0x1a6 (CP932 by default), including embedded-NUL termination; focused tests cover literals and local-string pointers. +### 0x2c6 `cp932-character-length` (cp932-character-length, argc 2) +- **summary:** (out)(string) - write the Japanese-locale multibyte character count of the CP932 string, counting a valid lead/trail pair as one character. +- **grounding:** source=investigation, confidence=high +- **evidence:** The real /v2 dispatch slot registers op_0x2c6_cp932_character_length@0x42a6d0. The handler sets LC_ALL to `japanese`, resolves operand 2, calls MSVC _mbstrlen, and writes the result to operand 1. INPUTNAME's sole site at 0x1002 uses this count as the loop bound before slicing each character with opcode 0x2c8. + +This is character count rather than .NET UTF-16 length or raw CP932 byte length. The implementation should use VmOptions.NativeStringCodePage and preserve valid CP932 multibyte boundaries. + +### 0x2c8 `cp932-substring` (cp932-substring, argc 4) +- **summary:** (out)(string)(start)(count) - copy a CP932 substring selected by multibyte-character index and count without splitting valid lead/trail pairs. +- **grounding:** source=investigation, confidence=high +- **evidence:** The real /v2 dispatch slot registers op_0x2c8_cp932_substring@0x42c420. It copies operand 2 into a 256-byte buffer, sets LC_ALL to `japanese`, obtains _mbstrlen, reads start and count, clamps end=start+count to the character length when end is below 1 or beyond that length, and walks bytes with _mbbtype so CP932 lead/trail pairs are copied together. It writes the selected byte interval back through operand 1. INPUTNAME's sole site at 0x1021 loops substring(name,index,1) into its eight local character cells. + +The release call uses nonnegative in-range indices and count 1. A compatible general implementation should reproduce native end clamping (`end = length` when start+count < 1 or > length) and select the half-open character interval [start,end). + ## control ### 0x1 `throw-exit-request` (throw-exit-request, argc 0) @@ -550,9 +564,9 @@ Native handler sleep_op_0xc8 @0x420ec0 is NON-BLOCKING: it arms a timer (sleep_t Implemented as process-lifecycle state owned by the persistent VM: it begins at one and op 0x9 clears it. It is not a script global, save/profile value, command-line seed, or script-name special case. ### 0x140 `coroutine-label-yield` (u0041F9C0, argc 4) -- **summary:** (out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls the NATIVE video/transition service (*DAT_005c6018)(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). DAT_005c6018 is runtime-resolved (all xrefs READ) = SAME class as the DirectDraw workers we don't model. PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the video service. See engine-re.md §Scene-coroutine framework. +- **summary:** (out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls AGERC host command 8 through g_pfnAgercHostCallback(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the service dialog. See engine-re.md §Scene-coroutine framework. - **grounding:** source=investigation, confidence=med -- **evidence:** Ghidra: handler 0x4299c0 (dispatch ctx[0x9b74c]=0x4299c0; created+typed EngineCtx*+annotated; Kelebek u0041F9C0 = VA-drift). Records the generic 9-dword instruction length; op2→local_204, op3→local_104, op4→local_208; (*DAT_005c6018)(8, ctx[0x54fe8], &local_210) → FUN_00425fb0(1,ret). DAT_005c6018: 6 xrefs all READ, no static writer; FUN_00405740 (screen-fade) calls it w/ cmd 3, branches on ret 1/2 = transition progress = native video service. +- **evidence:** Ghidra: handler 0x4299c0 (dispatch ctx[0x9b74c]=0x4299c0; created+typed EngineCtx*+annotated; Kelebek u0041F9C0 = VA-drift). Records the generic 9-dword instruction length; op2→local_204, op3→local_104, op4→local_208; g_pfnAgercHostCallback(8, ctx[0x54fe8], &local_210) → FUN_00425fb0(1,ret). A read-only probe resolves the callback to AGERC.DLL+0x1050; imported /support/AGERC.DLL identifies agerc_dispatch_host_command@0x100025f0 and its command-8 dialog-resource branch. FUN_00405740 calls the same dispatcher with command 3 and branches on return 1/2 for transition progress. Command 8's exact LABEL iterator calculation remains unresolved, so confidence remains medium. ### 0x143 `run-mounted-append-autoruns` (u00415FB0, argc 0) - **summary:** () - enqueue record zero from every mounted nonzero AAI selector in ascending selector order, then execute those packed scripts serially before resuming the caller. @@ -1232,6 +1246,17 @@ op 0x90 (u0041BEB0, argc 7): `0x90 x y w h tgt_a tgt_b tgt_c`. Kelebek left it " - **grounding:** source=investigation, confidence=high - **evidence:** Ghidra /v2: op_0x12e_find_hit_rectangle@0x428ff0 resolves the reference rectangle and three array operands as VM pointers, decodes rectangle/offset values with anti_tamper_a, and starts at incoming_index+1. Each candidate is [left,right,top,bottom]; the worker subtracts its per-entry x/y offsets from the pointer, inclusively intersects it with the reference rectangle, and writes the matched array index or -1 to operand 1. HISTORY.BIN uses decoded local arrays for scrollbar/control regions, close region 8, and visible text rows 9..13. +### 0x144 `edit-fullwidth-string-dialog` (edit-fullwidth-string-dialog, argc 2) +- **summary:** (result_inout)(initial_text) - synchronously open AGERC's modal full-width text editor. Accept writes at most eight valid CP932 double-byte characters to operand 1; cancel leaves operand 1 unchanged. Operand 2 supplies the initial edit text and is preserved. +- **grounding:** source=investigation, confidence=high +- **evidence:** The real /v2 dispatch slot registers op_0x144_edit_fullwidth_string_dialog@0x42bdd0. It copies both resolved string operands into 0x400-byte buffers, calls the runtime host callback g_pfnAgercHostCallback with command 10 and owner HWND, then writes both returned buffers back. A read-only Frida probe of the pristine game resolved that callback to AGERC.DLL+0x1050. Imported /support/AGERC.DLL shows agerc_dispatch_host_command@0x100025f0 opening dialog resource 10 with agerc_fullwidth_text_dialog_proc@0x10002140: edit control 1006 is initialized from operand 2; IDOK rejects more than 16 bytes (`字数オーバーです`) or any non-DBCS cell (`半角文字は使用できません`) before copying the accepted text to operand 1; IDCANCEL closes without changing operand 1. INPUTNAME's sole site at 0xdcd passes its assembled current name in both operands, then resplits operand 1 through 0x2c6/0x2c8. + +This is INPUTNAME's optional native keyboard-entry button, not the surrounding script-authored +character palette. The VM can use a synchronous host seam like the existing diagnostic dialog because +the Godot host runs script execution on its worker thread while the main thread owns the modal UI. +Compatibility requires the native full-width-only and 16-CP932-byte limits before accepting the result. + + ### 0x19a `get-message-skip` (u00414E50, argc 1) - **summary:** (out) - return the current all-message skip state set by op 0x88. - **grounding:** source=investigation, confidence=high @@ -1938,10 +1963,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting - **summary:** — - **grounding:** source=kelebek, confidence=low -### 0x144 `u004259D0` (u004259D0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x145 `u00416040` (u00416040, argc 1) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=low @@ -2325,19 +2346,11 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting - **grounding:** source=kelebek, confidence=low - **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table. -### 0x2c6 `u0042B5E0` (u0042B5E0, argc 2) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x2c7 `u0042B5F0` (u0042B5F0, argc 4) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=low - **evidence:** Not observed in Himegari's script corpus; ABI label/argc come from Kelebek's AGE table. -### 0x2c8 `u0042B610` (u0042B610, argc 4) -- **summary:** — -- **grounding:** source=kelebek, confidence=low - ### 0x2c9 `2C9` (2C9, argc 3) - **summary:** Broader AGE-catalog compatibility stub; the port currently traces and skips it. - **grounding:** source=kelebek, confidence=med diff --git a/docs/phase-b-framework.md b/docs/phase-b-framework.md index 3079ccf..1e29e63 100644 --- a/docs/phase-b-framework.md +++ b/docs/phase-b-framework.md @@ -909,6 +909,27 @@ and true stop/release. **NEXT:** investigate INPUTNAME's `0x144`/`0x2c6`/`0x2c8` cluster as one modal name-entry and CP932 string slice. +**INPUTNAME string/input slice investigated (2026-07-29):** all three singleton handlers are now exact. +`0x2c6(out,string)` is Japanese-locale `_mbstrlen`; `0x2c8(out,string,start,count)` slices by CP932 +multibyte-character positions while keeping lead/trail pairs intact. INPUTNAME uses them together to split +the current name into its eight script-owned display/edit cells. + +`0x144(result_inout,initial_text)` is the separate native keyboard-entry button. AGE copies both strings +into bounded buffers and calls runtime host command 10. A read-only live probe resolved that callback to +`AGERC.DLL+0x1050`; the shipped DLL is now imported and annotated in Ghidra. Its command-10 resource dialog +initializes from operand 2, accepts at most 16 CP932 bytes, rejects every non-double-byte character, writes +valid accepted text to operand 1, and leaves operand 1 unchanged on cancel. This resolves the former +runtime-pointer uncertainty and also identifies AGERC as the owner of the previously studied command-8 +LABEL service used by `0x140`. + +The port has a bounded implementation seam: the two pure helpers use `VmOptions.NativeStringCodePage`, +while the modal op follows the existing diagnostic-message pattern (VM worker waits, Godot main thread owns +the dialog, accept/cancel signals completion). Implementing the trio removes 3 of the remaining 9 opcode +gaps and 3 of 11 instructions; no persistence or save-format work is involved. + +**NEXT:** implement and test `0x144`/`0x2c6`/`0x2c8` together, including CP932 mixed-width helper cases, +the native 16-byte/full-width acceptance rules, accept/cancel behavior, and INPUTNAME's split/rejoin shape. + ## Later Phase B breadth **INIT data-semantics side track started (2026-07-22).** Before naming more gameplay state, the static diff --git a/tools/age_opcodes_himegari.py b/tools/age_opcodes_himegari.py index 1bde2ac..aadee4b 100644 --- a/tools/age_opcodes_himegari.py +++ b/tools/age_opcodes_himegari.py @@ -46,7 +46,7 @@ INFERRED: dict[int, dict] = { 0x10c: dict(name='map-keyboard-scancode', category='input', noop=False, confidence='high', source='investigation', summary='(action)(dik_scan_code) - map a DirectInput keyboard scan code to a logical input action.'), 0x10d: dict(name='consume-mouse-wheel-delta', category='input', noop=False, confidence='high', source='investigation', summary='(out) - return the accumulated signed mouse-wheel delta and clear it.'), 0x13a: dict(name='register-numeric-glyph-style', category='draw', noop=False, confidence='high', source='investigation', summary='Register one of 11 decimal-glyph atlas styles as (surface slot, source x/y, digit width/height).'), - 0x140: dict(name='coroutine-label-yield', category='control', noop=False, confidence='med', source='investigation', summary="(out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls the NATIVE video/transition service (*DAT_005c6018)(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). DAT_005c6018 is runtime-resolved (all xrefs READ) = SAME class as the DirectDraw workers we don't model. PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the video service. See engine-re.md §Scene-coroutine framework."), + 0x140: dict(name='coroutine-label-yield', category='control', noop=False, confidence='med', source='investigation', summary="(out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls AGERC host command 8 through g_pfnAgercHostCallback(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the service dialog. See engine-re.md §Scene-coroutine framework."), 0x143: dict(name='run-mounted-append-autoruns', category='control', noop=False, confidence='high', source='investigation', summary='() - enqueue record zero from every mounted nonzero AAI selector in ascending selector order, then execute those packed scripts serially before resuming the caller.'), 0x199: dict(name='yield-adv-coroutine', category='control', noop=False, confidence='high', source='investigation', summary='Yield/re-enter the registered ADV coroutine handler. The fifth standard chrome button uses this transition to enter the HIDEWIN/window-hidden flow.'), 0x19a: dict(name='get-message-skip', category='input', noop=False, confidence='high', source='investigation', summary='(out) - return the current all-message skip state set by op 0x88.'), diff --git a/vm-map/opcodes.toml b/vm-map/opcodes.toml index 7f9ebc6..ed1866a 100644 --- a/vm-map/opcodes.toml +++ b/vm-map/opcodes.toml @@ -2914,12 +2914,12 @@ abi_source = "kelebek+decode-validated" [opcode.semantics] name = "coroutine-label-yield" category = "control" -summary = "(out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls the NATIVE video/transition service (*DAT_005c6018)(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). DAT_005c6018 is runtime-resolved (all xrefs READ) = SAME class as the DirectDraw workers we don't model. PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the video service. See engine-re.md §Scene-coroutine framework." +summary = "(out)(name_str)(sub_str)(in) — scene-coroutine LOOP ITERATOR / labeled yield. Handler copies name/sub strings + the int operand and calls AGERC host command 8 through g_pfnAgercHostCallback(8, ctx[0x54fe8], &{name,sub,in}); writes the returned PC-like value to operand 1. In SC0000 label_462 'ループ開始' (@0x46d): `out=G[0x6be]=LABEL('J',G[0x6be])`; loop runs the intro-setup body (incl. call label_125bd = slot-table fill G[0x3239..0x324e]=4..11) and jmps back until out==G[0x6c3] (a per-scene exit-PC immediate) → mov aba5c 0 → content. The gate G[0xaba5c]==1 that opens this loop is NATIVE scene-entry state (no script sets it to 1). PORT = HOST-MODEL IMPLEMENTED: synthesize the ADV scene-entry gate, run the LABEL/J setup body once, then return the structurally discovered per-scene terminal; do not emulate the service dialog. See engine-re.md §Scene-coroutine framework." noop_headless = false source = "investigation" confidence = "med" depends_on = [] -evidence = "Ghidra: handler 0x4299c0 (dispatch ctx[0x9b74c]=0x4299c0; created+typed EngineCtx*+annotated; Kelebek u0041F9C0 = VA-drift). Records the generic 9-dword instruction length; op2→local_204, op3→local_104, op4→local_208; (*DAT_005c6018)(8, ctx[0x54fe8], &local_210) → FUN_00425fb0(1,ret). DAT_005c6018: 6 xrefs all READ, no static writer; FUN_00405740 (screen-fade) calls it w/ cmd 3, branches on ret 1/2 = transition progress = native video service." +evidence = "Ghidra: handler 0x4299c0 (dispatch ctx[0x9b74c]=0x4299c0; created+typed EngineCtx*+annotated; Kelebek u0041F9C0 = VA-drift). Records the generic 9-dword instruction length; op2→local_204, op3→local_104, op4→local_208; g_pfnAgercHostCallback(8, ctx[0x54fe8], &local_210) → FUN_00425fb0(1,ret). A read-only probe resolves the callback to AGERC.DLL+0x1050; imported /support/AGERC.DLL identifies agerc_dispatch_host_command@0x100025f0 and its command-8 dialog-resource branch. FUN_00405740 calls the same dispatcher with command 3 and branches on return 1/2 for transition progress. Command 8's exact LABEL iterator calculation remains unresolved, so confidence remains medium." [[opcode.semantics.args]] i = 1 @@ -3016,28 +3016,34 @@ record-zero failure. Natural SYSTEM4 boot proves BTANINIT2 -> `$1$AUTORUN.BIN` - [[opcode]] op = 0x144 -label = "u004259D0" +label = "edit-fullwidth-string-dialog" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u004259D0" -category = "unknown" -summary = "" +name = "edit-fullwidth-string-dialog" +category = "input" +summary = "(result_inout)(initial_text) - synchronously open AGERC's modal full-width text editor. Accept writes at most eight valid CP932 double-byte characters to operand 1; cancel leaves operand 1 unchanged. Operand 2 supplies the initial edit text and is preserved." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "The real /v2 dispatch slot registers op_0x144_edit_fullwidth_string_dialog@0x42bdd0. It copies both resolved string operands into 0x400-byte buffers, calls the runtime host callback g_pfnAgercHostCallback with command 10 and owner HWND, then writes both returned buffers back. A read-only Frida probe of the pristine game resolved that callback to AGERC.DLL+0x1050. Imported /support/AGERC.DLL shows agerc_dispatch_host_command@0x100025f0 opening dialog resource 10 with agerc_fullwidth_text_dialog_proc@0x10002140: edit control 1006 is initialized from operand 2; IDOK rejects more than 16 bytes (`字数オーバーです`) or any non-DBCS cell (`半角文字は使用できません`) before copying the accepted text to operand 1; IDCANCEL closes without changing operand 1. INPUTNAME's sole site at 0xdcd passes its assembled current name in both operands, then resplits operand 1 through 0x2c6/0x2c8." +details = """ +This is INPUTNAME's optional native keyboard-entry button, not the surrounding script-authored +character palette. The VM can use a synchronous host seam like the existing diagnostic dialog because +the Godot host runs script execution on its worker thread while the main thread owns the modal UI. +Compatibility requires the native full-width-only and 16-CP932-byte limits before accepting the result. +""" [[opcode.semantics.args]] i = 1 -role = "" +role = "result/inout string" observed_types = ["l-str"] [[opcode.semantics.args]] i = 2 -role = "" +role = "initial edit text" observed_types = ["l-str"] [[opcode]] @@ -6882,64 +6888,66 @@ observed_types = ["g-str", "l-str", "l-str-ptr"] [[opcode]] op = 0x2c6 -label = "u0042B5E0" +label = "cp932-character-length" argc = 2 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0042B5E0" -category = "unknown" -summary = "" +name = "cp932-character-length" +category = "compute" +summary = "(out)(string) - write the Japanese-locale multibyte character count of the CP932 string, counting a valid lead/trail pair as one character." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "The real /v2 dispatch slot registers op_0x2c6_cp932_character_length@0x42a6d0. The handler sets LC_ALL to `japanese`, resolves operand 2, calls MSVC _mbstrlen, and writes the result to operand 1. INPUTNAME's sole site at 0x1002 uses this count as the loop bound before slicing each character with opcode 0x2c8." +details = "This is character count rather than .NET UTF-16 length or raw CP932 byte length. The implementation should use VmOptions.NativeStringCodePage and preserve valid CP932 multibyte boundaries." [[opcode.semantics.args]] i = 1 -role = "" +role = "character count out" observed_types = ["l-int"] [[opcode.semantics.args]] i = 2 -role = "" +role = "source string" observed_types = ["l-str"] [[opcode]] op = 0x2c8 -label = "u0042B610" +label = "cp932-substring" argc = 4 abi_source = "kelebek+decode-validated" [opcode.semantics] -name = "u0042B610" -category = "unknown" -summary = "" +name = "cp932-substring" +category = "compute" +summary = "(out)(string)(start)(count) - copy a CP932 substring selected by multibyte-character index and count without splitting valid lead/trail pairs." noop_headless = false -source = "kelebek" -confidence = "low" +source = "investigation" +confidence = "high" depends_on = [] -evidence = "" +evidence = "The real /v2 dispatch slot registers op_0x2c8_cp932_substring@0x42c420. It copies operand 2 into a 256-byte buffer, sets LC_ALL to `japanese`, obtains _mbstrlen, reads start and count, clamps end=start+count to the character length when end is below 1 or beyond that length, and walks bytes with _mbbtype so CP932 lead/trail pairs are copied together. It writes the selected byte interval back through operand 1. INPUTNAME's sole site at 0x1021 loops substring(name,index,1) into its eight local character cells." +details = "The release call uses nonnegative in-range indices and count 1. A compatible general implementation should reproduce native end clamping (`end = length` when start+count < 1 or > length) and select the half-open character interval [start,end)." [[opcode.semantics.args]] i = 1 -role = "" +role = "substring out" observed_types = ["l-str-ptr"] [[opcode.semantics.args]] i = 2 -role = "" +role = "source string" observed_types = ["l-str"] [[opcode.semantics.args]] i = 3 -role = "" +role = "start character index" observed_types = ["l-int"] [[opcode.semantics.args]] i = 4 -role = "" +role = "character count" observed_types = ["imm"] [[opcode]] op = 0x4