- docker-compose.yml: changed from build: . to image: droideparanoico/dostalgia
- README: simplified quick start to pull instead of build
- README: added link to Docker Hub repo with CI note
Triggered on release published, builds and pushes to
droideparanoico/dostalgia on Docker Hub with tags:
- :latest (on default branch releases)
- :<version> (e.g. :1.2.3)
- :<major>.<minor> (e.g. :1.2)
Two bugs:
1. PlatformDetector buffer was 512 bytes — too small to read the NE
header at offset 0x0E00 in Blood's SETUP.EXE. Increased to 4096
bytes (0x1000), covering headers up to ~4KB.
2. findSetup() had no platform awareness — it picked SETUP.EXE
(Windows 3.x NE executable) over SETMAIN.EXE (actual DOS4GW
setup). Added platform detection to the sort, preferring DOS
executables over Windows ones.
Also added "setmain" to pattern list so Build engine games'
DOS setup utilities are detected.
When a game has both SETUP.BAT and SETUP.EXE, the sort comparator
was picking SETUP.BAT (smaller file size) over SETUP.EXE. Batch
files often contain 'win SETUP.EXE' to launch Windows-first — which
fails in DOSBox since WIN.COM isn't available.
Now .exe and .com files are sorted before .bat files at the same
directory depth, so the actual DOS setup utility is preferred.
- Add .env.example with Twitch credential instructions
- Update docker-compose.yml to use env_file instead of inline comments
- Add IGDB Metadata section to README with setup walkthrough
- Add .env / .env.local to .gitignore to prevent secret leaks
quarkus.resteasy-reactive.multipart.input-part.max-size was renamed
to quarkus.rest.multipart.input-part.max-size in Quarkus 3.x, but the
global quarkus.http.limits.max-body-size=2048M already caps upload
sizes so the property is redundant.
- GameService.load(): null-check bundleFile before resolving path
(Path.resolve(null) throws NullPointerException)
- ZipServiceTest: pass absolute exePath to createBundle (relativize
requires both paths to be absolute or both relative)
- Add exec-maven-plugin to pom.xml with 3 npm executions:
* npm-install (initialize phase)
* frontend-build (generate-resources phase)
* frontend-test (test phase)
- Add skipFrontend and skipFrontendTests Maven properties
- Dockerfile now runs npm test in frontend stage
- Dockerfile removes -DskipTests and passes -DskipFrontend=true
to the Maven stage (frontend already built in its own stage)
- Updated package declarations and cross-references in all 20 Java files
- Updated groupId in pom.xml
- Moved src/main/java/com/dostalgia/ → src/main/java/org/dostalgia/
- Moved src/test/java/com/dostalgia/ → src/test/java/org/dostalgia/
- Added FileUtils.findFilesByExtensions(dir, extensions) — generic
walk-and-collect utility for matching files by extension
- ExecutableDetector.findAll() now delegates to it, dropping
13 lines of boilerplate
- Created FileUtils.deleteDirectory() for recursive directory deletion
- GameService.delete() and UploadResource.deleteDir() now delegate to it
- Removed stale BasicFileAttributes import from GameService
PlatformDetector.detect() and GameService.detectPlatformInBundle()
both read only 0x80 bytes initially, but PE e_lfanew often points to
0x80 (128), putting the signature 2 bytes past the buffer. Added
fallback: re-read up to peOffset+2 bytes when needed.
Fixes PlatformDetectorTest detect_peHeader_returnsWindows and
detect_neHeader_returnsWindows assertions.
The sort order is: installer → platform → size → depth.
Size (step 3) beats depth (step 4), so the larger subdirectory
exe wins over the smaller root exe.
ExecutableDetector uses @Inject for PlatformDetector, but tests
instantiate it directly via 'new' — CDI doesn't run. Manually set
the package-private 'platform' field in an instance initializer.
Root cause: the separator check used '\\\\'.equals(sep) comparing a
2-char Java string literal to a 1-char substring result — always false.
This caused backslash paths (C:\FALLOUT1\MASTER.DAT) to never be split
into components, so stale prefixes were never detected or rewritten.
Fix: use char comparison (sep == '\\') which correctly identifies the
separator type. Affected both findReplacement and existsIgnoreCase.
This was a pre-existing bug from the original UploadResource code that
was carried over during the refactor. Re-uploading Fallout after this
fix will correctly patch FALLOUT.CFG paths.
Fallout needs 64MB of XMS memory to load its ~500MB of data files.
DOSBox defaults to 16MB, causing a crash during loading after the
loading screen appears.
Also fixes a pre-existing bug in GameService.buildDosboxConfBytes()
and UploadResource.buildCdOnlyDosboxConf() where \n was used instead
of actual newline escapes, producing config files with literal 'backslash-n'
characters instead of real line breaks.
Cleans up all leftover code from previous fix attempts (fix-paths endpoint,
hardcoded CONFIG_PATH_FIXES map, etc.).
js-dos v8 uses the jsdos.json autoexec.script instead of the
dosbox.conf [autoexec] section. Without mount/imgmount in the jsdos.json
script, the CD image was never mounted — games loaded base data from C:
(bundle root) but crashed when trying to access CD-ROM content (videos,
audio).
Also removes the now-unnecessary fix-paths endpoint (both endpoint and
GameService implementation) since config path fixing is automatic during
upload via the game-agnostic scanner.
Iterable<Path> cast on dirStream::iterator method reference fails
in some Java environments. Stream.toList() (Java 16+) is cleaner
and guaranteed to compile.
Removes hardcoded CONFIG_PATH_FIXES map with Fallout-specific entries.
Replaces with generic scanner that:
- Scans all small text files (<100KB) for X:\... patterns
- Only processes C: drive paths (D:/E:/ left alone as CD-ROM refs)
- Uses case-insensitive filesystem checks to find what exists after flattening
- Progressively tries shorter path suffixes to find the stale prefix
- Rewrites C:\STALEDIR\ → .\ only when the leaf file/dir is found at root
- Works with both \ and / path separators
Also refactors the GameService fixBundleConfigPaths to build a ZIP entry
index and use the same logic for existing bundle patching.
The runner container can't access the compose file path on the host.
Use the compose file's parameters (network: dockernet, volume:
/mnt/cache/appdata/dostalgia) directly in docker run instead.
The Gitea Actions runner is a container; it can't see host paths.
The dostalgia compose dir is mounted inside the runner at /unraid/dostalgia,
so that's what the workflow must reference.
Uses docker compose -f /appdata/dockhand/stacks/unraid/dostalgia/compose.yaml
so the container matches the user's Unraid stack configuration (correct volume
mount at /mnt/cache/appdata/dostalgia, proper network, labels, etc.)
- Add both uppercase C: and lowercase c: variants to CONFIG_PATH_FIXES
- Add fixBundleConfigPaths() to GameService to patch existing bundles
- Add POST /api/games/{id}/fix-paths endpoint for existing games
Adds fixAbsoluteConfigPaths() which runs after flattening and cue fixing
during game upload. Detects known config files (FALLOUT.CFG, etc.) with
hardcoded C:\dir\ paths and rewrites them to .\ relative paths, so games
like Fallout can find their DAT files after flattening moves everything
to the bundle root.
Uses byte-level replacement to preserve original CRLF line endings and
encoding. Extensible via CONFIG_PATH_FIXES map — add new entries for
other games with the same problem.
G1HeapUncommitPercent is not available in this JRE build. The
MinHeapFreeRatio=10 and MaxHeapFreeRatio=20 flags are sufficient
to keep JVM committed heap from growing unbounded after uploads.
-XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:G1HeapUncommitPercent=5
After large game uploads, the JVM committed heap stays high. These flags tell
G1GC to release free heap pages back to the OS after GC cycles, so Docker
memory usage drops back toward baseline instead of staying at peak.
Revert to commit 8108a66 (cue file fix + CD mounting fixes) and
apply only the Dockerfile JVM memory limit. All other changes
(StaticResource Range support, FileChannel transferTo) reverted.
CloneCD rips generate a .cue that references DOTT.bin but the actual
data file is DOTT.img. DOSBox fails to mount the CD when the referenced
file doesn't exist, causing 'Illegal command' for executables on the CD.
Now before bundle creation, we scan all .cue files and fix any FILE line
that points to a missing data file by rewriting it to reference .img
or .bin files actually present in the same directory.
Replace the broken descriptor-preference logic with a clean two-pass
approach: group images by parent directory, then pick the best format
per directory with priority .cue > .iso > .ccd > .img > .bin.
js-dos DOSBox's imgmount does NOT support .ccd (CloneCD descriptor)
natively, but .cue is widely supported. When both exist, .cue wins.
When only .ccd exists, it falls back to mounting the .img directly.
DOSBox's imgmount works best with descriptors because they contain track layout
info (audio tracks, subchannel data, copy protection). Raw .img/.bin mounts
lose this info, making the filesystem inaccessible and causing 'Illegal command'
when trying to run executables from the CD.
The bug: seenDirs.add(parentDir) was called BEFORE the descriptor-skip
check. When .ccd was processed first, it marked cd/ as seen even
though it was skipped. Then .img was skipped because cd/ was already
'seen', leaving zero imgmount lines.
Fix: use seenDirs.contains(parentDir) for the continue check, and
call seenDirs.add(parentDir) only after passing all checks, right
before the actual imgmount output.
CD-only games were failing because the second findCdImages() call
inside createBundle returned empty. Now detection happens once in
the upload method and the result is passed to createBundle.
CloneCD (.ccd) and cue sheets (.cue) contain internal FILE references
that may not match the actual filename on disk (case mismatch).
When a data format (.img, .iso, .bin) exists in the same directory,
it's mounted directly instead of the descriptor.
When no .exe/.com/.bat files are found but CD images are present,
the upload no longer fails. Instead it creates a CD-only game where
the autoexec mounts the CD-ROM image(s) and presents a DOS prompt
with instructions for the user.
- findMainExe returning null + CD images exist = CD-only game
- createBundle handles null exePath (generates CD-only config)
- New buildCdOnlyDosboxConf / buildCdOnlyJsdosJson methods
- Upload metadata handles null executable gracefully (empty string)
- Platform defaults to 'dos' when no executable to inspect
Since GameService now preserves CD images when regenerating configs,
the dedup logic is safe to re-add. Deduplicates by parent directory
so a .bin and .cue from the same folder only produce one imgmount.
Alphabetical sort ensures .bin (b) is mounted before .cue (c).
GameService.buildDosboxConfBytes() was rebuilding dosbox.conf with
only the executable path — CD image mount lines were lost.
- Added findCdImagesInBundle() to scan a .jsdos ZIP for CD images
- buildDosboxConfBytes() now accepts List<String> cdImages and
generates imgmount lines (same logic as UploadResource)
- Both setExecutable() and streamSetupBundle() now scan for
and include CD images when regenerating config
Removed the directory-level dedup and .bin-over-.cue preference
logic — it was causing imgmount lines to disappear entirely.
Now mounts every detected CD image (.iso/.cue/.img/.ccd/.bin)
with appropriate flags (.bin gets -fs iso for direct fs access).
.cue files contain an internal FILE reference (e.g. FILE "z.BIN")
that may not match the actual filename on disk (e.g. z.bin) due to
case differences. In js-dos's case-sensitive WASM filesystem, this
causes the imgmount to fail silently.
Changes:
- Added .bin to CD_EXT so findCdImages detects bin files
- When both .cue and .bin exist in the same directory, only the
.bin is mounted (with -fs iso flag for direct filesystem access)
- .cue files without a matching .bin are still mounted as before
- Deduplication prevents mounting both .cue and .bin from same dir
- Removed .iso, .cue, .img, .ccd from SKIP_EXT (they're now kept in bundles)
- Added CD_EXT set and findCdImages() method to detect mountable images
- buildDosboxConf() now generates imgmount D:/E:/… commands for each CD image
- Only .nrg, .mdf, .mds, .sub, .dmg are still stripped (unsupported formats)
Games with CD images will now work out of the box — DOSBox presents
them as CD-ROM drives and the game can find its disc.
Handles common Sierra game file layout issues:
- RESOURCE.CFG with directory paths pointing to \KQ6CD when
resources are actually at the bundle root (fixes paths to '.')
- INTERP.ERR/INTERP.ERRC files buried in subdirectories instead
of at root (copies them up so SIERRA.EXE can find them)
POST /api/games/{id}/patch-sierra triggers the fix on an existing
game bundle.
bundleFile changed from 'wolf3d.jsdos' to 'wolf3d/wolf3d.jsdos' when
bundles moved inside game directories. resolveSibling(bundleFile + '.tmp')
produced a wrong nested path. Now uses bundlePath.getFileName() to get
just the filename part before appending .tmp.
When a game ZIP is selected and IGDB returns no matches, the search
box is now pre-filled with the filename (minus .zip extension) so
the user can edit it rather than typing from scratch.
Setting body { width: 100vw; overflow-x: hidden; } means the body is
always the full viewport width regardless of scrollbar presence.
margin: 0 auto centers content within the body, which doesn't
change width when a vertical scrollbar appears — so the content
stays in place.
html { overflow-x: hidden; } clips any body overflow past the
viewport (body is 100vw while html content area shrinks with
scrollbar). No gutter discoloration since there's no gutter
reservation — the scrollbar appears naturally on the html element.
Replaces overflow-y: scroll with scrollbar-gutter: stable on html.
This reserves the scrollbar gutter space in the layout at all times,
so the content width never changes when a scrollbar appears/disappears.
The scrollbar itself only shows when content overflows (as desired).
Works on all modern browsers (Chrome 94+, Firefox 97+, Safari 15.4+).
Add overflow-y: scroll to body so the scrollbar is always present,
preventing the ~15px layout shift when a page exceeds viewport height
(e.g., games with long descriptions). Also add overflow-x: hidden to
guard against any horizontal overflow breaking the layout.
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.
replace('\', '/') is Java source for a char literal representing
a single backslash character. The previous commit had a single
backslash byte between quotes which made Java interpret it as
\\' (escaped single-quote) causing unclosed char literal errors.
- 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('\\', '/'))
- Fixed: navigating back to library no longer re-opens file dialog
(prev-value guard in uploadTriggered effect)
- Added bundleSize field to Game, populated at load time from .jsdos file
- Game detail page shows file size next to DOS badge (e.g. 'DOS · 127 MB')
- Added 'executables' (full list) and 'executable' (selected) fields to Game
- Upload now stores ALL discoverable .exe/.com/.bat files in game metadata
- Added setExecutable() to GameService that patches the .jsdos bundle
(updates dosbox.conf + jsdos.json inside the ZIP) when changing executable
- GameResource PATCH handler delegates executable changes to bundle patching
- GameDetail edit mode shows a radio-button picker of all executables
- Heuristics remain as first-guess default, user can always override
- No re-upload needed to fix wrong executable selection
When the main executable is in a subdirectory but DOS4GW.EXE (or other
DOS extender) is at the ZIP root, DOS/4GW fails with 'No such file or
directory' because it only searches the current directory and PATH.
Adding 'path=c:\' before the 'cd' command ensures root-level DOS
extenders are always findable regardless of where the game EXE lives.
- Added 'sfx' and 'makesfx' to SKIP_EXE_NAMES
- Increased self-extractor detection buffer to 32KB
- Fixed false-positive risk: PK signatures only checked after offset 0x40
using proper signature comparison (not sequential byte matching)
- Fixed setupExe metadata: only set when setup bundle was actually created,
preventing phantom Setup button when setup exe is Windows
- Added archiver/compression tool names to SKIP_EXE_NAMES (pksfx,
pkzip, pkunzip, unzip, arj, rar, lha, etc.)
- Added isLikelySelfExtractor() which reads the EXE body looking for
PKWARE signatures (PKZIP/PKSFX strings and PK\x03\x04 local file
headers) — catches renamed self-extractors regardless of filename
- Both filters applied during findMainExe candidate collection
Previously only checked the main game's platform. Fallout's SETUP.EXE
is a Windows executable even though FALLOUT.EXE is DOS — creating a
.setup.jsdos bundle for it would show 'This program cannot run in DOS mode'.
Now detects the setup EXE's own header and skips if it targets Windows.
Size is a better indicator of 'this is the actual game' than depth.
A 1MB+ FALLOUT.EXE in a subdirectory should beat a 50KB BOOTME.EXE
at the root. Sort order: non-installer → DOS → size → depth.
When a ZIP contains both a DOS version (FALLOUT.EXE) and a Windows
version (FALLOUTW.EXE), the sort now prefers DOS executables.
This reads the EXE header during candidate collection and ranks:
non-installer → DOS platform → depth → size
Fixes Fallout being mis-detected as a Windows game.
The sort was: depth → non-installer → size.
This meant a root-level SETUP.EXE (depth 1, installer) beat a
subdirectory FALLOUT.EXE (depth 2, non-installer).
New sort: non-installer → depth → size.
Now any non-installer game EXE wins over installers regardless of depth.
DOS4GW.EXE is a 32-bit DOS extender that ships with many DOS games
(DOOM, Fallout, Duke Nukem 3D, etc.). The findMainExe algorithm was
picking it as the main executable because it's typically large and at
the ZIP root, causing the autoexec to run 'DOS4GW' with no arguments
→ 'DOS/4GW fatal error (1004): syntax is DOS/4GW <executable.xxx>'
Added SKIP_EXE_NAMES set with known DOS extenders, DPMI hosts,
debuggers, memory managers, and uninstallers.
- Removed toolbar (Library title, search, upload button) from library
- Moved search box into the sidebar alongside Year/Genre filters
- Removed 'Filters' heading from sidebar
- Replaced 'Library' nav button in header with '+ Upload' button
- Genre filters now sorted by count (desc) instead of alphabetically
- Cleaned up unused CSS
- GameCard: removed 'Ready' badge from covers (keep '🪟 Win' for
Windows games only — it's informative since they can't be played)
- Library: sidebar with Year + Genre checkbox filter groups
- Each shows up to 5 items with game counts, 'Show all' button
- Active filters shown as removable chips above the grid
- Reset button clears all filters
- Filters reflected in URL hash (#/?genre=FPS&year=1996)
- GameDetail: year and genre tags are now clickable links to
#/?year=1996 or #/?genre=FPS, which opens the Library with that
filter pre-applied
- App.svelte: parses query params from hash and passes to Library
Two features:
1. Progress indicator: when upload starts, the dialog switches from
IGDB results to a centered spinner with 'Uploading and processing'
text + filename. The Cancel button stays visible but disabled.
2. Duplicate game blocking: before upload, checks if a game with the
same title (case-insensitive) already exists in the library.
Frontend checks against the loaded games list; backend also
checks as a second line of defense. Shows ⚠️ message in dialog.
Existing unique-ID suffix logic (foo-2, foo-3) is still in place
but now only reached when titles actually differ.
- Wrap flattenSingleDir in try-catch — if it fails, the upload continues
and the cd <subdir> fix in dosbox.conf handles subdirectory executables
- Add global catch to upload method returning the actual exception
message instead of a generic 500
- This will help diagnose why the user's King's Quest VI zip fails:
flattening can throw on special filenames, Mac resource forks,
symlinks, or other unusual ZIP contents
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.
For pure DOS executables, the e_lfanew field at MZ header offset 0x3C
is uninitialized garbage. Duke Nukem 2's EXE had bytes that form a
negative int32 (-1878619136), which was used as an array index causing
ArrayIndexOutOfBoundsException.
Now checks peOffset < 0 and returns 'dos' (no Windows signature found).
Many classic DOS games (Blood, DOOM, Duke Nukem 3D, Quake) use
DOS/4GW or DOS/32A — 32-bit DOS extenders with Linear Executable
(LE) format. My code was detecting LE as 'windows', causing these
games to be incorrectly flagged as unplayable.
Only PE (Portable Executable / Win32) and NE (New Executable /
Windows 3.x) are actually Windows.
Previously when no IGDB matches were found, the button next to the
search box changed from 'Search' to 'Upload' after the first search.
Since the text is editable and users may want to refine their query,
this button should always trigger a search.
Upload is still available via:
- The 'Upload anyway as "..."' button (shown after failed search)
- The 'Use filename: ...' button at the bottom
- Move platform detection before setup bundle creation
- Only create setup bundle when platform != 'windows'
- Also hide the Setup button in GameDetail for Windows games
- Windows setup executables (INSTALL.EXE) can't run in DOSBox anyway
js-dos v8's C extraction code (jsdos-libzip.c) iterates ZIP entries
linearly and calls mkdir() on each directory entry. If a child
directory appears before its parent, mkdir() fails with perror()
and exit(1) — fatal crash with 'No such file or directory'.
Previous approach wrote directory entries interleaved with files,
deepest-first (from the file path right-to-left). Now:
Phase 1: Walk entire file tree to discover ALL directories
Phase 2: Write ALL dir entries sorted (shallowest first via TreeSet
natural ordering — '/' sorts before any letter)
Phase 3: Write .jsdos/ config files
Phase 4: Write game files (parents already exist from Phase 2)
This guarantees that mkdir("NAS50TH/TRACKS/") runs before
mkdir("NAS50TH/TRACKS/WILKES/"), which runs before open().
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.
Flow when auto-search finds nothing:
1. User types a title → presses Enter/clicks button
2. System searches IGDB with that title
3a. Results found → shown as selectable cards
3b. No results → warning + amber 'Upload anyway as xxx' button
4. Button label changes: 'Search' → 'Upload' after manual search
Also changed Enter key behavior: first press searches, second
press uploads (if no results after manual search)
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
Upload flow is now:
1. Click +Upload → select a .zip file
2. IGDB is auto-searched with the filename
3. If matches found → show results as selectable cards + fallback
custom title search at the bottom
4. If no matches → show 'no matches' + manual title + Upload btn
+ a fallback 'Use filename: xxx' button
5. Clicking a result uploads immediately with that game's title
(so IGDB auto-scrape picks it up correctly)
6. Cancel button to dismiss
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.
Game Detail:
- Year/genre badges and developer/publisher info moved from
the info column to the cover column (below the cover image)
- Edit mode unaffected — fields remain in the form on the right
- Added CSS spacing for meta/dev in cover section
Library upload:
- Removed the always-visible title input field
- After selecting a .zip, a modal dialog appears with two options:
1. 'Use filename as title' — uploads immediately with filename
2. Custom title input + 'Upload' button
Plus a Cancel button
- Modal is dismissible by clicking outside or Cancel
The info-section grid column (1fr) was expanding to fit wide content
like long game titles in Press Start 2P font (each character ~24px).
Added min-width:0 on grid children to prevent overflow, and
word-break:break-word on text elements as a safety net.
Global:
- Viewport meta already present; added -webkit-text-size-adjust: 100%
- Container padding reduces to 12px on mobile (was 24px)
- Smaller heading sizes (h1: 1.5rem, h2: 1.25rem)
- Buttons get min-height 40px for touch targets
- Inputs forced to 16px font-size to prevent iOS zoom
- Thinner scrollbar on mobile
Header:
- Logo text shrinks to 1rem on mobile, icon to 28px
- Header padding reduces to 12px
Library:
- Toolbar stacks vertically on mobile, full-width inputs
- Grid forced to 2 columns on narrow screens (480px), reduced gap
GameCard:
- Smaller info text, padding, badge on 480px screens
GameDetail:
- Cover section capped at 200px width on mobile
- Media thumbnails shrink to 160x90
- Scrape header stacks vertically with full-width buttons
- Edit rows stack vertically on mobile
Play:
- DOS container caps at 50vh on mobile, smaller border-radius
- Overlay text/icon sizes reduced, more compact layout
- 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
- dos-container div always mounted (fixes circular dep: container needed for start, was gated on running)
- Overlay covers the empty canvas while booting: Loading → Booting (CDN) → Running
- No intermediate "Start Game" screen — emulator auto-starts immediately
- Error state shown inline if something fails
- Stop button + back arrow both kill emulator and return to detail
- Emulator auto-starts when Play page loads (no intermediate launch screen)
- Added ⏹ Stop button while game is running
- Back arrow and Stop both kill the emulator (js-dos CI.exit())
- Cleanup on unmount prevents background emulation
- Loading state shown while js-dos CDN loads
- Manual "Start Game" fallback if auto-start fails
- .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
The pre-built image is published to Docker Hub as [`droideparanoico/dostalgia`](https://hub.docker.com/r/droideparanoico/dostalgia). Published automatically on every GitHub release via CI.
# Uncomment to build from source instead of using the pre-built image:
# build: .
container_name:dostalgia
container_name:dostalgia
restart:unless-stopped
ports:
ports:
- "8765:8765"
- "8765:8765"
volumes:
volumes:
# Configure here your data directory if you want persistence.
- ./data:/data
- ./data:/data
env_file:
env_file:
- .env
- .env
environment:
environment:
- DOSTALGIA_DATA_DIR=/data
- DOSTALGIA_DATA_DIR=/data
restart:unless-stopped
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.