Many DOS game ZIPs include CloneCD backups (.ccd/.img/.sub) or
other disk images that are not needed for gameplay — the game data
is already extracted in the working directory. These can be 300MB+
and cause WASM memory exhaustion in js-dos.
Added a SKIP_EXT set that filters out known disk image extensions
during game file pass. The .jsdos config pass is unaffected.
js-dos reads ZIP entries sequentially and needs .jsdos/dosbox.conf and
.jsdos/jsdos.json first to configure DOSBox. Java's Files.walk() uses
filesystem readdir order which put them at the very end of the ZIP
(after the 328MB CloneCD image), causing extraction failures.
Now:
1. First pass: walk .jsdos/ directory and write config files first
2. Second pass: walk everything else (excluding .jsdos/) in sorted order
3. All directory ancestors are still added as explicit entries
4. .sorted() ensures deterministic, alphabetical entry order
Previously only the immediate parent directory was added as a ZIP
entry. For deeply nested paths like NAS50TH/TRACKS/WILKES/WILKES.DAT,
only NAS50TH/TRACKS/WILKES/ was created — but NAS50TH/TRACKS/ and
NAS50TH/ were skipped (they're only parents of other directories,
never the immediate parent of a file).
Emscripten's FS.createDirectory() fails if parent doesn't exist,
so intermediate directories must be in the ZIP too.
Now walks up the full ancestor chain with a while loop.
js-dos uses Emscripten's virtual filesystem which requires explicit
ZIP directory entries (names ending with '/') to create directories.
Java's ZipOutputStream only writes file entries, so subdirectories
like NAS50TH/TRACKS/WILKES/ were never created — causing Emscripten
'No such file or directory' errors for any path inside them.
- createBundle: before writing each file entry, add its parent
directory as an explicit ZIP entry if not already added
- Affects all games with subdirectory structures (NASCAR Racing 2,
Gus Goes to Cyberopolis, etc.)
- Use a HashSet to track already-added directories (no duplicates)
- Game.java: add 'platform' field ('dos'/'windows') + 'isPlayable' helper
- UploadResource.java: detect EXE type by reading PE/NE header during upload
- GameResource.java: allow PATCH to update platform
- GameCard.svelte: show 🪟 Win badge for Windows games (blue style)
- GameDetail.svelte: show platform badge, disable Play for Windows with
'Unplayable' button; add platform dropdown to edit form
- Play.svelte: show blocking overlay for Windows games with 'Try anyway'
option for users who want to attempt it
- Fix flattenSingleDir: flatten when exactly 1 root directory exists,
even if other metadata files (file_id.diz, *.nfo) are present at root
- Fix buildDosboxConf/buildJsdosJson: add 'cd <dir>' before executable
when it lives in a subdirectory, since DOS treats '/' as switch char
(previously 'mastori2/ORION2.EXE' was parsed as command 'mastori2'
with switch '/ORION2.EXE')
- Remove SockdriveResource.java entirely
- Remove bundleType field from Game.java (was 'standard'/'sockdrive')
- Simplify GameResource.java download endpoint
- Clean up upload resource
js-dos v8's window.Dos(container, { url }) expects the URL to point
to a .jsdos ZIP archive, not a loose dosbox.conf file. The
'sockdrive' mode was serving game files loosely with a URL pointing
to a raw config file, causing: 'Unable to add .jsdos/jsdos.json
into bundle.zip'.
Changes:
- Removed SOCKDRIVE_THRESHOLD_MB and the entire sockdrive branch
- Always create a .jsdos ZIP bundle via createBundle()
- Optimized createBundle: no temp dir copy (saves 2x I/O for large
games), uses STORE compression (level 0) since DOS files are
often already compressed
- Removed dirSizeMB(), copyDir() and setup config builders —
no longer needed after removing sockdrive mode
- frontend bundleUrl()/setupBundleUrl() always use the standard
/games/{id}.jsdos path
- GameService.delete cleanup simplified
The previous 200M limit on quarkus.http.limits.max-body-size and
multipart input-part.max-size rejected games over 200MB. DOS CD-ROM
games can easily exceed this (e.g. DOOM 95 full CD, Diablo). Raised
to 2048M to cover the largest DOS/CD-ROM game archives.
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.
Quarkus defaults to 10MB max body size. DOS games like
Warcraft 2 (50-100MB) were silently rejected. Added:
- quarkus.http.limits.max-body-size=200M
- quarkus.resteasy-reactive.multipart.input-part.max-size=200M
Completely new approach:
- After unzip, check if extractDir has exactly one entry (a directory)
- If so, move all its contents up and delete the empty folder
- This means game files are always at the bundle root (c:\)
- No more cd/subdirectory handling in config builders
- No more path separator conversions (forward/backslash)
- Removed splitPath() helper entirely
- Config builders are now trivial — relExe is always just a filename
Removed:
- injectDoomConfig() — WAD-detection and WASD config injection
- buildDoomDefaultCfg() — Doom config file generator
- All calls to injectDoomConfig in both bundle paths
Fixed:
- buildJsdosJson() now also uses splitPath() helper to handle
backslash separators (was using lastIndexOf('/') only)
- Extracted shared splitPath() method used by both
buildJsdosJson and buildDosboxConf
No more game-specific hacks. Just clean config generation.
extractDir.relativize() returns paths with native Linux forward
slashes. DOS requires backslashes. Changed .replace('\', '/')
to .replace('/', '\') and updated buildDosboxConf split logic
to also check for backslash separator.
When a ZIP contains game files in a subdirectory (e.g. doomiido/),
findMainExe() correctly finds the executable but the bundle autoexec
was using just the filename, causing DOSBox 'Illegal command' error.
- Use extractDir.relativize() to compute the relative exe path
- buildDosboxConf() now adds 'cd dirname' before running the exe
- buildJsdosJson() uses 'cd dirname && exe' for multi-line script
- Fix applies to both sockdrive and standard bundle paths
The dos.zone DEFAULT.CFG uses \r\n (DOS/CRLF) line endings.
Our injected config used \n (LF/Unix). While fgets() reads
both, the \r character stays in the buffer and the Doom
parser may behave differently when it's absent. Matching
the exact byte-for-byte format that works on dos.zone.
The missing piece — dos.zone's bundle has .jsdos/jsdos.json
which tells js-dos v8 how to configure the emulator (autoexec
command, autolock=true, etc.). Without it, js-dos may apply
different defaults that don't match our dosbox.conf.
Also simplified dosbox.conf to just the essential sections
and removed mapperfile=mapper-jsdos.map (not bundled).
Keeps usescancodes=true, autolock=true, SB16 audio.
Previous injectDoomConfig used Files.list() which only checks the
top-level directory — if the WAD was in a subdirectory, detection
failed silently and no config was written. Switched to Files.walk()
for recursive search.
Also:
- Write BOTH DEFAULT.CFG AND DOOM2.CFG (Doom II reads DOOM2.CFG
first as game-specific config, falling back to DEFAULT.CFG)
- Always overwrite (don't skip if file exists — the ZIP might
contain a stock DEFAULT.CFG with arrow-key defaults)
Root cause: Dosbox.conf was missing the [sdl] section with
'usescancodes=true' — without it DOSBox doesn't translate
keyboard scan codes and WASD keys don't register as game input.
Fix:
- Generate a complete dosbox.conf matching js-dos v8 standards
(all sections: sdl, dosbox, cpu, mixer, render, midi,
sblaster, gus, speaker, joystick, serial, dos, ipx, autoexec)
- Key setting: usescancodes=true + mapperfile=mapper-jsdos.map
- Uses svga_s3 machine type with SB16 audio (same as dos.zone)
Bonus: Auto-detect Doom-engine games (DOOM.WAD / DOOM2.WAD)
and inject a DEFAULT.CFG with WASD controls:
W=forward, S=backward, A=strafe-left, D=strafe-right
Arrows=turn, Ctrl=fire, Space=use, Alt=strafe, Shift=run
Mouse enabled for looking
Note: Existing uploaded games still have the old dosbox.conf.
They need to be re-uploaded to get the fix.
- Backend: Add GET /api/games/{id}/download endpoint (ZIP streaming)
- Frontend: All videos now shown in media row alongside screenshots
- Click any media thumbnail to load it into the media container
- Videos show YouTube thumbnail, screenshots show image preview
- Active thumbnail has green border highlight
- Download button between Edit and Delete triggers ZIP download
- Early return: if no media, the entire section is hidden
- Search now filters to DOS platform only (platform ID 13)
- New fetchVideos() queries IGDB game_videos endpoint for YouTube IDs
- New fetchScreenshots() queries IGDB screenshots endpoint for images
- autoScrape and applyIgdbId both fetch videos + screenshots after match
- Upload auto-save also triggers when videos/screenshots found
- GameDetail: embed first YouTube video + scrollable screenshot gallery
- Handles missing media gracefully (section hidden entirely if neither exists)
- New IgdbService: Twitch OAuth2, IGDB API v4 search, cover download
- Upload auto-scrapes IGDB after extracting game ZIP
- GameDetail page: 'Auto Scrape' + 'Search' buttons + result picker
- Upload dialog: optional title field (defaults to filename)
- Jackson SNAKE_CASE naming to match frontend expectations
- IGDB status endpoint to check credentials
- Replaced Go stdlib server with Quarkus (RESTEasy Reactive + Jackson)
- 7 Java classes: Game POJO, GameService (JSON file I/O), 4 REST resources, static file serving
- Same JSON-per-game storage, same ZIP upload + bundle creation
- Same Svelte frontend (unchanged), built into the JAR via maven-resources-plugin
- Multi-stage Dockerfile: Node → Maven → JRE runtime