From e11f19c7691571daec44215d5fed412d4c765d62 Mon Sep 17 00:00:00 2001 From: David Alvarez Date: Thu, 28 May 2026 09:45:45 +0200 Subject: [PATCH] fix: remove sockdrive mode, always create .jsdos ZIP bundles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/lib/api.js | 6 - src/main/java/com/dostalgia/GameService.java | 16 -- .../java/com/dostalgia/UploadResource.java | 178 ++++-------------- 3 files changed, 36 insertions(+), 164 deletions(-) diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js index f12cde1..1f54b3a 100644 --- a/frontend/src/lib/api.js +++ b/frontend/src/lib/api.js @@ -105,17 +105,11 @@ export function artworkUrl(path) { /** Get game bundle URL for normal play */ export function bundleUrl(game) { if (!game) return null; - if (game.bundle_type === "sockdrive") { - return `/api/sockdrive/${game.id}/file?path=.jsdos/dosbox.conf`; - } return `/games/${game.id}.jsdos`; } /** Get game bundle URL for setup mode (runs SETUP.EXE instead) */ export function setupBundleUrl(game) { if (!game) return null; - if (game.bundle_type === "sockdrive") { - return `/api/sockdrive/${game.id}/file?path=.jsdos-setup/dosbox.conf`; - } return `/games/${game.id}.setup.jsdos`; } diff --git a/src/main/java/com/dostalgia/GameService.java b/src/main/java/com/dostalgia/GameService.java index da0971e..82cb606 100644 --- a/src/main/java/com/dostalgia/GameService.java +++ b/src/main/java/com/dostalgia/GameService.java @@ -123,22 +123,6 @@ public class GameService { Files.deleteIfExists(gamesDir.resolve(id + ".jsdos")); // Remove flat setup .jsdos bundle (if any) Files.deleteIfExists(gamesDir.resolve(id + ".setup.jsdos")); - // Remove .jsdos-setup dir (sockdrive mode, if any) - Path setupDir = dir.resolve(".jsdos-setup"); - if (Files.exists(setupDir)) { - Files.walkFileTree(setupDir, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException { - Files.delete(f); - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult postVisitDirectory(Path d, IOException e) throws IOException { - Files.delete(d); - return FileVisitResult.CONTINUE; - } - }); - } return true; } diff --git a/src/main/java/com/dostalgia/UploadResource.java b/src/main/java/com/dostalgia/UploadResource.java index de3e979..8edd915 100644 --- a/src/main/java/com/dostalgia/UploadResource.java +++ b/src/main/java/com/dostalgia/UploadResource.java @@ -20,8 +20,6 @@ import java.util.logging.Logger; @Produces(MediaType.APPLICATION_JSON) public class UploadResource { - private static final double SOCKDRIVE_THRESHOLD_MB = 80.0; - @Inject GameService svc; @@ -30,6 +28,8 @@ public class UploadResource { private static final Logger LOG = Logger.getLogger(UploadResource.class.getName()); + // ─── Upload ────────────────────────────────────────────────── + @POST @Consumes(MediaType.MULTIPART_FORM_DATA) public Response upload( @@ -76,50 +76,24 @@ public class UploadResource { // Find setup executable (SETUP.EXE, INSTALL.EXE, CONFIG.EXE, etc.) String setupExe = findSetupExe(extractDir); - // Determine bundle strategy - double sizeMb = dirSizeMB(extractDir); - String bundleType = sizeMb > SOCKDRIVE_THRESHOLD_MB ? "sockdrive" : "standard"; - // Create game directory Path gameDir = svc.gameDir(gameId); Files.createDirectories(gameDir); - String bundleFile; - if ("sockdrive".equals(bundleType)) { - // Copy files loose - copyDir(extractDir, gameDir); - // Write js-dos config - Path jsdos = gameDir.resolve(".jsdos"); - Files.createDirectories(jsdos); - String relExe = extractDir.relativize(Path.of(mainExe)).toString(); - Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe)); - Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe)); - bundleFile = ".jsdos/dosbox.conf"; + // Create .jsdos bundle (ZIP with game files + .jsdos/ config) + String bundleFile = gameId + ".jsdos"; + Path bundlePath = svc.getGamesDir().resolve(bundleFile); + createBundle(extractDir, mainExe, bundlePath); - // Write setup config if a setup executable was found - if (setupExe != null) { - Path setupDir = gameDir.resolve(".jsdos-setup"); - Files.createDirectories(setupDir); - String relSetup = extractDir.relativize(Path.of(setupExe)).toString(); - Files.writeString(setupDir.resolve("dosbox.conf"), buildSetupDosboxConf(relSetup)); - Files.write(setupDir.resolve("jsdos.json"), buildSetupJsdosJson(relSetup)); - } - } else { - // Create .jsdos bundle (flat in games dir) - bundleFile = gameId + ".jsdos"; - Path bundlePath = svc.getGamesDir().resolve(bundleFile); - createBundle(extractDir, mainExe, bundlePath); - - // Create setup bundle if a setup executable was found - if (setupExe != null) { - Path setupBundlePath = svc.getGamesDir().resolve(gameId + ".setup.jsdos"); - createBundle(extractDir, setupExe, setupBundlePath); - } + // Create setup bundle if a setup executable was found + if (setupExe != null) { + Path setupBundlePath = svc.getGamesDir().resolve(gameId + ".setup.jsdos"); + createBundle(extractDir, setupExe, setupBundlePath); } // Save metadata Game game = new Game(gameId, title); - game.setBundleType(bundleType); + game.setBundleType("standard"); game.setBundleFile(bundleFile); game.setSetupExe(setupExe != null ? extractDir.relativize(Path.of(setupExe)).toString() : null); game.setReady(true); @@ -297,35 +271,33 @@ public class UploadResource { // ─── Bundle Creation ───────────────────────────────────────── - private void createBundle(Path extractDir, String mainExe, Path bundlePath) throws IOException { - // Create temporary bundle dir - Path tmpBundle = Files.createTempDirectory("dostalgia-bundle-"); - try { - copyDir(extractDir, tmpBundle); + private void createBundle(Path extractDir, String exePath, Path bundlePath) throws IOException { + // Write .jsdos config directly into the extract directory + Path jsdos = extractDir.resolve(".jsdos"); + Files.createDirectories(jsdos); + String relExe = extractDir.relativize(Path.of(exePath)).toString(); + Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe)); + Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe)); - // Create .jsdos config - Path jsdos = tmpBundle.resolve(".jsdos"); - Files.createDirectories(jsdos); - String relExe = extractDir.relativize(Path.of(mainExe)).toString(); - Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe)); - Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe)); - - // ZIP it up - try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) { - Files.walk(tmpBundle).filter(Files::isRegularFile).forEach(f -> { - try { - String entryName = tmpBundle.relativize(f).toString().replace('\\', '/'); - zos.putNextEntry(new java.util.zip.ZipEntry(entryName)); - Files.copy(f, zos); - zos.closeEntry(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } - } finally { - deleteDir(tmpBundle); + // ZIP it up directly from extractDir (no temp copy) + try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) { + zos.setLevel(0); // Store mode — DOS files are often already compressed + Files.walk(extractDir).filter(Files::isRegularFile).forEach(f -> { + try { + String entryName = extractDir.relativize(f).toString().replace('\\', '/'); + zos.putNextEntry(new java.util.zip.ZipEntry(entryName)); + Files.copy(f, zos); + zos.closeEntry(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } + + // Clean up .jsdos config files from the extract dir + Files.walk(jsdos).sorted(Comparator.reverseOrder()).forEach(p -> { + try { Files.deleteIfExists(p); } catch (IOException ignored) {} + }); } // ─── Configuration Helpers ─────────────────────────────────── @@ -380,86 +352,8 @@ public class UploadResource { return s.replace("\\", "\\\\").replace("\"", "\\\""); } - // ─── Setup Config Builders ─────────────────────────────────── - - private byte[] buildSetupJsdosJson(String relSetup) { - String json = "{" - + "\"autoexec\":{" - + "\"options\":{\"script\":{\"value\":\"" + escapeJson(relSetup) + "\"}}" - + "}," - + "\"output\":{" - + "\"options\":{\"autolock\":{\"value\":true}}}" - + "}"; - return json.getBytes(java.nio.charset.StandardCharsets.UTF_8); - } - - private String buildSetupDosboxConf(String relSetup) { - return "[sdl]\n" - + "autolock=true\n" - + "usescancodes=true\n" - + "output=surface\n" - + "\n" - + "[cpu]\n" - + "core=auto\n" - + "cycles=auto\n" - + "\n" - + "[mixer]\n" - + "nosound=false\n" - + "rate=44100\n" - + "\n" - + "[sblaster]\n" - + "sbtype=sb16\n" - + "irq=7\n" - + "dma=1\n" - + "hdma=5\n" - + "\n" - + "[dos]\n" - + "xms=true\n" - + "ems=true\n" - + "umb=true\n" - + "\n" - + "[autoexec]\n" - + "@echo off\n" - + "mount c .\n" - + "c:\n" - + "cls\n" - + "echo DOS Setup Utility\n" - + "echo -----------------\n" - + "echo Configure controls, sound, and other settings.\n" - + "echo When done, exit to return here. Changes are saved automatically.\n" - + "echo.\n" - + relSetup + "\n"; - } - // ─── File Utilities ────────────────────────────────────────── - private double dirSizeMB(Path dir) throws IOException { - long[] total = {0}; - Files.walkFileTree(dir, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path f, BasicFileAttributes a) { - total[0] += a.size(); - return FileVisitResult.CONTINUE; - } - }); - return total[0] / (1024.0 * 1024.0); - } - - private void copyDir(Path src, Path dst) throws IOException { - Files.walkFileTree(src, new SimpleFileVisitor<>() { - @Override - public FileVisitResult preVisitDirectory(Path d, BasicFileAttributes a) throws IOException { - Files.createDirectories(dst.resolve(src.relativize(d))); - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFile(Path f, BasicFileAttributes a) throws IOException { - Files.copy(f, dst.resolve(src.relativize(f)), StandardCopyOption.REPLACE_EXISTING); - return FileVisitResult.CONTINUE; - } - }); - } - private void deleteDir(Path dir) throws IOException { if (!Files.exists(dir)) return; Files.walkFileTree(dir, new SimpleFileVisitor<>() {