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) {
|
private Integer asInt(Object v) {
|
||||||
if (v instanceof Number n) return n.intValue();
|
if (v instanceof Number n) return n.intValue();
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -204,6 +204,97 @@ public class GameService {
|
|||||||
save(game);
|
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). */
|
/** Scan a .jsdos bundle for CD image files (preserves the case from the ZIP entry). */
|
||||||
private List<String> findCdImagesInBundle(Path bundlePath) throws IOException {
|
private List<String> findCdImagesInBundle(Path bundlePath) throws IOException {
|
||||||
Set<String> cdExt = Set.of(".iso", ".cue", ".img", ".ccd", ".bin");
|
Set<String> cdExt = Set.of(".iso", ".cue", ".img", ".ccd", ".bin");
|
||||||
|
|||||||
@@ -485,7 +485,11 @@ public class UploadResource {
|
|||||||
* replace with ".\\" (relative to the game root). */
|
* replace with ".\\" (relative to the game root). */
|
||||||
private static final java.util.Map<String, java.util.List<String>> CONFIG_PATH_FIXES =
|
private static final java.util.Map<String, java.util.List<String>> CONFIG_PATH_FIXES =
|
||||||
java.util.Map.of(
|
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