fix: remove .jsdos/ from bundles to avoid js-dos bundleUpdateConfig
Build & Deploy / build-and-deploy (push) Successful in 43s

js-dos v8.3.8 (loaded from CDN) calls bundleUpdateConfig to re-add
.jsdos/jsdos.json into the downloaded bundle. This fails on our
bundles because the WASM libzip _zipfile_add returns non-zero.

Since we can't fix js-dos's internal WASM code, we remove the .jsdos/
directory from bundles entirely. This means bundleConfig() returns null,
no editor config is set, and bundleUpdateConfig is never called.

Downside: games start with js-dos's default dosbox.conf (no custom
CD mounts or autoexec). The game files are on C:\ and the user can
type the executable name. Added autoStart + countDownStart=0 to
auto-skip the config UI.

The config API endpoint (/api/games/{id}/config) is kept for future
use where we can inject config after the emulator starts.
This commit is contained in:
Hermes Agent
2026-06-03 19:00:49 +02:00
parent 6e64793a2d
commit 79a0af86ad
2 changed files with 5 additions and 58 deletions
+4 -22
View File
@@ -1,5 +1,5 @@
<script> <script>
import { getGame, getGameConfig, bundleUrl, setupBundleUrl } from "../lib/api.js"; import { getGame, bundleUrl, setupBundleUrl } from "../lib/api.js";
import { push } from "../lib/router.js"; import { push } from "../lib/router.js";
let { id } = $props(); let { id } = $props();
@@ -68,27 +68,9 @@
// Start the DOS emulator // Start the DOS emulator
try { try {
if (isSetup) { const url = isSetup ? setupBundleUrl(game) : bundleUrl(game);
// Setup mode: use the streaming setup-bundle endpoint if (!url || !window.Dos) throw new Error("Failed to resolve game bundle");
const url = setupBundleUrl(game); dosCI = await window.Dos(dosContainer, { url, autoStart: true, countDownStart: 0 });
if (!url || !window.Dos) throw new Error("Failed to resolve game bundle");
dosCI = await window.Dos(dosContainer, { url });
} else {
// Normal mode: fetch config + bundle separately, use dosboxConf + initFs
// This bypasses js-dos's bundleUpdateConfig which fails on our bundles
const [config, bundleResp] = await Promise.all([
getGameConfig(game.id),
fetch(bundleUrl(game)),
]);
if (!bundleResp.ok) throw new Error("Failed to download game bundle");
const bundle = new Uint8Array(await bundleResp.arrayBuffer());
const jsdosConf = config.jsdos_conf ? JSON.parse(config.jsdos_conf) : undefined;
dosCI = await window.Dos(dosContainer, {
dosboxConf: config.dosbox_conf,
jsdosConf,
initFs: bundle,
});
}
running = true; running = true;
} catch (e) { } catch (e) {
error = e.message; error = e.message;
@@ -512,19 +512,6 @@ public class UploadResource {
); );
private void createBundle(Path extractDir, String exePath, List<String> cdImages, Path bundlePath) throws IOException { private void createBundle(Path extractDir, String exePath, List<String> cdImages, Path bundlePath) throws IOException {
// Write .jsdos config directly into the extract directory
Path jsdos = extractDir.resolve(".jsdos");
Files.createDirectories(jsdos);
if (exePath != null) {
String relExe = extractDir.relativize(Path.of(exePath)).toString();
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe, cdImages));
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
} else {
// CD-only game — no executable, mount CD and show a DOS prompt
Files.writeString(jsdos.resolve("dosbox.conf"), buildCdOnlyDosboxConf(cdImages));
Files.write(jsdos.resolve("jsdos.json"), buildCdOnlyJsdosJson());
}
// ZIP it up directly from extractDir (no temp copy) // ZIP it up directly from extractDir (no temp copy)
try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) { try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) {
@@ -550,26 +537,9 @@ public class UploadResource {
zos.closeEntry(); zos.closeEntry();
} }
// ── Phase 3: .jsdos config files (must come before game files) ── // ── Phase 3: All game files ──
try (var walk = Files.walk(jsdos)) {
walk.filter(Files::isRegularFile)
.sorted()
.forEach(f -> {
try {
String entryName = extractDir.relativize(f).toString().replace('\\', '/');
zos.putNextEntry(new java.util.zip.ZipEntry(entryName));
Files.copy(f, zos);
zos.closeEntry();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
// ── Phase 4: All other game files ──
try (var walk = Files.walk(extractDir)) { try (var walk = Files.walk(extractDir)) {
walk.filter(Files::isRegularFile) walk.filter(Files::isRegularFile)
.filter(f -> !f.startsWith(jsdos))
.filter(f -> { .filter(f -> {
String n = f.getFileName().toString().toLowerCase(); String n = f.getFileName().toString().toLowerCase();
int dot = n.lastIndexOf('.'); int dot = n.lastIndexOf('.');
@@ -588,11 +558,6 @@ public class UploadResource {
}); });
} }
} }
// Clean up .jsdos config files from the extract dir
Files.walk(jsdos).sorted(Comparator.reverseOrder()).forEach(p -> {
try { Files.deleteIfExists(p); } catch (IOException ignored) {}
});
} }
// ─── Configuration Helpers ────────────────────────────────── // ─── Configuration Helpers ──────────────────────────────────