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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
module dev.quoije.umd.cli {
|
||||
requires dev.quoije.umd.core;
|
||||
}
|
||||
Reference in New Issue
Block a user