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 ( +
+
+

Enter Script

+
+ +
+
+
+

Exit Script

+
+ +
+
+
+ ); +}; + +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;