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= TrueOn; with the ROM cache enabled, launch boots a local copy of the ROM.
attributedisplay_name= 'Eden'Human-readable name.
attributesave_root= DATA_DIRThe 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= TrueOn; activate empties both save subtrees.
attributerom_extensions= ROM_EXTENSIONSBootable formats, best first.
attributelog_path= EDEN_LOG_PATHThe 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) -> NoneInitialise the process handle and the session baseline.
paramselfReturns
Nonefuncclear_working_slot(self, excluded=()) -> NoneDrop 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.
paramselfparamexcludedtuple[str, ...]= ()Subtrees carried by the whole-card routes. Eden names no memory card subtree, so this is always empty.
Returns
Nonefuncimport_spec(self) -> imports.ImportSpecDeclare 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.
paramselfReturns
webstation_broker.imports.ImportSpecThe spec.
funcplace_import(self, member, spec, ctx) -> Union[imports.Placement, imports.ImportRefusal]Place one declared save file, or one of the profile store's.
paramselfparammemberimports.ImportMemberThe member, already past the kind gate.
paramspecimports.ImportSpecThis emulator's spec.
paramctximports.ImportCtxThe launch context.
Returns
typing.UnionThe 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.
paramselfparamplanlist[imports.Placement]The placements that passed every per-member check.
paramctximports.ImportCtxThe launch context; its archive_paths are the archive's ordinary members.
Returns
listOne 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.
paramselfReturns
typing.OptionalA hex16 source with no reader.
funcresolve_rom_file(self, path) -> Optional[Path]The file Eden should boot for path.
paramselfparampathPathA ROM file, or a folder searched up to two levels deep.
Returns
typing.OptionalThe file itself, the best-ranked bootable file in the folder, or None.
funclaunch(self, rom_path, resume_slot) -> NonePatch 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.
paramselfparamrom_pathPathThe file to boot.
paramresume_slotOptional[int]Ignored with a log line; Eden has no save states.
Returns
Nonefunc_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.
paramselfReturns
listThe 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.
paramselfparamslotOptional[int]Ignored; Eden has no save states.
Returns
dictstate_saved, state_slot and state_file, all None.