rough, this is a waste of tokens...

This commit is contained in:
2026-09-17 22:35:37 -04:00
parent 8fa8dc4ba9
commit d9c44d2f26
8 changed files with 156955 additions and 39 deletions
@@ -72,6 +72,17 @@ public final class PlayerApp extends Application {
private volatile VsmxNativeObject controllerObject;
private long lastMenuKeyNanos = 0;
/** The open disc image, kept for the lifetime of the app so menu resources
* (RCO files) can be switched at runtime. */
private volatile UmdIsoReader iso;
private volatile String isoPath;
private volatile Playlist playlist;
/** The script global scope, shared across resource switches so globals
* (e.g. {@code g_nResChangeInfo}) survive. */
private volatile VsmxNativeObject globalVariables;
/** False until the first resource has run; later loads fire onChangeResource. */
private boolean resourceInitialized;
/** The active decode engine, closed when a new stream starts. */
private volatile PlaybackEngine currentEngine;
/** Bumped each time playback starts so stale streams stop themselves. */
@@ -173,25 +184,55 @@ public final class PlayerApp extends Application {
}
private void loadMenu(String isoPath) {
try (UmdIsoReader iso = new UmdIsoReader(Path.of(isoPath))) {
Playlist playlist = Playlist.parse(iso);
byte[] rcoData = readFile(iso, "UMD_VIDEO/RESOURCE/EN100000.RCO");
try {
this.iso = new UmdIsoReader(Path.of(isoPath));
this.isoPath = isoPath;
this.playlist = Playlist.parse(iso);
this.resourceInitialized = false;
loadResource("EN100000");
} catch (Exception e) {
e.printStackTrace();
}
}
/** Loads (or switches to) a menu resource and starts its script. */
private void loadResource(String resourceName) {
UmdIsoReader disc = iso;
if (disc == null) {
return;
}
try {
byte[] rcoData = readFile(disc, "UMD_VIDEO/RESOURCE/" + resourceName + ".RCO");
if (rcoData == null) {
System.err.println("EN100000.RCO not found");
System.err.println("Menu resource not found: " + resourceName + ".RCO");
return;
}
RcoFile rco = new RcoFile(rcoData);
if (!rco.isValid()) {
System.err.println("RCO invalid");
System.err.println("RCO invalid: " + resourceName);
return;
}
Vsmx vsmx = new Vsmx(rco.vsmxData(), "main");
// Each resource gets a fresh interpreter so that switching
// resources never swaps the bytecode out from under an already
// running script (the old script keeps its own code and state).
VsmxInterpreter interpreter = new VsmxInterpreter();
Vsmx vsmx = new Vsmx(rco.vsmxData(), "main");
interpreter.setVsmx(vsmx);
this.menuInterpreter = interpreter;
if (this.globalVariables == null) {
this.globalVariables = GlobalVariables.create(interpreter, SCREEN_WIDTH, SCREEN_HEIGHT);
}
renderer = new RcoRenderer(menuCanvas);
if (renderer == null) {
renderer = new RcoRenderer(menuCanvas);
} else {
// The previous resource's object tree belongs to a different
// script; drop it before the new page tree is registered.
renderer.changeResource();
}
MenuHost menuHost = new MenuHost() {
@Override
@@ -199,7 +240,7 @@ public final class PlayerApp extends Application {
int audioFlag, int subtitleNumber, int subtitleFlag) {
Playlist.Stream stream = playlist.streamForPlayListNumber(playListNumber);
if (stream != null) {
startPlayback(isoPath, stream, playListNumber);
startPlayback(PlayerApp.this.isoPath, stream, playListNumber);
}
}
@@ -214,26 +255,32 @@ public final class PlayerApp extends Application {
}
@Override
public void changeResource(String resourceName) {
public void changeResource(String newResource) {
loadResource(newResource);
}
};
VsmxNativeObject globals = GlobalVariables.create(interpreter, SCREEN_WIDTH, SCREEN_HEIGHT);
VsmxNativeObject controllerObject = Controller.create(interpreter, menuHost, "EN100000");
VsmxNativeObject controllerObject = Controller.create(interpreter, menuHost, resourceName);
VsmxNativeObject movieplayer = MoviePlayer.create(interpreter, menuHost, controllerObject);
VsmxNativeObject resource = Resource.create(interpreter, renderer, controllerObject, rco);
VsmxNativeObject math = Math.create(interpreter);
globals.setPropertyValue(Controller.objectName, controllerObject);
globals.setPropertyValue(MoviePlayer.objectName, movieplayer);
globals.setPropertyValue(Resource.objectName, resource);
globals.setPropertyValue(Math.objectName, math);
globalVariables.setPropertyValue(Controller.objectName, controllerObject);
globalVariables.setPropertyValue(MoviePlayer.objectName, movieplayer);
globalVariables.setPropertyValue(Resource.objectName, resource);
globalVariables.setPropertyValue(Math.objectName, math);
interpreter.run(globalVariables);
interpreter.run(globals);
controller = (Controller) controllerObject.getObject();
moviePlayer = (MoviePlayer) movieplayer.getObject();
menuInterpreter = interpreter;
this.controllerObject = controllerObject;
controller.callCallback(interpreter, "onAutoPlay", null);
if (!resourceInitialized) {
resourceInitialized = true;
controller.callCallback(interpreter, "onAutoPlay", null);
} else {
controller.callCallback(interpreter, "onChangeResource", null);
}
} catch (Exception e) {
e.printStackTrace();
}
@@ -11,6 +11,7 @@ import javafx.application.Platform;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -158,10 +159,28 @@ public final class RcoRenderer implements Display {
if (data == null || data.length == 0) {
return null;
}
// Decode synchronously through ImageIO. JavaFX's `new Image(InputStream)`
// loads in the background and `GraphicsContext.drawImage` silently skips
// images whose pixels have not been uploaded yet, which leaves the whole
// menu invisible even though the image headers are readable.
try (ByteArrayInputStream in = new ByteArrayInputStream(data)) {
return new Image(in);
java.awt.image.BufferedImage buffered = javax.imageio.ImageIO.read(in);
if (buffered == null) {
return null;
}
return toFxImage(buffered);
} catch (IOException e) {
return null;
}
}
private static Image toFxImage(java.awt.image.BufferedImage buffered) {
int w = buffered.getWidth();
int h = buffered.getHeight();
int[] argb = new int[w * h];
buffered.getRGB(0, 0, w, h, argb, 0, w);
WritableImage image = new WritableImage(w, h);
image.getPixelWriter().setPixels(0, 0, w, h, javafx.scene.image.PixelFormat.getIntArgbInstance(), argb, 0, w);
return image;
}
}