fix(cli): surface typed parse errors for corrupt savegames (#206) #317

Merged
jqueuniet merged 1 commit from fix/206-save-detect-typed-errors into main 2026-09-04 15:56:18 +02:00
Owner

Closes the reopened half of #206.

Problem

Three of the four family probes (try_parse_graphics, try_parse_ftl, try_open_resource_fork) are Result<Option<_>>, so a fingerprint match plus a parse failure surfaces the typed error. detect_save was still Option<_> and ran the full SaveFile::parse with .ok(), collapsing "recognised savegame, corrupt" into the same None as "not a save at all". A truncated save fell through the whole probe chain and died with a generic message, even though the library had already computed the precise cause.

Change

detect_save (crates/dm2-cli/src/commands/mod.rs) now returns anyhow::Result<Option<_>>. The looks_like_* header predicates stay the family gate; once one matches, SaveFile::parse's error is propagated as matched the {variant} savegame fingerprint but failed to parse: {e}. Both arms are covered — LE (dos-retail / dos-beta-v09) and BE (mac-retail).

All five call sites ? the outer Result and keep their generic message for the genuine no-match case: info.rs, verify.rs, dump.rs, raw_extract.rs, save/mod.rs (load_save).

dm2-convert's mirrored detect_save already propagated, so it needed no change.

Before / after

The issue's exact repro:

$ head -c 5000 fixtures/dos-en/saves/sksave0.dat > trunc5k.dat

# before
Error: could not identify trunc5k.dat

# after
Error: trunc5k.dat
Caused by:
    matched the dos-retail savegame fingerprint but failed to parse: sksave truncated:
    bitstream_offset 43485 >= file_len 5000 (file too small for computed raw-section layout) at 0xa9dd

dm2 verify and dm2 save show give the same diagnostic.

Tests

  • mod.rs unit tests: fall-through on a non-save, typed error on a truncated one.
  • tests/cli.rs: info_truncated_save_surfaces_typed_error, verify_truncated_save_surfaces_typed_error, info_non_save_still_falls_through_to_generic_message.
  • tests/cli_save.rs: save_show_truncated_save_surfaces_typed_error.

cargo test --workspace — 132 test binaries, 0 failures. cargo clippy -p dm2-cli --all-targets clean for the changed files. CHANGELOG has an Unreleased/Fixed entry.

https://claude.ai/code/session_013dMZoqpmA6U2BhTcNFbX15

Closes the reopened half of #206. ## Problem Three of the four family probes (`try_parse_graphics`, `try_parse_ftl`, `try_open_resource_fork`) are `Result<Option<_>>`, so a fingerprint match plus a parse failure surfaces the typed error. `detect_save` was still `Option<_>` and ran the *full* `SaveFile::parse` with `.ok()`, collapsing "recognised savegame, corrupt" into the same `None` as "not a save at all". A truncated save fell through the whole probe chain and died with a generic message, even though the library had already computed the precise cause. ## Change `detect_save` (`crates/dm2-cli/src/commands/mod.rs`) now returns `anyhow::Result<Option<_>>`. The `looks_like_*` header predicates stay the family gate; once one matches, `SaveFile::parse`'s error is propagated as `matched the {variant} savegame fingerprint but failed to parse: {e}`. Both arms are covered — LE (dos-retail / dos-beta-v09) and BE (mac-retail). All five call sites `?` the outer `Result` and keep their generic message for the genuine no-match case: `info.rs`, `verify.rs`, `dump.rs`, `raw_extract.rs`, `save/mod.rs` (`load_save`). `dm2-convert`'s mirrored `detect_save` already propagated, so it needed no change. ## Before / after The issue's exact repro: ``` $ head -c 5000 fixtures/dos-en/saves/sksave0.dat > trunc5k.dat # before Error: could not identify trunc5k.dat # after Error: trunc5k.dat Caused by: matched the dos-retail savegame fingerprint but failed to parse: sksave truncated: bitstream_offset 43485 >= file_len 5000 (file too small for computed raw-section layout) at 0xa9dd ``` `dm2 verify` and `dm2 save show` give the same diagnostic. ## Tests - `mod.rs` unit tests: fall-through on a non-save, typed error on a truncated one. - `tests/cli.rs`: `info_truncated_save_surfaces_typed_error`, `verify_truncated_save_surfaces_typed_error`, `info_non_save_still_falls_through_to_generic_message`. - `tests/cli_save.rs`: `save_show_truncated_save_surfaces_typed_error`. `cargo test --workspace` — 132 test binaries, 0 failures. `cargo clippy -p dm2-cli --all-targets` clean for the changed files. CHANGELOG has an Unreleased/Fixed entry. https://claude.ai/code/session_013dMZoqpmA6U2BhTcNFbX15
fix(cli): surface typed parse errors for corrupt savegames (#206)
All checks were successful
CI / msrv (1.90) (pull_request) Successful in 43s
CI / audit (pull_request) Successful in 23s
CI / stable (pull_request) Successful in 1m18s
CI / msrv (1.90) (push) Successful in 48s
CI / audit (push) Successful in 21s
CI / stable (push) Successful in 1m17s
e41d0c925b
`detect_save` ran the *full* `SaveFile::parse` and `.ok()`-ed its error
away, collapsing "recognised family, corrupt" into the same `None` as
"not a save at all". A truncated save therefore fell through the whole
probe chain and died with a generic "could not identify" / "no known
format matched" / "is not a recognised DM2 savegame", even though the
library had already computed the precise cause.

Make it `Result<Option<_>>` like its `try_parse_graphics` /
`try_parse_ftl` / `try_open_resource_fork` siblings: the `looks_like_*`
header fingerprint stays the family gate, and once it matches a parse
failure is propagated instead of discarded. All five call sites (`info`,
`verify`, `dump`, `raw-extract`, `save` via `load_save`) `?` the outer
Result and keep their generic message for the genuine no-match case.

    $ head -c 5000 fixtures/dos-en/saves/sksave0.dat > truncated.dat
    $ dm2 info truncated.dat
    Error: truncated.dat
    Caused by:
        matched the dos-retail savegame fingerprint but failed to parse:
        sksave truncated: bitstream_offset 43485 >= file_len 5000 ...

Covers both the LE (DOS retail / Beta) and BE (Mac retail) arms.

Claude-Session: https://claude.ai/code/session_013dMZoqpmA6U2BhTcNFbX15
jqueuniet deleted branch fix/206-save-detect-typed-errors 2026-09-04 15:56:18 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jqueuniet/dm2-tools!317
No description provided.