imports
Declared imports: save data RomM names, placed by each emulator's own rules.
Declared imports: save data RomM names, placed by each emulator's own rules.
A restored dump puts every member back at its own path. An import cannot
work that way: a player's save from another emulator, a card from real
hardware or an EmulatorJS export has no path this broker wrote. So RomM
declares what each import member is (a save, a state or a memory card),
under the .import/<kind>/ prefix and in a version 2 manifest, and the
emulator either places it or refuses it with one of REASONS.
Everything here runs in preflight, before the working slot is cleared, so a
refusal always leaves the player's slot as it was. The emulator side is four
hooks on Emulator (import_spec, place_import, validate_import_plan
and identity_source); this module holds the shared machinery they lean on.
attributelog= logging.getLogger(__name__)attributeImportKind= Literal['save', 'state', 'memcard']What RomM declares an import member to be.
attributeOrigin= Literal['emulatorjs', 'standalone', 'hardware', 'unknown']Where RomM says a member came from. Advisory: placement never reads it.
attributeStateChannel= Literal['archive', 'push', 'none']How an emulator takes states: in the archive, pushed after activate, or not at all.
attributeIdFamily= Literal['ps_serial_dashed', 'ps2_card_dir', 'ps_serial_nodash', 'hex8', 'xbox', 'gc_wii_disc', 'hex16', 'wiiu_title', 'dc_product', 'scummvm_target']The game-id notations NORMALISERS can bring to one canonical form.
attributeREASONSfrozenset[str]= frozenset({'manifest_invalid', 'unsafe_path', 'unreadable_member', 'kind_not_accepted', 'state_uses_push', 'resume_slot_required', 'memcard_synced_separately', 'source_incompatible', 'needs_conversion', 'unrecognised_layout', 'shape_unverified', 'incomplete_unit', 'destination_unresolvable', 'identity_unknown', 'identity_mismatch', 'protected_destination', 'destination_conflict', 'too_large'})Every refusal code. The set is closed: RomM branches on these.
attributeREFUSAL_CAP= 200Most refusals one 422 lists; the rest are counted in truncated.
attributeHEAD_MAX_BYTES= 64 * 1024Most bytes ImportMember.head reads, however many are asked for.
attributeREADABLE_EXPECTED= 'an unencrypted, intact member, stored or compressed with deflate'The expected text on every unreadable_member refusal.
attributeLIBRETRO_STATE_RE= re.compile('^.+\\.state(\\d+|\\.auto)$', re.I | re.ASCII)A RetroArch numbered or auto state name (.state3, .state.auto), which no standalone loads.
A bare .state is not matched: flycast takes that name as its own.
attributeOWNER_MARKER_SUFFIX= '.rom'What flycast and duckstation append to a resume state's name to name its owner marker.
attributeNORMALISERSdict[str, Callable[[str], Optional[str]]]= {'ps_serial_dashed': _ps_serial_dashed, 'ps2_card_dir': lambda raw: raw.strip().upper() or None, 'ps_serial_nodash': _ps_serial_nodash, 'hex8': _hex8, 'xbox': _xbox, 'gc_wii_disc': _gc_wii_disc, 'hex16': _hex16, 'wiiu_title': _wiiu_title, 'dc_product': _dc_product, 'scummvm_target': lambda raw: raw.strip().casefold() or None}One normaliser per IdFamily: each brings every notation of an id to one form.
attributeIdentityPolicy= Literal['none', 'advisory', 'strict', 'required']How hard a hook holds a member to the session's id.
attributePSP_SAVEDATA_DIR= re.compile('[A-Z]{4}[0-9]{5}[A-Za-z0-9_\\-]{0,23}', re.ASCII)A PSP SAVEDATA folder name: a product code, then up to 23 characters the game picks.