feat: fix hardcoded DOS paths in configs during upload + fix-paths endpoint
Build & Deploy / build-and-deploy (push) Successful in 43s
Build & Deploy / build-and-deploy (push) Successful in 43s
- 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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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<String, java.util.List<String>> 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<Byte> 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<String> findCdImagesInBundle(Path bundlePath) throws IOException {
|
||||
Set<String> cdExt = Set.of(".iso", ".cue", ".img", ".ccd", ".bin");
|
||||
|
||||
@@ -485,7 +485,11 @@ public class UploadResource {
|
||||
* replace with ".\\" (relative to the game root). */
|
||||
private static final java.util.Map<String, java.util.List<String>> 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\\"
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user