webstation-broker
DeveloperPython referenceemulatorsemulators.cemu

emulators.cemu.Cemu

Wii U via Cemu, driven by command line flags and config file patching.

Wii U via Cemu, driven by command line flags and config file patching.

Cemu has no control API, so the broker pins settings.xml before every launch (no update check, no Discord presence, no boot sound, no menubar), seeds the player-0 GamePad profile once, and boots the game fullscreen with -f -m -g. Cemu installs no signal handler, so the stop is a hard SIGTERM kill; that is safe because the game's own save data is already on disk the moment the game writes it.

There are no save states: persistence is the title save tree under usr/save in the virtual MLC, which is what the archive carries. At exit every file in the title save dirs written during the session gets its mtime refreshed so the delta dump ships those saves whole while other titles' saves stay filtered out. A resume slot is logged and ignored.

Attributes

attributename
= 'cemu'

Provider key, cemu.

attributerom_cacheable
= True

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

attributedisplay_name
= 'Cemu'

Human-readable name.

attributeclears_stale_saves
= True

On; activate empties the title save tree.

attributesave_root
= MLC_DIR

The MLC directory the save subtrees hang off.

attributesave_subtrees
= ('usr/save',)

usr/save, the title save tree.

attributerom_extensions
= ROM_EXTENSIONS

Bootable formats, best first.

attributelog_path
= CEMU_LOG_PATH

The emulator log file.

attributeterm_timeout
= float(os.environ.get('CEMU_STOP_WAIT', '5'))

SIGTERM grace before SIGKILL (env CEMU_STOP_WAIT, default 5).

attribute_session_start
= float('inf')

Unix time launch started Cemu 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

Empty the title save tree before the archive restore.

Cemu keys a save by title id and by an account id the container shares across players, so the previous session's saves sit exactly where this one's belong. The restore only writes the members the incoming archive carries, and the exit restamp ships a title whole once anything under it is written, so a title the last player saved and this one's archive does not name would be readable here and would leave again in this player's dump.

The account store under usr/save/system stays: it is what the save paths are keyed by, not one player's data, and Cemu only writes it on a first boot, so clearing it every activate would drop the session onto account setup instead of the game.

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

Subtrees carried by the whole-card routes. Cemu has no memory card, so this is always empty.

Returns

None
funcprepare_restore(self) -> None

Stop a running Cemu so the archive can be extracted under it.

paramself

Returns

None
funcimport_spec(self) -> imports.ImportSpec

Declare what Cemu takes: title save trees, and nothing else.

Cemu 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 under the session's title.

More than one donor account in the archive is refused, for every file that names one: they would all land on Cemu's single account.

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.

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

Take the session's game from RomM's title id, in either Wii U spelling.

RomM writes a Wii U title id as the low half on its own (101C9400) or as the whole sixteen-digit id (00050000101C9400), the same two spellings a member's path may carry, and both name the one save folder. Cemu boots titles by file, not by a path that names an id, so there is no rom reader.

paramself

Returns

typing.Optional

A hex8 source with no reader, reading RomM's id as a wiiu_title.

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

The file Cemu should boot for path.

paramself
parampathPath

A ROM file, or a folder searched up to three 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 settings, seed the pad profile and boot the game fullscreen.

Raises

  • RuntimeError: When settings.xml cannot be patched, which would leave Cemu parked on its Getting Started modal.
paramself
paramrom_pathPath

The file to boot.

paramresume_slotOptional[int]

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

Returns

None
func_modified_title_saves(self) -> list[Path]

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

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

Only <8 hex>/<8 hex> pairs count. usr/save also holds the system tree, and both halves feed the restamp walk, so a name that is not a title id half is not a title save dir.

paramself

Returns

list

The usr/save/<titleHigh>/<titleLow> directories touched since

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

Stop Cemu and mark this session's title saves for the dump.

paramself
paramslotint

Ignored; there are no save states.

Returns

dict

state_saved, state_slot and state_file, all None.

On this page