feat: support CD-only games (no executable on filesystem)
Build & Deploy / build-and-deploy (push) Failing after 31s
Build & Deploy / build-and-deploy (push) Failing after 31s
When no .exe/.com/.bat files are found but CD images are present, the upload no longer fails. Instead it creates a CD-only game where the autoexec mounts the CD-ROM image(s) and presents a DOS prompt with instructions for the user. - findMainExe returning null + CD images exist = CD-only game - createBundle handles null exePath (generates CD-only config) - New buildCdOnlyDosboxConf / buildCdOnlyJsdosJson methods - Upload metadata handles null executable gracefully (empty string) - Platform defaults to 'dos' when no executable to inspect
This commit is contained in:
@@ -83,10 +83,15 @@ public class UploadResource {
|
||||
// Find main executable
|
||||
String mainExe = findMainExe(extractDir);
|
||||
if (mainExe == null) {
|
||||
// No executable on the filesystem — check if CD images exist instead
|
||||
List<String> cdOnlyImages = findCdImages(extractDir);
|
||||
if (cdOnlyImages.isEmpty()) {
|
||||
return Response.status(400)
|
||||
.entity(Map.of("error", "No executable (.exe/.com/.bat) found in the archive"))
|
||||
.build();
|
||||
}
|
||||
// CD-only game (executable is on the CD image) — proceed without one
|
||||
}
|
||||
|
||||
// Find setup executable (SETUP.EXE, INSTALL.EXE, CONFIG.EXE, etc.)
|
||||
String setupExe = findSetupExe(extractDir);
|
||||
@@ -101,7 +106,7 @@ public class UploadResource {
|
||||
createBundle(extractDir, mainExe, bundlePath);
|
||||
|
||||
// Detect platform (DOS vs Windows) by reading the main EXE header
|
||||
String platform = detectPlatform(Path.of(mainExe));
|
||||
String platform = mainExe != null ? detectPlatform(Path.of(mainExe)) : "dos";
|
||||
|
||||
// Store setup executable path if found and DOS-compatible.
|
||||
// Some games (e.g. Fallout) ship a Windows SETUP.EXE even though the main
|
||||
@@ -113,7 +118,9 @@ public class UploadResource {
|
||||
game.setBundleFile(bundleFile);
|
||||
game.setPlatform(platform);
|
||||
// Store the selected executable and the full list for the UI picker
|
||||
String relMain = extractDir.relativize(Path.of(mainExe)).toString().replace('\\', '/');
|
||||
String relMain = mainExe != null
|
||||
? extractDir.relativize(Path.of(mainExe)).toString().replace('\\', '/')
|
||||
: "";
|
||||
game.setExecutable(relMain);
|
||||
game.setExecutables(findAllExecutables(extractDir));
|
||||
if (setupExe != null) {
|
||||
@@ -438,11 +445,17 @@ public class UploadResource {
|
||||
// Write .jsdos config directly into the extract directory
|
||||
Path jsdos = extractDir.resolve(".jsdos");
|
||||
Files.createDirectories(jsdos);
|
||||
String relExe = extractDir.relativize(Path.of(exePath)).toString();
|
||||
// Find CD images for DOSBox imgmount (mountable via D:, E:, …)
|
||||
List<String> cdImages = findCdImages(extractDir);
|
||||
if (exePath != null) {
|
||||
String relExe = extractDir.relativize(Path.of(exePath)).toString();
|
||||
Files.writeString(jsdos.resolve("dosbox.conf"), buildDosboxConf(relExe, cdImages));
|
||||
Files.write(jsdos.resolve("jsdos.json"), buildJsdosJson(relExe));
|
||||
} else {
|
||||
// CD-only game — no executable, mount CD and show a DOS prompt
|
||||
Files.writeString(jsdos.resolve("dosbox.conf"), buildCdOnlyDosboxConf(cdImages));
|
||||
Files.writeString(jsdos.resolve("jsdos.json"), buildCdOnlyJsdosJson());
|
||||
}
|
||||
|
||||
// ZIP it up directly from extractDir (no temp copy)
|
||||
try (var zos = new java.util.zip.ZipOutputStream(Files.newOutputStream(bundlePath))) {
|
||||
@@ -550,6 +563,46 @@ public class UploadResource {
|
||||
return json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/** Build config for a CD-only game (no executables on the filesystem). */
|
||||
private String buildCdOnlyDosboxConf(List<String> cdImages) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[sdl]\nautolock=true\nusescancodes=true\noutput=surface\n\n");
|
||||
sb.append("[cpu]\ncore=auto\ncycles=auto\n\n");
|
||||
sb.append("[mixer]\nnosound=false\nrate=44100\n\n");
|
||||
sb.append("[sblaster]\nsbtype=sb16\nirq=7\ndma=1\nhdma=5\n\n");
|
||||
sb.append("[dos]\nxms=true\nems=true\numb=true\n\n");
|
||||
sb.append("[autoexec]\n@echo off\nmount c .\n");
|
||||
// Mount CD images as D:, E:, …
|
||||
Set<String> seenDirs = new java.util.HashSet<>();
|
||||
char driveLetter = 'D';
|
||||
for (String img : cdImages) {
|
||||
int slashIdx = img.lastIndexOf('/');
|
||||
String parentDir = slashIdx >= 0 ? img.substring(0, slashIdx) : "";
|
||||
if (!seenDirs.add(parentDir)) continue;
|
||||
String imgLower = img.toLowerCase();
|
||||
String flags = imgLower.endsWith(".bin") ? " -t cdrom -fs iso" : " -t cdrom";
|
||||
sb.append("imgmount ").append(driveLetter).append(" \"").append(img).append("\"").append(flags).append("\n");
|
||||
driveLetter++;
|
||||
}
|
||||
// Present a DOS prompt — the game runs from the CD
|
||||
sb.append("c:\n");
|
||||
sb.append("echo.\n");
|
||||
sb.append("echo This game runs from the CD-ROM.\n");
|
||||
sb.append("echo Type D: and press Enter, then look for the game executable (e.g. DOTT.EXE).\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private byte[] buildCdOnlyJsdosJson() {
|
||||
String json = "{"
|
||||
+ "\"autoexec\":{"
|
||||
+ "\"options\":{\"script\":{\"value\":\"c:\\\\\"}}"
|
||||
+ "},"
|
||||
+ "\"output\":{"
|
||||
+ "\"options\":{\"autolock\":{\"value\":true}}}"
|
||||
+ "}";
|
||||
return json.getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String buildDosboxConf(String relExe, List<String> cdImages) {
|
||||
// DOS uses \\ as path separator; / is a switch character.
|
||||
// If the executable is in a subdirectory, cd into it first.
|
||||
|
||||
Reference in New Issue
Block a user