feat(desktop): add safe explicit Flash save-file persistence
This commit is contained in:
parent
8ded9bc90e
commit
86f29018fd
17 changed files with 1032 additions and 133 deletions
|
|
@ -49,8 +49,10 @@ The repository is a Cargo workspace with three crates:
|
|||
| `crates/gba-core/src/cpu/tests.rs` | Condition truth tables and immediate arithmetic tests |
|
||||
| `crates/gba-core/src/cpu/instruction_tests.rs` | Register operations, transfers, branches, and edge cases |
|
||||
| `crates/gba-core/src/cartridge.rs` | Explicit GPIO/RTC selection, ROM read overlay, pin directions, caller-clock routing, and bounded diagnostics |
|
||||
| `crates/gba-core/src/cartridge/flash.rs` | Explicit save devices, Macronix identification/read controller, bank selection, and unsupported-write diagnostics |
|
||||
| `crates/gba-core/src/cartridge/flash.rs` | Explicit save devices, Macronix IDs/banks, delayed program/erase, bounded DQ7 polling, and staged mutations |
|
||||
| `crates/gba-core/tests/flash.rs` | Erased/supplied images, chip IDs, all bank addresses, invalid commands, reset limits, DMA rejection, and RTC independence |
|
||||
| `crates/gba-core/tests/flash_write.rs` | Program values, sector/chip scopes, delays, polling, cancellation, bit transitions, and HALT/STOP |
|
||||
| `crates/gba-core/src/memory/flash_write_tests.rs` | Store-phase start, completion overlays, dirty-state rollback, and speculative clock isolation |
|
||||
| `crates/gba-core/src/memory/flash_step_tests.rs` | ARM/Thumb Flash writes and RAM reads, wait costs, staged IDs, fetch restrictions, and failed-step/preflight isolation |
|
||||
| `crates/gba-core/src/cartridge/rtc.rs` | RTC framing, control/calendar commands, read snapshots, complete writes, and reset |
|
||||
| `crates/gba-core/src/cartridge/calendar.rs` | Validated decimal calendar, BCD representation, checked epoch conversion, and bounded elapsed-time arithmetic |
|
||||
|
|
@ -169,6 +171,8 @@ The repository is a Cargo workspace with three crates:
|
|||
| `crates/desktop/src/cartridge/suite/gameplay_tests.rs` | VBlank completion, input snapshots, captured pixels, and rendering failures |
|
||||
| `crates/desktop/tests/rom_suite_cli.rs` | File-backed suite execution, case isolation, output schema, determinism, and exit status |
|
||||
| `crates/desktop/examples/write_test_suite.rs` | Original ARM/Thumb/BIOS fixture generator with a local JSON suite |
|
||||
| `crates/desktop/src/cartridge/save_file.rs`, `crates/desktop/src/cartridge/save_file/tests.rs` | Explicit raw-save loading, locks, baseline checks, synced installation, backups, and file-safety tests |
|
||||
| `crates/desktop/tests/save_cli.rs` | Original program/save/restart/load tests, diagnostic isolation, and native-window persistence |
|
||||
| `crates/desktop/src/cartridge/rtc_clock.rs` | Host-only UTC seed and monotonic elapsed-time adapter, with injected epoch/duration tests |
|
||||
| `crates/desktop/src/cartridge/window.rs` | ROM-window session, bounded slices, capture, STOP/input handling, and progress limits |
|
||||
| `crates/desktop/src/cartridge/window/tests.rs` | Original ROM pixels, keyboard mapping, STOP wake, HALT, frame budgets, and diagnostics |
|
||||
|
|
|
|||
|
|
@ -75,6 +75,24 @@ The window runs on the main thread, as required by macOS AppKit.
|
|||
The dependency is pinned to that exact version. Review this workaround before upgrading minifb.
|
||||
Intel macOS and Linux desktop builds are not verified.
|
||||
|
||||
## Portability boundary
|
||||
|
||||
The Rust `gba-core` owns CPU, memory, video, audio generation, input state, and cartridge protocols.
|
||||
It has no external dependencies or filesystem, window, host-clock, or audio-device code.
|
||||
The same core can serve another frontend; porting does not require rewriting emulated hardware.
|
||||
This separation is a design boundary, not a completed Linux or WebAssembly port.
|
||||
|
||||
- Linux can reuse the desktop structure and Unix raw-save adapter. Build dependencies and native behavior still need validation.
|
||||
Host audio currently returns an explicit unsupported-platform error outside macOS; add and test a Linux backend separately.
|
||||
- A browser needs WebAssembly bindings, canvas presentation, browser input, Web Audio, and browser scheduling.
|
||||
Its frontend must load ROM/image bytes and persist save images through browser storage or explicit import/export.
|
||||
It cannot reuse desktop `.sav` filesystem operations or CoreAudio.
|
||||
- Core save loading/inspection is byte-based. Flash commands never access host files.
|
||||
Desktop [save persistence](hardware/saves.md#desktop-persistence) is tested on Darwin arm64 only.
|
||||
|
||||
Cross-compilation, performance, lifecycle handling, and save durability need tests on each target.
|
||||
The core currently uses Rust's standard library; it is dependency-free, not a `no_std` crate.
|
||||
|
||||
Without direnv, use:
|
||||
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ Headerless original programs are accepted.
|
|||
Reads past supplied cartridge bytes remain explicit memory errors, not open-bus values.
|
||||
All three Game Pak ROM windows use the same supplied bytes and existing wait-state rules.
|
||||
Ordinary cartridge bytes remain read-only.
|
||||
Optional `--save-type flash64` or `--save-type flash128` enables [Flash identification and array reads](saves.md).
|
||||
The desktop starts an erased device and does not load or write save files. Programming and erase remain diagnostic.
|
||||
Optional `--save-type flash64` or `--save-type flash128` enables [nominal Flash programming, erase, and reads](saves.md).
|
||||
Without `--save-file PATH`, changes remain volatile. With it, the frontend loads an exact-size image and persists changes on clean exit.
|
||||
Missing images start erased. Existing images receive unique backups before replacement; incomplete Flash operations refuse persistence.
|
||||
Read the [storage safeguards and limits](saves.md#desktop-persistence) before using a valuable save.
|
||||
Optional `--rtc` selection enables the bounded [Game Pak GPIO and RTC calendar interface](cartridge-gpio.md).
|
||||
It supports date/time reads, complete writes, reset, and explicit elapsed-time updates. RTC interrupts and persistence remain unsupported.
|
||||
The desktop seeds UTC once, then supplies monotonic host elapsed seconds independently of GBA cycles and STOP.
|
||||
|
|
@ -70,7 +72,7 @@ The report includes:
|
|||
|
||||
Exit status zero means the step limit was reached or STOP was entered.
|
||||
It does not mean the program completed or passed a hardware test.
|
||||
Invalid arguments, loading failures, output failures, and emulation diagnostics produce a nonzero exit status.
|
||||
Invalid arguments, loading failures, output failures, save-persistence failures, and emulation diagnostics produce a nonzero exit status.
|
||||
For emulation diagnostics, stdout contains the final machine state and stderr contains the core error.
|
||||
|
||||
Terminal mode has no window, keyboard input, or real-time pacing.
|
||||
|
|
@ -138,6 +140,6 @@ No game or firmware content is included.
|
|||
ROM windows can enable Darwin output with `--audio`; they are muted by default.
|
||||
The option requires a usable default audio device. Device/configuration errors fail explicitly; omit the flag to run muted.
|
||||
Focus loss and STOP clear queued audio. See [audio output and its limits](audio.md#darwin-output-adapter).
|
||||
Terminal mode has no audio. Both modes lack save writing/persistence and per-step tracing.
|
||||
Terminal mode has no audio. Both modes support explicit save files but lack per-step tracing.
|
||||
Commercial-game compatibility remains unverified and unsupported.
|
||||
Only load files that you may lawfully use. Do not commit game ROMs or firmware.
|
||||
|
|
|
|||
|
|
@ -1,122 +1,175 @@
|
|||
# Cartridge Flash identification and read subset
|
||||
# Cartridge Flash and save files
|
||||
|
||||
The core supports explicit Macronix Flash selection, chip identification, array reads, and 128 KiB bank selection.
|
||||
Programming, erase, busy polling, and host save-file persistence remain unimplemented.
|
||||
This is not working game-save support. Unsupported writes fail instead of reporting a completed save.
|
||||
The core supports explicit Macronix Flash selection, identification, bank selection, byte programming, and sector/chip erase.
|
||||
Operations complete after nominal delays and expose bounded data polling. They do not complete immediately on the command write.
|
||||
The desktop can load and persist raw images through explicit `--save-file PATH` selection.
|
||||
Original ROM tests verify save/restart/load across processes. Saving in an external game remains unverified.
|
||||
SRAM, EEPROM, and other manufacturers' protocols remain separate work.
|
||||
|
||||
## Selection and initial data
|
||||
## Selection and core API
|
||||
|
||||
The desktop accepts either `--save-type flash64` or `--save-type flash128` in ROM terminal/window modes.
|
||||
The option is independent of `--rtc`. Omitting it leaves save memory unmapped.
|
||||
Unknown values and duplicate options fail before opening the ROM.
|
||||
|
||||
```sh
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --steps 3000000
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --window --frames 60
|
||||
```
|
||||
|
||||
Selection is explicit, not inferred from a filename, ROM signature, or the first write.
|
||||
A 64 KiB device cannot silently become a 128 KiB device.
|
||||
The desktop starts each selected device with erased `0xff` bytes and reports the read-only subset's limits.
|
||||
It does not look for, read, create, or overwrite a save file. Existing game saves remain untouched.
|
||||
The ROM suite schema still uses no save device by default and has no save-selection field yet.
|
||||
Use `--save-type flash64` or `--save-type flash128` in terminal or window ROM modes.
|
||||
Selection is independent of `--rtc`. Omitting the save type leaves save memory unmapped.
|
||||
No filename, ROM signature, or first-write detection changes the selected device.
|
||||
Without `--save-file`, the selected device starts erased (`0xff`) and all changes remain volatile.
|
||||
The ROM suite schema still has no save-selection or save-file field.
|
||||
|
||||
Core APIs on `Memory`:
|
||||
|
||||
- `set_save_device(SaveDevice)` replaces save hardware and initializes its array to erased bytes.
|
||||
`None`, `Flash64`, and `Flash128` are available. The call preserves GPIO/RTC state.
|
||||
- `load_save_image(&[u8])` supplies an exact-capacity image and resets command state, ID mode, and bank selection.
|
||||
A wrong length or missing device changes nothing. This is host setup, not an emulated program operation.
|
||||
- `save_image()` inspects the complete array, independently of ID mode or selected bank.
|
||||
It returns `None` when no save device is selected.
|
||||
- `set_save_device(SaveDevice)` selects `None`, `Flash64`, or `Flash128` and initializes an erased array.
|
||||
GPIO/RTC state is preserved.
|
||||
- `load_save_image(&[u8])` accepts exactly the selected capacity. It resets commands, pending operations, ID mode, bank, and modification tracking.
|
||||
Invalid size or missing hardware changes nothing.
|
||||
- `save_image()` inspects the full committed array, independent of bank and ID mode. No selected device returns `None`.
|
||||
- `save_modified()` reports whether a completed operation changed bytes since setup or loading.
|
||||
It remains set after host persistence; hosts compare against their last persisted image to avoid redundant writes.
|
||||
- `save_write_pending()` reports an incomplete command or operation. Hosts must not persist an exit snapshot in this state.
|
||||
|
||||
Configuration and image replacement occur between machine steps. No filesystem operations belong to the core.
|
||||
Selecting GPIO/RTC hardware does not replace an attached save device or its bytes.
|
||||
This feature can inspect caller-supplied images but cannot modify their data through Flash commands.
|
||||
Configuration, loading, and host inspection occur between machine steps.
|
||||
The core has no file, clock, window, or host-audio dependency. Frontends supply image bytes and own durable storage.
|
||||
A browser frontend can use the same API with browser storage instead of filesystem paths.
|
||||
|
||||
## Bus behavior
|
||||
## Bus and commands
|
||||
|
||||
Only `0x0e000000` through `0x0e00ffff` are mapped when a device is selected.
|
||||
The Flash interface uses byte accesses. Halfword/word transfers, DMA, and instruction fetches remain diagnostic.
|
||||
Unverified aliases outside this window remain unmapped rather than returning mirrored data.
|
||||
Ordinary ROM writes remain read-only.
|
||||
|
||||
CPU Flash reads must execute from external or internal work RAM, as required by GBATEK's access description.
|
||||
A read from ROM/BIOS code reports a diagnostic rather than inventing a value for an unsupported bus sequence.
|
||||
Host byte inspection has no CPU execution restriction. CPU command writes may execute from ROM.
|
||||
Only `0x0e000000` through `0x0e00ffff` are mapped. Accesses are byte-only.
|
||||
Halfword/word transfers, DMA, instruction fetches, and unverified aliases remain diagnostic.
|
||||
CPU Flash reads must execute from external or internal work RAM, following GBATEK's access description.
|
||||
Reads from ROM/BIOS code remain diagnostic. Host inspection has no execution restriction; CPU writes can execute from ROM.
|
||||
Ordinary ROM bytes remain read-only.
|
||||
|
||||
WAITCNT bits 0–1 select save wait states 4, 3, 2, or 8.
|
||||
The nominal byte-access costs are therefore 5, 4, 3, or 9 cycles, for both sequential and non-sequential requests.
|
||||
Save waits do not alter ROM wait fields. Flash data accesses cancel Game Pak opcode prefetch through the existing nominal model.
|
||||
Changing only save wait bits preserves the queue until a cartridge access cancels it.
|
||||
These costs are source-backed estimates, not physical timing measurements.
|
||||
Nominal byte-access costs are therefore 5, 4, 3, or 9 cycles for sequential and non-sequential accesses.
|
||||
Flash data accesses cancel Game Pak opcode prefetch. Changing save wait bits alone preserves the queue.
|
||||
These costs are source-backed estimates, not physical measurements.
|
||||
|
||||
## Commands
|
||||
|
||||
Use these byte addresses and values for the unlock prefix:
|
||||
The unlock prefix consists of these byte writes:
|
||||
|
||||
1. Write `0xaa` to `0x0e005555`.
|
||||
2. Write `0x55` to `0x0e002aaa`.
|
||||
3. Write a command byte to `0x0e005555`.
|
||||
3. Write the command to `0x0e005555`.
|
||||
|
||||
Supported commands after the prefix:
|
||||
|
||||
| Command | Behavior |
|
||||
| Command | Behavior after the unlock prefix |
|
||||
| --- | --- |
|
||||
| `0x90` | Enter chip-identification mode |
|
||||
| `0xf0` | Exit identification mode and return to array reads |
|
||||
| `0xb0` | On Flash128 only, accept a bank byte next at `0x0e000000` |
|
||||
| `0x90` | Enter chip identification |
|
||||
| `0xf0` | Exit identification and return to array reads |
|
||||
| `0xb0` | Flash128 only: next write selects bank 0 or 1 at `0x0e000000` |
|
||||
| `0xa0` | Next byte write supplies the target address and program data |
|
||||
| `0x80` | Accept another `0xaa`/`0x55` unlock pair, then an erase confirmation |
|
||||
|
||||
In ID mode, byte zero is the manufacturer and byte one is the device:
|
||||
Erase confirmation is `0x10` at `0x0e005555` for the entire chip, or `0x30` at a 4 KiB sector base.
|
||||
Sector erase affects only the selected bank's sector. Chip erase affects both banks on Flash128.
|
||||
Byte programming can clear bits or retain their value. Requests that set a stored zero bit are diagnostic; erase first.
|
||||
Invalid commands, prefixes, bank values, and sector addresses retain the preceding state for diagnostic retry.
|
||||
This policy does not reproduce hardware behavior for every malformed sequence.
|
||||
|
||||
| Selection | Manufacturer | Device | Combined ID |
|
||||
| Device | Capacity | Manufacturer byte | Device byte |
|
||||
| --- | --- | --- | --- |
|
||||
| Flash64 | `0xc2` | `0x1c` | `0x1cc2` |
|
||||
| Flash128 | `0xc2` | `0x09` | `0x09c2` |
|
||||
| Flash64 | 64 KiB | `0xc2` | `0x1c` |
|
||||
| Flash128 | 128 KiB | `0xc2` | `0x09` |
|
||||
|
||||
Other ID-mode addresses remain diagnostic because their values are not established by this subset.
|
||||
Array mode reads bytes from the selected 64 KiB bank. Flash64 always uses bank zero.
|
||||
Flash128 accepts bank values zero and one only. Bank changes do not alter stored data.
|
||||
Entering and leaving ID mode preserves the selected bank.
|
||||
Reads during an incomplete unlock retain the previous read mode and bank.
|
||||
Identification exposes these bytes at offsets zero and one. Other ID addresses remain diagnostic.
|
||||
Bank selection survives ID entry/exit; incomplete unlock reads retain the previous mode and bank.
|
||||
|
||||
For the documented 64 KiB Macronix device, a direct `0xf0` write at `0x0e005555` resets command/ID state.
|
||||
For Flash128, the same write is accepted only as a redundant reset when already idle in array mode.
|
||||
This bounded no-op matches the inspected emulators' idle behavior. It does not establish direct ID exit or busy-command cancellation.
|
||||
Flash128 still requires the full unlock prefix to exit ID mode. Direct reset during an incomplete unlock remains diagnostic.
|
||||
No matching manufacturer datasheet or physical measurement was used to claim broader 128 KiB reset behavior.
|
||||
For Flash64, direct `0xf0` at `0x0e005555` resets commands/ID state or cancels a busy operation.
|
||||
Cancellation preserves the old array. Partial physical programming on cancellation is not modeled.
|
||||
After `0xa0`, `0xf0` at that address is program data, not reset.
|
||||
For Flash128, direct reset is accepted only as a redundant no-op while idle in array mode.
|
||||
Flash128 requires an unlocked ID exit; direct busy/partial-command cancellation remains diagnostic.
|
||||
|
||||
`0xa0` byte-program setup and `0x80` erase setup remain diagnostic at the command write.
|
||||
No operation starts, data changes, busy state, completion flag, or interrupt is fabricated.
|
||||
Unknown commands, invalid prefixes, and invalid bank writes are also diagnostic.
|
||||
Rejected accesses retain the previous command state and bytes, so retrying the same access gives the same result.
|
||||
This atomic diagnostic policy does not reproduce hardware handling of every malformed sequence.
|
||||
## Nominal completion and transactions
|
||||
|
||||
## Transactions and validation
|
||||
Byte programming takes 650 nominal cycles. Sector and chip erase each take 30,000 nominal cycles.
|
||||
These are simulation constants, not measured Macronix timings. Chip erase duration is deliberately not capacity-dependent.
|
||||
GBATEK leaves average Macronix operation times unknown. The constants match the pinned mGBA timing approximation below.
|
||||
No matching manufacturer datasheet or physical timing measurement establishes these values.
|
||||
|
||||
The small Flash controller state joins the existing cartridge machine-step transaction.
|
||||
The full save image stays outside the copied controller state; this read-only subset never mutates it during emulation.
|
||||
Failed CPU or DMA steps discard speculative controller changes and timing.
|
||||
Block-store preflight rejects a wide Flash destination before committing earlier RAM or device writes.
|
||||
HALT/STOP and elapsed cycles cannot create Flash completion events. There is no pending program or erase operation to finish.
|
||||
While busy, reads are accepted only at the operation's polling address:
|
||||
|
||||
Original tests cover both device IDs, every byte in both banks, exact image lengths, erased state, invalid sequences, and unchanged ROM/save data.
|
||||
They distinguish redundant idle reset from unsupported Flash128 ID/partial-command reset.
|
||||
ARM/Thumb probes verify ROM command writes, RAM byte reads, wait settings, restrictions, and retryable failed steps.
|
||||
Additional tests cover staged ID reads, block rollback, DMA rejection, GPIO/RTC independence, and prefetch cancellation.
|
||||
Desktop tests execute an original IWRAM byte reader with explicit device selection in terminal and native-window modes.
|
||||
No game code, save file, or copyrighted assets are included in these tests.
|
||||
- Program: the target byte.
|
||||
- Sector erase: the selected sector base.
|
||||
- Chip erase: `0x0e000000`.
|
||||
|
||||
## Remaining work
|
||||
Busy reads return the complement of the final data's bit 7 (DQ7), with other bits zero.
|
||||
Toggle/error bits, protection, wear, queued sector erase, and suspend/resume are not modeled.
|
||||
Other busy reads and writes remain diagnostic, except the documented Flash64 cancellation subset.
|
||||
After completion, ordinary array reads resume. No completion interrupt is generated.
|
||||
|
||||
Implement and validate program/erase commands, busy polling, cancellation, and byte-program bit transitions before claiming save functionality.
|
||||
Then add explicit host save loading and safe persistence, with exact-size validation and restart tests.
|
||||
Do not overwrite existing files or add immediate-success responses merely to pass a game check.
|
||||
A program starts at the successful byte-store bus phase, after preceding fetch/data clocks.
|
||||
The small controller and remaining delay participate in the machine-step transaction.
|
||||
Completed reads in a step see a staged overlay. The full array changes only when the step commits successfully.
|
||||
Failed CPU/DMA steps and speculative block-store validation discard staged progress, data changes, and modification flags.
|
||||
There is no full-image copy per instruction.
|
||||
|
||||
`Machine` execution and `Memory::advance_cycles` advance completion. CPU-only stepping does not clock devices automatically.
|
||||
HALT advances pending operations. STOP freezes them under the current nominal device-clock policy.
|
||||
This does not reproduce a physical Flash chip's independent oscillator during STOP.
|
||||
|
||||
## Desktop persistence
|
||||
|
||||
Persistence is explicit. A typical local command is:
|
||||
|
||||
```sh
|
||||
direnv exec . cargo run --locked --release -- --rom roms/pokemon-emerald.gba --rtc --save-type flash128 --save-file roms/pokemon-emerald.sav --window --audio
|
||||
```
|
||||
|
||||
Omit `--audio` for muted execution. This command is not a game-save compatibility claim.
|
||||
Use a copy of any valuable existing save while compatibility remains unverified.
|
||||
|
||||
The save path's parent directory must exist. File extensions do not select the format.
|
||||
Existing raw images must contain exactly 65,536 or 131,072 bytes, matching the selected hardware.
|
||||
Loading occurs before the first machine step. A missing file starts an erased array without creating a file immediately.
|
||||
Directories, symlinks, read-only files, and paths that alias the ROM are rejected.
|
||||
The ROM is never opened for writing.
|
||||
|
||||
Complete the game's own save operation before closing the window normally or pressing Escape.
|
||||
The frontend persists changed bytes only on a clean exit:
|
||||
|
||||
- Interactive window closure or Escape.
|
||||
- Reaching a requested window frame limit.
|
||||
- Reaching a terminal step limit or entering terminal STOP.
|
||||
|
||||
An incomplete Flash command/operation refuses persistence and returns an error, even if no bytes changed yet.
|
||||
CPU, DMA, rendering, window, or reported audio errors do not persist the session image.
|
||||
There is no autosave, crash recovery, save-state support, or RTC persistence.
|
||||
The emulator cannot detect a game's multi-command save transaction. Exiting between completed commands can still save incomplete game data.
|
||||
A crash or forced termination loses changes since startup and can leave a stale lock.
|
||||
|
||||
The host storage sequence is:
|
||||
|
||||
1. Reserve a sibling `.lock` file exclusively for the entire session.
|
||||
2. Before persistence, compare the current destination bytes with the loaded baseline. Refuse ordinary external changes.
|
||||
3. Write and sync a uniquely named same-directory `.tmp.<pid>.<counter>` file.
|
||||
4. Before replacement, write and sync the prior bytes to a unique `.bak.<pid>.<counter>` backup.
|
||||
5. Recheck the destination, then install the image. Existing files use atomic rename; new files use a no-clobber hard link.
|
||||
6. Remove the temporary name and sync the parent directory. Release the owned lock on normal scope exit.
|
||||
|
||||
Unchanged images create no save or backup. Backups are never overwritten or automatically deleted by this frontend.
|
||||
Backup files are ordinary files, not OS-enforced immutable files. Review and manage them manually when needed.
|
||||
A directory-sync failure after installation reports that the save was installed but durability is uncertain.
|
||||
Storage exhaustion or permission errors are explicit failures; previous backups are retained.
|
||||
|
||||
The lock coordinates cooperating emulator processes. It is not protection against hostile or noncooperating filesystem changes.
|
||||
The final comparison and rename are not an atomic compare-and-swap operation.
|
||||
After a crash, confirm that no emulator owns the save, inspect the save/backup files, then remove the stale `.lock` manually.
|
||||
The frontend never takes over an existing lock automatically.
|
||||
Persistence currently requires a Unix host. Darwin arm64 is validated; Linux and browser storage remain unvalidated.
|
||||
|
||||
## Validation and remaining work
|
||||
|
||||
Original tests cover all program byte values in both banks, erase scope, exact delays, polling, cancellation, and bit transitions.
|
||||
ARM/Thumb tests cover store timing, completion reads, failed-step rollback, block preflight, HALT, and STOP.
|
||||
File tests cover size checks, unchanged images, locks, external changes, aliases, read-only paths, and retained backups.
|
||||
Original ROM process tests program, restart, reload through an IWRAM byte reader, and verify no persistence after diagnostics.
|
||||
A native-window test persists on frame-limit exit and reloads in another process.
|
||||
No game code, user save, firmware, or copyrighted assets are included in the tests.
|
||||
|
||||
Next, validate an external game's completed save and load using a disposable save path.
|
||||
Add independent hardware coverage for timing, status bits, busy addresses, and reset behavior before broadening the model.
|
||||
Implement SRAM and EEPROM separately when a demonstrated compatibility requirement needs them.
|
||||
|
||||
## References
|
||||
|
||||
- [GBATEK backup Flash](https://problemkaputt.de/gbatek-gba-cart-backup-flash-rom.htm): capacities, IDs, unlock sequences, bank selection, 64 KiB Macronix reset, byte-only access, and RAM-execution requirements.
|
||||
- [GBATEK](https://mgba-emu.github.io/gbatek/), WAITCNT section: save wait fields and one-cycle-plus-wait timing.
|
||||
- [mGBA savedata at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/savedata.c): comparison for controller phases, bank addressing, ID overlays, and idle reset handling. Its default chip IDs differ; its write/erase timing and busy approximation are not copied.
|
||||
- [ares Flash at 9408cb43](https://github.com/ares-emulator/ares/blob/9408cb43d4948fc3ea6e152a307a34348df3fe04/ares/gba/cartridge/flash.cpp): comparison for IDs, banking, unlocked ID exit, and unchanged idle state after an unrecognized direct reset. Its immediate program/erase behavior and permissive unlock handling are not adopted.
|
||||
- [GBATEK backup Flash](https://problemkaputt.de/gbatek-gba-cart-backup-flash-rom.htm): capacities, IDs, command sequences, banking, program/erase polling, Macronix reset, byte-only access, and RAM execution.
|
||||
- [GBATEK](https://mgba-emu.github.io/gbatek/), WAITCNT section: save waits and one-cycle-plus-wait timing.
|
||||
- [mGBA savedata at 3a5e34be](https://github.com/mgba-emu/mgba/blob/3a5e34be33dc7f8f707e5bc9db69e8a430046f21/src/gba/savedata.c): comparison for controller phases, bank addressing, ID overlays, idle reset, and nominal 650/30,000-cycle delays. Its DQ7 approximation is not hardware evidence. Our array mutation is deferred to transactional completion rather than applied immediately.
|
||||
- [ares Flash at 9408cb43](https://github.com/ares-emulator/ares/blob/9408cb43d4948fc3ea6e152a307a34348df3fe04/ares/gba/cartridge/flash.cpp): comparison for IDs, banking, unlocked ID exit, and idle reset. Immediate program/erase and permissive unlock handling are not adopted.
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ Core tests inject deterministic time. Desktop `--rtc` uses UTC startup and monot
|
|||
Select it explicitly; ordinary ROM bytes and other ROMs' default hardware remain unchanged.
|
||||
|
||||
RTC support then reached a Flash unlock write (`0xaa` at `0x0e005555`, PC `0x082e1892`).
|
||||
The explicit [Flash read subset](../hardware/saves.md) now handles identification, array reads, and bank selection.
|
||||
Select `--save-type flash128` for this bounded run; the device begins erased and no save file is opened.
|
||||
The initial [Flash subset](../hardware/saves.md) added identification, array reads, and bank selection.
|
||||
This bounded run selected `--save-type flash128` without a save file; the device began erased.
|
||||
A later redundant `0xf0` write occurred after returning to idle array mode.
|
||||
The subset accepts that no-op without claiming direct Flash128 ID exit or busy-command cancellation.
|
||||
|
||||
|
|
@ -117,7 +117,8 @@ The pinned Pong debug/release reports match.
|
|||
A subsequent [scheduled-input probe](emerald-wave.md) exposed a wave-channel trigger with its gate disabled.
|
||||
The general single-bank wave model now passes that point and completes an extended Start/A input schedule.
|
||||
Visible menu semantics and gameplay still require verification; frame-limit success alone cannot establish either.
|
||||
Flash programming, erase, busy behavior, and safe persistence remain required before claiming save compatibility.
|
||||
Nominal Flash programming, erase, DQ7 polling, and explicit host persistence were added after these game probes.
|
||||
Original ROM save/restart/load tests pass, but Emerald's completed save and reload still need separate validation.
|
||||
RTC persistence and cartridge IRQ behavior remain separate missing features.
|
||||
Ordinary ROM writes remain read-only. Without `--rtc`, the earlier GPIO diagnostic is still expected.
|
||||
This result does not establish gameplay, linked transfers, or cartridge/serial GPIO interrupt support.
|
||||
|
|
@ -128,7 +129,7 @@ All supported channels use the instantaneous digital-level inspection interface.
|
|||
Wave channel 3 now supports single-bank playback. Two-bank playback remains missing.
|
||||
Fixed-rate capture and optional Darwin `--audio` output were added later; their counters do not establish correct game music.
|
||||
Do not treat digital-level tests as audible-game validation or suppress remaining diagnostics without implementing their behavior.
|
||||
Cartridge saves remain a separate required feature. SRAM alone will not support Emerald's Flash save protocol.
|
||||
External-game save compatibility remains unverified. SRAM alone will not support Emerald's Flash save protocol.
|
||||
|
||||
Hardware and emulator references are recorded in the linked subsystem documents.
|
||||
[GBATEK backup Flash](https://problemkaputt.de/gbatek-gba-cart-backup-flash-rom.htm) identifies the `0xaa`/`0x5555` unlock and chip-identification sequence.
|
||||
|
|
|
|||
|
|
@ -80,8 +80,10 @@ Update this file when a feature lands or a limit is removed.
|
|||
- Validated RTC date/time, BCD transfers, read snapshots, complete writes/reset, and caller-supplied battery-clock advancement.
|
||||
- Desktop `--rtc` seeds UTC once and supplies host elapsed seconds independently of CPU cycles and STOP.
|
||||
- Cartridge GPIO and Flash controller changes participate in CPU/DMA rollback and block-store preflight without modifying ROM bytes.
|
||||
- Explicit Macronix Flash64/Flash128 identification, read-only arrays, and 128 KiB bank selection.
|
||||
- Exact-size core save-image initialization and inspection; desktop save selection starts erased and performs no save-file access.
|
||||
- Explicit Macronix Flash64/Flash128 identification, bank selection, delayed byte programming, sector/chip erase, and nominal DQ7 polling.
|
||||
- Transactional Flash completion and modification tracking, with exact-size core image loading and inspection.
|
||||
- Optional desktop `--save-file` loading and clean-exit persistence, with exclusive locks, atomic installation, and unique previous-image backups.
|
||||
- Original ROM save/restart/load checks across processes, including native-window persistence on Darwin arm64.
|
||||
- Raw ROM-file loading with original BIOS boot, terminal diagnostics, and final CPU/step reports.
|
||||
- Firmware boot initializes BG2/BG3 identity scales for bitmap and affine programs that do not program their own matrices.
|
||||
- RegisterRamReset supports disabled sound and disconnected serial initialization, including the common `r0=0xff` request.
|
||||
|
|
@ -147,9 +149,11 @@ Internal affine origins, vertical window flags, and vertical mosaic counters tra
|
|||
Horizontal window comparators retain four-cycle event history.
|
||||
Sprite rows prepare one line ahead with a whole-row cycle40 sample and nominal per-row work limits.
|
||||
Individual fetch timing, hardware-accurate work cutoffs, video-bus contention, and per-pixel color composition remain unimplemented.
|
||||
ROM windows offer opt-in Darwin audio output with `--audio`. Save writing/persistence remains unavailable.
|
||||
The [Flash subset](hardware/saves.md) supports explicit device selection, identification, array reads, and banking.
|
||||
Flash programming, erase, busy polling, wider accesses, unverified aliases, and other save-device protocols remain diagnostic or unimplemented.
|
||||
ROM windows offer opt-in Darwin audio output with `--audio` and explicit save persistence with `--save-file`.
|
||||
The [Flash subset](hardware/saves.md) supports programming and erase with nominal delays and bounded DQ7 polling.
|
||||
Flash timing, STOP clocking, and cancellation are not hardware-verified. Wider accesses and unverified busy addresses remain diagnostic.
|
||||
SRAM, EEPROM, other manufacturers, autosave, crash recovery, and game-level save transactions remain unsupported.
|
||||
Persistence is tested on Darwin arm64 with original programs, not yet with external-game saves.
|
||||
The optional [cartridge GPIO/RTC subset](hardware/cartridge-gpio.md) supports calendar/control transfers and explicit clock updates, selected with `--rtc` in ROM modes.
|
||||
RTC persistence, interrupts, invalid-date correction, physical subsecond timing, GPIO aliases, and byte writes remain unsupported or unverified.
|
||||
The core uses deterministic caller time. Desktop RTC runs use host UTC/elapsed time and are not fully deterministic.
|
||||
|
|
@ -171,7 +175,7 @@ The presentation framebuffer is separate from emulated video RAM.
|
|||
All named graphics demos connect desktop input and rendering to the emulated CPU. The default display test remains host-generated.
|
||||
ROM files can run in bounded terminal mode or a window with the original BIOS replacement.
|
||||
ROM windows have no demo-specific startup checks. Input is sampled between bounded execution slices.
|
||||
Audio is muted by default; `--audio` opens the macOS output device for ROM windows only. Saves and external BIOS loading remain unavailable.
|
||||
Audio is muted by default; `--audio` opens the macOS output device for ROM windows only. Save files require explicit selection; external BIOS loading remains unavailable.
|
||||
The host output has bounded buffers, linear rate conversion, DC removal, and underrun/drop counters. Slow hosts can stutter.
|
||||
ROM loading and nonzero audio callbacks do not establish commercial-game compatibility or correct music.
|
||||
A local Emerald run now completes its all-device reset and master sound enable.
|
||||
|
|
@ -186,7 +190,7 @@ That run used no scripted input or pixel assertions.
|
|||
A later [fixed-time input probe](research/emerald-wave.md) found a wave trigger with its gate disabled.
|
||||
Wave support now passes that point and reaches 1,320 frames with scheduled Start/A presses and releases.
|
||||
Output differs from a no-input control, but readable menus and gameplay remain unverified.
|
||||
Flash writing and persistence remain unimplemented.
|
||||
Flash writing and host persistence now pass original save/restart/load tests. Emerald saving and reloading remain unverified.
|
||||
Without `--rtc`, cartridge GPIO writes still return read-only diagnostics.
|
||||
No title-screen, gameplay, or audible-game compatibility is established.
|
||||
See [the startup result](research/emerald-reset.md) for the bounded run and next investigation.
|
||||
|
|
|
|||
|
|
@ -74,10 +74,24 @@ Desktop tests execute original ARM calendar commands in terminal and native-wind
|
|||
Host-adapter tests inject UTC epoch values and elapsed durations without sleeping. Guest time changes survive subsequent elapsed updates.
|
||||
Physical pin timing, invalid-date hardware correction, GPIO aliases, and battery persistence remain unverified or unimplemented.
|
||||
Flash regressions use original command sequences and exact-size generated arrays, not game saves.
|
||||
They check device IDs, every byte in both 128 KiB banks, erased state, image validation, reset limits, and unsupported program/erase commands.
|
||||
They check device IDs, bank addressing, erased state, image validation, and bounded reset behavior.
|
||||
Program/erase tests cover all data bytes, sector/chip scope, nominal delays, DQ7 polling, bit transitions, HALT, and STOP.
|
||||
Ordered ARM/Thumb probes verify completion reads, store-phase start, speculative timing, and array/modification rollback.
|
||||
ARM/Thumb probes verify RAM byte reads, ROM command writes, all save wait settings, staged ID state, and failed-step rollback.
|
||||
Wide accesses, DMA, instruction fetches, and CPU reads outside work RAM retain explicit diagnostics.
|
||||
An original desktop ROM installs its Flash byte reader in IWRAM; terminal/native tests check explicit selection and unchanged files.
|
||||
An original desktop ROM installs its Flash byte reader in IWRAM; terminal/native tests check explicit selection and unchanged ROMs.
|
||||
Separate processes program, persist, restart, and read the image through that reader.
|
||||
Host tests reject incomplete operations, diagnostic exits, wrong sizes, aliases, read-only paths, lock conflicts, and external changes.
|
||||
They also verify unique retained backups, unchanged-image no-ops, and cleanup. All images are generated in temporary directories.
|
||||
|
||||
The save-window check requires a logged-in desktop session, but no physical input or audio device:
|
||||
|
||||
```sh
|
||||
direnv exec . cargo test --locked --release -p gameboy-rust-desktop --test save_cli native_rom_window_save -- --ignored --test-threads=1
|
||||
```
|
||||
|
||||
That check validates frame-limit persistence and a separate terminal reload on Darwin arm64.
|
||||
It does not validate external-game saves or crash/power-loss durability.
|
||||
They verify explicit activation errors and atomic byte/word/block-store/DMA failure behavior.
|
||||
Wave tests cover every frequency and bank, independent nibble rotation, full rotations, bounded maximum batches, and fractional gains.
|
||||
They also check inactive triggers, gates, length, status, master/BIOS reset, HALT/STOP, live-write limits, and CPU/DMA rollback.
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ Fixed-rate stereo capture and opt-in Darwin `--audio` output now work with origi
|
|||
A local Emerald run submits nonzero device frames but records underruns; listening quality and correct music remain unverified.
|
||||
Two-bank wave playback, hardware PWM sampling, and improved reconstruction/pacing remain missing.
|
||||
See [the runtime result](research/emerald-reset.md). Next, verify visible menu progress rather than treating frame counts as playability.
|
||||
The separate save requirement includes Flash programming/erase and safe host persistence. Identification/read support is not successful saving.
|
||||
Nominal Flash programming/erase and explicit clean-exit persistence now pass original save/restart/load tests across processes.
|
||||
A native-window save also reloads through a separate process on Darwin arm64. External-game saving remains unverified.
|
||||
SRAM is byte-addressable battery-backed save memory. Flash and EEPROM remain separate protocols.
|
||||
|
||||
[Paperdomo101/2048-GBA at ae36800d](https://github.com/Paperdomo101/2048-GBA/tree/ae36800dfa1314f96c4c8785b5c2d077fb731aff) is the next independent candidate.
|
||||
|
|
@ -65,11 +66,10 @@ Next implementation steps:
|
|||
|
||||
1. Inspect visible startup/menu progress and exercise input in the bounded local game run. Record the first demonstrated failure.
|
||||
Use original regressions for any hardware correction; do not invent connected serial devices.
|
||||
2. Extend explicit Flash selection with verified programming, erase, and busy behavior. Keep absent devices and unsupported commands diagnostic.
|
||||
Add SRAM separately when the independent candidate requires it; verify its widths, mirroring, and wait behavior first.
|
||||
3. Add bounded save loading and safe persistence in the desktop crate. Do not put file operations in the core.
|
||||
4. Test a save/restart/load cycle across processes with original ROM instructions before external-game validation.
|
||||
5. Resume the second-game run and implement demonstrated audio requirements rather than accepting writes without behavior.
|
||||
2. Validate an external game's completed save/restart/load cycle using a disposable `--save-file` path.
|
||||
Refine nominal Flash timing and polling only with independent evidence and original regressions.
|
||||
3. Add SRAM separately when the independent candidate requires it; verify widths, mirroring, and waits first.
|
||||
4. Resume the second-game run and implement demonstrated audio requirements rather than accepting writes without behavior.
|
||||
|
||||
## Development loop
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue