webstation-broker
DeveloperPython referenceemulatorsemulators.eden

emulators.eden.Eden

Nintendo Switch via Eden, driven by command line flags and a graceful SIGTERM.

Nintendo Switch via Eden, driven by command line flags and a graceful SIGTERM.

Eden has no external control API, so the broker patches qt-config.ini before every launch (close confirmation off) and boots fullscreen with -f -g. A patch that fails aborts the launch: the close confirmation is what the whole shutdown path rests on. SIGTERM goes through Eden's Qt event loop into a normal window close, so the stop is a graceful emulation teardown; SIGINT is never used because Eden maps it to _exit(1).

There are no save states: persistence is the game's own save data under the virtual NAND, and the archive carries it together with the profile store, since Switch save paths embed the profile UUID. At exit every file of a title whose save was written this session gets its mtime refreshed, the profile store with it, so the delta dump ships those saves whole while other titles' saves stay filtered out. A resume slot is logged and ignored.

Attributes

attributename
= 'eden'

Provider key, eden.

attributerom_cacheable
= True

On; with the ROM cache enabled, launch boots a local copy of the ROM.

attributedisplay_name
= 'Eden'

Human-readable name.

attributesave_root
= DATA_DIR

The data directory the save subtrees hang off.

attributesave_subtrees
= ('nand/user/save', 'nand/system/save/8000000000000010')

Game saves plus the profile store.

attributeclears_stale_saves
= True

On; activate empties both save subtrees.

attributerom_extensions
= ROM_EXTENSIONS

Bootable formats, best first.

attributelog_path
= EDEN_LOG_PATH

The emulator log file.

attributeterm_timeout
= float(os.environ.get('EDEN_STOP_WAIT', '15'))

SIGTERM grace before SIGKILL (env EDEN_STOP_WAIT, default 15).

attribute_session_start
= float('inf')

Unix time launch started Eden at; infinity until it does.

Infinity rather than zero so an instance that never launched matches no file at all. Zero is newer than nothing, so every title in the container would read as written this session and save_and_exit would restamp and ship all of them.

Functions

func__init__(self) -> None

Initialise the process handle and the session baseline.

paramself

Returns

None
funcclear_working_slot(self, excluded=()) -> None

Drop the previous session's saves and profile before the restore.

Eden has no save states and no fixed slot, so the whole clear is the save data (_clear_stale_save_data). It has to happen here rather than at exit: a session that crashes or is killed never reaches save_and_exit, and the exit restamp ships a save unit whole, so a leftover file in one would leave in the next player's archive.

paramself
paramexcludedtuple[str, ...]
= ()

Subtrees carried by the whole-card routes. Eden names no memory card subtree, so this is always empty.

Returns

None
funcimport_spec(self) -> imports.ImportSpec

Declare what Eden takes: NAND save exports and the profile store they resolve through.

Eden has no states and no cards, so the save kind is the only one.

paramself

Returns

webstation_broker.imports.ImportSpec

The spec.

funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]

Place one declared save file, or one of the profile store's.

paramself
parammemberimports.ImportMember

The member, already past the kind gate.

paramspecimports.ImportSpec

This emulator's spec.

paramctximports.ImportCtx

The launch context.

Returns

typing.Union

The placement, or a refusal.

funcvalidate_import_plan(self, plan, ctx) -> list[imports.ImportRefusal]

Hold an account save to its profile store, and the profile store to one per archive.

Save paths embed the profile's UUID, so an account save without the profile store that names that UUID would be readable by nothing. The store has to come in the same import: the archive's own is another player's, and is cleared with the rest before the restore. Two profile stores cannot both be Eden's, so an imported one beside the archive's is refused rather than merged.

A file that clashes with another member is left to the shared one-member-per-destination check, which has already refused it.

paramself
paramplanlist[imports.Placement]

The placements that passed every per-member check.

paramctximports.ImportCtx

The launch context; its archive_paths are the archive's ordinary members.

Returns

list

One refusal per account save that lacks its profile store, or per

funcidentity_source(self) -> Optional[imports.IdentitySource]

Take the session's game from RomM's title id, 16 hex digits.

Eden boots a file, not a path that names an id, so there is no rom reader.

paramself

Returns

typing.Optional

A hex16 source with no reader.

funcresolve_rom_file(self, path) -> Optional[Path]

The file Eden should boot for path.

paramself
parampathPath

A ROM file, or a folder searched up to two levels deep.

Returns

typing.Optional

The file itself, the best-ranked bootable file in the folder, or None.

funclaunch(self, rom_path, resume_slot) -> None

Patch qt-config.ini and boot the game fullscreen.

Raises

  • OSError: When qt-config.ini could not be patched, so nothing is spawned; see _patch_ini.
  • UnicodeDecodeError: Likewise, for an undecodable existing file.
paramself
paramrom_pathPath

The file to boot.

paramresume_slotOptional[int]

Ignored with a log line; Eden has no save states.

Returns

None
func_session_save_dirs(self) -> list[Path]

Title save directories holding a file written while the session ran.

A level that fails to list is logged and skipped rather than raised: the NAND can vanish under the walk, and the exit path calling this still has a process to stop and a report to hand back.

Only <save space id>/<user id>/<title id> leaves count. Anything whose leaf is not a title id is not a save unit, and handing an arbitrary directory to the restamp walk would ship whatever else the NAND keeps there.

paramself

Returns

list

The title directories touched since launch, or nothing at all when

funcsave_and_exit(self, slot) -> dict[str, Any]

Stop Eden and mark this session's saves and profile for the dump.

The dump ships files newer than the session baseline. A Switch save is a directory the game rewrites only partially, and the profile store the save paths are keyed by is not rewritten by a game session at all, so both are restamped: a restored archive then carries the whole save unit and the profile its path resolves through, instead of the handful of files the game happened to write.

paramself
paramslotOptional[int]

Ignored; Eden has no save states.

Returns

dict

state_saved, state_slot and state_file, all None.

On this page