Add native objects and bridge menu to playback

- NativeFunctionFactory resolves script method calls to native Java
  methods via reflection; VsmxNativeFunction wraps them for the VM.
- Native objects: Controller, MoviePlayer, Resource, Math, Timer,
  GlobalVariables, decoupled from the UI via a MenuHost interface.
- BaseNativeObject.callCallback invokes script callbacks like onAutoPlay.
- CLI 'rco' now wires the full menu and executes onAutoPlay headlessly.
- Validated against Batman Begins: onAutoPlay computes stream IDs and
  issues play(playList=3, chapter=1, video=0xE000, audio=0xBD00,
  audioFlag=1, subtitle=-1, subtitleFlag=0) — the anti-piracy warning
  clip with English video/audio.
This commit is contained in:
2026-09-15 22:45:04 -04:00
parent d56f6f12b0
commit 20d325d383
14 changed files with 742 additions and 2 deletions
+48 -2
View File
@@ -13,7 +13,13 @@ import dev.quoije.umd.rco.RcoFile;
import dev.quoije.umd.rco.vsmx.Vsmx;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
import dev.quoije.umd.rco.vsmx.objects.Controller;
import dev.quoije.umd.rco.vsmx.objects.GlobalVariables;
import dev.quoije.umd.rco.vsmx.objects.MenuHost;
import dev.quoije.umd.rco.vsmx.objects.MoviePlayer;
import dev.quoije.umd.rco.vsmx.objects.Resource;
import dev.quoije.umd.sfo.Sfo;
import java.io.ByteArrayOutputStream;
@@ -420,13 +426,53 @@ public final class Main {
VsmxInterpreter interpreter = new VsmxInterpreter();
interpreter.setVsmx(vsmx);
VsmxObject globals = new VsmxObject(interpreter, null);
final List<String> playCalls = new java.util.ArrayList<>();
MenuHost menuHost = new MenuHost() {
@Override
public void playMovie(int playListNumber, int chapterNumber, int videoNumber, int audioNumber,
int audioFlag, int subtitleNumber, int subtitleFlag) {
playCalls.add(String.format("play(playList=%d, chapter=%d, video=%d, audio=%d, audioFlag=%d, subtitle=%d, subtitleFlag=%d)",
playListNumber, chapterNumber, videoNumber, audioNumber, audioFlag, subtitleNumber, subtitleFlag));
}
@Override
public void stopMovie() {
playCalls.add("stop()");
}
@Override
public void changeResource(String resourceName) {
playCalls.add("changeResource(" + resourceName + ")");
}
};
String resourceName = rcoPath.substring(rcoPath.lastIndexOf('/') + 1).replace(".RCO", "");
VsmxNativeObject globals = GlobalVariables.create(interpreter, 854, 480);
VsmxNativeObject controller = Controller.create(interpreter, menuHost, resourceName);
VsmxNativeObject movieplayer = MoviePlayer.create(interpreter, menuHost, controller);
VsmxNativeObject resource = Resource.create(interpreter);
VsmxNativeObject math = dev.quoije.umd.rco.vsmx.objects.Math.create(interpreter);
globals.setPropertyValue(Controller.objectName, controller);
globals.setPropertyValue(MoviePlayer.objectName, movieplayer);
globals.setPropertyValue(Resource.objectName, resource);
globals.setPropertyValue(dev.quoije.umd.rco.vsmx.objects.Math.objectName, math);
interpreter.run(globals);
System.out.printf("global variables (%d):%n", globals.getPropertyNames().size());
for (String varName : globals.getPropertyNames()) {
VsmxBaseObject value = globals.getPropertyValue(varName);
System.out.printf(" %s = %s%n", varName, value);
if (!(value instanceof VsmxNativeObject)) {
System.out.printf(" %s = %s%n", varName, value);
}
}
// Execute the menu's autoplay handler (stored on the controller).
((Controller) controller.getObject()).callCallback(interpreter, "onAutoPlay", null);
System.out.printf("onAutoPlay play calls (%d):%n", playCalls.size());
for (String call : playCalls) {
System.out.println(" " + call);
}
if (!interpreter.getWarnings().isEmpty()) {