Skip to content

Private filesystem and standalone lifecycle

This is shared runtime implementation, independent of any game patch. The original compiled program uses normal libctru/newlib fopen, fread, fwrite, fflush, fseek, ftell, fclose, unlink and the standard save-archive commit request. It creates a record, rereads it, uses a Japanese filename, deletes a temporary file, commits, exits, then reloads and updates the record in a fresh process. The host independently checks both saved files and their exact second-run bytes. Corrupt snapshots are refused and preserved, rather than replaced with empty saves.

The portable runtime opens fs:USER, initializes its session and supports:

Interface Commands
FSUSER Initialize / InitializeWithSdkVersion; OpenArchive / CloseArchive; OpenFile; CreateFile / DeleteFile; GetFreeBytes; ControlArchive(commit); scoped read-only OpenFileDirectly
FSFILE Read / Write; GetSize / SetSize; Flush / Close
Explicit archives Savedata (4), private virtual SDMC (9), and current-process read-only RomFS view (3), supplied by the platform

No guest path names a host file. The platform owns a volume until guest teardown; mounts and file sessions retain its lifetime. Archive IDs are generation checked and bound to their service session. File sessions survive archive closure and are retained across kernel-handle duplication. Closing a file session invalidates all copies; final kernel-handle close also releases it. Deleting a live file is explicitly refused in this bounded profile. Archive/file/handle exhaustion rolls back ownership, including a create request when a file session cannot be allocated.

Volumes have 16 root files, at most 1 MiB per file and 4 MiB total data. Request buffers are limited to 1 MiB. ASCII and valid UTF-16 paths are decoded to exact, case-sensitive UTF-8 names; traversal, nested directories, device syntax, separators and invalid Unicode are refused. Sparse extensions are zero filled; reads clamp at EOF without truncating 64-bit offsets. Read/write rights and read-only volumes are enforced. Guest buffers are checked across the entire requested range, including physical aliases of IPC storage, before file or guest output changes. Unsupported wire formats and commands fault visibly.

Writes change the buffered volume. Flush and commit succeed for a writable volume only when its platform persistence callback succeeds. A failed flush can leave dirty buffered data but does not claim a successful save. The original snapshot adapter uses explicit little-endian framing, bounded whole-input validation and a CRC32 corruption check. CRC32 provides no authenticity. It publishes through an exclusive sibling .pending file. The loader validates any existing primary, moves it to an absent .previous, then renames the stage into the vacant primary name. Successful publication removes the old backup. A failed publish attempts rollback; if rollback fails, both siblings remain available. This avoids relying on POSIX replacement semantics: WUT passes rename directly to FSA. A no-overwrite filesystem model checks this platform constraint and the publication/rollback paths.

If the primary is missing, a validated .previous can be loaded with the explicit RECOVERED status. A corrupt primary is never masked by a backup or reset. Stale .pending or ambiguous existing backup blocks another update visibly; preserve both for investigation. There is no concurrent-writer or power-loss durability guarantee. Failure/abrupt-process/rollback/recovery cases run under sanitizers.

The guest names its failing phase; the independent result TXT records guest messages, load status and persistence errno even if the full trace fails.

Read-only Level-3 RomFS assets are implemented separately; ordinary libctru handles nested names and directory listing in guest code. See romfs-assets.md. NCCH/IVFC preparation is a separate host stage. Generic FSUSER directories, rename/subfiles, extdata, secure values, retail save formatting and commercial integration remain unfinished.

Standalone APT

The platform explicitly enables one standalone application (APPID_APPLICATION, 0x300). Only APT:U is advertised; privileged APT:S/APT:A remain unavailable. GetLockHandle returns copied handles to a retained recursive mutex. Initialize returns two real retained events; Enable queues one empty wakeup and signals the resume event. NotifyToWait, GlanceParameter and ReceiveParameter run the checked local state machine. Glance preserves the pending parameter, Receive consumes it. Checked static descriptors support the shell-open query and enable-transition unlock. CheckNew3DS returns the configured Old-family policy. PrepareToClose / CloseApplication terminate the process; Finalize unregisters it while copied objects retain their lifetime. This lets the unmodified libctru event thread, LightEvent/arbitration waits, normal initialization and exit code actually run.

The original boot checks aptIsActive, aptShouldClose and aptMainLoop in guest code. A single-instruction trace captures successful APT and FS requests; remaining lookup failures are exactly APT:S, APT:A, and optional ptm:sysm. The compiled runner explicitly mounts an empty read-only diagnostic virtual SDMC. This is not access to the user's SD card. There is no shell lid, HOME menu, sleep, cross-title launch, shared font, applet, CPU-time quota or New 3DS implementation. Unsupported operations fail; complete_service_startup remains false in reports.

Validation

make -C tests/filesystem check sanitize
python3 tools/validate-libctru.py --application filesystem --wiiu --output artifacts/filesystem

The original compiled program exercises UTF-16 paths, newlib file I/O, explicit commit and fresh-process reload. Independent snapshot inspection and API tests cover corruption, interrupted publication, resource lifetime, bounds/aliases and failure preservation. Native diagnostics use only an original private save; these checks do not establish retail-save compatibility or power-loss durability. Preserve primary, .pending and .previous files when investigating a failure.


Maintained source: docs/filesystem.md ยท 3849a94bd97b. GitHub source and issue links may require repository access.