Scaffold Java 21 multi-module project with UMD ISO + SFO parsing

- Gradle Kotlin DSL multi-module (core, ui, cli) with JPMS modules
- UMD ISO 9660 reader (UmdIsoReader/UmdIsoFile + sector devices)
- SFO (param.sfo) parser
- 'dump' CLI command: metadata + recursive file listing
- JUnit 5 + AssertJ tests (SFO and ISO reader)
- Switch license from MIT to GPL-3.0-or-later
This commit is contained in:
2026-09-15 21:00:22 -04:00
parent 3ad227615a
commit 797b0507bd
26 changed files with 2332 additions and 15 deletions
+18
View File
@@ -0,0 +1,18 @@
plugins {
application
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
application {
mainModule = "dev.quoije.umd.cli"
mainClass = "dev.quoije.umd.cli.Main"
}
dependencies {
implementation(project(":core"))
}
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.cli;
import dev.quoije.umd.iso.UmdIsoFile;
import dev.quoije.umd.iso.UmdIsoReader;
import dev.quoije.umd.sfo.Sfo;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Map;
/** Command-line front-end for the UMD player library. */
public final class Main {
private static final String USAGE = "usage: umdplayer dump <image.iso>";
private Main() {
}
public static void main(String[] args) {
if (args.length == 0) {
System.err.println(USAGE);
System.exit(2);
}
int exitCode;
try {
exitCode = switch (args[0]) {
case "dump" -> dump(requireArg(args, 1));
case "help", "--help", "-h" -> {
System.out.println(USAGE);
yield 0;
}
default -> {
System.err.println("unknown command: " + args[0]);
System.err.println(USAGE);
yield 2;
}
};
} catch (Exception e) {
System.err.println("error: " + e.getMessage());
exitCode = 1;
}
System.exit(exitCode);
}
private static String requireArg(String[] args, int index) {
if (args.length <= index) {
System.err.println(USAGE);
System.exit(2);
}
return args[index];
}
private static int dump(String imagePath) throws IOException {
Path path = Path.of(imagePath);
try (UmdIsoReader iso = new UmdIsoReader(path)) {
System.out.println("Image: " + path.toAbsolutePath());
System.out.println("Sectors: " + iso.getNumSectors()
+ " (" + (iso.getNumSectors() * 2048L) + " bytes)");
System.out.println("Joliet: " + iso.hasJolietExtension());
System.out.println();
printSfo(iso);
System.out.println();
printTree(iso);
}
return 0;
}
private static void printSfo(UmdIsoReader iso) throws IOException {
byte[] sfoBytes = readFile(iso, "UMD_VIDEO/param.sfo");
if (sfoBytes == null) {
System.out.println("param.sfo: not found");
return;
}
Sfo sfo = Sfo.parse(sfoBytes);
System.out.println("param.sfo entries:");
for (Map.Entry<String, Sfo.Entry> entry : sfo.entries().entrySet()) {
String value = switch (entry.getValue().format()) {
case Sfo.FORMAT_INT32 -> Integer.toString(entry.getValue().asInt());
case Sfo.FORMAT_UTF8 -> entry.getValue().asString();
default -> "(" + entry.getValue().data().length + " bytes)";
};
System.out.printf(" %-20s = %s%n", entry.getKey(), value);
}
String discId = sfo.getString("DISC_ID");
if (discId == null || discId.isBlank()) {
byte[] umdData = readFile(iso, "UMD_DATA.BIN");
if (umdData != null && umdData.length >= 9) {
discId = new String(umdData, 0, 9, StandardCharsets.US_ASCII);
System.out.println(" (DISC_ID from UMD_DATA.BIN: " + discId + ")");
}
}
}
private static void printTree(UmdIsoReader iso) throws IOException {
System.out.println("Files:");
printDirectory(iso, "", 0);
}
private static void printDirectory(UmdIsoReader iso, String path, int depth) throws IOException {
String[] files = iso.listDirectory(path);
for (String file : files) {
if (file.equals(".") || file.equals("\01")) {
continue;
}
String child = path.isEmpty() ? file : path + "/" + file;
if (iso.isDirectory(child)) {
System.out.println(" ".repeat(depth) + "D " + file);
printDirectory(iso, child, depth + 1);
} else {
try (UmdIsoFile umdFile = iso.getFile(child)) {
System.out.printf("%s%10d %s%n", " ".repeat(depth), umdFile.length(), file);
}
}
}
}
private static byte[] readFile(UmdIsoReader iso, String path) throws IOException {
if (!iso.hasFile(path)) {
return null;
}
try (UmdIsoFile file = iso.getFile(path)) {
byte[] buffer = new byte[(int) file.length()];
file.readFully(buffer);
return buffer;
}
}
}
+4
View File
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-3.0-or-later
module dev.quoije.umd.cli {
requires dev.quoije.umd.core;
}