feat(startup): add configurable game root

This commit is contained in:
gamer147
2026-07-28 23:05:25 -04:00
parent b1d4a791b5
commit d2ec834fc3
14 changed files with 339 additions and 25 deletions

View File

@@ -11,7 +11,7 @@ Workspace root: `S:\Game Hacking\Eushully\Himegari\`
```
S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
├── 姫狩りダンジョンマイスター/ ← SOURCE — pristine game install (read-only)
├── Himegari_Game/ ← SOURCE — pristine game install (read-only)
│ │ Never edit, move, or add to this folder. It holds ORIGINALS ONLY.
│ ├── AGE.EXE, AGERC.DLL, *.dll shipped engine (packed). Stays intact and
│ │ runnable in place — Frida launches it if needed.
@@ -101,8 +101,9 @@ S:\Game Hacking\Eushully\Himegari\ ← workspace root (three siblings)
│ └── manifest.json, opcode-coverage.md (opcode-coverage.md GENERATED from opcodes.toml)
├── engine/ DELIVERABLE — the .NET VM core (AgeEngine.sln: Age.Engine / Age.Cli / tests)
│ ├── Age.Engine/Sys4/ runtime catalog parser, loose-first bounded ALF asset store,
│ script provider, AGF/LZSS and Windows CUR decoders, and resource facade
│ ├── Age.Engine/Sys4/ runtime game-root selection, catalog parser, loose-first bounded
ALF asset store, script provider, AGF/LZSS and Windows CUR decoders,
│ and resource facade
│ └── Age.Engine/Persistence/ native S3SD/S4SD + S3RT codecs, shared payload/ReadTextDB,
│ layout-3 numbered state/history/gfx, DAT/STH pair + BMP codec,
│ and profile-owned state
@@ -119,7 +120,7 @@ page ordinals to their authoritative script offsets for `tools/locate_page.py`.
## Conventions
- **Three-way separation.** `姫狩りダンジョンマイスター/` = untouched originals; `extracted/` =
- **Three-way separation.** `Himegari_Game/` = untouched originals; `extracted/` =
game-derived data (regenerable, game-side); `age-reimpl/` = everything we authored. The first two
are consumed, never modified.
- **Tools never hard-code paths.** `tools/paths.py` derives `GAME_DIR`, `EXTRACTED`, `DATA1`,

View File

@@ -2,8 +2,8 @@
Working reference for porting *姫狩りダンジョンマイスター* (Eushully, 2009) to Godot.
Source install: `C:\Program Files (x86)\Eushully\姫狩りダンジョンマイスター\`
Workspace: `S:\Game Hacking\Eushully\Himegari\` — three siblings: `姫狩りダンジョンマイスター\` (pristine game), `extracted\` (extracted ALF data), `age-reimpl\` (our work). See `docs/PROJECT-STRUCTURE.md`.
Source install: `S:\Game Hacking\Eushully\Himegari\Himegari_Game\`
Workspace: `S:\Game Hacking\Eushully\Himegari\` — three siblings: `Himegari_Game\` (pristine game), `extracted\` (extracted ALF data), `age-reimpl\` (our work). See `docs/PROJECT-STRUCTURE.md`.
---
@@ -91,10 +91,10 @@ loop in `AGE.EXE`, which is the Ghidra task below. Graphics conversion (DATA2/5
save-format work remain deferred.
### Immediate (no tools needed beyond what's on disk)
1. ~~**Relocate the `Output\` tree**~~ **DONE**workspace now at `S:\Game Hacking\Eushully\Himegari\姫狩りダンジョンマイスター\`.
1. ~~**Relocate the `Output\` tree**~~ **DONE**pristine install now at `S:\Game Hacking\Eushully\Himegari\Himegari_Game\`.
2. **Convert remaining AGFs** in DATA2 (985 files) and DATA5 (210 files) with `AGF2BMP2AGF.exe`. *(Deferred — graphics not needed yet.)* The 3-file DATA1 gap is `CHAPTER.AGF`, `LOGO.AGF`, `TEST.AGF`.
3. ~~**Inventory the script files**~~ **DONE** — see [script-inventory.md](script-inventory.md). Key findings: all 481 scripts share magic `SYS4422 `; 49 loose root-dir script `.BIN` files shadow DATA1 copies (plus two root-only engine BINs; use overrides as authoritative); heavy game logic (damage calc, dungeon loop, battle flow) lives in bytecode, favoring a VM re-implementation in Godot.
1. ~~**Relocate the `Output\` tree**~~ **DONE**workspace now at `S:\Game Hacking\Eushully\Himegari\姫狩りダンジョンマイスター\`.
1. ~~**Relocate the `Output\` tree**~~ **DONE**pristine install now at `S:\Game Hacking\Eushully\Himegari\Himegari_Game\`.
2. **Convert remaining AGFs** in DATA2 (985 files) and DATA5 (210 files) with `AGF2BMP2AGF.exe`. *(Deferred — graphics not needed yet.)* The 3-file DATA1 gap is `CHAPTER.AGF`, `LOGO.AGF`, `TEST.AGF`.
3. ~~**Inventory the script files**~~ **DONE** — see [script-inventory.md](script-inventory.md). Key findings: all 481 scripts share magic `SYS4422 `; 49 loose root-dir script `.BIN` files shadow DATA1 copies (plus two root-only engine BINs; use overrides as authoritative); heavy game logic (damage calc, dungeon loop, battle flow) lives in bytecode, favoring a VM re-implementation in Godot.

View File

@@ -3569,6 +3569,33 @@ and an overridden `1200x700` request while continuing to report `logical-canvas=
width exits during startup with the expected diagnostic. Headless's minimized `64x64` backend remains
non-authoritative for visible client geometry.
### Runtime game-root selection result (implemented 2026-07-28)
Godot no longer obtains installed game data from the development-only `Paths.GameDir` workspace
assumption. `GameRootSelection` establishes one normalized read-only root before loading SYS4INI:
`--game-root <directory>` has highest precedence, followed by the running executable's directory and
then the current working directory. Candidates are accepted only when `SYS4INI.BIN` is present.
Relative overrides resolve against the working directory, repeated options use the last value, and an
invalid explicit override fails without silently selecting a different install. On Unix the frontend
uses inherited shell `PWD` as the launch working directory because Godot can change the process directory
to its project before managed startup.
The selected root supplies the base catalog, loose overrides, append catalogs, and ALF archives through
one injected `Sys4AssetStore`. Normal execution, the debug-scene catalog, and threaded self-test reuse
that same catalog/store rather than re-entering repository path discovery. The local ignored
`run-godot.ps1` development convenience passes one directly configured `$gameRoot` value because editor
runs are hosted by the Godot executable; changing that assignment is the local cross-game testing seam.
An exported executable placed beside the original game data can use the executable-directory default.
This is the command-line boundary a future multi-profile launcher can populate with an absolute install
path.
Eight focused cases cover explicit and relative selection, override precedence and duplicates,
executable-directory defaulting, working-directory fallback, missing values, invalid explicit roots,
and unresolved diagnostics. The complete engine suite passes **463/463**, the Godot project builds with
zero warnings, and the threaded headless self-test reports the selected `launch-argument` game root
before `SELFTEST OK`. Export packaging of generated opcode/profile metadata remains a separate
distribution step; this slice removes the installed-game hardcoding only.
## ADV requested-face selection implemented (2026-07-28)
Opcode `0x1a5` is no longer a presentation no-op. The VM retains its requested face in

View File

@@ -35,7 +35,7 @@ or replaced before claiming portable exports.
| ADV font discovery/raster fidelity | Opcode `0x1a5` reaches presentation; `godot/Main.cs` loads Windows ` 明朝`/` ゴシック` from their known TTC files when available, otherwise uses the existing Japanese-font/default fallback. AGE actually uses a display information DC, weight-700 `LOGFONTA`, `GGO_GRAY4_BITMAP`, and its own integer glyph/outline compositor; the current `Label` backend substitutes FreeType embolden and a Godot outline | On the reference Windows install, regular advances and direct SC0000 placement/bounds agree, but the behaviorally different provisional bold path has 45% fewer bright pixels and much softer edge coverage. Other platforms normally lack the proprietary faces, so substitute metrics remain profile-dependent | Deferred until gameplay settles: implement the Phase-E decoded mask/metrics/compositor backlog in `docs/remake-architecture-and-roadmap.md`, with a shipped GDI reference backend on Windows and an explicitly defined portable rasterizer/substitution policy |
| Filesystem semantics | Several filename and containment comparisons use `OrdinalIgnoreCase`; installed assets are conventionally uppercase | Needs validation on case-sensitive filesystems; may hide casing or containment mistakes | Add Linux/macOS tests with mixed-case synthetic roots and use filesystem-appropriate containment rules |
| Save/profile storage | Managed `DirectoryNativeDatStore` under Godot `user://SAVE`; native S3SD/S4SD/S3RT files and 24-bit BMP thumbnails | No Win32 path API at runtime; port saves remain isolated from the original installation | Validate replace/flush, case, permissions, and interrupted-write behavior on each export target |
| Install/repository discovery | `engine/Age.Engine/Sys4/Paths.cs` finds `age-reimpl` above `AppContext.BaseDirectory` and assumes the current workspace sibling layout | Suitable for development, not packaged exports on any OS | Replace runtime discovery with a user-selected game root/profile; retain repository paths only for developer tools/tests |
| Game-install and repository discovery | `GameRootSelection` accepts `--game-root`, then probes the executable directory and current working directory for `SYS4INI.BIN`; on Unix the frontend prefers inherited shell `PWD` because Godot may change the process directory during project startup. Godot injects the selected root into its catalog and loose-first ALF store. `Paths.cs` remains the development/test locator for generated opcode data, diagnostics, and CLI conveniences | Installed game data no longer depends on the workspace sibling layout, enabling an executable beside `AGE.EXE`, a terminal launch from the install, or a launcher-supplied absolute profile root. A packaged export still needs its generated runtime metadata bundled independently of repository discovery | Add export packaging for the opcode/profile artifacts, let the future profile launcher pass `--game-root`, and run executable-directory/CWD plus case/permission smoke gates on Linux and macOS |
| Archive parity oracle | One integration test launches `bin/BinExtractALF.exe` | Windows-only test helper, not a shipped runtime dependency | Skip/replace on non-Windows CI; runtime ALF/AAI readers do not depend on it |
| Native RE tools | Frida/Ghidra helpers target the original `AGE.EXE`; supporting utilities include Windows executables and Windows command conventions | Development/research only | Keep separate from export requirements; document platform prerequisites per tool |
| Python workflow | Operating guide uses Windows `py -3.11` invocation | Developer workflow only | Add equivalent `python3` instructions if non-Windows development becomes active |

View File

@@ -649,6 +649,14 @@ the toolchain at the new game's archives, re-run extraction, rebuild its global
**This is the core payoff of the VM approach:** the *engine* cost amortizes across all AGE games; only
content-mapping recurs — far less than re-coding each game's logic bespoke.
The first runtime install-selection boundary is now in place. Godot receives one normalized game root,
defaulting to the executable directory with a current-working-directory fallback and accepting an
explicit `--game-root` override; the selected root is injected into the generic SYS4 catalog/store
instead of inferred from the Himegari repository layout. A future multi-profile launcher can therefore
own install discovery and pass the chosen profile's absolute root through the same stable argument.
Generated engine/profile metadata still needs an export-owned bundle before this constitutes complete
drop-in packaging; invocation details live in `tools-reference.md`.
#### Opcode ABI registry must be independent of per-game coverage
A cursory Kamidori boot probe on 2026-07-20 validated much of this boundary: its own `SYS4INI.BIN` and

View File

@@ -162,7 +162,7 @@ public static class Paths
public static string Workspace => Directory.GetParent(Repo)!.FullName;
public static string Extracted => Path.Combine(Workspace, "extracted");
public static string Data1 => Path.Combine(Extracted, "DATA1");
public static string GameDir => Path.Combine(Workspace, "姫狩りダンジョンマイスター");
public static string GameDir => Path.Combine(Workspace, "Himegari_Game");
public static string Build => Path.Combine(Repo, "build");
public static string OpcodesJson => Path.Combine(Build, "opcodes.json");

View File

@@ -298,11 +298,24 @@ at 2.5M lines). All observe-only → parity preserved; all on `run`/`play`/`swee
**Godot frontend** (`S:/Godot/Godot_v4.7…`; project = `godot/`). Toolchain: `godot --headless --path godot
--import``dotnet build godot/Himegari.csproj``godot [--headless] --path godot [-- <userargs>]`.
Plays the real bytecode with call-script execution on (subroutines run live). A no-argument launch starts
the persistent `SYSTEM4.BIN` root and reaches TITLE naturally. The local `run-godot.ps1`/`.cmd` launchers
make that route explicit with `--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. `--headless` can't
render texture ops (no GPU context) — run windowed for real scenes. On VM termination the console prints
the exact halt reason and step count before the generic on-screen `-end-` marker. User args (after `--`):
Plays the real bytecode with call-script execution on (subroutines run live). The game root is selected
before catalog loading: `--game-root <directory>` wins, otherwise the directory containing the running
executable is tried, then the current working directory. A candidate must contain `SYS4INI.BIN`; invalid
explicit roots and unresolved defaults stop startup with the searched locations. Relative overrides are
resolved against the current working directory, and repeated overrides use the last value. On Unix,
Godot preserves the shell launch directory through inherited `PWD` because editor/project startup may
change the process directory before managed code runs.
With the exported executable placed in an AGE install, a no-argument launch therefore uses that install
and starts the persistent `SYSTEM4.BIN` root naturally. Direct development runs are hosted by the Godot
editor executable, so pass `--game-root <install>` after Godot's `--` separator; the local
`run-godot.ps1`/`.cmd` launchers pass the local script's directly configured `$gameRoot` value. Edit that
one assignment when testing another installed game. Those launchers make the natural route explicit with
`--scene SYSTEM4` and pass neither `--boot` nor SC0000 seeds. Examples below focus on their
feature-specific arguments and assume the game root is already selected this way. `--headless` can't
render texture ops (no GPU context) — run windowed for real scenes.
On VM termination the console prints the exact halt reason and step count before the generic on-screen
`-end-` marker. User args (after `--`):
The local launchers also accept **`-StartupDiagnostics`** for a native-faithful cold boot through TITLE,
Game Start, and SC0000. The switch overrides `run-godot.cmd`'s usual `-NativeDebugMenu` convenience for
@@ -316,6 +329,9 @@ wait, optionally press F3, and close the window normally. Until shutdown cancell
ADV input release, normal window close can append one trailing shutdown-only record to `page-map.jsonl`;
use the last `input-wait` event in `timeline.jsonl` as the authoritative manual stop coordinate.
- `--game-root <directory>` — select the read-only AGE installation supplying `SYS4INI.BIN`, loose
overrides, AAI append catalogs, and ALF archives. Absolute paths are launcher-friendly; relative paths
are current-working-directory-relative. This is the stable handoff for future game-profile launchers.
- `--scene <NAME>` — override the default `SYSTEM4` root with a direct diagnostic scene, e.g. `--scene SC0240` (executes 29 nested subroutines).
- `--selftest` — headless; runs a **synthesized** scene through the thread/suspend/`CallDeferred` plumbing and asserts it matches a live headless run (full handling; no vm0/frozen golden). Exits.
- `--window-width <pixels>` / `--window-height <pixels>` — override only the physical windowed client