webstation-broker
REST API

Declared imports

Handing the broker a save, state or memory card it did not write, and what each refusal means.

What an import is

A save archive the broker wrote is restored path for path. A save from somewhere else (another emulator, real hardware, an EmulatorJS export) has no path the broker knows. So RomM declares it instead: the member goes under .import/<kind>/ in the archive, and the manifest says what it is.

{
  "version": 2,
  "created_at": 1789000000,
  "files": [
    { "path": ".import/save/Game.srm", "kind": "save", "origin": "standalone" }
  ]
}

kind is save, state or memcard, and it must match the path's second segment. origin is emulatorjs, standalone, hardware or unknown. It is advisory: it sharpens a refusal but never changes where a member lands.

Every import member is checked before the working slot is cleared. If any member is refused, nothing is written and activate answers 422:

{
  "detail": {
    "error": "import_refused",
    "refusals": [
      {
        "reason": "kind_not_accepted",
        "member": ".import/save/Game.srm",
        "expected": "no imports",
        "detail": null,
        "suggest_emulator": null,
        "docs": "/docs/api/imports#kind-not-accepted"
      }
    ],
    "truncated": 0
  }
}

At most 200 refusals are listed; truncated counts the rest. A crash while checking imports answers 500 with {"detail": {"error": "import_preflight_failed"}}, still before anything is cleared.

Fourteen emulators accept imports so far. RetroArch, Flycast, DuckStation, PPSSPP, Dolphin, PCSX2, RPCS3, ScummVM, Cemu, Azahar, Eden, shadPS4, xemu and Xenia take the kinds listed under What each emulator takes, and every other emulator refuses each import member with kind_not_accepted. The structural checks run first either way: a bad manifest answers manifest_invalid and a bad path answers unsafe_path. When the session syncs the memory card on its own routes, a member that would land on that card answers memcard_synced_separately. If the card is the emulator's whole save tree, the archive is not restored at all and every import member answers memcard_synced_separately. Emulators opt in one at a time.

The refusals are sorted by member, then by reason; an archive-level refusal has member: null and sorts first. In an archive that holds imports, a regular member that would have failed the restore is listed here too, as unsafe_path, unrecognised_layout or unreadable_member, with the old error text as its detail. An archive without imports keeps its old save restore failed: <message> answer.

Activate's response lists each placed import under save_restore.imported, as {member, dest, sidecars}. The exit dump then ships every placed file even if the game never touched it, and lists those paths under the manifest's imported key, so an import is never lost to the "only what changed" rule.

What RomM can tell the broker

Three optional rom fields on activate help an emulator place an import:

FieldPurpose
title_idRomM's game id for the rom: a serial, title id or game code.
save_targetRomM's name for where this game keeps its saves, such as a PS2 card folder.
save_target_layoutHow save_target names that place: folder-exact, folder-prefix, file-exact, file-prefix or folder-split.

A save_target_layout the broker does not know is logged and passed through; it never fails the launch.

Asking what an emulator takes

GET /streaming/api/session/import-spec?emulator=pcsx2&platform=ps2

This answers import_api, manifest_version, the emulator and platform it was asked about, the accepted kinds (each with its shapes, requires_resume_slot and max_members), state_channel, card_subtree, state_slot, the value to send as save.resume_slot (null when the emulator takes no states, neither mid-session nor in the archive) and the full list of reasons. It needs X-Broker-Secret like every other RomM-facing route and answers 403 without it. An unrecognised emulator answers 422 unknown emulator: <name>.

A 404 from this route means the broker predates imports. Never send .import/ members to that broker: it cannot place them, and they would never reach the emulator.

Each emulator answers with the kinds it takes on the platform it was asked about; What each emulator takes lists them. A kind with an empty shapes list is refused on that platform, and the refusal says why. An emulator that takes nothing answers kinds: [] and state_channel: "none".

For an emulator author, the answer comes from Emulator.import_spec(). It takes no arguments and reads the platform from self.platform; the default accepts nothing. The registry tests hold every spec to the same rules. A declared state kind means state_channel: "archive", at most one state counting the archive's own, and requires_resume_slot. A push channel needs mid-session save states. The tests also place one example member of each kind, on one platform per emulator, and check that it lands inside the save tree. For every import, that rule is enforced when the plan is checked at activate: each file an import writes, sidecars such as an owner marker included, must land in a save subtree and not resolve out of the save root through a symlink, or the member is refused.

What each emulator takes

emulatorsavememcardstate
retroarchone .srm, on a platform whose core loads one, or one .sav where the core reads it as its .srm; on wii, title folders, title/<type>/<title id>/data/; on 3ds, folders inside Azahar's sdmc/Nintendo 3DS/ or nand/data/not taken; expected names savePUT /api/session/state-file after activate
flycastraw 131072-byte vmu_save_<A-D><1-2>.bin images, and dc_nvmem.bina raw 131072-byte VMU (Dreamcast memory card) imageone .state; needs save.resume_slot
duckstationa raw 131072-byte .mcd, .mcr, .mc or .srm cardthe same as saveone <serial>_resume.sav or <serial>_<n>.sav; needs save.resume_slot
ppssppPSP save folders, SAVEDATA/<product code><suffix>/, each holding PARAM.SFOnot takenone <game id>_<version>_<n>.ppst, with its .jpg screenshot if sent; needs save.resume_slot
dolphin on ngcGameCube .gci saves, each in <region>/Card A/the same as saveone <game id>.sNN that opens with the game's id; needs save.resume_slot
dolphin on wiiWii title saves, title/<type>/<title id>/data/not takenone <game id>.sNN that opens with the game's id; needs save.resume_slot
pcsx2one folder memory card: <card>/_pcsx2_superblock and its game foldersthe same as savePUT /api/session/state-file after activate
rpcs3PS3 save folders, home/00000001/savedata/<SAVEDIR>/ and game/<DIR>/, each holding PARAM.SFOnot takenone .SAVESTAT, .SAVESTAT.zst or .SAVESTAT.gz; needs save.resume_slot
scummvm<game>.NNN and <game>.sNN save filesnot takenone <game>.sNN or <game>.NNN file; needs save.resume_slot
cemuWii U save data for the game being launched: usr/save/00050000/<title>/not takennot taken
edenSwitch NAND save folders, nand/user/save/<space>/<user>/<title>/, with the profile store an account save needsnot takennot taken
azahar3DS save data from an unencrypted SD card or NAND folder: title/ and extdata/ foldersnot takennot taken
xemuthe launched game's UDATA/<title id>/ and TDATA/<title id>/ foldersnot takennot taken
xeniaXbox 360 save folders, content/<XUID>/<title id>/00000001/, with their Headers/ filenot takennot taken
shadps4PS4 save folders, <serial>/<save dir>/not takennot taken

Dolphin takes nothing on any other platform, and Desktop takes nothing yet.

RetroArch renames the .srm after the game being launched and places it in saves/<library_name>/, where the core loads it. library_name is the name the core reports about itself, such as mGBA or Genesis Plus GX, not the manifest's core (see RetroArch cores). On a platform that takes a .srm, it takes one per archive: a second one, or a save the archive already carries at the same path, is a destination_conflict. On the 15 of those platforms whose core reads a .sav as its .srm, a .sav is placed as the .srm, and discovery lists <name>.sav there (RetroArch cores lists them). A .sav and a .srm in the same archive would both become that one file, so both get destination_conflict. wii and 3ds take folders instead, described below the list. It refuses these:

  • On psp: destination_unresolvable, with suggest_emulator: "ppsspp". The PPSSPP core keeps SAVEDATA folders, not a .srm.
  • On ngc: shape_unverified. The Dolphin core keeps GameCube saves as GCI files in its own folders, which imports don't place yet. Import through dolphin instead; this refusal names no suggest_emulator.
  • A .srm on wii or 3ds: unrecognised_layout, whatever its name. The detail names the kind of folder that platform takes.
  • On any other platform whose core loads no .srm, such as dc or arcade: destination_unresolvable. The core keeps its save in a file of its own, or has none. Discovery lists no save shapes there, and RetroArch cores lists the 25 platforms that take a .srm and where each other core keeps its save.
  • On a platform RetroArch has no core for: destination_unresolvable.
  • On a platform that takes a .srm, a .rtc, .nv, .eep or .mpk file, and a .sav where the core does not read one as its .srm: needs_conversion, until each core's name for that file has been checked. A platform that takes no .srm gives its own refusal above first. On wii and 3ds a file with one of those names is judged as part of a save folder, so it gets the folder refusals instead.
  • A memcard member: kind_not_accepted, with expected naming save. On a platform that takes a .srm, send the core's .srm as a save instead.
  • A state member: state_uses_push. On jaguar, whose core has no states, and on a platform RetroArch has no core for, it is kind_not_accepted.

On wii, the Dolphin core keeps the same emulated NAND as standalone Dolphin, and a save is a game's title folder, under save:

  • Its path is title/<type>/<title id>/data/..., where the type is 00010000, 00010001 or 00010004. It can sit under Wii/, under saves/dolphin-emu/User/Wii/ or directly under .import/save/. It lands in saves/dolphin-emu/User/Wii/title/, with the title id lower-cased.
  • The title id must be RomM's title_id, or it is identity_mismatch. RetroArch reads no id off the rom, so with no title_id the title is taken on trust.
  • A loose data.bin is needs_conversion and a nand.bin is source_incompatible, as under Dolphin. title/00000001/ and a title's content/ folder are protected_destination. sys/ and ticket/ are unrecognised_layout here, since the core's save folders leave them out, and so is anything else, a .gci included.

On 3ds, the Azahar core's SD card and NAND are placed exactly as named, and nothing is renamed:

  • A member must already sit under saves/Azahar/Azahar/, and then inside sdmc/Nintendo 3DS/<id0>/<id1>/title/<high>/<low>/..., sdmc/Nintendo 3DS/<id0>/<id1>/extdata/<high>/<low>/... or nand/data/<id0>/.... Every id0 and id1 is 32 hexadecimal digits. (The Azahar core nests its own Azahar/ folder under the saves/Azahar/ dir RetroArch already sorted it into.)
  • A title's id, its high and low halves together, must be RomM's title_id, or it is identity_mismatch. Extdata and NAND data carry no title id and are not held to one, and with no title_id a title is taken on trust.
  • nand/data/<id0>/sysdata/ and a title's content/ folder are protected_destination.
  • Anything else, including a member that does not already sit under saves/Azahar/Azahar/, is shape_unverified. Azahar's layout has not been checked against a running core beyond these paths, so the broker refuses rather than guess.

Flycast places every file loose in its data folder, flycast/, under the name Flycast reads:

  • A VMU save keeps its name, with the port letter upper-cased. dc_nvmem.bin, the console's system flash, must not be empty.
  • A memcard member goes in the VMU socket its name picks, such as vmu_save_C2.bin, or else in A1.
  • A RetroArch .srm is source_incompatible, because it isn't a VMU image. A .vms, .vmi, .dci or .dcm export is needs_conversion.
  • The state is renamed after the game being launched, <rom>.state, and gets the owner marker that lets the broker resume it.

DuckStation keeps memory card 1 as one shared card (see DuckStation):

  • A card of either kind becomes memcards/shared_card_1.mcd, so an archive takes one card. A headered card (.gme, .psm, .ps, .ddf, .mem, .vgs or .psx) is needs_conversion.
  • A card in an archive that already carries another card under memcards/ is a destination_conflict. Once the shared card exists, the other card is never mounted again.
  • The state is renamed <serial>_resume.sav and gets the owner marker that lets the broker resume it. The serial is RomM's title_id when it sends one, else the serial in the member's name, else the name's own base. A member named for a different serial than RomM's is identity_mismatch.

PPSSPP places PSP save folders in its SAVEDATA folder, where PSP games look for them, and one state in its working slot:

  • A save folder is named for the game's product code (four capital letters and five digits) and a suffix the game picks, such as ULUS10041DATA00. It can sit under PSP/SAVEDATA/, under SAVEDATA/ or directly under .import/save/, and a copy of a whole memory stick, with one folder of its own above PSP/, works too. A folder whose name holds no product code is unrecognised_layout.
  • Every save folder needs its PARAM.SFO, the file PPSSPP lists a save by. A folder without one is incomplete_unit, detail missing PARAM.SFO.
  • A save folder's product code is only logged when it differs from RomM's title_id: a sequel can read the saves of the game before it.
  • A lone file, with no save folder around it, is unrecognised_layout, since a PSP save is a folder, and a lone RetroArch .srm is source_incompatible. Anything under PSP/SYSTEM/ is protected_destination. Any other folder under PSP/, such as PSP/GAME/, is unrecognised_layout, and so is a PPSSPP_STATE/ folder sent as a save, with a detail saying to declare it as a state.
  • The state keeps the game id and version in its name, <game id>_<version>_<n>.ppst; only the slot number is rewritten. A .jpg screenshot of the same name rides along, is renamed with it and does not count as a second state. Either can sit under PSP/PPSSPP_STATE/, under PPSSPP_STATE/ or directly under .import/state/.
  • The state's game id must be RomM's title_id, when RomM sends one, or it is identity_mismatch. An id that is not a product code, as a homebrew game's often is, is taken on trust.
  • A screenshot sent without its state is incomplete_unit, and so is an empty state. A RetroArch state is source_incompatible. A memcard member is kind_not_accepted: a PSP has no memory card.

Dolphin takes what the console being launched keeps. On ngc, a GameCube save is a .gci file, under save or memcard alike:

  • Its path must end in <region>/Card A/<name>.gci, where the region is USA, EUR or JAP. It can sit under GC/, under saves/dolphin-emu/User/GC/ or directly under .import/<kind>/. It lands in the same folder under GC/, the Slot A card. Dolphin shows a game only the folder for its own region, so a save sent to another region's folder is placed but never seen.
  • A GCI's game code is only logged when it differs from the game being launched: a game can read another game's saves.
  • A .gci anywhere else, loose or under Card B/ say, is unrecognised_layout. So is one whose size is not a 64-byte header followed by whole 8 KiB blocks, and one named .GCI in capitals. A .gcs or .sav export is unrecognised_layout too. A whole card image, .raw, .mcd or .gcp, is needs_conversion: export each save from it as a .gci.
  • An archive that already carries a card under GC/ refuses an imported GCI as destination_conflict: one GameCube card per archive.

On wii, a save is a game's title folder from the NAND, under save:

  • Its path is title/<type>/<title id>/data/..., where the type is 00010000, 00010001 or 00010004. It can sit under Wii/, under saves/dolphin-emu/User/Wii/ or directly under .import/save/. It lands in Wii/title/, with the title id lower-cased as Dolphin writes it.
  • The title id must be the game being launched, or it is identity_mismatch. When the broker has no id for the game being launched, the title is taken on trust.
  • A loose data.bin, outside a title's data/ folder, is needs_conversion: it is the export a Wii writes to an SD card, so import it with Dolphin's Import Wii Save and send the title folder that makes. A nand.bin is source_incompatible: send the title folder from the dump instead. sys/, ticket/, title/00000001/ and a title's content/ folder are protected_destination. Anything else, a .gci included, is unrecognised_layout. A memcard member is kind_not_accepted.

On either console, the state is one .sNN file, directly under .import/state/ or under StateSaves/:

  • It must open with a game id, or it is unrecognised_layout, and that id must be the game being launched, or it is identity_mismatch. The game being launched is the id Dolphin's rom holds, read off an .iso, .gcm or .wbfs image, else RomM's title_id. With neither, the id is taken on trust. With the rom's id in hand, the state is held to all six characters, maker code included, since the broker would otherwise refuse to resume it at boot.
  • It is renamed after the id it opens with, in the broker's slot: Dolphin finds a state by the running disc's id, not by the file's name.
  • lastState.sav, Dolphin's undo buffer, is protected_destination. An empty state is incomplete_unit, and a RetroArch state is source_incompatible.

PCSX2 takes one folder memory card and makes it the Slot 1 card, memcards/<card>/, where <card> is PCSX2_SLOT1_CARD (romm-slot1 by default):

  • The card is one folder holding _pcsx2_superblock and the card's game folders, under save or memcard alike. It can sit under memcards/ or directly under .import/<kind>/. The folder's own name is replaced by the Slot 1 card's.
  • One card per archive. Two card folders are a destination_conflict whose detail names both, and so is a card in an archive that already carries a card under memcards/.
  • A card without _pcsx2_superblock is shape_unverified: PCSX2 does not mount a folder without one, and the files may be one game's folder from a card rather than a whole card. A card whose _pcsx2_superblock is empty is incomplete_unit.
  • A single file is no folder card. A card image or a single-save export (.ps2, .mc2, .mcd, .mcr, .bin, .max, .psu, .cbs, .xps or .sps) is needs_conversion, and any other file is unrecognised_layout. Either way, send the folder card instead.
  • A card holds every game's saves, so no member is held to the game being launched.
  • A state member is state_uses_push. Send it with the state-file PUT after activate, which refuses a state named for another game's serial.

RPCS3 places PS3 save folders in its dev_hdd0 and one state in its savestates folder:

  • A save folder holds PARAM.SFO at its root. Save data goes to home/00000001/savedata/<SAVEDIR>/ and can arrive under dev_hdd0/home/<user>/savedata/, home/<user>/savedata/, PS3/SAVEDATA/ or SAVEDATA/. The user is eight digits and is rewritten to 00000001, the only user the broker's layout has, so two users' folders of one name are a destination_conflict. Game data goes to game/<DIR>/ and can arrive under dev_hdd0/game/ or game/.
  • A folder with none of those around it is unrecognised_layout: RPCS3 keeps save data and game data apart, and a bare folder does not say which it is. So is a folder name of anything but letters, digits, _ and -, up to 32 of them, a single file where a folder belongs, and a state sent as a save.
  • A folder without PARAM.SFO at its root is incomplete_unit, detail missing PARAM.SFO. One further down does not count.
  • A folder named for another game's serial is placed and only logged: a sequel or DLC writes its predecessor's folders.
  • home/<user>/exdata/ and home/<user>/trophy/, a game's USRDIR/EBOOT.*, RPCS3's lock folders under game/ (a name starting with $ or its full-width form) and the game folder of the installed title being booted are protected_destination. An installed title is an EBOOT.BIN under the game folder, or a .pkg.
  • The state is one .SAVESTAT, .SAVESTAT.zst or .SAVESTAT.gz, in exactly that case, alone or in one folder named for the game's serial. It is renamed <serial>_import.SAVESTAT and filed under the serial the launch will look in. The serial is the one the boot target holds; RomM's title_id is not used. A boot target with none, a bare .iso, refuses a state as identity_unknown, and a folder named for another serial is identity_mismatch. An empty state is incomplete_unit, and a RetroArch state is source_incompatible.

ScummVM names every save after a target, the name scummvm --add registers a game folder under in scummvm.ini, and keeps saves and states in one folder:

  • A save is one file, <game>.NNN or <game>.sNN, directly under .import/save/ or under saves/. Its <game> must be a name of the game in the rom folder, in any case: a target, the game id or the engine id. It lands as saves/<name>.<ext> with the name as scummvm.ini spells it. A save for another target of the same folder, such as another language, keeps that target's name, and only shows in a session that boots that target.
  • A name that is none of the folder's is identity_mismatch, listing the ones that are. A folder ScummVM detects no game in is identity_unknown. A generic SAVEGAME name, a .srm, a .state and scummvm.ini are unrecognised_layout.
  • A save in the working slot under the session target's own name is a destination_conflict, whose detail says to declare it as a state. The same slot under another name is an ordinary save.
  • The state is one <game>.sNN or <game>.NNN file naming any target of the folder. It is renamed onto the session's target in the working slot, keeping its spelling. It counts every save the archive already carries in that slot under the session target's name, so leave the old one out.
  • The first import into a folder ScummVM has not seen registers it, which runs scummvm --add and can take up to two minutes. The registration stays even if the import is then refused.

Cemu, Eden, Azahar, xemu, Xenia and shadPS4 take saves and nothing else. A state or memcard member is kind_not_accepted, since none of them has either. A save that lands on a file the archive already holds is a destination_conflict. The import does not merge with the archive's copy, so leave that copy out to send the new one.

Cemu keeps one account, 80000001, and files every save under it:

  • A save is usr/save/00050000/<title>/, with user/<account>/, user/common/ and meta/ inside. The tree can sit under mlc01/usr/save/, storage_mlc/usr/save/, usr/save/ or save/. A save collected as one 16-digit Saviine folder name is split. A donor console's account folder, user/<8 hex digits> starting with 8, becomes user/80000001. Two different donor accounts in one archive are a destination_conflict for every file that names one, while user/common/ and meta/ files are not held to it.
  • The title folder is lower case, as Cemu writes it, and must be the game being launched: RomM's title_id is read as the title's low half, such as 1010EC00, or as the whole sixteen-digit id, such as 000500001010EC00. Another game's save is identity_mismatch. A path that names its own title is still placed when RomM sends no title_id.
  • A member that names no title, such as user/80000001/slot0.dat, is placed under the launched game's when RomM names one, and is identity_unknown when it does not. A sixteen-digit id of any other title type, such as an update's 0005000E..., names no save folder, so it is read as no id.
  • Anything under usr/save/ whose first folder is not a title id, the account store system included, is protected_destination. A title type other than 00050000 is unrecognised_layout. So is a user/<x> folder that is neither common nor an account id of that shape, an account folder with no file in it, a loose file, and a 00050000/... path with none of the wrappers above.

Eden takes verbatim NAND exports:

  • A save is nand/user/save/<space>/<user>/<title>/<files>. The ids are upper-cased, as Eden writes them. When RomM sends a title_id, the title must be the game being launched, or it is identity_mismatch.
  • A device save, whose user is 32 zeros, stands alone. An account save needs the profile store in the same import, nand/system/save/8000000000000010/, or it is incomplete_unit. The check is that the import holds at least one file under that folder; it does not verify that the store is complete.
  • An archive that already carries a profile store refuses the imported one as destination_conflict, and its own store does not stand in for the import's. So an account save can only be imported into an archive with no profile store yet.
  • A bis/ folder, a Nintendo/ or sdmc/ folder and a .bin file that fits no shape and is too large to be a save (a raw dump) are source_incompatible. A path that starts with a title id, a JKSV folder and a Checkpoint folder name no save space or profile Eden could find, so they are destination_unresolvable. Anything else, a loose file and the rest of the NAND included, is unrecognised_layout.

Azahar takes the folders of a 3DS SD card or NAND that has not been encrypted, and rewrites the console and card ids to the ones it uses:

  • Saves are title/<high>/<low>/... and extdata/<high>/<low>/... under sdmc/Nintendo 3DS/<id0>/<id1>/, and extdata/<high>/<low>/... under nand/data/<id0>/. The ids are 32 hex digits. The SD card's folders can sit under saves/Azahar/Azahar/ or Nintendo 3DS/, or be the two ids alone. The NAND's can sit under saves/Azahar/Azahar/ or be bare. (The doubled form is where the RetroArch build of this same core actually writes, and is what an archive dumped through RetroArch now carries. A single saves/Azahar/ with no second Azahar/ is never what the core itself produces, so it is left unrecognised rather than guessed at.) The <high> and <low> folders are lower-cased.
  • A title/ save is held to the game being launched whenever RomM names it, and another game's is identity_mismatch. The game is named by save_target, the title's <high>/<low> pair, or, when there is none, by title_id, which is that same pair written as one sixteen-digit id. With neither the title is taken on trust. An extdata folder is not checked, since its id is not always the game's.
  • An SD card whose ids are not zeros and which has a dbs/ or backups/ folder beside its title/ is a console's, and encrypted: every member under that card's ids, the folder's own files included, is source_incompatible. A donor card with no such folder is taken, and its ids rewritten. Two cards' copies of one title are a destination_conflict, since they land in one folder.
  • A JKSM or Checkpoint backup folder is shape_unverified. A file under sysdata is protected_destination. A title/ or extdata/ tree with no console and card folders above it is unrecognised_layout.
  • A loose file, or a bare data/ folder, at the top of the archive names no title. It is destination_unresolvable when RomM sends a title_id or a save_target, and unrecognised_layout otherwise. The broker never places a member from save_target alone.

xemu files a save under its own title id, which must be the session's, and the launch then writes it into the hard drive image:

  • A save is UDATA/<title id>/<files> or TDATA/<title id>/<files>, at the top of the member's path. The folder names can be in either case, and land upper-cased. The session's title id comes from the disc's own certificate, and from nowhere else: another game's folder is identity_mismatch, and a disc whose certificate cannot be read is identity_unknown even when RomM sends a title_id.
  • The disc's title id is also what the launch clears stale saves under and what the exit dumps, and it does neither without one. A save taken on RomM's word alone would reach the shared hard drive image, outlive every later session and never leave in a dump, so it is refused instead.
  • A folder above UDATA or TDATA, such as a drive letter, a partition folder or saves/, is unrecognised_layout. A whole hard drive image, named .qcow2, .img, .raw, .vhd, .vhdx or .vmdk or opening with the qcow2 header, is source_incompatible. A file under a title folder is a save whatever its name.
  • FATX stores names of 42 bytes at most, so a longer one is unsafe_path. Names compare without regard to case: Save.dat and save.dat are one file, so they are a destination_conflict, and so is a save that differs from the archive's copy only in case.

Xenia files a save under the profile that is signed in, which the broker reads from logged_profile_slot_0_xuid in Xenia's config, xenia-edge.config.toml under its storage root:

  • The key is found in any table. Its value is 16 hex digits, with or without 0x, in either case, and the folder is named for it in upper case. A missing file, a missing or blank key, a value that is not a XUID, or two different XUIDs is destination_unresolvable: create a profile on the desktop first.
  • A save is content/<XUID>/<title id>/00000001/<save>/..., with its header file at <title id>/Headers/00000001/<save>. The tree can sit under content/, Content/ or nothing, and the XUID folder can be left out. The donor's XUID is replaced with the signed-in profile's.
  • The title is the one in the member's path, and must be the game being launched. The launched game is named by the rom's own folder, when it is laid out as <title id>/000D0000/<package> or <title id>/00007000/<package>, or by RomM's title_id. With neither it is identity_unknown, and another game's title is identity_mismatch.
  • A profile package, FFFE07D1, is protected_destination, whether or not a profile is signed in. Any other content type, DLC and updates included, is unrecognised_layout, and so is a file where a save folder belongs. A save that is one STFS package file rather than a folder is shape_unverified. A RetroArch .srm is source_incompatible.

shadPS4 files a save under the default user, whichever user it was collected from:

  • A save is <serial>/<save dir>/<files>, optionally under savedata/ or home/<n>/savedata/, where <n> is a number. The serial, such as CUSA12345, is four letters and five digits in either case, and is upper-cased. Everything below it is kept as sent.
  • The serial must be the launched game's when the session names one. The broker reads TITLE_ID out of the rom's own sce_sys/param.sfo, the file shadPS4 itself names a save folder for, and falls back to RomM's title_id. Another game's serial is identity_mismatch, since the exit dump ships the whole savedata subtree and would carry it into this rom's archive. A .pkg, a .zar or an archive is still packed when the import is checked, so a launch of one is compared against RomM's title_id alone; a launch with neither takes a save without comparing it.
  • The param.sfo is only read when it is a regular file resolving inside the ROM library root, so a library that carries a symlink out of the tree, or a named pipe, names no serial rather than reading a host file.
  • A save that names no serial, or one of the wrong length, is identity_unknown. sce_sys/corrupted, the marker shadPS4 leaves while a save is mounted, is protected_destination. config.json, extracted/ and sys_modules/, another folder under home/<n>/, and a home/<name>/ whose name is not a number are unrecognised_layout. So are a loose file and a serial with no save folder and file below it. A flat .srm is source_incompatible.
  • Two users' copies of one save are a destination_conflict, since they land in the same folder. A name too long for the filesystem is unsafe_path.

Flycast, DuckStation and RPCS3 have no mid-session save states, but each boots straight into a state the archive carries, the same way it resumes the one it writes on exit. PPSSPP, Dolphin and ScummVM do have them, and resume a state the archive carries the way they resume one pushed after activate. So all six answer state_channel: "archive", take at most one state counting any the archive already holds, and need discovery's state_slot sent as save.resume_slot. An archive that already holds a state of its own refuses an imported one as destination_conflict, so leave the old state out to send a new one. An undo buffer is not a state and does not count: PPSSPP's *.undo.ppst and load_undo.ppst, or Dolphin's lastState.sav. PCSX2 answers state_channel: "push", and so does RetroArch on a platform whose core has states.

Flycast and DuckStation name no card_subtree: their cards are ordinary save files, restored with the rest of the archive. PPSSPP has no memory card and names none, and neither do Dolphin on wii, RPCS3, ScummVM or RetroArch. Dolphin on ngc names GC, and PCSX2 names memcards. When the session syncs that card on the memory-card routes, a member that would land on it answers memcard_synced_separately. Otherwise an imported card, or a GCI, in an archive that already carries a card there is a destination_conflict.

Refusal reasons

manifest_invalid

The archive holds .import/ members but no usable version 2 manifest, or a member is undeclared, declared twice, declared but absent from the archive, declared with a kind other than save, state or memcard, or declared as a kind that does not match its path.

unsafe_path

A path component is empty, hidden, .., __MACOSX or too long, or the name has a backslash, colon, control character or bidirectional control character, or it is non-ASCII without the zip UTF-8 flag. Also used when the archive holds the same name twice, and when the destination would resolve outside the save root through a symlink.

unreadable_member

The broker cannot read the member: it is encrypted, it uses a compression method other than stored or deflate, or its data is corrupt (it fails its CRC check or will not decompress). Bzip2 and lzma members are refused outright rather than decompressed: zipfile's reader for both has no cap on a single call's output, so a small compressed member can expand to an unbounded amount of memory before anything checks a size limit. A regular member outside .import/ whose timestamp is invalid is refused with this code too. The check runs on every member the restore will write, before the working slot is cleared, so a corrupt member never costs the player the save they already had.

A member can also be refused this way once the checks read into it: an emulator that reads a member to place it, as Dolphin reads the id a state or a GCI opens with, or to check the archive's imports as a whole, refuses one whose data turns out to be corrupt, with the detail the member's data is corrupt.

kind_not_accepted

This emulator does not take this kind. expected lists the kinds it does.

state_uses_push

This emulator takes states through PUT /api/session/state-file after activate, not inside the archive.

resume_slot_required

The state only loads when save.resume_slot is set on the activate.

memcard_synced_separately

The memory card travels on the memory-card routes this session, so it cannot also come in through the archive.

source_incompatible

The member is in a format this emulator cannot read, such as a RetroArch state given to a standalone emulator. suggest_emulator names one that can, when there is one.

needs_conversion

The data is right but in a container this emulator does not read directly.

unrecognised_layout

The member's path does not match any shape this emulator accepts. When the member's origin is emulatorjs or hardware, the refusal becomes source_incompatible instead, with a note on the origin after the detail.

shape_unverified

The shape is recognised, but loading it is unproven or blocked by a known broker limit, so the broker refuses rather than place a file that silently won't load.

A PCSX2 folder card without _pcsx2_superblock is refused this way. So are an Xbox 360 save that is one STFS package file, a 3DS save manager's JKSM or Checkpoint backup folder, a RetroArch GameCube save, and a RetroArch 3DS member that is not in one of Azahar's save folders.

incomplete_unit

A save that spans several files is missing one, or a file is empty: a PSP or PS3 save folder without its PARAM.SFO, a PPSSPP screenshot sent without its .ppst, a PCSX2 card whose _pcsx2_superblock is empty, or an Eden account save sent without a profile store. When other members were also refused, a missing-file detail says this may be a knock-on effect of those refusals.

destination_unresolvable

The emulator cannot work out where this member belongs for this rom. On Eden, a save collected by JKSV or Checkpoint, or as a folder that starts with a title id, has no save space or profile in its path. On Azahar, a loose file or bare data/ folder names no title, when RomM knows the game. On Xenia, the config names no single signed-in profile.

identity_unknown

The emulator needs to know which game this is, and neither the rom nor RomM says. ScummVM refuses a save this way when it detects no game in the rom folder, and RPCS3 a state when the boot target carries no title id. For Xenia, neither the rom nor RomM names the title; for xemu, the disc's own certificate does not, whatever RomM sends; for Cemu, neither does when the member's path names no title; for shadPS4, the member's path names no serial.

identity_mismatch

The member belongs to a different game than the one being launched. When RomM supplied the session's id, the detail says how to correct it. Cemu, Eden, Azahar, shadPS4, xemu and Xenia check a save's title this way; Cemu, Eden, Azahar and shadPS4 check only when the session names a game at all, which for shadPS4 means the rom's own param.sfo or RomM's title_id.

protected_destination

The member would overwrite emulator configuration, not save data, or data the console owns. Examples are Cemu's account store, Xenia's profile package, 3DS sysdata, the mount marker shadPS4 leaves in a save, an RPCS3 licence, trophy or EBOOT, and a Wii system title.

destination_conflict

Two members land on the same file, a member lands where a restored save already goes, or there are more members of a kind than the emulator allows. Cemu refuses an archive that holds saves for two donor accounts this way, and Eden refuses an imported profile store when the archive already carries one.

too_large

The archive, imports included, is over the size or file-count limit.

On this page