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:
@@ -13,7 +13,13 @@ import dev.quoije.umd.rco.RcoFile;
|
|||||||
import dev.quoije.umd.rco.vsmx.Vsmx;
|
import dev.quoije.umd.rco.vsmx.Vsmx;
|
||||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
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.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 dev.quoije.umd.sfo.Sfo;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -420,14 +426,54 @@ public final class Main {
|
|||||||
|
|
||||||
VsmxInterpreter interpreter = new VsmxInterpreter();
|
VsmxInterpreter interpreter = new VsmxInterpreter();
|
||||||
interpreter.setVsmx(vsmx);
|
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);
|
interpreter.run(globals);
|
||||||
|
|
||||||
System.out.printf("global variables (%d):%n", globals.getPropertyNames().size());
|
System.out.printf("global variables (%d):%n", globals.getPropertyNames().size());
|
||||||
for (String varName : globals.getPropertyNames()) {
|
for (String varName : globals.getPropertyNames()) {
|
||||||
VsmxBaseObject value = globals.getPropertyValue(varName);
|
VsmxBaseObject value = globals.getPropertyValue(varName);
|
||||||
|
if (!(value instanceof VsmxNativeObject)) {
|
||||||
System.out.printf(" %s = %s%n", varName, value);
|
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()) {
|
if (!interpreter.getWarnings().isEmpty()) {
|
||||||
System.out.printf("warnings (%d):%n", interpreter.getWarnings().size());
|
System.out.printf("warnings (%d):%n", interpreter.getWarnings().size());
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
|
||||||
|
/** A native (Java-implemented) function callable from the VSMX script. */
|
||||||
|
public interface INativeFunction {
|
||||||
|
|
||||||
|
int getArgs();
|
||||||
|
|
||||||
|
VsmxBaseObject call(VsmxBaseObject[] arguments);
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.INativeFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.NativeFunctionFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method reference: an object plus a method name, resolved at call time.
|
* A method reference: an object plus a method name, resolved at call time.
|
||||||
*/
|
*/
|
||||||
@@ -58,6 +61,16 @@ public class VsmxMethod extends VsmxBaseObject {
|
|||||||
return (VsmxFunction) object;
|
return (VsmxFunction) object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INativeFunction nativeFunction = null;
|
||||||
|
if (object instanceof VsmxNativeObject nativeObject) {
|
||||||
|
nativeFunction = NativeFunctionFactory.getInstance().getNativeFunction(nativeObject, name, numberOfArguments);
|
||||||
|
} else {
|
||||||
|
nativeFunction = NativeFunctionFactory.getInstance().getNativeFunction(object, name, numberOfArguments);
|
||||||
|
}
|
||||||
|
if (nativeFunction != null) {
|
||||||
|
return new VsmxNativeFunction(interpreter, nativeFunction);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.INativeFunction;
|
||||||
|
|
||||||
|
/** A function value backed by a native Java method. */
|
||||||
|
public final class VsmxNativeFunction extends VsmxFunction {
|
||||||
|
|
||||||
|
private final INativeFunction nativeFunction;
|
||||||
|
private final VsmxBaseObject[] arguments;
|
||||||
|
private VsmxBaseObject returnValue;
|
||||||
|
|
||||||
|
public VsmxNativeFunction(VsmxInterpreter interpreter, INativeFunction nativeFunction) {
|
||||||
|
super(interpreter, nativeFunction.getArgs(), 0, -1);
|
||||||
|
this.nativeFunction = nativeFunction;
|
||||||
|
this.arguments = new VsmxBaseObject[nativeFunction.getArgs() + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void call(VsmxCallState callState) {
|
||||||
|
arguments[0] = callState.getThisObject();
|
||||||
|
for (int i = 1; i < arguments.length; i++) {
|
||||||
|
arguments[i] = callState.getLocalVar(i);
|
||||||
|
}
|
||||||
|
returnValue = nativeFunction.call(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VsmxBaseObject getReturnValue() {
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "VsmxNativeFunction[" + nativeFunction + ", returnValue=" + returnValue + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
package dev.quoije.umd.rco.vsmx.interpreter;
|
package dev.quoije.umd.rco.vsmx.interpreter;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.INativeFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.objects.NativeFunctionFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to a property or array index of an object, so assignments and
|
* A reference to a property or array index of an object, so assignments and
|
||||||
* increments/decrements can write back through it.
|
* increments/decrements can write back through it.
|
||||||
@@ -48,6 +51,13 @@ public class VsmxReference extends VsmxBaseObject {
|
|||||||
if (refProperty == null) {
|
if (refProperty == null) {
|
||||||
return refObject.getPropertyValue(refIndex);
|
return refObject.getPropertyValue(refIndex);
|
||||||
}
|
}
|
||||||
|
if (!refObject.hasPropertyValue(refProperty) && refObject instanceof VsmxNativeObject nativeObject) {
|
||||||
|
INativeFunction nativeFunction = NativeFunctionFactory.getInstance()
|
||||||
|
.getNativeFunction(nativeObject, refProperty, numberOfArguments);
|
||||||
|
if (nativeFunction != null) {
|
||||||
|
return new VsmxNativeFunction(interpreter, nativeFunction);
|
||||||
|
}
|
||||||
|
}
|
||||||
return refObject.getPropertyValue(refProperty);
|
return refObject.getPropertyValue(refProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
package dev.quoije.umd.rco.vsmx.objects;
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +31,14 @@ public abstract class BaseNativeObject {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Invokes a script callback property (e.g. "onAutoPlay") stored on this object. */
|
||||||
|
public void callCallback(VsmxInterpreter interpreter, String callbackName, VsmxBaseObject[] arguments) {
|
||||||
|
VsmxBaseObject callback = object.getPropertyValue(callbackName);
|
||||||
|
if (callback instanceof VsmxFunction function) {
|
||||||
|
interpreter.interpretFunction(function, null, arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onDisplayUpdated() {
|
public void onDisplayUpdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxArray;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBoolean;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNull;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxString;
|
||||||
|
|
||||||
|
/** The controller native object: input focus and resource management. */
|
||||||
|
public class Controller extends BaseNativeObject {
|
||||||
|
|
||||||
|
public static final String objectName = "controller";
|
||||||
|
|
||||||
|
private final VsmxInterpreter interpreter;
|
||||||
|
private final MenuHost menuHost;
|
||||||
|
private VsmxBaseObject userData;
|
||||||
|
private String resource;
|
||||||
|
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter, MenuHost menuHost, String resource) {
|
||||||
|
Controller controller = new Controller(interpreter, menuHost);
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, controller);
|
||||||
|
controller.setObject(object);
|
||||||
|
controller.resource = resource;
|
||||||
|
|
||||||
|
object.setPropertyValue("onChangeResource", VsmxNull.singleton);
|
||||||
|
object.setPropertyValue("onMenu", VsmxNull.singleton);
|
||||||
|
object.setPropertyValue("onExit", VsmxNull.singleton);
|
||||||
|
object.setPropertyValue("onAutoPlay", VsmxNull.singleton);
|
||||||
|
object.setPropertyValue("onContinuePlay", VsmxNull.singleton);
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Controller(VsmxInterpreter interpreter, MenuHost menuHost) {
|
||||||
|
this.interpreter = interpreter;
|
||||||
|
this.menuHost = menuHost;
|
||||||
|
this.userData = new VsmxArray(interpreter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxInterpreter getInterpreter() {
|
||||||
|
return interpreter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResource() {
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject getUserData(VsmxBaseObject object) {
|
||||||
|
return userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject setUserData(VsmxBaseObject object, VsmxBaseObject userData) {
|
||||||
|
this.userData = userData;
|
||||||
|
return VsmxBoolean.getValue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject changeResource(VsmxBaseObject object, VsmxBaseObject newResource) {
|
||||||
|
String resourceName = newResource.getStringValue();
|
||||||
|
if (!resource.equals(resourceName)) {
|
||||||
|
resource = resourceName;
|
||||||
|
menuHost.changeResource(resourceName);
|
||||||
|
}
|
||||||
|
return VsmxBoolean.getValue(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLeft() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRight() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPush() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxArray;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBoolean;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNumber;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxUndefined;
|
||||||
|
|
||||||
|
/** The global scope object, exposing standard globals and console helpers. */
|
||||||
|
public class GlobalVariables extends BaseNativeObject {
|
||||||
|
|
||||||
|
private final StringBuilder log = new StringBuilder();
|
||||||
|
|
||||||
|
/** Creates the global variables object, including standard globals and the timer. */
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter, int screenWidth, int screenHeight) {
|
||||||
|
GlobalVariables globalVariables = new GlobalVariables();
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, globalVariables);
|
||||||
|
globalVariables.setObject(object);
|
||||||
|
|
||||||
|
object.setPropertyValue("undefined", VsmxUndefined.singleton);
|
||||||
|
object.setPropertyValue("Array", new VsmxArray(interpreter));
|
||||||
|
object.setPropertyValue("Object", new VsmxObject(interpreter, null));
|
||||||
|
object.setPropertyValue("timer", Timer.create(interpreter));
|
||||||
|
object.setPropertyValue("x", new VsmxNumber(interpreter, 0));
|
||||||
|
object.setPropertyValue("y", new VsmxNumber(interpreter, 0));
|
||||||
|
object.setPropertyValue("width", new VsmxNumber(interpreter, screenWidth));
|
||||||
|
object.setPropertyValue("height", new VsmxNumber(interpreter, screenHeight));
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLog() {
|
||||||
|
return log.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeln(VsmxBaseObject object) {
|
||||||
|
log.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeln(VsmxBaseObject object, VsmxBaseObject s1) {
|
||||||
|
log.append(s1.getStringValue()).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeln(VsmxBaseObject object, VsmxBaseObject s1, VsmxBaseObject s2) {
|
||||||
|
log.append(s1.getStringValue()).append(s2.getStringValue()).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeln(VsmxBaseObject object, VsmxBaseObject s1, VsmxBaseObject s2, VsmxBaseObject s3) {
|
||||||
|
log.append(s1.getStringValue()).append(s3.getStringValue()).append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject parseFloat(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(object.getInterpreter(), value.getFloatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject parseInt(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(object.getInterpreter(), value.getIntValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject isNaN(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return VsmxBoolean.getValue(Float.isNaN(value.getFloatValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject Float(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(object.getInterpreter(), value.getFloatValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject Int(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(object.getInterpreter(), value.getIntValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
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.VsmxNumber;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/** The Math native object. */
|
||||||
|
public class Math extends BaseNativeObject {
|
||||||
|
|
||||||
|
public static final String objectName = "Math";
|
||||||
|
|
||||||
|
private final VsmxInterpreter interpreter;
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter) {
|
||||||
|
Math math = new Math(interpreter);
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, math);
|
||||||
|
math.setObject(object);
|
||||||
|
object.setPropertyValue("PI", new VsmxNumber(interpreter, (float) java.lang.Math.PI));
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Math(VsmxInterpreter interpreter) {
|
||||||
|
this.interpreter = interpreter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject random(VsmxBaseObject object) {
|
||||||
|
return new VsmxNumber(interpreter, random.nextFloat());
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject floor(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(interpreter, (float) java.lang.Math.floor(value.getFloatValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject abs(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(interpreter, (float) java.lang.Math.abs(value.getFloatValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject sin(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(interpreter, (float) java.lang.Math.sin(value.getFloatValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject cos(VsmxBaseObject object, VsmxBaseObject value) {
|
||||||
|
return new VsmxNumber(interpreter, (float) java.lang.Math.cos(value.getFloatValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bridge from the menu script's MoviePlayer to the actual playback engine.
|
||||||
|
* Implemented by the UI/player layer.
|
||||||
|
*/
|
||||||
|
public interface MenuHost {
|
||||||
|
|
||||||
|
void playMovie(int playListNumber, int chapterNumber, int videoNumber,
|
||||||
|
int audioNumber, int audioFlag, int subtitleNumber, int subtitleFlag);
|
||||||
|
|
||||||
|
void stopMovie();
|
||||||
|
|
||||||
|
void changeResource(String resourceName);
|
||||||
|
|
||||||
|
default void onPlayListEnd(int playListNumber) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onChapter(int chapterNumber) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxArray;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNumber;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxString;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxUndefined;
|
||||||
|
|
||||||
|
/** The movie player native object, bridging to the playback engine via MenuHost. */
|
||||||
|
public class MoviePlayer extends BaseNativeObject {
|
||||||
|
|
||||||
|
public static final int DEFAULT_WIDTH = 854;
|
||||||
|
public static final int DEFAULT_HEIGHT = 480;
|
||||||
|
public static final String objectName = "movieplayer";
|
||||||
|
|
||||||
|
private final VsmxInterpreter interpreter;
|
||||||
|
private final MenuHost menuHost;
|
||||||
|
private final VsmxNativeObject controller;
|
||||||
|
|
||||||
|
private boolean playing;
|
||||||
|
private boolean menuMode;
|
||||||
|
private int playListNumber;
|
||||||
|
private int chapterNumber;
|
||||||
|
private int videoNumber;
|
||||||
|
private int audioNumber;
|
||||||
|
private int audioFlag;
|
||||||
|
private int subtitleNumber;
|
||||||
|
private int subtitleFlag;
|
||||||
|
private int width = DEFAULT_WIDTH;
|
||||||
|
private int height = DEFAULT_HEIGHT;
|
||||||
|
private int x;
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter, MenuHost menuHost, VsmxNativeObject controller) {
|
||||||
|
MoviePlayer moviePlayer = new MoviePlayer(interpreter, menuHost, controller);
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, moviePlayer);
|
||||||
|
moviePlayer.setObject(object);
|
||||||
|
|
||||||
|
object.setPropertyValue("audioLanguageCode", new VsmxString(interpreter, "en"));
|
||||||
|
object.setPropertyValue("subtitleLanguageCode", new VsmxString(interpreter, "en"));
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MoviePlayer(VsmxInterpreter interpreter, MenuHost menuHost, VsmxNativeObject controller) {
|
||||||
|
this.interpreter = interpreter;
|
||||||
|
this.menuHost = menuHost;
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void play(VsmxBaseObject object,
|
||||||
|
VsmxBaseObject pauseMode,
|
||||||
|
VsmxBaseObject menuMode,
|
||||||
|
VsmxBaseObject playListNumber,
|
||||||
|
VsmxBaseObject chapterNumber,
|
||||||
|
VsmxBaseObject videoNumber,
|
||||||
|
VsmxBaseObject audioNumber,
|
||||||
|
VsmxBaseObject audioFlag,
|
||||||
|
VsmxBaseObject subtitleNumber,
|
||||||
|
VsmxBaseObject subtitleFlag,
|
||||||
|
VsmxBaseObject unknownBool) {
|
||||||
|
playing = true;
|
||||||
|
boolean previousMenuMode = this.menuMode;
|
||||||
|
this.menuMode = menuMode.getBooleanValue();
|
||||||
|
this.playListNumber = playListNumber.getIntValue();
|
||||||
|
this.chapterNumber = chapterNumber.getIntValue();
|
||||||
|
this.videoNumber = videoNumber.getIntValue();
|
||||||
|
this.audioNumber = audioNumber.getIntValue();
|
||||||
|
this.audioFlag = audioFlag.getIntValue();
|
||||||
|
this.subtitleNumber = subtitleNumber.getIntValue();
|
||||||
|
this.subtitleFlag = subtitleFlag.getIntValue();
|
||||||
|
|
||||||
|
menuHost.playMovie(this.playListNumber, this.chapterNumber, this.videoNumber,
|
||||||
|
this.audioNumber, this.audioFlag, this.subtitleNumber, this.subtitleFlag);
|
||||||
|
|
||||||
|
if (!previousMenuMode && this.menuMode) {
|
||||||
|
VsmxBaseObject callback = controller.getPropertyValue("onMenu");
|
||||||
|
if (callback instanceof VsmxFunction function) {
|
||||||
|
interpreter.interpretFunction(function, null, new VsmxBaseObject[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(VsmxBaseObject object, VsmxBaseObject unknownInt, VsmxBaseObject unknownBool) {
|
||||||
|
playing = false;
|
||||||
|
menuHost.stopMovie();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resume(VsmxBaseObject object) {
|
||||||
|
playing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject getResumeInfo(VsmxBaseObject object) {
|
||||||
|
if (playing) {
|
||||||
|
VsmxBaseObject resumeInfo = new VsmxObject(interpreter, "ResumeInfo");
|
||||||
|
resumeInfo.setPropertyValue("playListNumber", new VsmxNumber(interpreter, playListNumber));
|
||||||
|
return resumeInfo;
|
||||||
|
}
|
||||||
|
return VsmxUndefined.singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void changeResumeInfo(VsmxBaseObject object, VsmxBaseObject videoNumber, VsmxBaseObject audioNumber,
|
||||||
|
VsmxBaseObject audioFlag, VsmxBaseObject subtitleNumber, VsmxBaseObject subtitleFlag) {
|
||||||
|
this.videoNumber = videoNumber.getIntValue();
|
||||||
|
this.audioNumber = audioNumber.getIntValue();
|
||||||
|
this.audioFlag = audioFlag.getIntValue();
|
||||||
|
this.subtitleNumber = subtitleNumber.getIntValue();
|
||||||
|
this.subtitleFlag = subtitleFlag.getIntValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject getPlayerStatus(VsmxBaseObject object) {
|
||||||
|
if (playing) {
|
||||||
|
VsmxBaseObject status = new VsmxObject(interpreter, "PlayerStatus");
|
||||||
|
status.setPropertyValue("playListNumber", new VsmxNumber(interpreter, playListNumber));
|
||||||
|
status.setPropertyValue("chapterNumber", new VsmxNumber(interpreter, chapterNumber));
|
||||||
|
status.setPropertyValue("videoNumber", new VsmxNumber(interpreter, videoNumber));
|
||||||
|
status.setPropertyValue("audioNumber", new VsmxNumber(interpreter, audioNumber));
|
||||||
|
status.setPropertyValue("audioFlag", new VsmxNumber(interpreter, audioFlag));
|
||||||
|
status.setPropertyValue("subtitleNumber", new VsmxNumber(interpreter, subtitleNumber));
|
||||||
|
status.setPropertyValue("subtitleFlag", new VsmxNumber(interpreter, subtitleFlag));
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return VsmxUndefined.singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject getSize(VsmxBaseObject object) {
|
||||||
|
VsmxArray size = new VsmxArray(interpreter, 2);
|
||||||
|
size.setPropertyValue(0, new VsmxNumber(interpreter, width));
|
||||||
|
size.setPropertyValue(1, new VsmxNumber(interpreter, height));
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSize(VsmxBaseObject object, VsmxBaseObject width, VsmxBaseObject height) {
|
||||||
|
this.width = width.getIntValue();
|
||||||
|
this.height = height.getIntValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject getPos(VsmxBaseObject object) {
|
||||||
|
VsmxArray pos = new VsmxArray(interpreter, 2);
|
||||||
|
pos.setPropertyValue(0, new VsmxNumber(interpreter, x));
|
||||||
|
pos.setPropertyValue(1, new VsmxNumber(interpreter, y));
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPos(VsmxBaseObject object, VsmxBaseObject x, VsmxBaseObject y) {
|
||||||
|
this.x = x.getIntValue();
|
||||||
|
this.y = y.getIntValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPlayListEnd(int playListNumber) {
|
||||||
|
VsmxBaseObject callback = getObject().getPropertyValue("onPlayListEnd");
|
||||||
|
if (callback instanceof VsmxFunction function) {
|
||||||
|
VsmxBaseObject argument = new VsmxObject(interpreter, null);
|
||||||
|
argument.setPropertyValue("playListNumber", new VsmxNumber(interpreter, playListNumber));
|
||||||
|
interpreter.interpretFunction(function, null, new VsmxBaseObject[]{argument});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChapter(int chapterNumber) {
|
||||||
|
VsmxBaseObject callback = getObject().getPropertyValue("onChapter");
|
||||||
|
if (callback instanceof VsmxFunction function) {
|
||||||
|
VsmxBaseObject argument = new VsmxObject(interpreter, null);
|
||||||
|
argument.setPropertyValue("chapterNumber", new VsmxNumber(interpreter, chapterNumber));
|
||||||
|
interpreter.interpretFunction(function, null, new VsmxBaseObject[]{argument});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUp() {
|
||||||
|
((Controller) controller.getObject()).onUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDown() {
|
||||||
|
((Controller) controller.getObject()).onDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLeft() {
|
||||||
|
((Controller) controller.getObject()).onLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRight() {
|
||||||
|
((Controller) controller.getObject()).onRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPush() {
|
||||||
|
((Controller) controller.getObject()).onPush();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.INativeFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves script method calls to native Java methods via reflection.
|
||||||
|
* Ported from JPCSP.
|
||||||
|
*/
|
||||||
|
public final class NativeFunctionFactory {
|
||||||
|
|
||||||
|
private static final NativeFunctionFactory SINGLETON = new NativeFunctionFactory();
|
||||||
|
|
||||||
|
private final List<String> missing = new ArrayList<>();
|
||||||
|
|
||||||
|
private NativeFunctionFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NativeFunctionFactory getInstance() {
|
||||||
|
return SINGLETON;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Names of native methods that were requested but not found. */
|
||||||
|
public List<String> getMissing() {
|
||||||
|
return missing;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record NativeFunction(Object object, Method method, int args) implements INativeFunction {
|
||||||
|
@Override
|
||||||
|
public int getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VsmxBaseObject call(VsmxBaseObject[] arguments) {
|
||||||
|
VsmxBaseObject returnValue = null;
|
||||||
|
try {
|
||||||
|
Object result = method.invoke(object, (Object[]) arguments);
|
||||||
|
if (result instanceof VsmxBaseObject obj) {
|
||||||
|
returnValue = obj;
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private INativeFunction getNativeFunctionInterface(Object object, String name, int numberOfArguments) {
|
||||||
|
for (int args = numberOfArguments + 1; args < 12; args++) {
|
||||||
|
Class<?>[] arguments = new Class<?>[args];
|
||||||
|
for (int i = 0; i < arguments.length; i++) {
|
||||||
|
arguments[i] = VsmxBaseObject.class;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Method method = object.getClass().getMethod(name, arguments);
|
||||||
|
return new NativeFunction(object, method, args - 1);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
missing.add(object.getClass().getSimpleName() + "." + name + "(args=" + (numberOfArguments + 1) + ")");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public INativeFunction getNativeFunction(VsmxNativeObject object, String name, int numberOfArguments) {
|
||||||
|
BaseNativeObject nativeObject = object.getObject();
|
||||||
|
return getNativeFunctionInterface(nativeObject, name, numberOfArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public INativeFunction getNativeFunction(VsmxBaseObject object, String name, int numberOfArguments) {
|
||||||
|
return getNativeFunctionInterface(object, name, numberOfArguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource native object, exposing the RCO object tables to the script.
|
||||||
|
* The object tree population is layered in with the renderer.
|
||||||
|
*/
|
||||||
|
public class Resource extends BaseNativeObject {
|
||||||
|
|
||||||
|
public static final String objectName = "resource";
|
||||||
|
public static final String rootName = "root";
|
||||||
|
public static final String childrenName = "children";
|
||||||
|
public static final String textureName = "texture";
|
||||||
|
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter) {
|
||||||
|
Resource resource = new Resource();
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, resource);
|
||||||
|
resource.setObject(object);
|
||||||
|
|
||||||
|
object.setPropertyValue("pagetable", new VsmxObject(interpreter, null));
|
||||||
|
object.setPropertyValue("animtable", new VsmxObject(interpreter, null));
|
||||||
|
object.setPropertyValue("soundtable", new VsmxObject(interpreter, null));
|
||||||
|
object.setPropertyValue("texturetable", new VsmxObject(interpreter, null));
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
package dev.quoije.umd.rco.vsmx.objects;
|
||||||
|
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxArray;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxBaseObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxFunction;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxInterpreter;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNativeObject;
|
||||||
|
import dev.quoije.umd.rco.vsmx.interpreter.VsmxNumber;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** The Timer native object: setTimeout-style intervals for menu animations. */
|
||||||
|
public class Timer extends BaseNativeObject {
|
||||||
|
|
||||||
|
private final VsmxInterpreter interpreter;
|
||||||
|
private int currentTimerId;
|
||||||
|
private final Map<Integer, Thread> timers = new HashMap<>();
|
||||||
|
|
||||||
|
public static VsmxNativeObject create(VsmxInterpreter interpreter) {
|
||||||
|
Timer timer = new Timer(interpreter);
|
||||||
|
VsmxNativeObject object = new VsmxNativeObject(interpreter, timer);
|
||||||
|
timer.setObject(object);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Timer(VsmxInterpreter interpreter) {
|
||||||
|
this.interpreter = interpreter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval,
|
||||||
|
VsmxBaseObject... parameters) {
|
||||||
|
int id = currentTimerId++;
|
||||||
|
long millis = interval.getIntValue() * 1000L;
|
||||||
|
VsmxBaseObject[] params = parameters;
|
||||||
|
Thread thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(millis);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (function instanceof VsmxFunction f) {
|
||||||
|
interpreter.interpretFunction(f, object, params);
|
||||||
|
}
|
||||||
|
}, "umd-timer-" + id);
|
||||||
|
thread.setDaemon(true);
|
||||||
|
timers.put(id, thread);
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
VsmxArray result = new VsmxArray(interpreter, 1);
|
||||||
|
result.setPropertyValue(0, new VsmxNumber(interpreter, id));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval) {
|
||||||
|
return setInterval(object, function, interval, new VsmxBaseObject[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval, VsmxBaseObject param1) {
|
||||||
|
return setInterval(object, function, interval, new VsmxBaseObject[]{param1});
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval, VsmxBaseObject param1, VsmxBaseObject param2) {
|
||||||
|
return setInterval(object, function, interval, new VsmxBaseObject[]{param1, param2});
|
||||||
|
}
|
||||||
|
|
||||||
|
public VsmxBaseObject setInterval(VsmxBaseObject object, VsmxBaseObject function, VsmxBaseObject interval, VsmxBaseObject param1, VsmxBaseObject param2, VsmxBaseObject param3) {
|
||||||
|
return setInterval(object, function, interval, new VsmxBaseObject[]{param1, param2, param3});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearInterval(VsmxBaseObject object, VsmxBaseObject id) {
|
||||||
|
int idValue = id.getPropertyValue(0).getIntValue();
|
||||||
|
Thread thread = timers.remove(idValue);
|
||||||
|
if (thread != null) {
|
||||||
|
thread.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user