fix: bypass js-dos bundleUpdateConfig by providing config + initFs separately
Build & Deploy / build-and-deploy (push) Successful in 1m16s
Build & Deploy / build-and-deploy (push) Successful in 1m16s
js-dos v8.3.8 calls bundleUpdateConfig which uses libzip WASM to
re-add .jsdos/jsdos.json into the downloaded bundle. This fails on
our bundles (Unable to add .jsdos/jsdos.json into bundle.zip).
Fix: Instead of using { url } option (which triggers bundleUpdateConfig),
fetch the config from a new API endpoint (/api/games/{id}/config) and
pass it directly via { dosboxConf, jsdosConf, initFs }. This bypasses
the broken bundleUpdateConfig entirely.
Changes:
- New GET /api/games/{id}/config endpoint (GameResource + GameService)
- Exposed resolveCdImages/pickBestCdImage as package-private
- Frontend Play.svelte: fetch config + bundle separately, use new API
- Frontend api.js: new getGameConfig() function
This commit is contained in:
@@ -153,6 +153,20 @@ public class GameResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{id}/config")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response config(@PathParam("id") String id) {
|
||||
try {
|
||||
var config = svc.getBundleConfig(id);
|
||||
return Response.ok(config).build();
|
||||
} catch (NoSuchFileException e) {
|
||||
return Response.status(404).entity(Map.of("error", "Game not found")).build();
|
||||
} catch (Exception e) {
|
||||
return Response.serverError().entity(Map.of("error", e.getMessage())).build();
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
public Response delete(@PathParam("id") String id) {
|
||||
|
||||
@@ -371,5 +371,64 @@ public class GameService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Get bundle config (dosbox.conf + jsdos.json) for a game.
|
||||
* Regenerates from stored metadata rather than reading from the bundle. */
|
||||
public Map<String, Object> getBundleConfig(String id) throws IOException {
|
||||
Game game = load(id);
|
||||
String exe = game.getExecutable();
|
||||
Path bundlePath = gamesDir.resolve(game.getBundleFile());
|
||||
List<String> cdImages = exe != null && !exe.isBlank()
|
||||
? findCdImagesInBundle(bundlePath)
|
||||
: List.of();
|
||||
|
||||
byte[] dosboxConf;
|
||||
byte[] jsdosConf;
|
||||
if (exe != null && !exe.isBlank()) {
|
||||
dosboxConf = buildDosboxConfBytes(exe, cdImages);
|
||||
jsdosConf = buildJsdosJsonBytes(exe);
|
||||
} else {
|
||||
// CD-only game — generate CD-only config
|
||||
dosboxConf = buildCdOnlyDosboxConfBytes(cdImages);
|
||||
jsdosConf = buildCdOnlyJsdosJsonBytes();
|
||||
}
|
||||
|
||||
Map<String, Object> config = new java.util.LinkedHashMap<>();
|
||||
config.put("dosbox_conf", new String(dosboxConf, java.nio.charset.StandardCharsets.UTF_8));
|
||||
config.put("jsdos_conf", new String(jsdosConf, java.nio.charset.StandardCharsets.UTF_8));
|
||||
return config;
|
||||
}
|
||||
|
||||
/** Build dosbox.conf for a CD-only game (no executable on filesystem). */
|
||||
private byte[] buildCdOnlyDosboxConfBytes(List<String> cdImages) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[sdl]\nautolock=true\nusescancodes=true\noutput=surface\n\n");
|
||||
sb.append("[cpu]\ncore=auto\ncycles=auto\n\n");
|
||||
sb.append("[mixer]\nnosound=false\nrate=44100\n\n");
|
||||
sb.append("[sblaster]\nsbtype=sb16\nirq=7\ndma=1\nhdma=5\n\n");
|
||||
sb.append("[dos]\nxms=true\nems=true\numb=true\n\n");
|
||||
sb.append("[autoexec]\n@echo off\nmount c .\n");
|
||||
// Mount CD images as D:, E:, …
|
||||
char driveLetter = 'D';
|
||||
for (String img : UploadResource.resolveCdImages(cdImages)) {
|
||||
String imgLower = img.toLowerCase();
|
||||
String flags = (imgLower.endsWith(".bin") && !imgLower.endsWith(".cue"))
|
||||
? " -t cdrom -fs iso" : " -t cdrom";
|
||||
sb.append("imgmount ").append(driveLetter).append(" \"").append(img).append("\"").append(flags).append("\n");
|
||||
driveLetter++;
|
||||
}
|
||||
sb.append("c:\n");
|
||||
sb.append("echo.\n");
|
||||
sb.append("echo This game runs from the CD-ROM.\n");
|
||||
sb.append("echo Type D: and press Enter, then look for the game executable (e.g. DOTT.EXE).\n");
|
||||
return sb.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/** Build jsdos.json for a CD-only game. */
|
||||
private byte[] buildCdOnlyJsdosJsonBytes() {
|
||||
String json = "{\"autoexec\":{\"options\":{\"script\":{\"value\":\"c:\\\\\"}}},"
|
||||
+ "\"output\":{\"options\":{\"autolock\":{\"value\":true}}}}";
|
||||
return json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public Path getGamesDir() { return gamesDir; }
|
||||
}
|
||||
|
||||
@@ -634,7 +634,7 @@ public class UploadResource {
|
||||
/** Resolve one CD image per parent directory with best-format priority.
|
||||
* Priority: .cue > .iso > .ccd > .img > .bin
|
||||
* js-dos DOSBox (Emscripten) supports .cue natively, but .ccd often fails. */
|
||||
private static List<String> resolveCdImages(List<String> cdImages) {
|
||||
static List<String> resolveCdImages(List<String> cdImages) {
|
||||
// Group by parent directory
|
||||
java.util.Map<String, java.util.List<String>> byDir = new java.util.LinkedHashMap<>();
|
||||
for (String img : cdImages) {
|
||||
@@ -651,7 +651,7 @@ public class UploadResource {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String pickBestCdImage(java.util.List<String> images) {
|
||||
static String pickBestCdImage(java.util.List<String> images) {
|
||||
// Priority: .cue [5] > .iso [4] > .ccd [3] > .img [2] > .bin [1]
|
||||
String best = null;
|
||||
int bestScore = -1;
|
||||
|
||||
Reference in New Issue
Block a user