diff --git a/src/main/java/com/dostalgia/GameResource.java b/src/main/java/com/dostalgia/GameResource.java index 136819d..66adc07 100644 --- a/src/main/java/com/dostalgia/GameResource.java +++ b/src/main/java/com/dostalgia/GameResource.java @@ -164,6 +164,25 @@ public class GameResource { } } + @POST + @Path("/{id}/fix-paths") + @Produces(MediaType.APPLICATION_JSON) + public Response fixPaths(@PathParam("id") String id) { + try { + boolean patched = svc.fixBundleConfigPaths(id); + if (patched) { + return Response.ok(Map.of("status", "patched", + "message", "Fixed hardcoded DOS paths in game config")).build(); + } + return Response.ok(Map.of("status", "noop", + "message", "No known config files needed patching")).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(); + } + } + private Integer asInt(Object v) { if (v instanceof Number n) return n.intValue(); return null; diff --git a/src/main/java/com/dostalgia/GameService.java b/src/main/java/com/dostalgia/GameService.java index e529b8b..ca483bf 100644 --- a/src/main/java/com/dostalgia/GameService.java +++ b/src/main/java/com/dostalgia/GameService.java @@ -204,6 +204,97 @@ public class GameService { save(game); } + /** + * Known game config files with hardcoded DOS paths that need fixing. + * Same as UploadResource.CONFIG_PATH_FIXES but duplicated here for + * the fix-paths endpoint (no dependency on UploadResource). + */ + private static final java.util.Map> CONFIG_PATH_FIXES = + java.util.Map.of( + "fallout.cfg", java.util.List.of( + "c:\\fallout1\\", "C:\\fallout1\\", + "c:\\fallout2\\", "C:\\fallout2\\", + "c:\\fallout\\", "C:\\fallout\\" + ) + ); + + /** Patch hardcoded DOS paths (C:\dir\ → .\) in known game config files within a .jsdos bundle. */ + public boolean fixBundleConfigPaths(String id) throws IOException { + Game game = load(id); + String bundleFile = game.getBundleFile(); + if (bundleFile == null) return false; + Path bundlePath = gamesDir.resolve(bundleFile); + if (!Files.exists(bundlePath)) return false; + + Path tmpPath = bundlePath.resolveSibling(bundlePath.getFileName() + ".tmp"); + boolean patched = false; + + try (var zis = new java.util.zip.ZipInputStream(Files.newInputStream(bundlePath)); + var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(tmpPath))) { + + java.util.zip.ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + String name = entry.getName(); + String loName = name.toLowerCase(); + var prefixes = CONFIG_PATH_FIXES.get(loName); + zos.putNextEntry(new java.util.zip.ZipEntry(name)); + + if (prefixes != null) { + // Read the entry content and patch paths + byte[] data = zis.readAllBytes(); + byte[] result = data; + for (String prefix : prefixes) { + byte[] pBytes = prefix.getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + byte[] pSlash = prefix.replace('\\', '/') + .getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + byte[] rel = ".\\".getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + byte[] relSlash = "./" + .getBytes(java.nio.charset.StandardCharsets.ISO_8859_1); + result = replaceBytesInPlace(result, pBytes, rel); + result = replaceBytesInPlace(result, pSlash, relSlash); + } + if (!java.util.Arrays.equals(result, data)) { + patched = true; + } + zos.write(result); + } else { + zis.transferTo(zos); + } + zos.closeEntry(); + zis.closeEntry(); + } + } + + if (patched) { + Files.move(tmpPath, bundlePath, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.deleteIfExists(tmpPath); + } + return patched; + } + + private static byte[] replaceBytesInPlace(byte[] src, byte[] search, byte[] replacement) { + if (search.length == 0) return src; + java.util.ArrayList result = new java.util.ArrayList<>(src.length); + int i = 0; + while (i <= src.length - search.length) { + boolean match = true; + for (int j = 0; j < search.length; j++) { + if (src[i + j] != search[j]) { match = false; break; } + } + if (match) { + for (byte b : replacement) result.add(b); + i += search.length; + } else { + result.add(src[i++]); + } + } + while (i < src.length) result.add(src[i++]); + byte[] out = new byte[result.size()]; + for (int k = 0; k < out.length; k++) out[k] = result.get(k); + return out; + } + /** Scan a .jsdos bundle for CD image files (preserves the case from the ZIP entry). */ private List findCdImagesInBundle(Path bundlePath) throws IOException { Set cdExt = Set.of(".iso", ".cue", ".img", ".ccd", ".bin"); diff --git a/src/main/java/com/dostalgia/UploadResource.java b/src/main/java/com/dostalgia/UploadResource.java index 6c157c9..9e6566d 100644 --- a/src/main/java/com/dostalgia/UploadResource.java +++ b/src/main/java/com/dostalgia/UploadResource.java @@ -485,7 +485,11 @@ public class UploadResource { * replace with ".\\" (relative to the game root). */ private static final java.util.Map> CONFIG_PATH_FIXES = java.util.Map.of( - "fallout.cfg", java.util.List.of("c:\\fallout1\\", "c:\\fallout2\\", "c:\\fallout\\") + "fallout.cfg", java.util.List.of( + "c:\\fallout1\\", "C:\\fallout1\\", + "c:\\fallout2\\", "C:\\fallout2\\", + "c:\\fallout\\", "C:\\fallout\\" + ) ); /**