emulators.xemu
xemu launcher (original Xbox): FATX-level save sync on a raw HDD image.
xemu launcher (original Xbox): FATX-level save sync on a raw HDD image.
xemu keeps game saves inside the one hard-disk image its xemu.toml points at, and QEMU exposes that image only as an opaque block device, so the previous design shipped the entire qcow2 as the save artifact. This version syncs at the filesystem level instead: the image is kept in raw format so pyfatx (userspace libfatx bindings: no FUSE, no NBD, container-safe) can read and write the FATX E partition directly, and the save archive carries only the launched title's save data.
How a session moves saves in and out of the image:
- image: the first launch after deploy finds the configured qcow2 and
converts it in place with qemu-img (same filename, raw content, sparse on
disk), keeping the original alongside as
<name>.backup. A raw image cannot hold QEMU internal snapshots, so the save-state interface is gone; save data is the whole artifact now. - launch: when the activate restored an archive, its files are written into E:/UDATA and E:/TDATA before xemu boots (pre-launch hook).
- exit: once xemu has stopped and flushed, the launched title's UDATA and TDATA trees are extracted from the image into the staging dir (post-close hook), where the standard dump zips them.
- title: the title id (the UDATA directory name) is read from the disc's
default.xbe certificate. If the disc cannot be parsed, nothing is cleared
or extracted that session (
_save_roots): scoping to every title on the disk would archive, and later restore, the whole drive's save data. - imports: a declared import (
import_spec,place_import) is filed in the staging directory under the session's title id, and the launch injects it like any restored archive. A disc whose title id cannot be read takes no import, since that session clears and extracts nothing. See docs/content/docs/api/imports.mdx.
attributelog= logging.getLogger(__name__)attributeXEMU_BIN= os.environ.get('XEMU_BIN', '/opt/xemu/AppRun')The xemu executable to spawn (env XEMU_BIN, default /opt/xemu/AppRun).
attributeXEMU_LOG_PATH= Path(os.environ.get('XEMU_LOG_PATH', '/config/xemu.log'))The emulator log file (env XEMU_LOG_PATH, default /config/xemu.log).
attributeXEMU_STRAY_TERM_WAIT= float(os.environ.get('XEMU_STRAY_TERM_WAIT', '5'))Grace a stray xemu gets to exit before SIGKILL (env XEMU_STRAY_TERM_WAIT, default 5).
QEMU flushes the HDD image on a clean shutdown and not on SIGKILL, and the save hooks read that image seconds later, so a stray is asked to leave first.
attributeXEMU_RENDERER= os.environ.get('XEMU_RENDERER', 'OPENGL').strip().upper()Renderer pinned into xemu.toml before each launch (env XEMU_RENDERER, default OPENGL).
Vulkan aborts xemu on the AMD Renoir/RADV stack these containers run on, and
the choice persists in xemu.toml, so one session spent switching renderers
leaves every later launch broken. Pinned before each launch; set XEMU_RENDERER
to VULKAN where the driver is known good, or to KEEP to leave the file alone.
attributeXEMU_TOML= Path(os.environ.get('XEMU_TOML', str(_default_toml_path())))The xemu.toml the HDD path is read from and display settings are pinned into (env XEMU_TOML).
Defaults to the SDL pref dir location _default_toml_path computes.
Safe to move, because the launch states it: -config_path names this file on
xemu's own command line. Without that the knob would move only the file the
broker reads the HDD path out of and pins the display settings into, while
xemu kept loading the one in its pref dir.
attributeFALLBACK_HDD_IMAGE= Path(os.environ.get('XEMU_HDD_IMAGE', '/config/xemu/xbox_hdd.qcow2'))HDD image assumed when xemu.toml cannot say (env XEMU_HDD_IMAGE).
Defaults to /config/xemu/xbox_hdd.qcow2, and is used only when xemu.toml
cannot tell us: a fresh container where xemu has never run, or a config with
no usable hdd_path.
attributeSAVE_STAGING_DIRNAME= 'saves'Name of the staging directory next to the HDD image.
The generic dump/restore reads and writes host files here, and the launch/exit hooks move them in and out of the FATX filesystem.
attributeQCOW2_MAGIC= b'QFI\xfb'The four-byte header that marks a qcow2 image; its absence means raw content.
attributeFATX_NAME_LIMIT= 42The longest file or folder name FATX stores, in bytes.
attributeROM_EXTENSIONS= ('.iso',)Bootable disc formats: only XISO, always named .iso, including the .xiso.iso double extension.
attributeXISO_MAX_DIR_BYTES= 8 * 1024 * 1024Ceiling on one XISO directory table read, in bytes.
The root table's size is a 32-bit field taken straight from an untrusted disc image, so a corrupt or hostile ISO can ask for a 4 GiB allocation. A real Xbox root table is a few kilobytes; megabytes of it is already past anything a disc carries.