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.
- 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.
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.
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.
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).