Skip to content

[WIP] feat: playable Win3.x and Win9.x using js-dos - #4061

Draft
bphenriques wants to merge 1 commit into
rommapp:masterfrom
bphenriques:3143-jsdos-backend
Draft

[WIP] feat: playable Win3.x and Win9.x using js-dos#4061
bphenriques wants to merge 1 commit into
rommapp:masterfrom
bphenriques:3143-jsdos-backend

Conversation

@bphenriques

@bphenriques bphenriques commented Aug 1, 2026

Copy link
Copy Markdown

draft/WIP. I need to manually review the code, the PR description and the accompanying changes ot the docs.


Add a js-dos player for Windows 3.x / 9x (win3x, win9x)

Closes #3143

Related PR: rommapp/docs#110

Test Titles:

  • Fine Artist
  • Timon & Pumba Jungle Games

Summary

EmulatorJS (dosbox-pure) boots DOS games but blank-screens Windows 3.x titles:
its WASM build cannot render Windows' SVGA output. This adds js-dos v8 (DOSBox-X
backend) as a fourth in-browser player so win3x and win9x ROMs become
playable. DOS stays on EmulatorJS.

Validated locally with Fine Artist and four Magic School Bus titles (all Win16
GUI programs), which run under js-dos where EmulatorJS blank-screened.

What this adds

  • New v2 player view frontend/src/v2/views/Player/JsDos.vue, modeled on
    Ruffle.vue (pre-game config panel plus a running stage).
  • Route /rom/:id/jsdos (plugins/router.ts and v2/router/routes.ts).
  • Per-platform selection: isJsDosEmulationSupported (@/utils), canPlayJsDos
    (useCanPlay), and a dispatch branch in useGameActions that puts js-dos
    ahead of EmulatorJS for win3x and win9x.
  • Admin toggle DISABLE_JSDOS (env var plus heartbeat.EMULATION.DISABLE_JSDOS),
    mirroring DISABLE_EMULATOR_JS and DISABLE_RUFFLE_RS.
  • Runtime vendored in the Docker build (js-dos v8.4.1, sha256-pinned) under
    /assets/jsdos, served same-origin, mirroring how Ruffle vendors its runtime.
  • COOP/COEP: nginx adds the same cross-origin-isolation headers to /jsdos that
    /ejs already receives.
  • Content reuses getDownloadPath (the same bundle the download endpoint serves,
    no repackaging).

js-dos options set in v1: url, backend: "dosboxX", pathPrefix (the vendored
emulators, since js-dos otherwise fetches its wasm from a CDN that require-corp
blocks), autoStart: true and autoSave: true (matching EmulatorJS defaults,
EJS_startOnLoaded and auto-persisted saves), and setNoCloud(true) (a
self-hosted instance does not surface the dos.zone cloud). Every other js-dos
option uses its default.

Scope and non-goals

  • v1 owns win3x and win9x. DOS stays on EmulatorJS. Adding dos later is a
    one-line slug addition.
  • No per-game engine selection and no server-side conf generation.
  • Saves persist to browser OPFS (survive reload, single browser). RomM-synced
    saves (fsChanges to SaveSchema, so js-dos saves appear in RomM's save UI
    and are portable) are a tracked follow-up, matching the EmulatorJS save
    integration.
  • win9x uses identical player code; only the bundle carries a heavier system
    image, which is a bundling and docs concern, not code.

Test Plan

Automated

Frontend (from frontend/):

  • npm run generate then npm run typecheck: the heartbeat schema gained
    DISABLE_JSDOS, so the generated EmulationDict is regenerated and typechecks.
  • npm run test: new unit tests
    • isJsDosEmulationSupported (utils/index.test.ts): supports win3x and win9x,
      case-insensitive, rejects dos/flash/snes, respects the DISABLE_JSDOS toggle,
      honours a PLATFORMS_VERSIONS remap onto win3x.
    • js-dos dispatch (useGameActions/index.test.ts): a win3x/win9x game
      full-loads /rom/:id/jsdos ahead of EmulatorJS.
  • npm run build: passes.

Backend (from backend/):

  • uv run pytest tests/endpoints/test_heartbeat.py: DISABLE_JSDOS is present
    and boolean in the EMULATION payload.

Repo-wide:

  • trunk fmt && trunk check: clean (ESLint, Prettier, ruff, black, mypy, bandit).
  • No database migration is added, so no alembic step.

Manual (browser, uiVersion v2)

  • A win3x ROM launches into /rom/:id/jsdos (not EmulatorJS, not the
    "not migrated to v2" fallback) and boots, with DOSBox-X rendering Windows.
  • self.crossOriginIsolated === true on the player page (COOP/COEP applied).
  • No second Play prompt (autoStart).
  • An in-game save persists across a reload (autoSave). Note: some Windows apps
    only commit a save to disk when exited to Program Manager, so autoSave captures
    it after the app exits, not on the app's Save button. This is guest behavior,
    not a player bug.
  • Quit returns to the game page with no blank stage.
  • The js-dos (dos.zone) cloud UI is hidden.
  • With DISABLE_JSDOS=true, a win3x ROM no longer routes to js-dos.
  • DOS and every other platform are unchanged (still EmulatorJS, Ruffle, Stream).
  • Both themes (v2-dark, v2-light) on the pre-game panel, responsive to 320px.

Follow-ups

  • RomM-synced saves (fsChanges to SaveSchema).
  • Plain-zip support (dosboxConf plus initFs) and a CD folder-mount recipe
    (js-dos's DOSBox-X has no imgmount).
  • Companion docs PR to rommapp/docs: library layout (win3x vs dos), bundling
    a win3x game, and the save-on-exit caveat for apps like Fine Artist.

AI assistance disclosure

This contribution was developed with the assistance of Claude (Anthropic), an AI
coding assistant. I reviewed, tested, and validated the design, implementation,
and tests, and I take responsibility for the change.

@gantoine
gantoine self-requested a review August 1, 2026 23:10
@gantoine gantoine added the on-hold Pending further research or blocked by another issue label Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

on-hold Pending further research or blocked by another issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Introduce JS-DOS as a new DOS backend

2 participants