Skip to content

First-run disc image installer: pick your ISO, everything else is automatic - #27

Open
Alexbeav wants to merge 3 commits into
LittleBitUA:mainfrom
Alexbeav:iso-install-wizard
Open

First-run disc image installer: pick your ISO, everything else is automatic#27
Alexbeav wants to merge 3 commits into
LittleBitUA:mainfrom
Alexbeav:iso-install-wizard

Conversation

@Alexbeav

@Alexbeav Alexbeav commented Aug 6, 2026

Copy link
Copy Markdown

First-run disc image installer: pick your ISO, everything else is automatic

What this does

Fresh installs currently require the user to produce the extracted assets/ tree themselves before the game will boot. This PR teaches downpour.exe to do it: when assets/default.xex is missing at startup, a wizard (same AcquireWizardDialog pattern as the existing Title Update 1 installer) asks for the user's Silent Hill: Downpour Xbox 360 disc image and extracts the XDVDFS game partition straight into game_data_root, with byte progress. After extraction it best-effort auto-stages TU1 through the existing download path, so the typical first run is exactly one user action:

run PlayDownpour.exe → Play → select your .iso → wait ~2 minutes → playing.

If TU staging fails (offline, mirror down — see #19), the existing TU wizard still appears on the next boot, unchanged, as the fallback.

Details

  • src/downpour_iso_installer.{h,cpp} — new. A minimal read-only XDVDFS (GDF) reader in the same style as the existing StfsPackageReader: directory tables are AVL trees of 4-byte-aligned entries, files are contiguous sector runs, so extraction is a seek + copy per file, sorted by disc position for sequential reads. Supports Redump-style full images (game partition at 0xFD90000), XGD3 (0x2080000), and bare game-partition dumps (offset 0). Rejects images with no default.xex at the partition root with a clear message — separating "wrong/bad dump" from "broken install" at the earliest possible point (would have shortcut the diagnosis in disk read error need help #6).
  • Resumable: files already present with the right size are skipped, and default.xex — the install-complete marker IsGameDataInstalled keys off — is extracted last, so an interrupted extraction re-opens the installer on the next launch and finishes the remaining files in seconds instead of booting with a partial tree.
  • Free-space check up front (needs ~4.5 GiB).
  • OnFinalizePaths now chains: game data missing → ISO wizard; TU missing → TU wizard (unchanged). It also honors DOWNPOUR_INSTALL_ISO=<path> and DOWNPOUR_INSTALL_TU=<path|download> for headless installs — the TU env override previously lived only in RunTitleUpdateInstallWizardBlocking, which the app never calls, so it was dead code in practice.
  • RelaunchSelfOrResume — the post-install process-restart block (the Win32 LaunchModule-hang workaround) is factored out of the TU wizard's completion lambda and shared by both wizards. No behavior change.
  • Linux/Proton note: the wizard uses the same GTK file-chooser path as the TU installer, so Steam Deck users (Is a solution for Proton/Steam Deck also in the works? #16) get the identical flow — no Windows-only launcher work needed.

Testing

Built against rexglue-sdk-dpour dpour-main (public HEAD) with the merged TU1 XEX (produced with XenonRecomp's XexPatcher, per the downpour_manifest.toml note). All runs on Windows 11, i9-13900K + RTX 4070 Ti, USA/Europe Redump image (7.3 GiB, game partition 1331 files / 4.49 GiB):

  • Fresh headless install (DOWNPOUR_INSTALL_ISO=<path> + DOWNPOUR_INSTALL_TU=download): empty assets/ → full extraction in ~105 s → TU1 downloaded and staged (payload SHA-256 matches the kPayloads whitelist) → runtime constructs → game boots to the main menu at 60 FPS. Fully unattended.
  • Interrupted-install resume: with default.xex and two other files removed to simulate a mid-extraction kill, relaunch restored exactly the missing files in 0.6 s (1329 files skipped by the size check) and booted.
  • Negative path: pointing the installer at a non-ISO file logs The selected file is not an Xbox 360 disc image (no XDVDFS game partition found) and falls through to the wizard; an image without default.xex at the partition root is rejected as not a Downpour disc.
  • Already installed: subsequent launches skip both installers and boot straight to the game.

The XDVDFS layout handling (partition base detection, AVL walk, sector math) was additionally validated standalone against the real Redump image before being ported into the wizard. The interactive wizard path shares InstallGameDataFromIso with the headless path and uses the same AcquireWizardDialog flow as the existing TU installer.

Extract the XDVDFS game partition of a user-supplied Xbox 360 disc
image straight into game_data_root on first launch, so a fresh install
is: run the game, pick your .iso, play. Redump (XGD2), XGD3, and bare
game-partition images are supported; extraction resumes if interrupted
and preserves already-extracted files.

After extraction the wizard best-effort auto-stages Title Update 1 via
the existing download path, so the usual first run needs exactly one
user action. If TU staging fails (offline), the existing TU wizard
still appears on next boot as the fallback.

Also honor DOWNPOUR_INSTALL_ISO / DOWNPOUR_INSTALL_TU env overrides in
OnFinalizePaths for headless installs (the TU override previously only
existed in RunTitleUpdateInstallWizardBlocking, which the app never
calls), and factor the post-install process relaunch into a shared
RelaunchSelfOrResume helper.
default.xex is the install-complete marker (IsGameDataInstalled), so it
must be the final file written: if extraction dies midway, the next
launch re-opens the installer and the skip-existing logic finishes the
remaining files in seconds instead of booting with a partial tree.
@LittleBitUA

Copy link
Copy Markdown
Owner

Thanks — this is a well-put-together PR. The XDVDFS reader is correct against the layouts I checked (Redump XGD2 base, XGD3, bare partition dumps), the resume-by-size + default.xex-last marker design is genuinely nice, and the wizard/env plumbing matches the existing TU installer patterns exactly. Three things I'd like addressed before merging:

1. Path traversal in extraction paths.
DiscFileEntry::relative_path is built from names read out of the image and joined as game_root / f.relative_path without sanitization. A crafted image with an entry named ..\..\something (or an absolute path, or a name containing separators) would write outside game_data_root. The practical risk is low since the user picks their own dump, but the fix is small: when building child_path in WalkDirectory, reject names that contain / or \, equal . or .., or are empty — skip the entry (or fail the listing) with a log line.

2. macOS branch is empty.
In the file-picker section, #elif defined(__APPLE__) has no body, so PickIsoFile is undefined on macOS and any future Apple build breaks at link/compile time inside this TU. Either add a stub (return {}; with a log message) or make it explicit with #error "PickIsoFile not implemented on macOS".

3. Unbounded directory recursion.
WalkDirectory recurses per subdirectory with no depth limit, so a malformed/crafted image with deeply nested (or self-referencing via different table sectors, which the per-table visited set can't catch) directories can overflow the stack. Passing a depth parameter and bailing out at ~32 levels closes it — real discs are nowhere near that.

Minor, non-blocking: has_xex only checks presence. Reading the first 4 bytes and checking the XEX2 magic would catch corrupt dumps at selection time rather than at first boot, which fits the "separate wrong dump from broken install" goal this PR states.

No conflicts with anything in flight on our side. With 1–3 addressed I'm happy to merge.

@Alexbeav

Alexbeav commented Aug 8, 2026

Copy link
Copy Markdown
Author

Thank you for the careful review. I accepted all of the comments and have submitted a correction that follows them, including the three requested fixes and the suggested XEX2 validation. Appreciate the clear, actionable feedback, and thank you for the project as a whole!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants