feat: SETUP.EXE detection + separate setup .jsdos bundle
Build & Deploy / build-and-deploy (push) Successful in 1m1s

When a game archive contains SETUP.EXE, INSTALL.EXE, CONFIG.EXE or
CUSTOM.EXE (or .com/.bat variants), the upload now:

- Detects and stores the path in game metadata (setup_exe / has_setup)
- Generates a second .jsdos bundle with SETUP.EXE as autoexec:
  - Standard mode: {gameId}.setup.jsdos (full game + setup autoexec)
  - Sockdrive mode: .jsdos-setup/ directory alongside .jsdos/
- Both bundles share the same game files; only the autoexec differs

Frontend changes:
- Router strips query params from hash so ?setup=1 doesn't leak into id
- GameDetail shows a "Setup" button between Play and Edit (only when
  game.has_setup is true), navigating to /play/{id}?setup=1
- Play.svelte detects ?setup=1, loads setupBundleUrl() instead, and
  shows setup-specific overlay text

Persistence is handled automatically by js-dos v8 via the built-in
auto-save mechanism (ci.persist() → browser OPFS), so any config
changes made via SETUP.EXE survive across sessions.
This commit is contained in:
David Alvarez
2026-05-27 16:49:15 +02:00
parent 00267da3dd
commit 5f5cb8411f
7 changed files with 185 additions and 8 deletions
+16 -1
View File
@@ -1,5 +1,5 @@
<script>
import { getGame, deleteGame, updateGame, searchIGDB, scrapeIGDB, downloadGame, bundleUrl, artworkUrl } from "../lib/api.js";
import { getGame, deleteGame, updateGame, searchIGDB, scrapeIGDB, downloadGame, bundleUrl, setupBundleUrl, artworkUrl } from "../lib/api.js";
import { push } from "../lib/router.js";
let { id } = $props();
@@ -98,6 +98,10 @@
push(`/play/${id}`);
}
function handleSetup() {
push(`/play/${id}?setup=1`);
}
// ─── IGDB Scrape ───────────────────────────────────────────
async function handleSearchIGDB() {
@@ -205,6 +209,9 @@
{#if game.ready}
<button class="btn btn-primary" onclick={handlePlay}> Play</button>
{/if}
{#if game.has_setup}
<button class="btn btn-setup" onclick={handleSetup}>🛠 Setup</button>
{/if}
<button class="btn" onclick={startEdit}> Edit</button>
<button class="btn" onclick={handleDownload}> Download</button>
<button class="btn btn-danger" onclick={handleDelete}> Delete</button>
@@ -564,4 +571,12 @@
.edit-row { flex-direction: column; }
.edit-row input { width: 100% !important; }
}
.btn-setup {
border-color: var(--phosphor-dark);
color: var(--phosphor);
}
.btn-setup:hover {
background: var(--phosphor-burn);
box-shadow: 0 0 12px var(--phosphor-dark);
}
</style>
+21 -5
View File
@@ -1,9 +1,12 @@
<script>
import { getGame, bundleUrl } from "../lib/api.js";
import { getGame, bundleUrl, setupBundleUrl } from "../lib/api.js";
import { push } from "../lib/router.js";
let { id } = $props();
// Read query params from the hash — window.location is available after mount
let isSetup = $state(false);
let game = $state(null);
let loading = $state(true);
let error = $state("");
@@ -16,6 +19,8 @@
async function load() {
loading = true;
// Detect ?setup=1 from the hash-based URL
isSetup = window.location.hash.includes("setup=1");
try {
game = await getGame(id);
} catch (e) {
@@ -54,7 +59,7 @@
// Start the DOS emulator
try {
const url = bundleUrl(game);
const url = isSetup ? setupBundleUrl(game) : bundleUrl(game);
if (!url || !window.Dos) throw new Error("Failed to resolve game bundle");
dosCI = await window.Dos(dosContainer, { url });
running = true;
@@ -71,6 +76,10 @@
window.location.reload();
}
function goBack() {
push(`/game/${id}`);
}
$effect(() => { load(); });
// Auto-start when both game data and the container exist
@@ -98,7 +107,9 @@
<!-- Top bar: shown when game is loaded -->
<div class="play-header" class:active={game}>
{#if game}
<button class="link-button" onclick={stopEmulator}> {game.title}</button>
<button class="link-button" onclick={stopEmulator}>
{isSetup ? "Back" : game.title}
</button>
{#if running}
<button class="btn stop-btn" onclick={stopEmulator}> Stop</button>
{/if}
@@ -119,8 +130,13 @@
{:else if booting}
<div class="overlay-icon">⚙️</div>
<h2>{game?.title || ""}</h2>
<p>Starting emulator...</p>
<p class="save-hint">💾 Game saves are stored automatically</p>
{#if isSetup}
<p>Starting setup utility...</p>
<p class="save-hint">⚙️ Configure controls, then exit SETUP to save</p>
{:else}
<p>Starting emulator...</p>
<p class="save-hint">💾 Game saves are stored automatically</p>
{/if}
{:else if error}
<div class="overlay-icon">⚠️</div>
<h2>Failed to start</h2>