Files
gen1recomp/docs/rfcs/0010-required-mod-imports.md
T
2026-08-14 20:34:14 -05:00

4.4 KiB

RFC 0010 — Manifest-declared user imports for mods

Status

Proposed. Engine: Manifest.lua, RequiredImports.lua, Loader.lua, LauncherMods.lua. Launcher: RomImporter.lua, LauncherView.lua. Native picker bridges: Android and iOS.

Motivation

Some mods derive presentation data from another cartridge the player owns. Shipping those bytes in a mod is not acceptable, while asking each mod to escape the sandbox, implement native pickers, and maintain platform-specific paths defeats the sandbox's purpose. Pokemon Stadium and Pokemon Stadium 2 are the first concrete consumers, but the ownership/import problem is generic.

The decision it extends

This extends the manifest as the engine-owned declaration of mod dependencies and preserves the sandbox rule in src/mods/Sandbox.lua: mods do not receive raw host filesystem access. It also extends the launcher's established ROM, save, and mod archive picker/inbox flows instead of introducing a second host integration.

Exact manifest delta

Additive required_imports and optional_imports arrays are accepted:

{
  "required_imports": [{
    "id": "stadium2",
    "name": "Pokemon Stadium 2 ROM",
    "file": "stadium2.z64",
    "format": "n64",
    "md5": ["00000000000000000000000000000000"]
  }]
}

Both arrays use the same object schema. A missing required_imports entry blocks the mod; a missing optional_imports entry only leaves that bonus functionality unavailable.

  • id is unique within the manifest and uses the mod-id vocabulary.
  • name is launcher-facing text.
  • file is one safe filename below the mod's baseroms/ directory.
  • md5 is one 32-digit hexadecimal digest or a non-empty array of accepted digests. MD5 is a known-dump identity convention, not a trust primitive.
  • format is raw by default. n64 recognizes a canonical big-endian dump, pair-byte-swapped and little-endian-word dumps, with or without a recognized 512-byte copier header. Validation and stored output use canonical big-endian bytes.

The launcher copies a validated file to mods/<manifest.id>/baseroms/<file>. Missing required imports make the launcher row need attention and make the loader refuse that enabled mod before its entry chunk runs. Missing optional imports remain selectable without changing load status. A matching validated import owned by another installed mod is copied automatically. Replace and remove remain explicit per-mod actions.

Desktop and UWP use their existing native/host picker routes. Android and iOS add a required_import picker kind which stages picked_required_import.bin. NX scans the engine-owned imports/baseroms/ MTP inbox. All writes continue through CacheFs, preserving portable-mode placement.

modkit validate and modkit pack report MK307 for every file beneath a source mod's baseroms/ directory, so the packaging path cannot accidentally distribute a file the launcher placed there. The installer also rejects a mod archive containing baseroms/ files, covering packages built without modkit.

Mod-facing API and sandbox statement

There is no new runtime API and no new permission. A mod reads its own copied file through the existing mod:read("baseroms/<file>") capability. It never learns the selected host path, cannot browse another mod's tree, and receives no raw io, love.filesystem, or platform-picker access.

Migration and compatibility

Existing manifests omit both import arrays and behave exactly as before. The fields are additive for both manifest API levels. Mods currently maintaining their own cross-platform picker can declare the source file and remove that host integration; their processing code changes only to read the declared baseroms/<file> path.

Parity tests

  • Empty/absent required_imports leaves existing manifests and loader behavior unchanged.
  • Manifest validation refuses path traversal, duplicate ids/files, malformed MD5 values, and unknown normalization formats.
  • N64 byte orders and the recognized copier-header form produce identical canonical bytes before MD5 validation.
  • A mismatched selection is never written.
  • An enabled mod with a missing declared file never executes its entry chunk.
  • A matching import in another installed mod is copied into the requesting mod's own baseroms/ directory.
  • The packaging gate refuses every baseroms/ file.
  • The launcher refuses an archive that contains baseroms/ files.

Deprecation etiquette

Nothing deprecated or removed.