Skip to content

feat(zaparoo): auto-save cartridge SRAM on a timer#9

Open
wizzomafizzo wants to merge 1 commit into
masterfrom
auto-save
Open

feat(zaparoo): auto-save cartridge SRAM on a timer#9
wizzomafizzo wants to merge 1 commit into
masterfrom
auto-save

Conversation

@wizzomafizzo

@wizzomafizzo wizzomafizzo commented Jun 11, 2026

Copy link
Copy Markdown
Member
  • Adds support/zaparoo/auto_save.{cpp,h}: a poll-driven module that periodically pulses the core's "Save Backup RAM" / "Save Memory Card" status bit so dirty cartridge SRAM flushes to the mounted save file without opening the OSD. The pulse is byte-for-byte what the OSD menu item does (menu.cpp fires user_io_status_set(opt, 1) then (opt, 0)), so no new write path to the SD card exists.
  • Guard chain before every pulse: trigger discovered from the core's conf string at load (works on SNES, NES, SMS, Genesis, MegaCD, NeoGeo, GBA, TGFX16, Saturn, PSX, N64, 3DO, GB, WonderSwan and more), .fla N64 FlashRAM deny, 5s post-mount settle window, 1s sector-write quiet window, OSD-closed check, and the core's live H/D menumask read via UIO_GET_OSDMASK — auto-save can only fire when the user could select the menu item themselves (masks no-battery games, GBA's write-gated bk_ena, SRAM-as-RAM quirk titles).
  • Recovery layers per save file: rolling .bak.0.bak.2 generations via atomic tmp+fsync+rename copies, a once-per-mount .mount anchor, an all-zero/all-0xFF/shrink corruption check that aborts the cycle, and an external-modification disarm (mtime/size baseline) so FTP-imported saves are never stomped.
  • Saves are tracked per SD slot: N64 mounts several save files at incrementing indexes and PSX mounts its memcard at slot 2, so the in-flight and backup logic keys on the write's disk index rather than the slot-0-only use_save global.
  • Upstream-file footprint is 8 hook lines: scheduler.cpp poll call, user_io.cpp mount/unmount + sector-write notifications, and an AUTO_SAVE ini var in cfg.{h,cpp} (seconds between attempts, 0 = disabled = default).
  • AUTO_SAVE_PLAN.md documents the design, the per-core verification against MiSTer-devel core sources (conf-string bits and bk_save wiring), the safety analysis, the recovery procedure, and the on-hardware verification matrix.
  • Also documents ./docker-build.sh as the preferred build path in AGENTS.md.

Mechanism credit: Biduleman's SNES_MiSTer_DirectSave fork (branch skip-osd-save).

Refs MiSTer-devel#789

Summary by CodeRabbit

  • New Features

    • Automatic SRAM/cartridge save backup with periodic flushing without displaying the on-screen menu.
    • Auto-save includes safety guards for external modification detection, corruption validation, and rolling backups.
    • Configurable auto-save interval via settings.
  • Documentation

    • Updated build instructions to prefer Docker-based workflow.
    • Added comprehensive design documentation for the auto-save feature.

Periodically pulse the core's "Save Backup RAM" / "Save Memory Card"
status bit so dirty SRAM flushes to the mounted save file without the
user opening the OSD. The pulse is identical to selecting the OSD menu
item, so no new write path to the SD card is introduced.

Guard chain before each pulse: conf-string trigger discovered at core
load, .fla (N64 FlashRAM) deny, 5s post-mount settle, 1s sector-write
quiet window, OSD closed, and the core's own H/D menumask honored via
UIO_GET_OSDMASK (masks no-battery games, GBA's write-gated bk_ena, etc).

Recovery layers per save: rolling .bak.0..2 generations with atomic
tmp+fsync+rename copies, a per-mount .mount anchor, an all-zero/all-FF/
shrink sanity abort, and an external-modification disarm (mtime/size).

Saves are tracked per SD slot (N64 mounts several save files; PSX
mounts its memcard at slot 2). Off by default; enable with AUTO_SAVE=N
seconds in MiSTer.ini.

Mechanism credit: Biduleman's SNES_MiSTer_DirectSave (skip-osd-save).
Design doc and per-core verification: AUTO_SAVE_PLAN.md.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces an auto-save subsystem for the MiSTer emulator that periodically pulses core save menu triggers to flush SRAM and memory card data without user intervention, protected by seven guards (menu visibility, timers, menumask gating, external-modification detection, corruption checks) and four durability layers (rolling backups, per-mount anchors, sanity validation, atomic operations).

Changes

Auto-save Feature Implementation

Layer / File(s) Summary
Design and Architecture Documentation
AUTO_SAVE_PLAN.md
Comprehensive design document specifying the auto-save approach, problem statement (SRAM backup flushing via menu triggers), core polling loop with per-slot state tracking, trigger discovery via config string scanning, deny-list handling (N64 FlashRAM .fla), seven guard conditions (menu visibility, mount settle window, no in-flight writes, OSD closed, ROM not loading, menumask gating, trigger found), recovery and durability layers (rolling backups, per-mount anchors, corruption heuristics, external-modification detection), atomic file operations with .tmp + fsync + rename semantics, failure handling and race conditions, configuration via MiSTer.ini, and verification/test matrix.
Configuration Infrastructure
cfg.h, cfg.cpp
Adds uint32_t auto_save field to configuration struct and parses AUTO_SAVE INI entry with range 0–86400 seconds.
Auto-save Public Interface
support/zaparoo/auto_save.h
Declares polling entry point auto_save_poll() and event handlers auto_save_on_save_mounted(), auto_save_on_save_unmounted(), and auto_save_on_sector_write() for integration with scheduler and SD layer.
Auto-save Core Implementation
support/zaparoo/auto_save.cpp
Implements stateful auto-save polling with timing constants, global and per-slot state tracking, trigger discovery by config string parsing (extracting menu item bit index and H/D gating masks), atomic file operations (directory fsync, copy via *.tmp + rename + fsync), rolling backup rotation (.bak.0...bak.N-1), corruption detection (all-zero/all-0xFF and shrink heuristics), per-mount baseline anchoring, external-modification guards via mtime/size comparison, menumask holdoff (SPI-based OSD mask reads), and main polling loop with seven guard gates before triggering status-bit pulse.
Scheduler and User I/O Integration
scheduler.cpp, user_io.cpp
Includes auto_save.h and integrates auto-save into the FPGA polling cycle (scheduler_co_poll() calls auto_save_poll()); hooks save mount/unmount events (calling auto_save_on_save_mounted()/auto_save_on_save_unmounted()) and SD sector-write notifications (auto_save_on_sector_write()) from user I/O layer.
Build Documentation Update
AGENTS.md
Replaces generic toolchain note with Docker-first preferred build guidance using ./docker-build.sh and explicit make argument forwarding (e.g., clean, DEBUG=1 V=1); clarifies bare-metal fallback via source setup_default_toolchain.sh.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Saves are precious, so here's the deal:
Pulse the menu bit, make the SRAM real,
With guards and backups stacked up high,
No crashes, corruptions—we're not shy!
Rolling snapshots, atomicity's grace,
Auto-save brings peace to every place. 🎮

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(zaparoo): auto-save cartridge SRAM on a timer' accurately and concisely summarizes the main feature addition—an auto-save mechanism that periodically saves cartridge SRAM without user interaction.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto-save

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 33-38: The fenced code block containing the docker-build.sh
examples is missing a language identifier; update the opening fence to "```bash"
(i.e., change the block that lists "./docker-build.sh", "./docker-build.sh
clean", etc.) so the snippet is labeled as bash for proper syntax highlighting
and to satisfy the linter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9bcd24b0-8296-4974-a0be-2edea87c59da

📥 Commits

Reviewing files that changed from the base of the PR and between 4625c83 and 083cd59.

📒 Files selected for processing (8)
  • AGENTS.md
  • AUTO_SAVE_PLAN.md
  • cfg.cpp
  • cfg.h
  • scheduler.cpp
  • support/zaparoo/auto_save.cpp
  • support/zaparoo/auto_save.h
  • user_io.cpp

Comment thread AGENTS.md
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.

1 participant