Load shared save preview metadata

This commit is contained in:
gamer147
2026-07-24 17:05:13 -04:00
parent eff45f8dad
commit aba29b9078
7 changed files with 183 additions and 20 deletions

View File

@@ -1708,6 +1708,17 @@ entry 1 equal to 81, 83 extended values, and the nine-DWORD reserved tail. The p
file exactly; catalog/extended/tail sections remain opaque and are preserved losslessly rather than file exactly; catalog/extended/tail sections remain opaque and are preserved losslessly rather than
reinterpreted as mod state. reinterpreted as mod state.
`SAVE.BIN` also proves why a numbered `.DAT`/`.STH` pair alone cannot reproduce a complete menu row.
The numbered fixed header supplies only timestamp and accumulated playtime, and `.STH` supplies the
112x84 screenshot. The remaining preview record is maintained through selected-cell ops in shared
`SAVE.DAT`: three parallel 210-cell string banks hold chapter/title (`GStr[0x0+]`), location
(`GStr[0xd2+]`), and protagonist name (`GStr[0x1a4+]`); eight 210-cell integer banks beginning at
`GInt[0x5]`, `0xd7`, `0x1a9`, `0x27b`, `0x34d`, `0x41f`, `0x4f1`, and `0x5c3` hold portrait entity,
level, training count, growth, personality, difficulty/mode badge, cleared-ending mask, and installed
append mask respectively. Save and stage-select paths snapshot these cells with `0x1a2`/`0x1a9`;
the menu restores them with `0x1a3`/`0x1aa`. For the NG+ history display, `SAVE.BIN` scans bits 0..14
of the restored `GInt[0x4f1 + slot]` mask and draws one 17x17 ending badge for every set bit.
#### Common `.DAT` container #### Common `.DAT` container
Both shared and numbered `.DAT` payloads use the same native container. The fixed header is exactly Both shared and numbered `.DAT` payloads use the same native container. The fixed header is exactly

View File

@@ -1,7 +1,7 @@
<!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build --> <!-- DO NOT EDIT -- generated from vm-map/globals.toml by tools/globals_build.py --build -->
# Global Variable Reference (generated) # Global Variable Reference (generated)
4278 globals (435 curated, 3843 auto shape-inferred). Source of truth: `vm-map/globals.toml`. 4278 globals (445 curated, 3833 auto shape-inferred). Source of truth: `vm-map/globals.toml`.
## choice-output ## choice-output
@@ -51,6 +51,14 @@
| address | name | conf | source | usage | | address | name | conf | source | usage |
|---|---|---|---|---| |---|---|---|---|---|
| `0x5` | save_slot_portrait_entity_indices | high | investigation | SAVE and SELSTAGE snapshot current_entity_index through op 0x1a2. SAVE.BIN restores the slot cell with op 0x1a3 and uses its form range when choosing the portrait atlas region. |
| `0xd7` | save_slot_protagonist_levels | high | investigation | SAVE and SELSTAGE copy unit_current_levels[current_entity_index] into this shared SAVE.DAT-selected bank. SAVE.BIN restores and renders it as the row's LV value. |
| `0x1a9` | save_slot_training_action_counts | high | investigation | SAVE and SELSTAGE snapshot training_action_total_execution_count into this shared SAVE.DAT-selected per-slot bank; copy/move/delete operations keep it aligned with the rest of the preview record. |
| `0x27b` | save_slot_growth_values | high | investigation | SAVE and SELSTAGE snapshot familiar_training_progress through op 0x1a2. SAVE.BIN restores and renders it as the row's growth value. |
| `0x34d` | save_slot_personality_values | high | investigation | SAVE and SELSTAGE snapshot familiar_alignment through op 0x1a2. SAVE.BIN restores and renders the signed value in the row's personality field. |
| `0x41f` | save_slot_difficulty_indices | high | investigation | SAVE snapshots difficulty_index through op 0x1a2. SAVE.BIN restores the per-slot value and selects the corresponding 34x17 row badge from SO010. |
| `0x4f1` | save_slot_cleared_ending_masks | high | investigation | SAVE snapshots the current cleared-ending inheritance mask through op 0x1a2. SAVE.BIN restores it from shared SAVE.DAT, scans bits 0..14, and draws one 17x17 badge for every achieved ending on that slot and subsequent inherited-loop saves. |
| `0x5c3` | save_slot_append_install_masks | high | investigation | SAVE snapshots the current installed-append mask through op 0x1a2. SAVE.BIN compares each saved bit against the current mask and marks previews whose required append data is missing. |
| `0x6d3` | story_event_flags | high | investigation | Shared one-based story/event flag bank. Progression, recruitment, item, and stage logic read or write individual cells; STINIT object prerequisite ids are decremented by one before SETOBJ tests this array. | | `0x6d3` | story_event_flags | high | investigation | Shared one-based story/event flag bank. Progression, recruitment, item, and stage logic read or write individual cells; STINIT object prerequisite ids are decremented by one before SETOBJ tests this array. |
| `0x2e49` | character_voice_suppressed | high | investigation | Base of the per-character voice enable/suppress settings. INITCONFIG zero-fills all 13 cells and registers each with the shared profile service; LOADCONFIG restores them. CONFIG indexes the table to preview a character voice and write 0/1. ROOM reads cell 0 before assigning its selected greeter's greeting/farewell voice ids, so the port's former scalar interpretation of zero-int-range (writing 13 into the base cell) suppressed those voices on every natural boot. This names the script-visible setting array without choosing a persistence backend for op 0x1a2/0x1a3. | | `0x2e49` | character_voice_suppressed | high | investigation | Base of the per-character voice enable/suppress settings. INITCONFIG zero-fills all 13 cells and registers each with the shared profile service; LOADCONFIG restores them. CONFIG indexes the table to preview a character voice and write 0/1. ROOM reads cell 0 before assigning its selected greeter's greeting/farewell voice ids, so the port's former scalar interpretation of zero-int-range (writing 13 into the base cell) suppressed those voices on every natural boot. This names the script-visible setting array without choosing a persistence backend for op 0x1a2/0x1a3. |
| `0x3239` | adv_layer_surface_slots | high | investigation | Shared ADV graphics-layer surface-slot registry. Scene setup initializes the three banks. The CG loader uses the primary slot for a fresh retained object, alternates between columns zero and one when replacing an already-bound layer, and uses column two for the transition/crossfade surface and cleanup. The corpus has 2,657 table-base accesses across 309 scripts. Columns: 0=primary_surface_slot, 1=alternate_surface_slot, 2=transition_surface_slot. | | `0x3239` | adv_layer_surface_slots | high | investigation | Shared ADV graphics-layer surface-slot registry. Scene setup initializes the three banks. The CG loader uses the primary slot for a fresh retained object, alternates between columns zero and one when replacing an already-bound layer, and uses column two for the transition/crossfade surface and cleanup. The corpus has 2,657 table-base accesses across 309 scripts. Columns: 0=primary_surface_slot, 1=alternate_surface_slot, 2=transition_surface_slot. |
@@ -346,15 +354,7 @@
| `0x15a1e0` | character_profile_portrait_x_offsets | high | investigation | INFOCH adds the selected cell to the centered portrait x coordinate. The 100-cell spacing to the adjacent CIINIT arrays and the direct indexed reader establish the reserved per-profile placement column. | | `0x15a1e0` | character_profile_portrait_x_offsets | high | investigation | INFOCH adds the selected cell to the centered portrait x coordinate. The 100-cell spacing to the adjacent CIINIT arrays and the direct indexed reader establish the reserved per-profile placement column. |
| `0x15a244` | character_profile_portrait_y_offsets | high | investigation | INFOCH adds the selected cell to the bottom-aligned portrait y coordinate. The 100-cell spacing to the adjacent CIINIT arrays and the direct indexed reader establish the reserved per-profile placement column. | | `0x15a244` | character_profile_portrait_y_offsets | high | investigation | INFOCH adds the selected cell to the bottom-aligned portrait y coordinate. The 100-cell spacing to the adjacent CIINIT arrays and the direct indexed reader establish the reserved per-profile placement column. |
| `0x15a2a9` | glossary_topic_unlock_seen_decision_ids | high | investigation | VIINIT's three unlock prerequisites per glossary topic. INFOVO subtracts one from each positive value and exposes the topic when any referenced scene_decision_seen_flags cell is set; debug modes expose the same populated topics unconditionally. Columns: 0=unlock_seen_decision_1, 1=unlock_seen_decision_2, 2=unlock_seen_decision_3. | | `0x15a2a9` | glossary_topic_unlock_seen_decision_ids | high | investigation | VIINIT's three unlock prerequisites per glossary topic. INFOVO subtracts one from each positive value and exposes the topic when any referenced scene_decision_seen_flags cell is set; debug modes expose the same populated topics unconditionally. Columns: 0=unlock_seen_decision_1, 1=unlock_seen_decision_2, 2=unlock_seen_decision_3. |
| `0x5` | — | low | auto-shape | array |
| `0xd2` | — | low | auto-shape | array |
| `0xd7` | — | low | auto-shape | array |
| `0x1a4` | — | low | auto-shape | array |
| `0x1a9` | — | low | auto-shape | array |
| `0x2e2` | — | low | auto-shape | array | | `0x2e2` | — | low | auto-shape | array |
| `0x41f` | — | low | auto-shape | array |
| `0x4f1` | — | low | auto-shape | array |
| `0x5c3` | — | low | auto-shape | array |
| `0x69e` | — | low | auto-shape | array | | `0x69e` | — | low | auto-shape | array |
| `0x6fe` | — | low | auto-shape | array | | `0x6fe` | — | low | auto-shape | array |
| `0x712` | — | low | auto-shape | array | | `0x712` | — | low | auto-shape | array |
@@ -504,6 +504,8 @@
| address | name | conf | source | usage | | address | name | conf | source | usage |
|---|---|---|---|---| |---|---|---|---|---|
| `0xd2` | save_slot_location_names | high | investigation | SAVE and SELSTAGE snapshot the current location label into this shared SAVE.DAT-selected string bank. SAVE.BIN restores it with op 0x1aa for the row's location field. |
| `0x1a4` | save_slot_protagonist_names | high | investigation | SAVE and SELSTAGE snapshot the active familiar's display name into this shared SAVE.DAT-selected string bank. SAVE.BIN restores it with op 0x1aa beside the portrait. |
| `0x27e` | unit_class_titles | high | investigation | Persistent per-unit class/title string table. CALCCC writes the selected class_change_title_output into the current unit's cell; character and status presentation scripts read the resulting title. | | `0x27e` | unit_class_titles | high | investigation | Persistent per-unit class/title string table. CALCCC writes the selected class_change_title_output into the current unit's cell; character and status presentation scripts read the resulting title. |
| `0x315` | unit_story_display_names | high | investigation | CNINIT's sparse unit-id keyed story-name table. Scene scripts and HISTORY draw the selected speaker's value, DEBUGADV uses it for its scripted speaker previews, and INPUTNAME rejects a player-entered familiar name that collides with any populated row. The table intentionally leaves Lily's form ids 2..4 empty. | | `0x315` | unit_story_display_names | high | investigation | CNINIT's sparse unit-id keyed story-name table. Scene scripts and HISTORY draw the selected speaker's value, DEBUGADV uses it for its scripted speaker previews, and INPUTNAME rejects a player-entered familiar name that collides with any populated row. The table intentionally leaves Lily's form ids 2..4 empty. |
| `0x7db` | modal_message_lines | high | investigation | Shared ten-string modal buffer. Producers append messages or menu options at modal_message_line_count; MES measures and draws each string, and SBUNKI reuses the same entries for an interactive selection list. | | `0x7db` | modal_message_lines | high | investigation | Shared ten-string modal buffer. Producers append messages or menu options at modal_message_line_count; MES measures and draws each string, and SBUNKI reuses the same entries for an interactive selection list. |
@@ -533,7 +535,6 @@
| `0x278` | — | med | auto-shape | string-table (written by FIELD) | | `0x278` | — | med | auto-shape | string-table (written by FIELD) |
| `0x279` | — | med | auto-shape | string-table (written by INPUTNAME) | | `0x279` | — | med | auto-shape | string-table (written by INPUTNAME) |
| `0x27a` | — | med | auto-shape | string-table (written by READY) | | `0x27a` | — | med | auto-shape | string-table (written by READY) |
| `0x27b` | — | med | auto-shape | string-table (written by READY) |
| `0x27c` | — | med | auto-shape | string-table (written by READY) | | `0x27c` | — | med | auto-shape | string-table (written by READY) |
| `0x27d` | — | med | auto-shape | string-table (written by READY) | | `0x27d` | — | med | auto-shape | string-table (written by READY) |
| `0x316` | — | med | auto-shape | string-table (written by CNINIT) | | `0x316` | — | med | auto-shape | string-table (written by CNINIT) |
@@ -589,7 +590,6 @@
| `0x349` | — | med | auto-shape | string-table (written by CNINIT) | | `0x349` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x34b` | — | med | auto-shape | string-table (written by CNINIT) | | `0x34b` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x34c` | — | med | auto-shape | string-table (written by CNINIT) | | `0x34c` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x34d` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x34e` | — | med | auto-shape | string-table (written by CNINIT) | | `0x34e` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x34f` | — | med | auto-shape | string-table (written by CNINIT) | | `0x34f` | — | med | auto-shape | string-table (written by CNINIT) |
| `0x350` | — | med | auto-shape | string-table (written by CNINIT) | | `0x350` | — | med | auto-shape | string-table (written by CNINIT) |

View File

@@ -3510,15 +3510,24 @@ executes `0x1a1`, runs `CALLBACK_LOAD.BIN`, and reaches the saved script's activ
A second, read-only compatibility gate points the same real-script path at the installed Himegari A second, read-only compatibility gate points the same real-script path at the installed Himegari
`SAVE00.DAT`. It decodes the complete native layout, resolves the installed save's first persisted script `SAVE00.DAT`. It decodes the complete native layout, resolves the installed save's first persisted script
through the mounted SYS4 catalogs, and enters that frame with `FrameCause.SaveRestore`; the test stops at through the mounted SYS4 catalogs, and enters that frame with `FrameCause.SaveRestore`; the test stops at
that boundary before gameplay continues and never writes to the original AppData tree. No codec, VM, or that boundary before gameplay continues and never writes to the original AppData tree.
script-control correction was required by these gates.
The Godot profile remains intentionally isolated at `user://SAVE`. For visual acceptance, a copied The first visual acceptance run exposed an incomplete-fixture/lifecycle issue rather than a numbered
`SAVE00.DAT`/`.STH` pair can be placed there without exposing the original save directory to writes. codec error. A numbered pair carries the fixed timestamp/playtime header and screenshot, but `SAVE.BIN`
JSON inspection/export, namespaced mod state, migrations, and richer import UX remain extended-mode work. loads its chapter, location, protagonist name, level, growth, personality, difficulty badge, 15-bit
cleared-ending/NG+ mask, and append-install mask from slot-indexed selected cells in shared `SAVE.DAT`.
Godot had injected the store without loading `SharedProfile`, so those `0x1a3`/`0x1aa` reads correctly
returned zero/empty. Startup now loads shared `SAVE.DAT` and `RT.DAT` before constructing the gameplay VM
(self-test remains isolated), and the real-script regression covers the three preview strings, numeric
glyph values, and one 17x17 badge per set ending bit.
Validation: all 386 engine tests pass, opcode lint reports zero errors/warnings, the Godot C# build has The Godot profile remains intentionally isolated at `user://SAVE`. Visual compatibility therefore uses
zero warnings, and the threaded headless run reports `SELFTEST OK`. copied `SAVE00.DAT`/`.STH`, `SAVE.DAT`, and `RT.DAT` files without exposing the original save directory
to writes. JSON inspection/export, namespaced mod state, migrations, and richer import UX remain
extended-mode work.
Validation: all 386 engine tests pass, global/opcode lint reports zero errors/warnings, the Godot C#
build has zero warnings, and the threaded headless run reports `SELFTEST OK`.
## Data-semantics sidebar: focused append EBINIT inspection (2026-07-24) ## Data-semantics sidebar: focused append EBINIT inspection (2026-07-24)

View File

@@ -101,12 +101,26 @@ public class SaveUiIntegrationTests
} }
store.SaveNumberedThumbnail( store.SaveNumberedThumbnail(
0, NumberedThumbnailCodec.Encode(new(112, 84, pixels))); 0, NumberedThumbnailCodec.Encode(new(112, 84, pixels)));
var authoredProfile = new SharedProfile();
authoredProfile.StoreString(0x000, "序章 封印、そして");
authoredProfile.StoreString(0x0d2, "base part-time job");
authoredProfile.StoreString(0x1a4, "Lily");
authoredProfile.StoreInteger(0x0d7, 80);
authoredProfile.StoreInteger(0x27b, 98);
authoredProfile.StoreInteger(0x34d, 69);
authoredProfile.StoreInteger(0x41f, 0);
authoredProfile.StoreInteger(0x4f1, 0b1011);
authoredProfile.Save(
store, new NativeSystemTime(2026, 7, 5, 24, 13, 42, 17, 321), 7_445);
var loadedProfile = new SharedProfile();
Assert.True(loadedProfile.Load(store));
var scripts = Sys4ScriptProvider.Load(Table); var scripts = Sys4ScriptProvider.Load(Table);
var host = new StopAtFirstMenuPollHost(); var host = new StopAtFirstMenuPollHost();
var vm = new VirtualMachine( var vm = new VirtualMachine(
scripts.RequireByName("SAVE.BIN"), Table, host, scripts.RequireByName("SAVE.BIN"), Table, host,
new VmOptions(MaxSteps: 500_000), scripts, new VmOptions(MaxSteps: 500_000), scripts,
sharedProfile: loadedProfile,
nativeDatStore: store); nativeDatStore: store);
Assert.Throws<MenuReadyException>(() => vm.Run()); Assert.Throws<MenuReadyException>(() => vm.Run());
@@ -118,6 +132,15 @@ public class SaveUiIntegrationTests
Assert.Contains(host.SurfaceStrings, item => item.Text == "07"); Assert.Contains(host.SurfaceStrings, item => item.Text == "07");
Assert.Contains(host.SurfaceStrings, item => item.Text == "24"); Assert.Contains(host.SurfaceStrings, item => item.Text == "24");
Assert.Contains(host.SurfaceStrings, item => item.Text == "13"); Assert.Contains(host.SurfaceStrings, item => item.Text == "13");
Assert.Contains(host.SurfaceStrings, item => item.Text == "序章 封印、そして");
Assert.Contains(host.SurfaceStrings, item => item.Text == "base part-time job");
Assert.Contains(host.SurfaceStrings, item => item.Text == "Lily");
var objects = vm.Gfx.SnapshotVisibleObjects().ToDictionary(item => item.Handle);
Assert.Equal((0, 96), (objects[0x13137].SrcX, objects[0x13138].SrcX)); // level 80
Assert.Equal((96, 108), (objects[0x13146].SrcX, objects[0x13147].SrcX)); // growth 98
Assert.Equal((108, 72), (objects[0x13155].SrcX, objects[0x13156].SrcX)); // personality 69
Assert.Equal(3, host.TextureDraws.Count(
item => item.Slot == 193 && item.Width == 17 && item.Height == 17));
} }
finally finally
{ {
@@ -191,11 +214,15 @@ public class SaveUiIntegrationTests
[(0x2, Array.Empty<Operand>())], []), 0x00fefefd); [(0x2, Array.Empty<Operand>())], []), 0x00fefefd);
var provider = new SaveUiProvider(nativeScripts, null, loadCallback); var provider = new SaveUiProvider(nativeScripts, null, loadCallback);
var host = new ClickFirstSlotHost(); var host = new ClickFirstSlotHost();
var store = new DirectoryNativeDatStore(root, Identity);
var sharedProfile = new SharedProfile();
Assert.True(sharedProfile.Load(store));
var vm = new VirtualMachine( var vm = new VirtualMachine(
nativeScripts.RequireByName("SAVE.BIN"), Table, host, nativeScripts.RequireByName("SAVE.BIN"), Table, host,
new VmOptions(MaxSteps: 1_000_000), provider, new VmOptions(MaxSteps: 1_000_000), provider,
new StopAtInstalledResumeSink(), new StopAtInstalledResumeSink(),
nativeDatStore: new DirectoryNativeDatStore(root, Identity)); sharedProfile: sharedProfile,
nativeDatStore: store);
host.Vm = vm; host.Vm = vm;
vm.Globals[0x6241b] = 1; vm.Globals[0x6241b] = 1;
vm.Globals[0x696] = 0; vm.Globals[0x696] = 0;

View File

@@ -27,6 +27,8 @@ internal class RecordingHost : IHost
public readonly List<SurfaceRectFill> SurfaceFills = new(); public readonly List<SurfaceRectFill> SurfaceFills = new();
public readonly List<SurfaceRectCopy> SurfaceCopies = new(); public readonly List<SurfaceRectCopy> SurfaceCopies = new();
public readonly List<(long First, long Count)> PresentedRanges = new(); public readonly List<(long First, long Count)> PresentedRanges = new();
public readonly List<(int Slot, int SourceX, int SourceY, int Width, int Height, int X, int Y)>
TextureDraws = new();
public readonly List<AdvWaitIndicatorConfig> WaitIndicators = new(); public readonly List<AdvWaitIndicatorConfig> WaitIndicators = new();
public readonly List<bool> WaitIndicatorEnabledChanges = new(); public readonly List<bool> WaitIndicatorEnabledChanges = new();
public readonly List<int> PublishedAdvTextLayouts = new(); public readonly List<int> PublishedAdvTextLayouts = new();
@@ -144,7 +146,8 @@ internal class RecordingHost : IHost
public void SetTexture(long resId, int slot) => Textures.Add((resId, slot)); public void SetTexture(long resId, int slot) => Textures.Add((resId, slot));
public void ClearRenderTarget(int surfaceSlot) => ClearedRenderTargets.Add(surfaceSlot); public void ClearRenderTarget(int surfaceSlot) => ClearedRenderTargets.Add(surfaceSlot);
public void ReleaseSurfaceRange(int firstSlot, int count) => ReleasedSurfaceRanges.Add((firstSlot, count)); public void ReleaseSurfaceRange(int firstSlot, int count) => ReleasedSurfaceRanges.Add((firstSlot, count));
public void DrawTexture(int slot, int sx, int sy, int w, int h, int dx, int dy) { } public void DrawTexture(int slot, int sx, int sy, int w, int h, int dx, int dy)
=> TextureDraws.Add((slot, sx, sy, w, h, dx, dy));
public (int Width, int Height) GetTextureSize(int slot) => (0, 0); public (int Width, int Height) GetTextureSize(int slot) => (0, 0);
public void PlayBgm(long id) { } public void PlayBgm(long id) { }
public void PlayVoice(long id) => Voices.Add(id); public void PlayVoice(long id) => Voices.Add(id);

View File

@@ -257,8 +257,11 @@ public partial class Main : Godot.Control
new NativeSaveIdentity( new NativeSaveIdentity(
NativeSaveMagic.S4SD, 0x4a343234, "姫狩りダンジョンマイスター", NativeSaveMagic.S4SD, 0x4a343234, "姫狩りダンジョンマイスター",
SaveVersion1: 3, SaveVersion2: 10, NumberedCompatibilityId: 0x42323234)); SaveVersion1: 3, SaveVersion2: 10, NumberedCompatibilityId: 0x42323234));
var sharedProfile = new SharedProfile();
if (!_selftest) sharedProfile.Load(nativeSaveStore);
_vm = new VirtualMachine(script, table, _host, _vm = new VirtualMachine(script, table, _host,
new VmOptions(MaxSteps: 20_000_000, IgnoreExitRequests: nativeDebugMenu), provider, sink, new VmOptions(MaxSteps: 20_000_000, IgnoreExitRequests: nativeDebugMenu), provider, sink,
sharedProfile: sharedProfile,
nativeDatStore: nativeSaveStore); nativeDatStore: nativeSaveStore);
if (scripts != null) if (scripts != null)
{ {

View File

@@ -6,6 +6,116 @@
[meta] [meta]
note = "Curated global addresses override the auto shape-inference map (build/global-var-map.json)." note = "Curated global addresses override the auto shape-inference map (build/global-var-map.json)."
[[global]]
address = "0x0"
name = "save_slot_chapter_titles"
category = "string-table"
type = "string[210]"
value_domain = "per-slot chapter/title preview text; empty means no authored preview"
usage = "SAVE and SELSTAGE snapshot the current chapter/title into this shared SAVE.DAT-selected string bank. SAVE.BIN restores the indexed cell with op 0x1aa and draws it at the top of each populated row; it is not stored in the numbered DAT header."
source = "investigation"
confidence = "high"
[[global]]
address = "0xd2"
name = "save_slot_location_names"
category = "string-table"
type = "string[210]"
value_domain = "per-slot player-facing location/stage name"
usage = "SAVE and SELSTAGE snapshot the current location label into this shared SAVE.DAT-selected string bank. SAVE.BIN restores it with op 0x1aa for the row's location field."
source = "investigation"
confidence = "high"
[[global]]
address = "0x1a4"
name = "save_slot_protagonist_names"
category = "string-table"
type = "string[210]"
value_domain = "per-slot protagonist/familiar display name"
usage = "SAVE and SELSTAGE snapshot the active familiar's display name into this shared SAVE.DAT-selected string bank. SAVE.BIN restores it with op 0x1aa beside the portrait."
source = "investigation"
confidence = "high"
[[global]]
address = "0x5"
name = "save_slot_portrait_entity_indices"
category = "data-table"
type = "int[210]"
value_domain = "runtime entity/form index used to select the save-row portrait"
usage = "SAVE and SELSTAGE snapshot current_entity_index through op 0x1a2. SAVE.BIN restores the slot cell with op 0x1a3 and uses its form range when choosing the portrait atlas region."
source = "investigation"
confidence = "high"
[[global]]
address = "0xd7"
name = "save_slot_protagonist_levels"
category = "data-table"
type = "int[210]"
value_domain = "nonnegative unit level"
usage = "SAVE and SELSTAGE copy unit_current_levels[current_entity_index] into this shared SAVE.DAT-selected bank. SAVE.BIN restores and renders it as the row's LV value."
source = "investigation"
confidence = "high"
[[global]]
address = "0x1a9"
name = "save_slot_training_action_counts"
category = "data-table"
type = "int[210]"
value_domain = "nonnegative accumulated training-action count"
usage = "SAVE and SELSTAGE snapshot training_action_total_execution_count into this shared SAVE.DAT-selected per-slot bank; copy/move/delete operations keep it aligned with the rest of the preview record."
source = "investigation"
confidence = "high"
[[global]]
address = "0x27b"
name = "save_slot_growth_values"
category = "data-table"
type = "int[210]"
value_domain = "familiar training progress"
usage = "SAVE and SELSTAGE snapshot familiar_training_progress through op 0x1a2. SAVE.BIN restores and renders it as the row's growth value."
source = "investigation"
confidence = "high"
[[global]]
address = "0x34d"
name = "save_slot_personality_values"
category = "data-table"
type = "int[210]"
value_domain = "signed familiar alignment/personality value"
usage = "SAVE and SELSTAGE snapshot familiar_alignment through op 0x1a2. SAVE.BIN restores and renders the signed value in the row's personality field."
source = "investigation"
confidence = "high"
[[global]]
address = "0x41f"
name = "save_slot_difficulty_indices"
category = "data-table"
type = "int[210]"
value_domain = "difficulty/mode badge index"
usage = "SAVE snapshots difficulty_index through op 0x1a2. SAVE.BIN restores the per-slot value and selects the corresponding 34x17 row badge from SO010."
source = "investigation"
confidence = "high"
[[global]]
address = "0x4f1"
name = "save_slot_cleared_ending_masks"
category = "data-table"
type = "int[210]"
value_domain = "15-bit ending/NG+ inheritance mask; bit i draws ending badge i"
usage = "SAVE snapshots the current cleared-ending inheritance mask through op 0x1a2. SAVE.BIN restores it from shared SAVE.DAT, scans bits 0..14, and draws one 17x17 badge for every achieved ending on that slot and subsequent inherited-loop saves."
source = "investigation"
confidence = "high"
[[global]]
address = "0x5c3"
name = "save_slot_append_install_masks"
category = "data-table"
type = "int[210]"
value_domain = "32-bit installed append-selector mask at save time"
usage = "SAVE snapshots the current installed-append mask through op 0x1a2. SAVE.BIN compares each saved bit against the current mask and marks previews whose required append data is missing."
source = "investigation"
confidence = "high"
[[global]] [[global]]
address = "0x8c877" address = "0x8c877"
name = "current_item_id" name = "current_item_id"