Vendor grrr emote locally and use PNG in README

This commit is contained in:
2026-09-15 22:49:24 -04:00
parent c26250fb53
commit a29f27cd1d
19 changed files with 538 additions and 0 deletions
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco;
/** The rendering surface backing an RCO menu. Implemented by the UI layer. */
public interface Display {
void setFocus(IDisplay display);
void add(IDisplay display);
void remove(IDisplay display);
void repaint();
void changeResource();
}
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco;
/** A renderable RCO object (plane, button, text, ...). */
public interface IDisplay {
Object getImage();
Object getAnimImage();
int getX();
int getY();
int getWidth();
int getHeight();
float getAlpha();
void setFocus();
void focusOut();
void onUp();
void onDown();
void onLeft();
void onRight();
void onPush();
}
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/** Orders the typed fields of an RCO object record for serialization. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ObjectField {
int order();
}
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco;
import dev.quoije.umd.rco.object.BaseObject;
import dev.quoije.umd.rco.object.ImageObject;
import java.util.Map;
/**
* Mutable read cursor and shared reference tables used when deserializing
* RCO object and animation records.
*/
public final class RcoContext {
public byte[] buffer;
public int offset;
public Map<Integer, String> events;
public Map<Integer, ImageObject> images;
public Map<Integer, BaseObject> objects;
public RcoContext(byte[] buffer, int offset, Map<Integer, String> events,
Map<Integer, ImageObject> images, Map<Integer, BaseObject> objects) {
this.buffer = buffer;
this.offset = offset;
this.events = events;
this.images = images;
this.objects = objects;
}
}
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.object;
import dev.quoije.umd.rco.Display;
import dev.quoije.umd.rco.ObjectField;
import dev.quoije.umd.rco.RcoContext;
import dev.quoije.umd.rco.RcoFile;
import dev.quoije.umd.rco.type.BaseType;
import dev.quoije.umd.rco.type.EventType;
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.VsmxString;
import dev.quoije.umd.rco.vsmx.objects.BaseNativeObject;
import dev.quoije.umd.rco.vsmx.objects.Controller;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
/**
* Base of RCO object records (pages, planes, buttons, ...). Fields are
* declared as {@link BaseType} subclasses and read/measured via reflection,
* mirroring JPCSP's object model.
*/
public abstract class BaseObject extends BaseNativeObject implements IDisplay {
protected Display display;
protected Controller controller;
private String name;
private BaseNativeObject parent;
private Field[] getSortedFields() {
Field[] fields = getClass().getFields();
Arrays.sort(fields, Comparator.comparingInt(f -> {
ObjectField annotation = f.getAnnotation(ObjectField.class);
return annotation == null ? Integer.MAX_VALUE : annotation.order();
}));
return fields;
}
/** Deserializes all typed fields from the record data. */
public void read(RcoContext context) {
for (Field field : getSortedFields()) {
if (BaseType.class.isAssignableFrom(field.getType())) {
try {
BaseType type = (BaseType) field.get(this);
if (type == null) {
type = (BaseType) field.getType().getDeclaredConstructor().newInstance();
field.set(this, type);
}
type.read(context);
} catch (ReflectiveOperationException e) {
// Ignore
}
}
}
}
/** Size in bytes of the serialized typed fields. */
public int size() {
int size = 0;
for (Field field : getSortedFields()) {
if (BaseType.class.isAssignableFrom(field.getType())) {
try {
BaseType type = (BaseType) field.get(this);
if (type == null) {
type = (BaseType) field.getType().getDeclaredConstructor().newInstance();
}
size += type.size();
} catch (ReflectiveOperationException e) {
// Ignore
}
}
}
return size;
}
/** Resolves reference fields against the RCO tables. */
public void init(RcoContext context) {
for (Field field : getSortedFields()) {
if (BaseType.class.isAssignableFrom(field.getType())) {
try {
BaseType type = (BaseType) field.get(this);
if (type != null) {
type.init(context);
}
} catch (ReflectiveOperationException e) {
// Ignore
}
}
}
}
/** Creates the VSMX-side object and registers it under the entry label. */
public VsmxBaseObject createVsmxObject(VsmxInterpreter interpreter, VsmxBaseObject parent, RcoFile.Entry entry) {
VsmxNativeObject object = new VsmxNativeObject(interpreter, this);
setObject(object);
if (entry.label != null) {
name = entry.label;
object.setPropertyValue("name", new VsmxString(interpreter, entry.label));
parent.setPropertyValue(entry.label, object);
}
if (entry.parent != null && entry.parent.vsmxObject instanceof VsmxNativeObject parentNative) {
setParent(parentNative.getObject());
}
return object;
}
public void setDisplay(Display display) {
this.display = display;
}
public void setController(Controller controller) {
this.controller = controller;
}
public BaseNativeObject getParent() {
return parent;
}
public void setParent(BaseNativeObject parent) {
this.parent = parent;
}
public String getName() {
return name;
}
protected void trigger(EventType event) {
if (event.getEvent() != null && controller != null) {
controller.getInterpreter().interpretScript(getObject(), event.getEvent());
}
}
protected void onDisplayUpdated() {
if (display != null) {
display.repaint();
}
}
@Override
public Object getImage() {
return null;
}
@Override
public Object getAnimImage() {
return getImage();
}
@Override
public int getX() {
return 0;
}
@Override
public int getY() {
return 0;
}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public float getAlpha() {
return 1f;
}
@Override
public void setFocus() {
}
@Override
public void focusOut() {
}
@Override
public void onUp() {
}
@Override
public void onDown() {
}
@Override
public void onLeft() {
}
@Override
public void onRight() {
}
@Override
public void onPush() {
}
@Override
public String toString() {
return getClass().getSimpleName() + "[name=" + name + "]";
}
}
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.object;
/** An image entry from the RCO IMG table (raw bytes until decoded by the UI). */
public final class ImageObject {
private final byte[] data;
public ImageObject(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
/** Returns the decoded image, or null (decoded lazily by the renderer). */
public Object getImage() {
return null;
}
@Override
public String toString() {
return "ImageObject[" + (data == null ? 0 : data.length) + " bytes]";
}
}
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
import dev.quoije.umd.rco.RcoContext;
import dev.quoije.umd.rco.object.BaseObject;
import dev.quoije.umd.rco.object.ImageObject;
/**
* An 8-byte reference field: a 16-bit reference type, a 16-bit unknown
* value, and a 32-bit reference index.
*/
public class BaseReferenceType extends BaseType {
public static final int REFERENCE_TYPE_NONE = 0xFFFF;
public static final int REFERENCE_TYPE_EVENT = 0x400;
public static final int REFERENCE_TYPE_TEXT = 0x401;
public static final int REFERENCE_TYPE_IMAGE = 0x402;
public static final int REFERENCE_TYPE_MODEL = 0x403;
public static final int REFERENCE_TYPE_FONT = 0x405;
public static final int REFERENCE_TYPE_OBJECT = 0x407;
public static final int REFERENCE_TYPE_ANIM = 0x408;
public static final int REFERENCE_TYPE_POSITION_OBJECT = 0x409;
public int referenceType;
public int unknownShort;
protected String event;
protected BaseObject object;
protected ImageObject image;
@Override
public int size() {
return 8;
}
@Override
public void read(RcoContext context) {
referenceType = read16(context);
unknownShort = read16(context);
super.read(context);
}
@Override
public void init(RcoContext context) {
switch (referenceType) {
case REFERENCE_TYPE_NONE -> { }
case REFERENCE_TYPE_EVENT -> event = context.events.get(value);
case REFERENCE_TYPE_OBJECT, REFERENCE_TYPE_POSITION_OBJECT, REFERENCE_TYPE_ANIM ->
object = context.objects.get(value);
case REFERENCE_TYPE_IMAGE -> image = context.images.get(value);
default -> { }
}
super.init(context);
}
public String getEvent() {
return event;
}
public BaseObject getObject() {
return object;
}
public ImageObject getImage() {
return image;
}
@Override
public String toString() {
return "referenceType=0x" + Integer.toHexString(referenceType)
+ ", short1=0x" + Integer.toHexString(unknownShort)
+ ", value=0x" + Integer.toHexString(value);
}
}
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
import dev.quoije.umd.rco.RcoContext;
/** Base of the typed fields inside an RCO object record. */
public class BaseType {
protected int value;
public int size() {
return 4;
}
protected int read8(RcoContext context) {
return context.buffer[context.offset++] & 0xFF;
}
protected int read16(RcoContext context) {
return read8(context) | (read8(context) << 8);
}
protected int read32(RcoContext context) {
return read16(context) | (read16(context) << 16);
}
public void read(RcoContext context) {
value = read32(context);
}
public void init(RcoContext context) {
}
public int getIntValue() {
return value;
}
public void setIntValue(int value) {
this.value = value;
}
public float getFloatValue() {
return (float) value;
}
public void setFloatValue(float value) {
this.value = (int) value;
}
@Override
public String toString() {
return "value=0x" + Integer.toHexString(getIntValue());
}
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to a script event. */
public class EventType extends BaseReferenceType {
}
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** A 32-bit floating-point field stored as IEEE 754 bits. */
public class FloatType extends BaseType {
@Override
public float getFloatValue() {
return Float.intBitsToFloat(super.getIntValue());
}
@Override
public void setFloatValue(float value) {
super.setIntValue(Float.floatToRawIntBits(value));
}
@Override
public int getIntValue() {
return (int) getFloatValue();
}
@Override
public void setIntValue(int value) {
setFloatValue((float) value);
}
@Override
public String toString() {
return "value=" + getFloatValue();
}
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to an image entry. */
public class ImageType extends BaseReferenceType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** A 32-bit integer field. */
public class IntType extends BaseType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to a model entry. */
public class ModelType extends BaseReferenceType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to an object entry. */
public class ObjectType extends BaseReferenceType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to an arbitrary RCO entry. */
public class RefType extends BaseReferenceType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** Reference to a text entry. */
public class TextType extends BaseReferenceType {
}
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package dev.quoije.umd.rco.type;
/** A 32-bit field of unknown semantics. */
public class UnknownType extends BaseType {
}