webstation-broker
DeveloperPython referenceemulators

emulators.azahar

Azahar (Nintendo 3DS) launcher: qt-config.ini patching and SIGTERM shutdown.

Azahar (Nintendo 3DS) launcher: qt-config.ini patching and SIGTERM shutdown.

Azahar has no control API reachable from outside the process. Its only network surface is a UDP RPC server that reads and writes emulated memory, with nothing for state or shutdown, and its command line takes a ROM path and little else. So there is no mid-session save state here: persistence is the game's own save data, written to host files under the emulated SD card and NAND. Azahar installs no SIGTERM handler, so the stop is a hard kill and the dump takes whatever the game had already committed to disk.

The 3DS data root is XDG-derived ($XDG_DATA_HOME/azahar-emu), with config alongside it under $XDG_CONFIG_HOME. Azahar also has a portable mode that triggers on a user/ directory in the working directory, which is why the launch never chdir's anywhere it might find one.

Save data paths are stable across containers: the console and SD card ids Azahar files saves under are hardcoded to all-zeros rather than generated per install, so a save dumped in one session restores into the next.

Declared imports (import_spec, place_import) rewrite a donor's console and SD card ids to those two constants. See docs/content/docs/api/imports.mdx for the shapes.

attributelog
= logging.getLogger(__name__)
attributeROM_ROOT
= Path(os.environ.get('ROM_ROOT', '/romm'))

Library root a resolved ROM must live under (env ROM_ROOT, default /romm).

attributeUSER_DIR
= xdg_data_dir('azahar-emu')

Azahar's data root, which is also the save root.

Not configurable, and deliberately: nothing on Azahar's command line names this directory, so an override here would move only the half of the pair the broker dumps and restores. The emulator would keep writing saves where XDG puts them, and a session would restore into a tree nothing reads. launch exports the root this resolved to instead, which is an agreement the two cannot fall out of.

attributeCONFIG_DIR
= xdg_config_dir('azahar-emu')

Azahar's config directory, holding the qt-config.ini patched before each launch.

Not configurable, for the same reason as USER_DIR: the patch would land in a file Azahar never opens, leaving it parked on its first-run setup.

attributeCONFIG_PATH
= CONFIG_DIR / 'qt-config.ini'

The qt-config.ini patched before every launch.

attributeAZAHAR_LOG_PATH
= Path(os.environ.get('AZAHAR_LOG_PATH', '/config/azahar.log'))

The emulator log file (env AZAHAR_LOG_PATH, default /config/azahar.log).

attributeSYSTEM_ID
= '0' * 32

The console id Azahar files saves under.

Fixed all-zeros rather than generated per install, so these paths are the same in every container and a dumped save restores where the next session looks.

attributeSDCARD_ID
= '0' * 32

The SD card id Azahar files saves under, fixed all-zeros like SYSTEM_ID.

attributeSDMC_DIR
= USER_DIR / 'sdmc' / 'Nintendo 3DS' / SYSTEM_ID / SDCARD_ID

The emulated SD card's per-console directory.

attributeNAND_DATA_DIR
= USER_DIR / 'nand' / 'data' / SYSTEM_ID

The emulated NAND's per-console data directory.

attributeROM_EXTENSIONS
= ('.3ds', '.cci', '.zcci', '.cxi', '.zcxi', '.app', '.3dsx', '.z3dsx', '.elf', '.axf', '.bin')

Formats Azahar boots directly, best first; a folder holding several candidates picks by this order.

No .cia: that is an installable package rather than something the emulator boots.