Skip to content

fix(scanner): relax PS1 memcard trailing-frame check for emulator-formatted cards (fixes #3)#4

Closed
terafin wants to merge 1 commit into
joeblack2k:mainfrom
intarweb:feat/relax-ps1-memcard-trailing-frame-check
Closed

fix(scanner): relax PS1 memcard trailing-frame check for emulator-formatted cards (fixes #3)#4
terafin wants to merge 1 commit into
joeblack2k:mainfrom
intarweb:feat/relax-ps1-memcard-trailing-frame-check

Conversation

@terafin

@terafin terafin commented Jun 7, 2026

Copy link
Copy Markdown

Fixes #3.

Problem

validate_ps1_raw_memcard (helpers/{mister,steamdeck,windows}/src/scanner.rs:1970) requires the trailing frame at byte offset 8064 ("write test sector", frame 63) to start with the same MC magic as frame 0. Real-hardware-formatted PS1 cards do this; SwanStation / Beetle PSX / Duckstation libretro do NOT — they either zero-fill frame 63 or write game-state continuation bytes through it.

Net effect today: every RetroArch-produced PSX memcard is rejected at the helper layer with "Skipping non-supported save (outside allowed console families)", breaking the PSX sync path entirely for Steam Deck / RetroDECK users. See #3 for full evidence including hexdumps.

Fix

Drop the trailing-frame requirement. Frame 0 magic + checksum + the 15 directory-frame checksums are already strong evidence of a real PS1 memcard. The trailing-frame "write test sector" is a real-hardware artifact emulators don't replicate.

Applied identically to all 3 helpers (mister / steamdeck / windows) since the validator code is duplicated across them.

Tests

Added 6 new tests in helpers/steamdeck/src/scanner.rs (mirrors the existing inline test module pattern with build_valid_ps1_memcard()):

Test What it proves
ps1_memcard_accepted_when_trailing_frame_lacks_mc_magic THE FIX: real SwanStation Rainbow Six bytes at frame 63 → accepted
ps1_memcard_accepted_when_trailing_frame_is_zero_filled Common emulator pattern (just zeros) → accepted
ps1_memcard_still_rejected_when_size_wrong Regression guard: wrong size still rejects
ps1_memcard_still_rejected_when_frame0_magic_absent Regression guard: no MC at frame 0 → reject
ps1_memcard_still_rejected_when_directory_frame_checksum_broken Regression guard: bad directory checksum → reject
ps1_memcard_regression_strict_format_still_accepted Regression guard: real-hardware-strict format still accepted

Verification flow

After merging + building + deploying the fork release:

  1. Steam Deck helper retries the previously-skipped Rainbow Six PSX memcard
  2. Helper classifies it as psx, validator now passes, payload uploads to RSM
  3. skipped=1 → 0, PSX appears in the RSM API and lands on yuna under Sony - PlayStation

🤖 Generated with Claude Code

…matted cards

validate_ps1_raw_memcard required frame 63 ("write test sector") at byte
offset 8064 to start with the same MC magic as frame 0. Real-hardware-
formatted PS1 cards do this; SwanStation / Beetle PSX / Duckstation
libretro do NOT — they either leave frame 63 zero-filled or write actual
game-state continuation bytes through it.

Net effect today: every RetroArch/SwanStation-produced PSX memcard is
rejected with "Skipping non-supported save (outside allowed console
families)", breaking the PSX sync path completely for Steam Deck /
RetroDECK users.

Repro on a Steam Deck running RetroDECK + SwanStation core: Tom Clancy's
Rainbow Six - Lone Wolf saves to a 131072-byte .srm with valid frame 0
(MC + correct checksum), valid frame 1-15 directory checksums, but
frame 63 bytes "03 00 00 00 80 0C 5A 27 ..." (game data, not MC).

Fix: drop the trailing-frame requirement. Frame 0 magic + checksum +
the 15 directory-frame checksums are already strong evidence of a real
PS1 memcard. The trailing-frame "write test sector" is a real-hardware
artifact emulators don't replicate.

Applied identically to all 3 helpers (mister/steamdeck/windows) since
the validator code is duplicated across them.

Tests (in helpers/steamdeck/src/scanner.rs, mirrors the existing inline
test module pattern):

  - ps1_memcard_accepted_when_trailing_frame_lacks_mc_magic
      THE FIX: real SwanStation Rainbow Six bytes at frame 63 -> accepted
  - ps1_memcard_accepted_when_trailing_frame_is_zero_filled
      Common emulator pattern -> accepted
  - ps1_memcard_still_rejected_when_size_wrong
      Regression guard -> wrong size still rejects
  - ps1_memcard_still_rejected_when_frame0_magic_absent
      Regression guard -> bad frame 0 still rejects
  - ps1_memcard_still_rejected_when_directory_frame_checksum_broken
      Regression guard -> bad directory checksum still rejects
  - ps1_memcard_regression_strict_format_still_accepted
      Regression guard -> real-hardware-strict format still accepted

Fixes #3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@terafin terafin force-pushed the feat/relax-ps1-memcard-trailing-frame-check branch from c6b5056 to 5130b5e Compare June 8, 2026 06:19
terafin referenced this pull request in intarweb/SGM-Helper Jun 8, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@terafin

terafin commented Jun 8, 2026

Copy link
Copy Markdown
Author

Consolidated into #11 (squashed with the other 3 scanner.rs format-gap fixes under the same libretro-vs-standalone lens). Closing in favor of #11 — same fixes, single commit, fewer threads for the maintainer to track.

@terafin terafin closed this Jun 8, 2026
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 11, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 11, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 12, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 13, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 13, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 14, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 14, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 14, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
intarweb-sync-bot Bot referenced this pull request in intarweb/SGM-Helper Jun 14, 2026
…C/MPK/CPK + TG-16)

Four related libretro-vs-standalone scanner gaps in one batch — all fix
real data-loss / silent-skip paths reported by SGM-Helper users on Steam
Deck (RetroDECK) and SS1 (libretro cores), all touch helpers/*/scanner.rs
(plus helpers/*/syncer.rs for the RTC dedup-key fix), all ship with
happy-path + regression-guard tests.

## Part 1 — PS1 memcard trailing-frame relax (was PR #4, fixes #3)

Real PS1 hardware writes "MC" magic at memcard frame 63 (the "write test
sector" — bytes 8064-8191). SwanStation (libretro Duckstation port) and
Beetle PSX leave frame 63 zero-filled OR write actual game-state
continuation bytes through it. Validator was rejecting every libretro
PS1 save with "outside allowed console families."

Drops the over-strict trailing-frame check. Frame 0 magic + frames 1-15
directory frame checksums are sufficient — these are real structural
validators a corrupt memcard would fail. Tests added: zero-filled
trailing-frame accepted, non-MC bytes accepted, frame 0 magic still
required, directory checksum still required, strict-format memcards
still accepted (regression guard).

## Part 2 — Sega RetroDECK path-hints (was PR #6, fixes #5)

RetroDECK's flatpak (net.retrodeck.retrodeck) writes saves under
single-word lowercase directory names. The Sega classifier had matched
some (megadrive, megacd, sega32x) but was missing several:
gamegear, mastersystem, megacdjp, sega32xjp, saturnjp, sega32xna,
megadrivejp.

Adds the missing variants. Test enumerates all 11 cases (5 fixes + 6
regression guards proving the previously-working paths still classify
correctly).

## Part 3 — Preserve .rtc / .mpk / .cpk through dedup (was PR #8, fixes #7)

Helper's save_selection_key collapsed Pokemon Crystal.rtc and
Pokemon Crystal.srm to the same key (stem-only), so the .rtc was
silently dropped — losing the in-game real-time clock state. Same
class of bug exists for N64 controller-pak data (.mpk / .cpk) which
sits alongside cart .srm.

Suffixes the dedup key with the extension when ext is rtc/mpk/cpk:
"crystal:rtc" vs "crystal:srm" no longer collide. Also adds
classifier size-check arm for gameboy .rtc (1..=64 bytes) so the
small clock-state payload doesn't get filtered as "implausible save."

Test: classify_accepts_tiny_rtc_files_for_gameboy with 8 / 13 / 32 /
48 / 64-byte .rtc payloads, regression guard that an 8-byte .srm is
still rejected as bogus (proves the relaxation is .rtc-scoped).

## Part 4 — TurboGrafx-16 / PC Engine classifier (was PR #10, fixes #9)

The classify_supported_save function had branches for every other
Sega/Nintendo/Sony system but no TG-16 / PCEngine / SuperGrafx block.
Saves from RetroArch's Beetle PCE Fast / Beetle SuperGrafx core were
returning None and getting skipped with "outside allowed console
families" — but they're well-formed BRAM/SRAM saves.

Adds contains_any block matching pcengine|tgfx16|turbografx|supergrafx,
an infer_nec_slug returning "tgfx16", and an is_plausible_save_for_system
arm for tgfx16 covering .sav 2KB / 8KB and .brm 2KB BRAM sizes. Test:
classify_recognizes_pcengine_tgfx16_paths enumerates RetroDECK,
RetroArch, and Beetle subdir variants.

## Scope

- All 4 fixes are additive: missing branches added, over-strict checks
  relaxed in a single direction. No previously-accepted save shape is
  now rejected.
- All test coverage is parallel across helpers/mister, helpers/steamdeck,
  helpers/windows — the triplet structure stays in sync.

Consolidates and supersedes PR #4 + PR #6 + PR #8 + PR #10. All four
share helpers/*/scanner.rs and the same libretro-vs-standalone lens.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

PS1 memcard validator rejects all RetroArch/SwanStation saves — trailing-frame 'MC' check too strict

1 participant