diff --git a/src/main/java/com/dostalgia/UploadResource.java b/src/main/java/com/dostalgia/UploadResource.java index f5755e0..d6bc4fe 100644 --- a/src/main/java/com/dostalgia/UploadResource.java +++ b/src/main/java/com/dostalgia/UploadResource.java @@ -62,6 +62,8 @@ public class UploadResource { Path extractDir = tmpDir.resolve("extracted"); unzip(zipPath, extractDir); + // If the ZIP has a single root directory, flatten it + flattenSingleDir(extractDir); // Find main executable String mainExe = findMainExe(extractDir); @@ -86,7 +88,7 @@ public class UploadResource { // Write js-dos config Path jsdos = gameDir.resolve(".jsdos"); Files.createDirectories(jsdos); - String relExe = extractDir.relativize(Path.of(mainExe)).toString().replace('/', '\\'); + 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"; @@ -170,6 +172,40 @@ public class UploadResource { } } + /** + * If the extraction directory contains a single subdirectory, move + * its contents up (flatten). This handles ZIPs where all game files + * are inside a folder (e.g. "game/DOOM2.EXE" → "DOOM2.EXE"). + */ + private void flattenSingleDir(Path dir) throws IOException { + List entries; + try (var files = Files.list(dir)) { + entries = files.toList(); + } + if (entries.size() != 1) return; + Path only = entries.get(0); + if (!Files.isDirectory(only)) return; + + LOG.info("Flattening single root directory: " + only.getFileName()); + // Walk in reverse order so directories are empty when we delete them + try (var walk = Files.walk(only)) { + var list = walk.sorted(Comparator.reverseOrder()).toList(); + for (Path f : list) { + if (f.equals(only)) continue; + Path target = dir.resolve(only.relativize(f)); + if (Files.isDirectory(f)) { + Files.createDirectories(target); + Files.delete(f); + } else { + Files.createDirectories(target.getParent()); + Files.move(f, target, StandardCopyOption.REPLACE_EXISTING); + } + } + } + Files.delete(only); + LOG.info("Flattened " + only.getFileName()); + } + // ─── Executable Detection ──────────────────────────────────── private String findMainExe(Path dir) throws IOException { @@ -211,7 +247,7 @@ public class UploadResource { // Create .jsdos config Path jsdos = tmpBundle.resolve(".jsdos"); Files.createDirectories(jsdos); - String relExe = extractDir.relativize(Path.of(mainExe)).toString().replace('/', '\\'); + String relExe = extractDir.relativize(Path.of(mainExe)).toString(); Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe)); Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe)); @@ -236,11 +272,10 @@ public class UploadResource { // ─── Configuration Helpers ─────────────────────────────────── private byte[] buildJsdosJson(String relExe) { - String parts[] = splitPath(relExe); - String script = (parts[0].isEmpty() ? "" : "cd " + parts[0] + " && ") + parts[1]; + // relExe is always just a filename after flattenSingleDir String json = "{" + "\"autoexec\":{" - + "\"options\":{\"script\":{\"value\":\"" + escapeJson(script) + "\"}}" + + "\"options\":{\"script\":{\"value\":\"" + escapeJson(relExe) + "\"}}" + "}," + "\"output\":{" + "\"options\":{\"autolock\":{\"value\":true}}}" @@ -249,10 +284,7 @@ public class UploadResource { } private String buildDosboxConf(String relExe) { - String parts[] = splitPath(relExe); - String dir = parts[0]; - String exe = parts[1]; - + // relExe is always just a filename after flattenSingleDir return "[sdl]\n" + "autolock=true\n" + "usescancodes=true\n" @@ -282,21 +314,7 @@ public class UploadResource { + "mount c .\n" + "c:\n" + "cls\n" - + (dir.isEmpty() ? "" : "cd " + dir + "\n") - + exe + "\n"; - } - - /** - * Split a DOS path into directory and filename parts. - * Handles both backslash and forward-slash separators. - * Returns [dir, exe] where dir is empty for root-level files. - */ - private String[] splitPath(String path) { - int idx = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); - if (idx >= 0) { - return new String[]{path.substring(0, idx), path.substring(idx + 1)}; - } - return new String[]{"", path}; + + relExe + "\n"; } private String escapeJson(String s) {