Fix Extra Room catalog unlock handling
This commit is contained in:
@@ -1720,9 +1720,29 @@ generic save operation:
|
||||
| `0x1ae(status,slot,surface)` | write `SAVE%02d.STH` from a surface |
|
||||
| `0x1af(status,slot,surface)` | load `SAVE%02d.STH` into a surface |
|
||||
|
||||
Opcode `0x19d`, adjacent in number and used by CGMODE/ED/HMODE/MMODE, is not persistence: its handler is a
|
||||
resource/compatibility lookup. It is deliberately excluded rather than named from proximity. The actual
|
||||
persistence cluster has calls in `SAVE`, `SELSTAGE`, `GAMESTART`, `GAMECLEAR`, `INPUTNAME`, and INIT2.
|
||||
Opcode `0x19d`, adjacent in number and used by CGMODE/ED/HMODE/MMODE, queries the shared-profile catalog
|
||||
unlock database:
|
||||
|
||||
- `op_0x19d_is_catalog_resource_unlocked@0x427810` fetches `(out, packed_resource_id)`. Packed append
|
||||
ids return zero when the configured save version is older than 3.10; otherwise it calls
|
||||
`asset_catalog_is_resource_unlocked@0x415920` on `EngineCtx+0x9c24c`.
|
||||
- Base ids use the marker table at asset-catalog `+0x41c`. A nonzero high byte selects the append
|
||||
pointer at `+0x3844 + selector*4`; the low 24 bits are the table index.
|
||||
- The marker is valid exactly when its low word equals
|
||||
`low16(index * 0x053d6f99 + 0xb0b0b0b0)`. `asset_catalog_mark_resource_opened@0x44e410`, called only
|
||||
after `asset_open_indexed_entry` succeeds, writes the plain marker and its modular-exponent encrypted
|
||||
SAVE.DAT counterpart.
|
||||
- `shared_profile_load` decrypts base and flattened append marker arrays through
|
||||
`modular_exponent_u32@0x471e60`, then
|
||||
`asset_catalog_restore_resource_unlock_marker@0x404ca0` installs and re-encrypts them under the
|
||||
current session key. The two leading table DWORDs are private-exponent XOR `0x87912345` and modulus.
|
||||
|
||||
CGMODE passes each CGINIT full-image AGF id; HMODE passes each SPINIT scene-script id. The copied port
|
||||
profile validates all 851 gallery images and all 118 H-scene scripts. The former all-locked presentation
|
||||
was therefore an opcode/runtime integration defect, not missing save data. The port now decodes and
|
||||
queries the native markers, marks successful VFS opens, and writes a native-decodable encrypted table.
|
||||
The actual numbered/selected-cell persistence cluster remains the adjacent `0x19e` through `0x1af`
|
||||
family described above.
|
||||
|
||||
**Numbered operation status contracts.** Save/load open failure is `1`; metadata uses `0=valid`,
|
||||
`1=absent/open failure`, and `2=invalid/incompatible`. Delete/copy attempt both members of the pair and use
|
||||
|
||||
@@ -323,6 +323,13 @@ Implemented as FIFO TryDequeue: nonempty writes success=1 plus the signed dword;
|
||||
|
||||
This is the logical inverse of op 0x194 string-equals. Skipping it is stateful: the destination is not cleared. At BUNKI@0x905, local 0x99 still contains a nonzero graphics handle, so the missing write falsely reserves a 30-pixel title row and shifts every choice down. The C# VM implements ordinal inequality through the shared string resolver; focused tests cover literal/global/local/pointer operands and the exact empty-title stale-handle overwrite.
|
||||
|
||||
### 0x19d `is-catalog-resource-unlocked` (is-catalog-resource-unlocked, argc 2)
|
||||
- **summary:** (out)(packed_resource_id) - return 1 when the shared SAVE.DAT catalog marker for the selected base or append resource decodes to AGE's deterministic per-index unlock stamp, otherwise 0.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
- **evidence:** Ghidra /v2: op_0x19d_handler@0x427810 fetches operand 2, rejects packed append ids when `set:SaveVersion1/2` is older than 3.10, calls asset_catalog_is_resource_unlocked@0x415920 on ctx+0x9c24c, and writes the bool to operand 1. The helper selects base table +0x41c or append pointer table +0x3844 and compares the stored low word to `(short)index * 0x6f99 - 0x4f50`. asset_catalog_mark_resource_opened@0x44e410 writes the equivalent full stamp and encrypted profile marker after asset_open_indexed_entry succeeds. shared_profile_load decodes the catalog arrays with modular exponentiation before populating those tables. Corpus: four calls, in CGMODE, HMODE, MMODE, and ED. The installed port SAVE.DAT decodes to 7,966/13,208 base markers and 75/81 append markers; all 851 CGINIT gallery images and all 118 SPINIT H-scene scripts validate unlocked.
|
||||
|
||||
Base ids use the low 24-bit index in the primary catalog marker table. A nonzero high-byte selector addresses that append catalog's independent table; selectors require SaveVersion 3.10 or later, and AGE's signed selector lookup only supports 1..127. Successful native catalog opens write the stamp `low16(index * 0x053d6f99 + 0xb0b0b0b0)` and its encrypted SAVE.DAT form. CGMODE tests CGINIT image ids and HMODE tests SPINIT scene-script ids through this predicate. Port status (2026-07-28): shared-profile import decodes both catalog sections, successful VFS opens mark resources, shared SAVE.DAT writes native-decodable encrypted markers, and the opcode returns the live profile predicate.
|
||||
|
||||
### 0x1a6 `half-byte-string-length` (halve-strlen, argc 2)
|
||||
- **summary:** Write half the resolved string's byte length, using integer truncation.
|
||||
- **grounding:** source=investigation, confidence=high
|
||||
@@ -1332,10 +1339,6 @@ Port status (2026-07-24): implemented through the same profile-lifetime setting
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=med
|
||||
|
||||
### 0x19d `u0041C680` (u0041C680, argc 2)
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=low
|
||||
|
||||
### 0x1a5 `set-font` (set-font, argc 1)
|
||||
- **summary:** —
|
||||
- **grounding:** source=kelebek, confidence=med
|
||||
|
||||
@@ -272,6 +272,22 @@ voice ids were reaching `play-voice` but failing the port's SC-section-only look
|
||||
section. The initial compatibility fix added a raw fallback, but later native RE proved the broader rule:
|
||||
voice and frontend texture operands are already universal packed catalog ids and never receive a scene base.
|
||||
|
||||
**Extra Room CG/H-scene unlock database (`CGMODE.BIN` / `HMODE.BIN`, 2026-07-28).** The copied
|
||||
`user://SAVE/SAVE.DAT` was intact: native decode finds 7,966/13,208 valid base-catalog markers and
|
||||
75/81 append markers, including all 851 CGINIT gallery-image ids and all 118 SPINIT H-scene script ids.
|
||||
The encyclopedia and New Game settings looked correct because they restore selected integer cells through
|
||||
`0x1a3`; CGMODE/HMODE instead filter their entries through opcode `0x19d`, which the port still stubbed and
|
||||
therefore left as zero/locked.
|
||||
|
||||
Native RE identifies `0x19d` as the profile catalog-resource unlock predicate. Successful catalog opens
|
||||
write a deterministic per-index stamp into a live marker table and its modular-exponent encrypted
|
||||
SAVE.DAT table; shared-profile load decrypts base and selector-keyed append arrays before the query runs.
|
||||
`SharedProfile` now owns that decoded set, the VM implements the query, and a shared VFS wrapper marks only
|
||||
successful script/texture/audio/movie/cursor opens. Saving emits native-decodable base and append tables,
|
||||
so imported unlocks display immediately and newly encountered resources persist. Focused regressions cover
|
||||
exact native ciphertexts, base/append queries, successful-open timing, round-trip import/export, and known
|
||||
installed CG/H-scene entries.
|
||||
|
||||
**Natural Game Start diagnostic gate (2026-07-21; captured).** The opt-in `-StartupDiagnostics` route kept
|
||||
the persistent `SYSTEM4` root and native exit semantics and added no seeds, boot prefix, timing changes, or
|
||||
input automation. A user-driven cold boot traversed the complete initialization family, `LOGO`/`LOGO.AGF`,
|
||||
|
||||
@@ -468,7 +468,9 @@ JSON inspection/export, migrations, and namespaced mod state are additive extend
|
||||
replacement for compatibility-mode import/export. The recovered native contract lives in
|
||||
`docs/engine-re.md`. The common container codec/store and typed shared `SAVE.DAT` payload landed on
|
||||
2026-07-24. Profile-owned selected integer/string cells now survive across scene VMs and are wired to
|
||||
their four native opcodes while opaque catalog/version sections round-trip unchanged. Native `RT.DAT`
|
||||
their four native opcodes. The base/append catalog sections are now decoded as encrypted resource-seen
|
||||
markers: opcode `0x19d` queries them, successful VFS opens add them, and compatibility saves write
|
||||
native-decodable tables. Native `RT.DAT`
|
||||
import/export and the packed-script/T1 ReadTextDB queue/commit/query lifecycle are also implemented,
|
||||
including `message:ReadTextSkip` ops `0x1ca`/`0x1cb` and state query `0x1cc`. Numbered active-frame state
|
||||
is now implemented in native layout 3: metadata query, paired `.DAT`/`.STH` lifecycle, exact native BMP
|
||||
|
||||
@@ -111,11 +111,11 @@ After decoding the common container, the shared payload is sequential:
|
||||
|
||||
| Order | Field |
|
||||
|---:|---|
|
||||
| 1 | `catalog_count:u32`, then `catalog_values[catalog_count]:u32` |
|
||||
| 1 | `catalog_count:u32`, then the encrypted base-catalog unlock table |
|
||||
| 2 | `integer_count:u32`, then `integer_entries[integer_count]`, 16 bytes each |
|
||||
| 3 | `string_count:u32`, `string_blob_dwords:u32`, then the DWORD-padded string blob |
|
||||
| 4 | for version 3.10 or later, `selector_counts[256]:u32` |
|
||||
| 5 | for version 3.10 or later, `extended_count:u32`, then `extended_values[extended_count]:u32` |
|
||||
| 4 | for version 3.10 or later, `selector_counts[256]:u32` for append-catalog unlock tables |
|
||||
| 5 | for version 3.10 or later, `extended_count:u32`, then the flattened encrypted append tables |
|
||||
| 6 | `reserved_tail[9]:u32` |
|
||||
|
||||
An integer entry is:
|
||||
@@ -135,14 +135,35 @@ type byte `0x05` plus the same eight-digit address and NUL; values are CP932. Th
|
||||
a DWORD count. AGE rounds with `(unpadded_bytes / 4) + 1`, deliberately adding a whole zero DWORD when
|
||||
the last value already ends on a DWORD boundary.
|
||||
|
||||
The nine-DWORD tail consists of one explicit terminator plus eight DWORDs produced by the native
|
||||
allocation formula. Catalog, selector, extended, and tail sections are structurally bounded but not
|
||||
fully assigned game-level meanings, so compatibility import/export preserves them rather than treating
|
||||
them as mod storage.
|
||||
The catalog sections are AGE's profile-wide **resource-seen/unlock database**. Each encrypted table begins
|
||||
with two header DWORDs:
|
||||
|
||||
The installed Himegari profile is a complete oracle: version 3.10 decodes to 13,210 catalog values,
|
||||
24,070 integer cells, 605 string cells, selector entry 1 equal to 81, 83 extended values, and nine tail
|
||||
DWORDs.
|
||||
| Table word | Meaning |
|
||||
|---:|---|
|
||||
| `0` | private exponent XOR `0x87912345` |
|
||||
| `1` | modular-exponentiation modulus |
|
||||
| `index + 2` | zero when locked/unseen; otherwise the encrypted resource marker |
|
||||
|
||||
Loading computes `plain = cipher^private_exponent mod modulus`. A marker is available when its low word
|
||||
equals `low16(index * 0x053d6f99 + 0xb0b0b0b0)`. Native `asset_open_indexed_entry` writes that marker
|
||||
only after successfully opening the resource, and opcode `0x19d` queries it. Thus ordinary CG assets
|
||||
unlock when their AGF is opened and H-scene entries unlock when their SP script is opened.
|
||||
|
||||
The base table maps universal resource ids whose high byte is zero. For version 3.10 or later,
|
||||
`selector_counts[selector]` gives the slot count of each append catalog; the extended array has its own
|
||||
two-word crypto header followed by those selector tables concatenated in selector order. Packed resource
|
||||
ids use the high byte as selector and the low 24 bits as the per-catalog index. Runtime lookup accepts
|
||||
selectors 1 through 127.
|
||||
|
||||
The nine-DWORD tail consists of one explicit terminator plus eight DWORDs produced by the native
|
||||
allocation formula. It remains compatibility-owned rather than mod storage.
|
||||
|
||||
The installed Himegari profile is a complete oracle: version 3.10 decodes to 13,210 base-table DWORDs
|
||||
(13,208 resource slots), 24,070 integer cells, 605 string cells, selector entry 1 equal to 81, 83
|
||||
extended-table DWORDs (two header plus 81 resource slots), and nine tail DWORDs. The port copy contains
|
||||
7,966 valid base markers and 75 valid append markers. Those include all 851 CGINIT gallery images and
|
||||
all 118 SPINIT H-scene scripts; a different encryption key can change every nonzero stored DWORD without
|
||||
changing the decoded unlock set.
|
||||
|
||||
The integer/string maps are AGE's generic selected-cell service, not copies of whole global banks.
|
||||
Himegari scripts assign their meanings through opcodes `0x1a2`/`0x1a3` and `0x1a9`/`0x1aa`. Known
|
||||
@@ -345,7 +366,7 @@ The remaining uncertainty is deliberately narrower:
|
||||
|
||||
- numbered layouts 1 and 2 are structurally identified but are not the implemented or installed
|
||||
Himegari compatibility target;
|
||||
- the shared catalog/extended arrays and some selected cells are still opaque at the game-semantic level;
|
||||
- some shared selected cells and the nine-DWORD tail remain opaque at the game-semantic level;
|
||||
- the `0x2d4` retained-graphics record is byte-bounded, but not every field is named.
|
||||
|
||||
This distinction is important: AGE owns most numbered-save and ReadTextDB structure, while Himegari
|
||||
|
||||
Reference in New Issue
Block a user