diff --git a/src/components/Header.js b/src/components/Header.js
index 02a3cfb..8b27000 100644
--- a/src/components/Header.js
+++ b/src/components/Header.js
@@ -16,6 +16,7 @@ import meteor from '../assets/meteor.png';
const navigation = [
{ name: 'Rooms', href: '/rooms/1' },
{ name: 'Room Gfx', href: '/roomgfx/0' },
+ { name: 'Scripts', href: '/scripts/1' },
{ name: 'Prepositions', href: '/preps' },
{ name: 'ROM map', href: '/rom-map' },
{ name: 'Settings', href: '/settings', sideBarOnly: true },
diff --git a/src/components/RoomScripts.js b/src/components/RoomScripts.js
new file mode 100644
index 0000000..62d510c
--- /dev/null
+++ b/src/components/RoomScripts.js
@@ -0,0 +1,22 @@
+import ScriptCode from './ScriptCode';
+
+const RoomScripts = ({ excdScript, encdScript }) => {
+ return (
+
+ );
+};
+
+export default RoomScripts;
diff --git a/src/components/RoomTabs.js b/src/components/RoomTabs.js
index 9ca3f5b..6ec4a56 100644
--- a/src/components/RoomTabs.js
+++ b/src/components/RoomTabs.js
@@ -4,7 +4,7 @@ const RoomTabs = ({ currentTab, setCurrentTab }) => {
const tabs = [
{ name: 'Palettes', current: currentTab === 'Palettes' },
{ name: 'Tilesets', current: currentTab === 'Tilesets' },
- // { name: 'Scripts', current: currentTab === 'Scripts' },
+ { name: 'Scripts', current: currentTab === 'Scripts' },
];
return (
diff --git a/src/components/Script.js b/src/components/Script.js
new file mode 100644
index 0000000..96c6037
--- /dev/null
+++ b/src/components/Script.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import MainHeader from './MainHeader';
+import ResourceMetadata from './ResourceMetadata';
+import ScriptCode from './ScriptCode';
+
+const Script = ({ script }) => {
+ if (!script) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Script;
diff --git a/src/components/ScriptCode.js b/src/components/ScriptCode.js
new file mode 100644
index 0000000..746ec1f
--- /dev/null
+++ b/src/components/ScriptCode.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import ScriptCodeInstruction from './ScriptCodeInstruction.js';
+
+const ScriptCode = ({ code }) => {
+ if (!code) {
+ return null;
+ }
+
+ return (
+
+ {code.map(({ 0: address, 1: command }) => (
+
+
+ 0x{address}
+
+
+
+ ))}
+
+ );
+};
+
+export default ScriptCode;
diff --git a/src/components/ScriptCodeInstruction.js b/src/components/ScriptCodeInstruction.js
new file mode 100644
index 0000000..7d62634
--- /dev/null
+++ b/src/components/ScriptCodeInstruction.js
@@ -0,0 +1,146 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { hex } from '../lib/utils.js';
+
+const prettifyArguments = (operation) => {
+ const opCode = operation[0];
+ let args = operation.slice(2);
+
+ switch (opCode) {
+ case 0x18: // goTo
+ args = [
+
+ 0x{args[0]}
+ ,
+ ];
+ break;
+ case 0x24: // loadRoomWithEgo
+ args = [
+ args[0],
+
+ {args[1]}
+ ,
+ args[2],
+ args[3],
+ ];
+ break;
+ case 0x72:
+ args = [
+
+ {args[0]}
+ ,
+ ];
+ break;
+ case 0x2d: // putActorInRoom
+ args = [
+ args[0],
+
+ {args[1]}
+ ,
+ ];
+ break;
+ case 0x42: // startScript
+ case 0x62: // stopScript
+ case 0x4a: // chainScript
+ args = [
+
+ {args[0]}
+ ,
+ ];
+ break;
+ case 0x48: // isEqual
+ case 0xc8: // isEqual
+ case 0x78: // isGreater
+ case 0xf8: // isGreater
+ case 0x04: // isGreaterEqual
+ case 0x84: // isGreaterEqual
+ case 0x44: // isLess
+ case 0xc4: // isLess
+ case 0x08: // isNotEqual
+ case 0x88: // isNotEqual
+ case 0x38: // lessOrEqual
+ case 0xb8: // lessOrEqual
+ args = [
+ args[0],
+ args[1],
+
+ 0x{args[2]}
+ ,
+ ];
+ break;
+ case 0x28: // equalZero
+ case 0xa8: // notEqualZero
+ args = [
+ args[0],
+
+ 0x{args[1]}
+ ,
+ ];
+ break;
+ case 0x28: // equalZero
+ case 0xa8: // notEqualZero
+ args = [
+ args[0],
+
+ 0x{args[1]}
+ ,
+ ];
+ break;
+ case 0x3f: // ifNotState01
+ case 0x5f: // ifNotState02
+ case 0x2f: // ifNotState04
+ case 0x0f: // ifNotState08
+ case 0x7f: // ifState01
+ case 0x1f: // ifState02
+ case 0x6f: // ifState04
+ case 0x4f: // ifState08
+ args = [
+ args[0],
+
+ 0x{args[1]}
+ ,
+ ];
+ break;
+ }
+ return args;
+};
+
+const ScriptCodeInstruction = ({ command }) => {
+ const opCode = hex(command[0], 2);
+ const instruction = command[1];
+ const args = prettifyArguments(command);
+
+ return (
+
+
+ (${opCode}) {instruction}
+ {' '}
+ {args.map((arg, i) => (
+
+ {arg}
+ {i < args.length - 1 ? , : ''}
+
+ ))}
+
+ );
+};
+
+export default ScriptCodeInstruction;
diff --git a/src/components/ScriptsList.js b/src/components/ScriptsList.js
new file mode 100644
index 0000000..afc467c
--- /dev/null
+++ b/src/components/ScriptsList.js
@@ -0,0 +1,31 @@
+import ColumnListHeader from './ColumnListHeader';
+import ColumnListItem from './ColumnListItem';
+
+const ScriptsList = ({ scripts, currentId }) => {
+ return (
+ <>
+ Scripts
+ {scripts
+ .sort((a, b) => a.num > b.num)
+ .map(({ metadata, code }) => {
+ if (code.length == 0) {
+ return null;
+ }
+
+ const selected = metadata.id === currentId;
+ const path = `/scripts/${metadata.id}`;
+ const label = `Script ${metadata.id}`;
+
+ return (
+
+ {label}
+
+ );
+ })}
+ >
+ );
+};
+
+export default ScriptsList;
diff --git a/src/containers/ResourceExplorer.js b/src/containers/ResourceExplorer.js
index 017aa0d..ed2178b 100644
--- a/src/containers/ResourceExplorer.js
+++ b/src/containers/ResourceExplorer.js
@@ -4,6 +4,7 @@ import RoomGfxContainer from './RoomGfxContainer';
import PrepositionsContainer from './PrepositionsContainer';
import RomMapContainer from './RomMapContainer';
import SettingsContainer from './SettingsContainer';
+import ScriptContainer from './ScriptContainer';
const ResourceExplorer = ({ rom, res, resources }) => {
if (!resources) {
@@ -49,6 +50,14 @@ const ResourceExplorer = ({ rom, res, resources }) => {
/>
}
/>
+ }>
+ }
+ />
+
{
const { roomId } = useParams();
@@ -115,7 +116,12 @@ const RoomsContainer = ({ rooms, roomgfx, globdata }) => {
roomgfc={roomgfc}
/>
)}
- {currentTab === 'Scripts' && Scripts
}
+ {currentTab === 'Scripts' && (
+
+ )}
>
)}
diff --git a/src/containers/ScriptContainer.js b/src/containers/ScriptContainer.js
new file mode 100644
index 0000000..20eded8
--- /dev/null
+++ b/src/containers/ScriptContainer.js
@@ -0,0 +1,37 @@
+import { useParams } from 'react-router-dom';
+import PrimaryColumn from '../components/PrimaryColumn';
+import Main from '../components/Main';
+import ScriptsList from '../components/ScriptsList';
+import Script from '../components/Script';
+
+const ScriptContainer = ({ scripts }) => {
+ const { scriptId } = useParams();
+
+ const currentScriptId =
+ typeof scriptId === 'undefined' ? null : parseInt(scriptId, 10);
+
+ const script =
+ scripts.find(({ metadata }) => metadata.id === currentScriptId) || null;
+
+ return (
+ <>
+
+
+
+
+ {!script ? (
+ Scripts
+ ) : (
+ <>
+
+ >
+ )}
+
+ >
+ );
+};
+
+export default ScriptContainer;
diff --git a/src/lib/opcodes.js b/src/lib/opcodes.js
new file mode 100644
index 0000000..e857863
--- /dev/null
+++ b/src/lib/opcodes.js
@@ -0,0 +1,399 @@
+const opCodes = {
+ 0x00: { name: 'stopObjectCode' },
+ 0x01: { name: 'putActor' },
+ 0x02: { name: 'startMusic' },
+ 0x03: { name: 'getActorRoom' },
+ /* 04 */
+ 0x04: { name: 'isGreaterEqual' },
+ 0x05: { name: 'drawObject' },
+ 0x06: { name: 'getActorElevation' },
+ 0x07: { name: 'setState08' },
+ /* 08 */
+ 0x08: { name: 'isNotEqual' },
+ 0x09: { name: 'faceActor' },
+ 0x0a: { name: 'assignVarWordIndirect' },
+ 0x0b: { name: 'setObjPreposition' },
+ /* 0C */
+ 0x0c: { name: 'resourceRoutines' },
+ 0x0d: { name: 'walkActorToActor' },
+ 0x0e: { name: 'putActorAtObject' },
+ 0x0f: { name: 'ifNotState08' },
+ /* 10 */
+ 0x10: { name: 'getObjectOwner' },
+ 0x11: { name: 'animateActor' },
+ 0x12: { name: 'panCameraTo' },
+ 0x13: { name: 'actorOps' },
+ /* 14 */
+ 0x14: { name: 'print' },
+ 0x15: { name: 'actorFromPos' },
+ 0x16: { name: 'getRandomNr' },
+ 0x17: { name: 'clearState02' },
+ /* 18 */
+ 0x18: { name: 'goTo' },
+ 0x19: { name: 'doSentence' },
+ 0x1a: { name: 'move' },
+ 0x1b: { name: 'setBitVar' },
+ /* 1C */
+ 0x1c: { name: 'startSound' },
+ 0x1d: { name: 'ifClassOfIs' },
+ 0x1e: { name: 'walkActorTo' },
+ 0x1f: { name: 'ifState02' },
+ /* 20 */
+ 0x20: { name: 'stopMusic' },
+ 0x21: { name: 'putActor' },
+ 0x22: { name: 'saveLoadGame' },
+ 0x23: { name: 'getActorY' },
+ /* 24 */
+ 0x24: { name: 'loadRoomWithEgo' },
+ 0x25: { name: 'drawObject' },
+ 0x26: { name: 'setVarRange' },
+ 0x27: { name: 'setState04' },
+ /* 28 */
+ 0x28: { name: 'equalZero' },
+ 0x29: { name: 'setOwnerOf' },
+ 0x2a: { name: 'addIndirect' },
+ 0x2b: { name: 'delayVariable' },
+ /* 2C */
+ 0x2c: { name: 'assignVarByte' },
+ 0x2d: { name: 'putActorInRoom' },
+ 0x2e: { name: 'delay' },
+ 0x2f: { name: 'ifNotState04' },
+ /* 30 */
+ 0x30: { name: 'setBoxFlags' },
+ 0x31: { name: 'getBitVar' },
+ 0x32: { name: 'setCameraAt' },
+ 0x33: { name: 'roomOps' },
+ /* 34 */
+ 0x34: { name: 'getDist' },
+ 0x35: { name: 'findObject' },
+ 0x36: { name: 'walkActorToObject' },
+ 0x37: { name: 'setState01' },
+ /* 38 */
+ 0x38: { name: 'isLessEqual' },
+ 0x39: { name: 'doSentence' },
+ 0x3a: { name: 'subtract' },
+ 0x3b: { name: 'waitForActor' },
+ /* 3C */
+ 0x3c: { name: 'stopSound' },
+ 0x3d: { name: 'setActorElevation' },
+ 0x3e: { name: 'walkActorTo' },
+ 0x3f: { name: 'ifNotState01' },
+ /* 40 */
+ 0x40: { name: 'cutscene' },
+ 0x41: { name: 'putActor' },
+ 0x42: { name: 'startScript' },
+ 0x43: { name: 'getActorX' },
+ /* 44 */
+ 0x44: { name: 'isLess' },
+ 0x45: { name: 'drawObject' },
+ 0x46: { name: 'increment' },
+ 0x47: { name: 'clearState08' },
+ /* 48 */
+ 0x48: { name: 'isEqual' },
+ 0x49: { name: 'faceActor' },
+ 0x4a: { name: 'chainScript' },
+ 0x4b: { name: 'setObjPreposition' },
+ /* 4C */
+ 0x4c: { name: 'waitForSentence' },
+ 0x4d: { name: 'walkActorToActor' },
+ 0x4e: { name: 'putActorAtObject' },
+ 0x4f: { name: 'ifState08' },
+ /* 50 */
+ 0x50: { name: 'pickupObject' },
+ 0x51: { name: 'animateActor' },
+ 0x52: { name: 'actorFollowCamera' },
+ 0x53: { name: 'actorOps' },
+ /* 54 */
+ 0x54: { name: 'setObjectName' },
+ 0x55: { name: 'actorFromPos' },
+ 0x56: { name: 'getActorMoving' },
+ 0x57: { name: 'setState02' },
+ /* 58 */
+ 0x58: { name: 'beginOverride' },
+ 0x59: { name: 'doSentence' },
+ 0x5a: { name: 'add' },
+ 0x5b: { name: 'setBitVar' },
+ /* 5C */
+ 0x5c: { name: 'dummy' },
+ 0x5d: { name: 'ifClassOfIs' },
+ 0x5e: { name: 'walkActorTo' },
+ 0x5f: { name: 'ifNotState02' },
+ /* 60 */
+ 0x60: { name: 'cursorCommand' },
+ 0x61: { name: 'putActor' },
+ 0x62: { name: 'stopScript' },
+ 0x63: { name: 'getActorFacing' },
+ /* 64 */
+ 0x64: { name: 'loadRoomWithEgo' },
+ 0x65: { name: 'drawObject' },
+ 0x66: { name: 'getClosestObjActor' },
+ 0x67: { name: 'clearState04' },
+ /* 68 */
+ 0x68: { name: 'isScriptRunning' },
+ 0x69: { name: 'setOwnerOf' },
+ 0x6a: { name: 'subIndirect' },
+ 0x6b: { name: 'dummy' },
+ /* 6C */
+ 0x6c: { name: 'getObjPreposition' },
+ 0x6d: { name: 'putActorInRoom' },
+ 0x6e: { name: 'dummy' },
+ 0x6f: { name: 'ifState04' },
+ /* 70 */
+ 0x70: { name: 'lights' },
+ 0x71: { name: 'getActorCostume' },
+ 0x72: { name: 'loadRoom' },
+ 0x73: { name: 'roomOps' },
+ /* 74 */
+ 0x74: { name: 'getDist' },
+ 0x75: { name: 'findObject' },
+ 0x76: { name: 'walkActorToObject' },
+ 0x77: { name: 'clearState01' },
+ /* 78 */
+ 0x78: { name: 'isGreater' },
+ 0x79: { name: 'doSentence' },
+ 0x7a: { name: 'verbOps' },
+ 0x7b: { name: 'getActorWalkBox' },
+ /* 7C */
+ 0x7c: { name: 'isSoundRunning' },
+ 0x7d: { name: 'setActorElevation' },
+ 0x7e: { name: 'walkActorTo' },
+ 0x7f: { name: 'ifState01' },
+ /* 80 */
+ 0x80: { name: 'breakHere' },
+ 0x81: { name: 'putActor' },
+ 0x82: { name: 'startMusic' },
+ 0x83: { name: 'getActorRoom' },
+ /* 84 */
+ 0x84: { name: 'isGreaterEqual' },
+ 0x85: { name: 'drawObject' },
+ 0x86: { name: 'getActorElevation' },
+ 0x87: { name: 'setState08' },
+ /* 88 */
+ 0x88: { name: 'isNotEqual' },
+ 0x89: { name: 'faceActor' },
+ 0x8a: { name: 'assignVarWordIndirect' },
+ 0x8b: { name: 'setObjPreposition' },
+ /* 8C */
+ 0x8c: { name: 'resourceRoutines' },
+ 0x8d: { name: 'walkActorToActor' },
+ 0x8e: { name: 'putActorAtObject' },
+ 0x8f: { name: 'ifNotState08' },
+ /* 90 */
+ 0x90: { name: 'getObjectOwner' },
+ 0x91: { name: 'animateActor' },
+ 0x92: { name: 'panCameraTo' },
+ 0x93: { name: 'actorOps' },
+ /* 94 */
+ 0x94: { name: 'print' },
+ 0x95: { name: 'actorFromPos' },
+ 0x96: { name: 'getRandomNr' },
+ 0x97: { name: 'clearState02' },
+ /* 98 */
+ 0x98: { name: 'restart' },
+ 0x99: { name: 'doSentence' },
+ 0x9a: { name: 'move' },
+ 0x9b: { name: 'setBitVar' },
+ /* 9C */
+ 0x9c: { name: 'startSound' },
+ 0x9d: { name: 'ifClassOfIs' },
+ 0x9e: { name: 'walkActorTo' },
+ 0x9f: { name: 'ifState02' },
+ /* A0 */
+ 0xa0: { name: 'stopObjectCode' },
+ 0xa1: { name: 'putActor' },
+ 0xa2: { name: 'saveLoadGame' },
+ 0xa3: { name: 'getActorY' },
+ /* A4 */
+ 0xa4: { name: 'loadRoomWithEgo' },
+ 0xa5: { name: 'drawObject' },
+ 0xa6: { name: 'setVarRange' },
+ 0xa7: { name: 'setState04' },
+ /* A8 */
+ 0xa8: { name: 'notEqualZero' },
+ 0xa9: { name: 'setOwnerOf' },
+ 0xaa: { name: 'addIndirect' },
+ 0xab: { name: 'switchCostumeSet' },
+ /* AC */
+ 0xac: { name: 'drawSentence' },
+ 0xad: { name: 'putActorInRoom' },
+ 0xae: { name: 'waitForMessage' },
+ 0xaf: { name: 'ifNotState04' },
+ /* B0 */
+ 0xb0: { name: 'setBoxFlags' },
+ 0xb1: { name: 'getBitVar' },
+ 0xb2: { name: 'setCameraAt' },
+ 0xb3: { name: 'roomOps' },
+ /* B4 */
+ 0xb4: { name: 'getDist' },
+ 0xb5: { name: 'findObject' },
+ 0xb6: { name: 'walkActorToObject' },
+ 0xb7: { name: 'setState01' },
+ /* B8 */
+ 0xb8: { name: 'isLessEqual' },
+ 0xb9: { name: 'doSentence' },
+ 0xba: { name: 'subtract' },
+ 0xbb: { name: 'waitForActor' },
+ /* BC */
+ 0xbc: { name: 'stopSound' },
+ 0xbd: { name: 'setActorElevation' },
+ 0xbe: { name: 'walkActorTo' },
+ 0xbf: { name: 'ifNotState01' },
+ /* C0 */
+ 0xc0: { name: 'endCutscene' },
+ 0xc1: { name: 'putActor' },
+ 0xc2: { name: 'startScript' },
+ 0xc3: { name: 'getActorX' },
+ /* C4 */
+ 0xc4: { name: 'isLess' },
+ 0xc5: { name: 'drawObject' },
+ 0xc6: { name: 'decrement' },
+ 0xc7: { name: 'clearState08' },
+ /* C8 */
+ 0xc8: { name: 'isEqual' },
+ 0xc9: { name: 'faceActor' },
+ 0xca: { name: 'chainScript' },
+ 0xcb: { name: 'setObjPreposition' },
+ /* CC */
+ 0xcc: { name: 'pseudoRoom' },
+ 0xcd: { name: 'walkActorToActor' },
+ 0xce: { name: 'putActorAtObject' },
+ 0xcf: { name: 'ifState08' },
+ /* D0 */
+ 0xd0: { name: 'pickupObject' },
+ 0xd1: { name: 'animateActor' },
+ 0xd2: { name: 'actorFollowCamera' },
+ 0xd3: { name: 'actorOps' },
+ /* D4 */
+ 0xd4: { name: 'setObjectName' },
+ 0xd5: { name: 'actorFromPos' },
+ 0xd6: { name: 'getActorMoving' },
+ 0xd7: { name: 'setState02' },
+ /* D8 */
+ 0xd8: { name: 'printEgo' },
+ 0xd9: { name: 'doSentence' },
+ 0xda: { name: 'add' },
+ 0xdb: { name: 'setBitVar' },
+ /* DC */
+ 0xdc: { name: 'dummy' },
+ 0xdd: { name: 'ifClassOfIs' },
+ 0xde: { name: 'walkActorTo' },
+ 0xdf: { name: 'ifNotState02' },
+ /* E0 */
+ 0xe0: { name: 'cursorCommand' },
+ 0xe1: { name: 'putActor' },
+ 0xe2: { name: 'stopScript' },
+ 0xe3: { name: 'getActorFacing' },
+ /* E4 */
+ 0xe4: { name: 'loadRoomWithEgo' },
+ 0xe5: { name: 'drawObject' },
+ 0xe6: { name: 'getClosestObjActor' },
+ 0xe7: { name: 'clearState04' },
+ /* E8 */
+ 0xe8: { name: 'isScriptRunning' },
+ 0xe9: { name: 'setOwnerOf' },
+ 0xea: { name: 'subIndirect' },
+ 0xeb: { name: 'dummy' },
+ /* EC */
+ 0xec: { name: 'getObjPreposition' },
+ 0xed: { name: 'putActorInRoom' },
+ 0xee: { name: 'dummy' },
+ 0xef: { name: 'ifState04' },
+ /* F0 */
+ 0xf0: { name: 'lights' },
+ 0xf1: { name: 'getActorCostume' },
+ 0xf2: { name: 'loadRoom' },
+ 0xf3: { name: 'roomOps' },
+ /* F4 */
+ 0xf4: { name: 'getDist' },
+ 0xf5: { name: 'findObject' },
+ 0xf6: { name: 'walkActorToObject' },
+ 0xf7: { name: 'clearState01' },
+ /* F8 */
+ 0xf8: { name: 'isGreater' },
+ 0xf9: { name: 'doSentence' },
+ 0xfa: { name: 'verbOps' },
+ 0xfb: { name: 'getActorWalkBox' },
+ /* FC */
+ 0xfc: { name: 'isSoundRunning' },
+ 0xfd: { name: 'setActorElevation' },
+ 0xfe: { name: 'walkActorTo' },
+ 0xff: { name: 'ifState01' },
+};
+
+const varNames = [
+ /* 0 */
+ 'VAR_EGO',
+ 'VAR_RESULT',
+ 'VAR_CAMERA_POS_X',
+ 'VAR_HAVE_MSG',
+ /* 4 */
+ 'VAR_ROOM',
+ 'VAR_OVERRIDE',
+ 'VAR_MACHINE_SPEED',
+ 'VAR_CHARCOUNT',
+ /* 8 */
+ 'VAR_ACTIVE_VERB',
+ 'VAR_ACTIVE_OBJECT1',
+ 'VAR_ACTIVE_OBJECT2',
+ 'VAR_NUM_ACTOR',
+ /* 12 */
+ 'VAR_CURRENT_LIGHTS',
+ 'VAR_CURRENTDRIVE',
+ null,
+ null,
+ /* 16 */
+ null,
+ 'VAR_MUSIC_TIMER',
+ 'VAR_VERB_ALLOWED',
+ 'VAR_ACTOR_RANGE_MIN',
+ /* 20 */
+ 'VAR_ACTOR_RANGE_MAX',
+ null,
+ null,
+ 'VAR_CAMERA_MIN_X',
+ /* 24 */
+ 'VAR_CAMERA_MAX_X',
+ 'VAR_TIMER_NEXT',
+ 'VAR_SENTENCE_VERB',
+ 'VAR_SENTENCE_OBJECT1',
+ /* 28 */
+ 'VAR_SENTENCE_OBJECT2',
+ 'VAR_SENTENCE_PREPOSITION',
+ 'VAR_VIRT_MOUSE_X',
+ 'VAR_VIRT_MOUSE_Y',
+ /* 32 */
+ 'VAR_CLICK_AREA',
+ 'VAR_CLICK_VERB',
+ null,
+ 'VAR_CLICK_OBJECT',
+ /* 36 */
+ 'VAR_ROOM_RESOURCE',
+ 'VAR_LAST_SOUND',
+ 'VAR_BACKUP_VERB',
+ 'VAR_KEYPRESS',
+ /* 40 */
+ 'VAR_CUTSCENEEXIT_KEY',
+ 'VAR_TALK_ACTOR',
+ null,
+ null,
+];
+
+const verbs = {
+ 0x01: 'Open',
+ 0x02: 'Close',
+ 0x03: 'Give',
+ 0x04: 'Turn On',
+ 0x05: 'Turn Off',
+ 0x06: 'Fix',
+ 0x07: 'New Kid',
+ 0x08: undefined,
+ 0x09: 'Push',
+ 0x0a: 'Pull',
+ 0x0b: 'Use',
+ 0x0c: 'Read',
+ 0x0d: 'Go To',
+ 0x0e: 'Get',
+ 0x0f: undefined,
+};
+
+export { opCodes, varNames, verbs };
diff --git a/src/lib/parser/parseRom.js b/src/lib/parser/parseRom.js
index ed41634..190a7c3 100644
--- a/src/lib/parser/parseRom.js
+++ b/src/lib/parser/parseRom.js
@@ -2,12 +2,15 @@ import parseRooms from './parseRooms.js';
import parseRoomGfx from './parseRoomGfx.js';
import parseGlobdata from './parseGlobdata.js';
import parsePreps from './parsePreps.js';
+import parseScript from './parseScript.js';
const parseRom = (arrayBuffer, res) => {
const rooms = [];
const roomgfx = [];
const globdata = [];
+ const scripts = [];
const preps = [];
+ let objects = [];
for (let i = 0; i < res?.rooms?.length; i++) {
const [offset, length] = res.rooms[i];
@@ -36,6 +39,14 @@ const parseRom = (arrayBuffer, res) => {
globdata.push(item);
}
+ for (let i = 0; i < res.scripts.length; i++) {
+ const [offset, length] = res.scripts[i];
+
+ const resBuffer = arrayBuffer.slice(offset, offset + length);
+ const script = parseScript(resBuffer, i, offset);
+ scripts.push(script);
+ }
+
for (let i = 0; i < res?.preplist?.length; i++) {
const [offset, length] = res.preplist[i];
@@ -50,6 +61,8 @@ const parseRom = (arrayBuffer, res) => {
roomgfx,
globdata,
preps,
+ scripts,
+ objects,
};
};
diff --git a/src/lib/parser/parseRooms.js b/src/lib/parser/parseRooms.js
index 75344d7..5351835 100644
--- a/src/lib/parser/parseRooms.js
+++ b/src/lib/parser/parseRooms.js
@@ -4,6 +4,8 @@ import parseRoomNametable from './room/parseRoomNametable.js';
import parseRoomAttributes from './room/parseRoomAttributes.js';
import parseRoomBoxes from './room/parseRoomBoxes.js';
import parseRoomMatrix from './room/parseRoomMatrix.js';
+import parseScriptCode from './parseScriptCode.js';
+import { verbs } from '../opcodes.js';
const assert = console.assert;
@@ -252,7 +254,18 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => {
// End of objects scripts (usually beginning of name offset).
break;
}
- objScripts.push([verbId, objectScriptOffsParser.getUint8()]);
+
+ const objectScriptStart = objectScriptOffsParser.getUint8();
+
+ const scriptOffsParser = new Parser(
+ arrayBuffer.slice(
+ objectOffs + objectScriptStart,
+ objectOffs + objectScriptStart + objectSize,
+ ),
+ );
+
+ const script = parseScriptCode(scriptOffsParser, 0, 0);
+ objScripts.push([verbs[verbId] ?? `(${verbId})`, script]);
}
// Parse object images.
@@ -350,6 +363,18 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => {
map.push(matrixMap);
+ let excdScript;
+ if (excdOffs !== 0) {
+ const excdScriptParser = new Parser(arrayBuffer.slice(excdOffs));
+ excdScript = parseScriptCode(excdScriptParser, 0);
+ }
+
+ let encdScript;
+ if (encdOffs !== 0) {
+ const encdScriptParser = new Parser(arrayBuffer.slice(encdOffs));
+ encdScript = parseScriptCode(encdScriptParser, 0);
+ }
+
// Order ROM map by starting offset.
map.sort((a, b) => a.from - b.from);
@@ -367,6 +392,8 @@ const parseRooms = (arrayBuffer, i = 0, offset = 0, characters = {}) => {
objectImages,
objects,
hasMask,
+ excdScript,
+ encdScript,
map,
};
};
diff --git a/src/lib/parser/parseScript.js b/src/lib/parser/parseScript.js
new file mode 100644
index 0000000..12b6951
--- /dev/null
+++ b/src/lib/parser/parseScript.js
@@ -0,0 +1,44 @@
+import parseScriptCode from './parseScriptCode.js';
+import Parser from './parser.js';
+const assert = console.assert;
+
+const parseScript = (arrayBuffer, i, offset = 0) => {
+ const parser = new Parser(arrayBuffer);
+ const metadata = {
+ id: i,
+ offset,
+ size: arrayBuffer.byteLength,
+ };
+
+ if (arrayBuffer.byteLength === 0) {
+ return { metadata, header: {}, code: [] };
+ }
+
+ const chunkSize = parser.getUint16();
+ const unk1 = parser.getUint8();
+ const unk2 = parser.getUint8();
+
+ assert(
+ chunkSize === arrayBuffer.byteLength,
+ 'Script res size flag does not match chunk size.',
+ );
+
+ assert(unk1 === 0, 'Unknown 1 is not 0.');
+
+ const header = {
+ chunkSize,
+ unk1,
+ unk2,
+ };
+
+ // Skip script 59 and 90 as they seem to be corrupted
+ if (i === 0x3b || i === 0x5a) {
+ return { metadata, header, code: [] };
+ }
+
+ const code = parseScriptCode(parser, 0x0004);
+
+ return { metadata, header, code };
+};
+
+export default parseScript;
diff --git a/src/lib/parser/parseScriptCode.js b/src/lib/parser/parseScriptCode.js
new file mode 100644
index 0000000..56a52e9
--- /dev/null
+++ b/src/lib/parser/parseScriptCode.js
@@ -0,0 +1,999 @@
+import { opCodes, varNames } from '../opcodes.js';
+import { hex } from '../utils.js';
+
+const getByte = (parser) => {
+ return parser.getUint8();
+};
+
+const getWord = (parser) => {
+ return parser.getUint16();
+};
+
+const getVariable = (parser) => {
+ const i = parser.getUint8();
+
+ if (i & 0x2000) {
+ // Not implemented
+ console.error('Not implemented');
+ }
+
+ if (i < varNames.length && varNames[i]) {
+ return varNames[i];
+ } else if (i & 0x8000) {
+ return `Var[${(i & 0x0fff) >> 4} Bit ${i & 0x000f}]`;
+ } else {
+ const varIndex = i & 0xfff;
+ const s = varIndex >= 0x320 ? '??Var??' : 'Var';
+ return `${s}[${varIndex}]`;
+ }
+};
+
+const getVariableOrByte = (parser, condition) => {
+ if (condition) {
+ return getVariable(parser);
+ } else {
+ return getByte(parser);
+ }
+};
+
+const getVariableOrWord = (parser, condition) => {
+ if (condition) {
+ return getVariable(parser);
+ } else {
+ return getWord(parser);
+ }
+};
+
+const getOffset = (parser, startAddress) => {
+ const offset = getWord(parser);
+ const currentRow = parser.pointer - startAddress;
+ return hex((currentRow + offset) % 0x10000, 4);
+};
+
+const getString = (parser) => {
+ let str = '';
+ let charCode;
+ do {
+ charCode = getByte(parser);
+ if (charCode === 0x00) {
+ break;
+ }
+ const flag = (charCode & 0x80) !== 0;
+ charCode &= 0x7f;
+
+ // TODO: Properly handle escape codes
+ if (charCode < 8) {
+ str += `\\u${hex(charCode, 4)}`;
+ if (charCode > 3) {
+ str += `\\${parser.getUint8()}`;
+ }
+ } else {
+ if (charCode === '\\' || charCode === '"') {
+ str += '\\';
+ }
+ }
+ str += String.fromCharCode(charCode);
+
+ if (flag) {
+ str += ' ';
+ }
+ } while (true);
+
+ return str;
+};
+
+const parseScriptCode = (parser, startAddress, parentOffset = 0) => {
+ const script = [];
+ let stop = false;
+
+ do {
+ const scriptRow = [];
+ const rowAddress = parser.pointer - startAddress;
+
+ const opCode = getByte(parser);
+
+ scriptRow.push(opCode);
+ scriptRow.push(`${opCodes[opCode].name ?? opCode}`);
+
+ switch (opCode) {
+ // stopObjectCode
+ case 0x00:
+ case 0xa0:
+ stop = true;
+ break;
+
+ // putActor
+ case 0x01:
+ case 0x21:
+ case 0x41:
+ case 0x61:
+ case 0x81:
+ case 0xa1:
+ case 0xc1:
+ case 0xe1:
+ // Actor Id
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ // X
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+ // Y
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x20));
+
+ break;
+
+ // startMusic
+ case 0x02:
+ case 0x82:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // getActorRoom
+ case 0x03:
+ case 0x83:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // drawObject
+ case 0x05:
+ case 0x25:
+ case 0x45:
+ case 0x65:
+ case 0x85:
+ case 0xa5:
+ case 0xc5:
+ case 0xe5:
+ // ObjectId
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x20));
+
+ break;
+
+ // faceActor
+ case 0x09:
+ case 0x49:
+ case 0x89:
+ case 0xc9:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ break;
+
+ // setObjPreposition
+ case 0x0b:
+ case 0x4b:
+ case 0x8b:
+ case 0xcb:
+ // ObjectId
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ // Preposition Id
+ scriptRow.push(getByte(parser));
+
+ break;
+
+ // resourceRoutines
+ case 0x0c:
+ case 0x8c: {
+ const resTypes = [
+ 0, // Invalid
+ 0, // Invalid
+ 'Costume',
+ 'Room',
+ '0', // Invalid
+ 'Script',
+ 'Sound',
+ ];
+
+ const resId = getVariableOrByte(parser, opCode & 0x80);
+ const subOp = getByte(parser);
+
+ let routine;
+ if ((subOp & 0x0f) === 0 || (subOp & 0x0f) === 1) {
+ if (subOp & 1) {
+ routine = 'load';
+ } else {
+ routine = 'nuke';
+ }
+ } else {
+ if (subOp & 1) {
+ routine = 'lock';
+ } else {
+ routine = 'unlock';
+ }
+ }
+
+ const type = resTypes[subOp >> 4];
+ if (type === 0) {
+ console.error(`Resource Routines: Invalid type ${type}`);
+ }
+
+ scriptRow.push(`${routine}${type}`);
+ scriptRow.push(resId);
+
+ break;
+ }
+
+ // putActorAtObject
+ case 0x0e:
+ case 0x4e:
+ case 0x8e:
+ case 0xce:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x40));
+
+ break;
+
+ // walkActorToActor
+ case 0x0d:
+ case 0x4d:
+ case 0x8d:
+ case 0xcd:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ scriptRow.push(getByte(parser));
+
+ break;
+
+ // getObjectOwner
+ case 0x10:
+ case 0x90:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ break;
+
+ // animateActor
+ case 0x11:
+ case 0x51:
+ case 0x91:
+ case 0xd1:
+ // Actor Id
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ // Animation
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+ break;
+
+ //panCameraTo
+ case 0x12:
+ case 0x92:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // actorOps
+ case 0x13:
+ case 0x53:
+ case 0x93:
+ case 0xd3:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ const subOpArg = getVariableOrByte(parser, opCode & 0x40);
+
+ const subOp = getByte(parser);
+ switch (subOp) {
+ case 0x01:
+ scriptRow.push(`Sound(${subOpArg})`);
+ break;
+ case 0x02:
+ const value = getByte(parser);
+ scriptRow.push(`Color(${value}, ${subOpArg})`);
+ break;
+ case 0x03:
+ const name = getString(parser);
+ scriptRow.push(`Name("${name}")`);
+ break;
+ case 0x04:
+ scriptRow.push(`Costume(${subOpArg})`);
+ break;
+ case 0x05:
+ scriptRow.push(`TalkColor(${subOpArg})`);
+ break;
+ default:
+ scriptRow.push(`// Unknown subop: 0x${hex(subOp, 2)}`);
+ }
+ break;
+
+ // print
+ case 0x14:
+ case 0x94:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(`"${getString(parser)}"`);
+ break;
+
+ // actorFromPos
+ case 0x15:
+ case 0x55:
+ case 0x95:
+ case 0xd5:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ break;
+
+ // getRandomNr
+ case 0x16:
+ case 0x96:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ break;
+
+ // goto
+ case 0x18:
+ {
+ const offset = getWord(parser);
+ const currentRow = parser.pointer - startAddress;
+ scriptRow.push(hex((currentRow + offset) % 0x10000, 4));
+ }
+
+ break;
+
+ // doSentence
+ case 0x19:
+ case 0x39:
+ case 0x59:
+ case 0x79:
+ case 0x99:
+ case 0xb9:
+ case 0xd9:
+ case 0xf9:
+ const tmpValue = parser.peekUint8();
+ if (!(opCode & 0x80) && tmpValue === 0xfc) {
+ scriptRow.push('STOP');
+ parser.getUint8();
+ } else if (!(opCode & 0x80) && tmpValue === 0xfb) {
+ scriptRow.push('RESET');
+ parser.getUint8();
+ } else {
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x40));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x20));
+
+ scriptRow.push(getByte(parser));
+ }
+ break;
+
+ // setBitVar
+ case 0x1b:
+ case 0x5b:
+ case 0x9b:
+ case 0xdb:
+ scriptRow.push(getWord(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ break;
+
+ // startSound
+ case 0x1c:
+ case 0x9c:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ break;
+
+ // walkActorTo
+ case 0x1e:
+ case 0x3e:
+ case 0x5e:
+ case 0x7e:
+ case 0x9e:
+ case 0xbe:
+ case 0xde:
+ case 0xfe:
+ // Actor id
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ // x
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ // y
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x20));
+
+ break;
+
+ // stopMusic
+ case 0x20:
+ // No arguments
+ break;
+
+ // saveLoadGame
+ case 0x22:
+ case 0xa2:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x20));
+
+ break;
+
+ // loadRoomWithEgo
+ case 0x24:
+ case 0x64:
+ case 0xa4:
+ case 0xe4:
+ // Object Id
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ // Room Id
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ // x
+ scriptRow.push(getByte(parser));
+
+ // y
+ scriptRow.push(getByte(parser));
+
+ break;
+
+ // setVarRange
+ case 0x26:
+ case 0xa6:
+ scriptRow.push(getVariable(parser));
+
+ let length = getByte(parser);
+ scriptRow.push(length);
+
+ const values = [];
+ while (length > 0) {
+ if (opCode & 0x80) {
+ values.push(getWord(parser));
+ } else {
+ values.push(getByte(parser));
+ }
+ length--;
+ }
+ scriptRow.push(`[${values}]`);
+
+ break;
+
+ // setOwnerOf
+ case 0x29:
+ case 0x69:
+ case 0xa9:
+ case 0xe9:
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ break;
+
+ // addIndirect
+ case 0x2a:
+ case 0xaa:
+ // subtract
+ case 0x3a:
+ case 0xba:
+ // subIndirect
+ case 0x6a:
+ case 0xea:
+ // assignVarWordIndirect
+ case 0x0a:
+ case 0x8a:
+ // move
+ case 0x1a:
+ case 0x9a:
+ // add
+ case 0xda:
+ case 0x5a:
+ // increment/decrement
+ case 0x46:
+ case 0xc6:
+ if (
+ (opCode & 0x7f) === 0x0a ||
+ (opCode & 0x7f) === 0x2a ||
+ (opCode & 0x7f) === 0x6a
+ ) {
+ // Assign to a variable defined by the value of another variable
+ const i = getByte(parser);
+ scriptRow.push(`Var[Var[${i}]]`);
+ } else {
+ // Assign to a variable
+ scriptRow.push(getVariable(parser));
+ }
+
+ if ((opCode & 0x7f) === 0x2c) {
+ scriptRow.push(getByte(parser));
+ } else if ((opCode & 0x7f) !== 0x46) {
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ }
+ break;
+
+ // delayVariable
+ case 0x2b:
+ scriptRow.push(getVariable(parser));
+ break;
+
+ // delay
+ case 0x2e:
+ let d = getByte(parser);
+ d |= getByte(parser) << 8;
+ d |= getByte(parser) << 16;
+ d = 0xffffff - d;
+
+ scriptRow.push(d);
+
+ break;
+
+ // putActorInRoom
+ case 0x2d:
+ case 0x6d:
+ case 0xad:
+ case 0xed:
+ // Actor Id
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ // Room
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+ break;
+
+ // setBoxFlags
+ case 0x30:
+ case 0xb0:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getByte(parser));
+
+ break;
+
+ // getBitVar
+ case 0x31:
+ case 0xb1:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getWord(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // setCameraAt
+ case 0x32:
+ case 0xb2:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ break;
+
+ // roomOps
+ case 0x33:
+ case 0x73:
+ case 0xb3:
+ case 0xf3: {
+ const valueA = getVariableOrByte(parser, opCode & 0x80);
+ const valueB = getVariableOrByte(parser, opCode & 0x40);
+
+ const subOp = getByte(parser);
+
+ console.log(
+ `Room Ops: ${(subOp & 0x1f).toString(16, 2)}, ${valueA} ${valueB}`,
+ );
+
+ switch (subOp & 0x1f) {
+ case 0x01:
+ scriptRow.push(`RoomScroll`);
+ scriptRow.push(valueA);
+ scriptRow.push(valueB);
+ break;
+ case 0x02:
+ // Not used on NES
+ console.error(`RoomOps invalid Op ${subOp & 0x1f}`);
+ break;
+ case 0x03:
+ scriptRow.push(`SetScreen`);
+ scriptRow.push(valueA);
+ scriptRow.push(valueB);
+ break;
+ case 0x04:
+ scriptRow.push(`SetPalColor`);
+ scriptRow.push(valueA);
+ scriptRow.push(valueB);
+ break;
+ case 0x05:
+ scriptRow.push(`ShakeOn`);
+ break;
+ case 0x06:
+ scriptRow.push(`ShakeOff`);
+ break;
+ default:
+ scriptRow.push(
+ `[[unknown subop ${(subOp & 0x1f).toString(16, 2)}]]`,
+ );
+ console.error(
+ `Room Ops: unknown subop ${(subOp & 0x1f).toString(16, 2)}`,
+ );
+ }
+ break;
+ }
+
+ // getDist
+ case 0x34:
+ case 0x74:
+ case 0xb4:
+ case 0xf4:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x40));
+ break;
+
+ // findObject
+ case 0x35:
+ case 0x75:
+ case 0xb5:
+ case 0xf5:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+
+ break;
+
+ // walkActorToObject
+ case 0x36:
+ case 0x76:
+ case 0xb6:
+ case 0xf6:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x40));
+ break;
+
+ // setState01 to value
+ case 0x37:
+ case 0xb7:
+ // setState02 to value
+ case 0x57:
+ case 0xd7:
+ // setState04 to value
+ case 0x27:
+ case 0xa7:
+ // setState08 to value
+ case 0x07:
+ case 0x87:
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ break;
+
+ // waitForActor
+ case 0x3b:
+ case 0xbb:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // stopSound
+ case 0x3c:
+ case 0xbc:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // setActorElevation
+ case 0x3d:
+ case 0x7d:
+ case 0xbd:
+ case 0xfd:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x40));
+ break;
+
+ // ifNotState01
+ case 0x3f:
+ case 0xbf:
+ // ifNotState02
+ case 0x5f:
+ case 0xdf:
+ // ifNotState04
+ case 0x2f:
+ case 0xaf:
+ // ifNotState08
+ case 0x0f:
+ case 0x8f:
+ // ifState01
+ case 0x7f:
+ case 0xff:
+ // ifState02
+ case 0x1f:
+ case 0x9f:
+ // ifState04
+ case 0x6f:
+ case 0xef:
+ // ifState08
+ case 0x4f:
+ case 0xcf:
+ // Object Id
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ scriptRow.push(getOffset(parser, startAddress));
+
+ break;
+
+ // cutscene
+ case 0x40:
+ // No arguments
+ break;
+
+ // startScript
+ case 0x42:
+ case 0xc2:
+ // stopScript
+ case 0x62:
+ case 0xe2:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // getActorX
+ case 0x43:
+ case 0xc3:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // getActorY
+ case 0x23:
+ case 0xa3:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // chainScript
+ case 0x4a:
+ case 0xca:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ break;
+
+ // waitForSentence
+ case 0x4c:
+ // No arguments
+ break;
+
+ // isEqual
+ case 0x48:
+ case 0xc8:
+ // isGreater
+ case 0x78:
+ case 0xf8:
+ // isGreaterEqual
+ case 0x04:
+ case 0x84:
+ // isLess
+ case 0x44:
+ case 0xc4:
+ // isNotEqual
+ case 0x08:
+ case 0x88:
+ // lessOrEqual
+ case 0x38:
+ case 0xb8:
+ // equalZero
+ case 0x28:
+ // notEqualZero
+ case 0xa8:
+ // Variable to compare
+ scriptRow.push(getVariable(parser));
+
+ // For opCode different than equalZero and notEqualZero
+ if (opCode !== 0x28 && opCode !== 0xa8) {
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ }
+
+ scriptRow.push(getOffset(parser, startAddress));
+
+ break;
+
+ // pickupObject
+ case 0x50:
+ case 0xd0:
+ // Object Id
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ break;
+
+ // actorFollowCamera
+ case 0x52:
+ case 0xd2:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ break;
+
+ // setObjectName
+ case 0x54:
+ case 0xd4:
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ const name = getString(parser);
+ scriptRow.push(`"${name}"`);
+
+ break;
+
+ // getActorMoving
+ case 0x56:
+ case 0xd6:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // beginOverride
+ case 0x58:
+ // No arguments
+ break;
+
+ case 0x5c: // dummy
+ case 0x6b: // dummy
+ case 0x6e: // dummy
+ case 0xab: // dummy
+ case 0xdc: // dummy
+ case 0xeb: // dummy
+ case 0xee: // dummy
+ break;
+
+ // cursorCommand
+ case 0x60:
+ case 0xe0:
+ if (opCode & 0x80) {
+ const variable = getVariable(parser);
+ scriptRow.push(`Hi(${variable})`);
+ scriptRow.push(`Lo(${variable})`);
+ } else {
+ const value = getWord(parser);
+ scriptRow.push((value >> 8) & 0xff);
+ scriptRow.push(value & 0xff);
+ }
+ break;
+
+ // getClosestObjActor
+ case 0x66:
+ case 0xe6:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+
+ // isScriptRunning
+ case 0x68:
+ case 0xe8:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // getObjPreposition
+ case 0x6c:
+ case 0xec:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ break;
+
+ // lights
+ case 0x70:
+ case 0xf0:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getByte(parser));
+ scriptRow.push(getByte(parser));
+
+ break;
+
+ // loadRoom
+ case 0x72:
+ case 0xf2:
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // clearState01
+ case 0x77:
+ case 0xf7:
+ // clearState02
+ case 0x17:
+ case 0x97:
+ // clearState04
+ case 0x67:
+ case 0xe7:
+ // clearState08
+ case 0x47:
+ case 0xc7:
+ scriptRow.push(getVariableOrWord(parser, opCode & 0x80));
+ break;
+
+ // verbOps
+ case 0x7a:
+ case 0xfa: {
+ const subOp = getByte(parser);
+
+ switch (subOp) {
+ case 0:
+ scriptRow.push(`Delete`);
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+ case 0xff:
+ // Not implemented for NES
+ break;
+ default:
+ scriptRow.push(`New-${subOp}`);
+
+ scriptRow.push(getByte(parser));
+ scriptRow.push(getByte(parser));
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+
+ scriptRow.push(getByte(parser));
+
+ scriptRow.push(`"${getString(parser)}"`);
+ }
+ break;
+ }
+
+ // isSoundRunning
+ case 0x7c:
+ case 0xfc:
+ scriptRow.push(getVariable(parser));
+
+ scriptRow.push(getVariableOrByte(parser, opCode & 0x80));
+ break;
+
+ // breakHere
+ case 0x80:
+ // No arguments
+ break;
+
+ // restart
+ case 0x98:
+ // No arguments
+ break;
+
+ //drawSentence
+ case 0xac:
+ // No arguments
+ break;
+
+ // waitForMessage
+ case 0xae:
+ // No arguments
+ break;
+
+ // endCutscene
+ case 0xc0:
+ // No arguments
+ break;
+
+ case 0xd8: // printEgo
+ const str = getString(parser);
+ scriptRow.push(`"${str}"`);
+
+ break;
+
+ default:
+ scriptRow.push(
+ `NOT IMPLEMENTED (${hex(opCode)}) ${opCodes[opCode].name ?? opCode}`,
+ );
+ console.error(
+ `NOT IMPLEMENTED (${hex(opCode)}) ${opCodes[opCode].name ?? opCode}`,
+ );
+
+ stop = true;
+
+ break;
+ }
+
+ script.push([hex(rowAddress, 4), scriptRow]);
+ } while (!stop);
+
+ return script;
+};
+
+export default parseScriptCode;
diff --git a/src/lib/parser/parser.js b/src/lib/parser/parser.js
index 74ff5ea..a29d31e 100644
--- a/src/lib/parser/parser.js
+++ b/src/lib/parser/parser.js
@@ -34,6 +34,10 @@ class Parser {
return char;
}
+ peekUint8() {
+ return this.#view.getUint8(this.#ptr);
+ }
+
// Return the position of the next byte to read.
get pointer() {
return this.#ptr;