Before: data/games/{id}.jsdos (flat, alongside the game dir)
After: data/games/{id}/{id}.jsdos (inside the game directory)
Benefits:
- Delete is atomic — removing the game dir removes everything
- Cleaner data directory structure
- No orphan .jsdos files on delete
Also updated frontend bundleUrl() to use game.bundle_file from
the backend, making it path-agnostic. Old flat layout is still
served via StaticResource for backward compat.
- Add streamSetupBundle() to GameService — reads main bundle as zip,
replaces config files (.jsdos/dosbox.conf, .jsdos/jsdos.json) with
setup variants pointing to SETUP.EXE, streams directly to response
- Add GET /api/games/{id}/setup-bundle endpoint that calls the above
- Remove .setup.jsdos disk creation during upload — setupExe is still
detected and stored in game metadata, but no duplicate bundle is saved
- Update frontend api.js to point setupBundleUrl() to the new endpoint
- Fix pre-existing backslash escaping bug in UploadResource.java path
normalization (replace('\', '/') instead of replace('\\', '/'))
Two issues fixed:
1. Frontend closure bug: IGDB result buttons used Svelte {#each} closure
with onclick={() => doUpload(result.name)}. When clicking the 3rd
result, the 1st result's handler sometimes fired. Fixed by reading
data-name from e.currentTarget.dataset instead of JS closure.
2. Backend scrape mismatch: when user selected 'Blood' from IGDB
results, backend's autoScrape searched for 'Blood' and could match
'Captain Blood' first (alphabetical), overwriting metadata.
Now passes igdb_id along with title so backend uses the EXACT
IGDB entry the user selected via applyIgdbId() instead of
auto-searching.
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
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.
- 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
- 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
- .jsdos bundles saved flat in data/games/ so /games/{id}.jsdos serves them
- bundleUrl() uses game.id (matches JSON) instead of game.slug (undefined)
- Delete handler also removes flat .jsdos bundle on game deletion