From 3a055f96f4358c420bcc30c9e07aff7ec81bfe76 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sun, 28 Jan 2024 19:26:13 -0600 Subject: [PATCH] Starting to run code. --- CMakeLists.txt | 7 +- include/interpreter.h | 46 ++++ include/memory.h | 13 +- include/messages.h | 65 ++++++ include/opcodes.h | 1 + include/{text.h => portme.h} | 12 +- include/state.h | 20 +- src/interpreter.c | 437 +++++++++++++++++++++++++++++++++++ src/main.c | 12 +- src/memory.c | 105 +++++++-- src/oc_0op.c | 7 +- src/oc_1op.c | 8 +- src/oc_2op.c | 35 ++- src/oc_var_op.c | 71 +++++- src/opcodes.c | 28 ++- src/{text.c => portme.c} | 25 +- src/state.c | 8 + 17 files changed, 844 insertions(+), 56 deletions(-) create mode 100644 include/interpreter.h create mode 100644 include/messages.h rename include/{text.h => portme.h} (88%) create mode 100644 src/interpreter.c rename src/{text.c => portme.c} (78%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c8af77..9b9b402 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,22 +3,25 @@ cmake_minimum_required(VERSION 3.12) project(zip LANGUAGES C) set(HEADERS + interpreter.h memory.h + messages.h oc_0op.h oc_1op.h oc_2op.h oc_var_op.h oc_ext.h opcodes.h + portme.h state.h stddclmr.h story.h - text.h zork1.h ) list(TRANSFORM HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/include/") set(SOURCE + interpreter.c main.c memory.c oc_0op.c @@ -27,9 +30,9 @@ set(SOURCE oc_var_op.c oc_ext.c opcodes.c + portme.c state.c story.c - text.c ) list(TRANSFORM SOURCE PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/src/") diff --git a/include/interpreter.h b/include/interpreter.h new file mode 100644 index 0000000..b6ba6ba --- /dev/null +++ b/include/interpreter.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef INTERPRETER_H +#define INTERPRETER_H + + +#include +#include "memory.h" + + +uint32_t interpreterDecodeZSCII(uint32_t zstr, bool abbr, char *buf, uint32_t *bufLen); +char interpreterDecodeZSCIIChar(uint16_t z); +void interpreterDoBranch(int32_t b); +void interpreterDoReturn(uint16_t r); +uint8_t interpreterGetObjectPointer(uint16_t objectId); +uint8_t interpreterGetObjectProperty(uint16_t objectId, uint32_t propertyId, uint8_t *size); +bool interpreterParseOperand(uint8_t opType, uint8_t operandId); +uint8_t interpreterParseVariableOperands(uint8_t index); +void interpreterPrintChars(char *chars, uint16_t len); +uint32_t interpreterPrintZSCII(uint32_t pc, bool abbr); +void interpreterRun(void); +uint8_t interpreterVariableAddress(uint8_t var, bool writing); + + +#endif // INTERPRETER_H diff --git a/include/memory.h b/include/memory.h index ee28a62..0e8aa5e 100644 --- a/include/memory.h +++ b/include/memory.h @@ -34,22 +34,33 @@ #define true 1 #define false 0 +// Memory access. #define PEEK(a) memoryByte(a) #define POKE(a,v) memorySetByte(a,v) #define PEEKW(a) memoryWord(a) #define POKEW(a,v) memorySetWord(a,v) +// Stack access. +#define SPEEKD(a) ((uint32_t)*(uint32_t *)&__state.stack[a]) + typedef unsigned char byte; typedef unsigned char bool; +extern char *__memoryBuffer; +extern uint16_t __memoryBufferLen; + + uint8_t memoryByte(uint16_t address); +void memoryEnsureBuffer(uint32_t newSize); +void memoryLoadStory(void); void memorySetByte(uint16_t address, uint8_t value); void memorySetWord(uint16_t address, uint16_t value); +void memoryShutdown(void); +void memoryStartup(void); uint32_t memoryUnpackAddress(uint16_t address, uint8_t type); uint16_t memoryWord(uint16_t address); -void memoryLoadStory(void); #endif // MEMORY_H diff --git a/include/messages.h b/include/messages.h new file mode 100644 index 0000000..59c1c26 --- /dev/null +++ b/include/messages.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +#ifndef MESSAGES_H +#define MESSAGES_H + +#define MSG_USE_MESSAGES // Undef this to save memory. + + +#ifdef MSG_USE_MESSAGES +#define MSG_UNIMPLEMENTED "Unimplemented." +#define MSG_INT_INVALID_EXT_OPCODE "Invalid extended opcode!" +#define MSG_INT_NO_ABBR "No abbreviations in abbreviations!" +#define MSG_INT_OBJECT0_REF "Object 0 referenced!" +#define MSG_INT_OBJECT_BAD_ID "Invalid object ID!" +#define MSG_INT_REF_UNALLOCATED_LOCAL "Referenced unallocated local variable!" +#define MSG_INT_STACK_OVERFLOW "Stack overflow!" +#define MSG_INT_STACK_UNDERFLOW "Stack underflow!" +#define MSG_INT_UNIMPLEMENTED_OPCODE "Unimplemented opcode! %s %d" +#define MSG_INT_V12_SHIFT "Add V1/V2 shifting." +#define MSG_INT_V12_SHIFT_LOCK "Add V1/V2 shift locking." +#define MSG_MEM_BUFFER "Unable to allocate memory buffer." +#define MSG_OP_VAR_MISSING_PROPERTY "Missing object property." +#define MSG_OP_VAR_TOO_MANY_LOCALS "Too many local variables!" +#define MSG_OP_VAR_STACK_OVERFLOW "Stack overflow!" +#else // MSG_USE_MESSAGES +#define MSG_UNIMPLEMENTED "" +#define MSG_INT_INVALID_EXT_OPCODE "" +#define MSG_INT_NO_ABBR "" +#define MSG_INT_OBJECT0_REF "" +#define MSG_INT_OBJECT_BAD_ID "" +#define MSG_INT_REF_UNALLOCATED_LOCAL "" +#define MSG_INT_STACK_OVERFLOW "" +#define MSG_INT_STACK_UNDERFLOW "" +#define MSG_INT_UNIMPLEMENTED_OPCODE "" +#define MSG_INT_V12_SHIFT "" +#define MSG_INT_V12_SHIFT_LOCK "" +#define MSG_MEM_BUFFER "" +#define MSG_OP_VAR_MISSING_PROPERTY "" +#define MSG_OP_VAR_TOO_MANY_LOCALS "" +#define MSG_OP_VAR_STACK_OVERFLOW "" +#endif // MSG_USE_MESSAGES + + +#endif // MESSAGES_H diff --git a/include/opcodes.h b/include/opcodes.h index 196084c..55f2223 100644 --- a/include/opcodes.h +++ b/include/opcodes.h @@ -28,6 +28,7 @@ typedef void (*opcodeT)(void); +void opcodesBuiltInitialTable(void); void opcodesSetup(void); diff --git a/include/text.h b/include/portme.h similarity index 88% rename from include/text.h rename to include/portme.h index 8faa766..d58863a 100644 --- a/include/text.h +++ b/include/portme.h @@ -21,11 +21,15 @@ */ -#ifndef TEXT_H -#define TEXT_H +#ifndef PORTME_H +#define PORTME_H -void textPrint(char *what); +#include -#endif // TEXT_H +void portDie(char *fmt, ...); +void portPrintChars(char *chars, uint16_t len); + + +#endif // PORTME_H diff --git a/include/state.h b/include/state.h index 9aa5f56..642d1af 100644 --- a/include/state.h +++ b/include/state.h @@ -29,17 +29,31 @@ #include "opcodes.h" +#define DEBUGGING + + typedef struct stateS { - byte stack[2048]; //***TODO*** How big does this really need to be? Old games are 1024, new...? + uint16_t stack[2048]; //***TODO*** How big does this really need to be? Old games are 1024, new...? bool quit; - uint16_t pc; - uint16_t sp; + uint32_t pc; // Program Counter + uint16_t sp; // Stack Pointer + uint16_t bp; // Base Pointer opcodeT opcodes[256]; opcodeT extOpcodes[30]; + uint8_t operandCount; + uint16_t operands[8]; + char alphabetTable[78]; +#ifdef DEBUGGING + char *opcodesName[256]; + char *extOpcodesName[30]; +#endif } stateT; extern stateT __state; +void stateReset(void); + + #endif // STATE_H diff --git a/src/interpreter.c b/src/interpreter.c new file mode 100644 index 0000000..2394d67 --- /dev/null +++ b/src/interpreter.c @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2024 Scott Duensing, scott@kangaroopunch.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "interpreter.h" +#include "portme.h" +#include "messages.h" +#include "story.h" +#include "state.h" + +#ifdef DEBUGGING +#include +#endif + + +uint32_t interpreterDecodeZSCII(uint32_t zstr, bool abbr, char *buf, uint32_t *bufLen) { + uint16_t code = 0; + uint8_t alphabet = 0; + uint8_t useAbbrTable = 0; + uint8_t zsciiCollector = 0; + uint16_t zsciiCode = 0; + uint32_t decodedChars = 0; + uint32_t bufPtr = 0; + uint32_t pc = zstr; + char printVal = 0; + uint32_t bufLenTemp; + uint32_t index; + uint32_t ptr; + uint16_t abbrAddr; + uint32_t abbrDecodedChars; + int8_t i; + uint8_t ch; + int16_t newShift; + + do { + code = PEEKW(pc); + pc += 2; + + // Characters are 5 bits each, packed three to a 16-bit word. + for (i=10; i>=0; i-=5) { + ch = ((code >> 1) & 0x1f); + + if (zsciiCollector) { + if (zsciiCollector == 2) { + zsciiCode |= ((uint16_t)ch) << 5; + } else { + zsciiCode |= ((uint16_t)ch); + } + + zsciiCollector--; + + if (!zsciiCollector) { + printVal = interpreterDecodeZSCIIChar(zsciiCode); + if (printVal) { + decodedChars++; + if (bufPtr < *bufLen) { + buf[bufPtr++] = printVal; + } + } + alphabet = 0; + useAbbrTable = 0; + zsciiCode = 0; + } // !zsciiCollector + + continue; + + } else if (useAbbrTable) { // zsciiCollector + + if (abbr) portDie(MSG_INT_NO_ABBR); + + index = ((32 * (((uint32_t)useAbbrTable) - 1)) + (uint32_t)ch); + ptr = storyAbbreviationTableAddress() + (index * 2); + abbrAddr = PEEKW(ptr); + + abbrDecodedChars = *bufLen - bufPtr; + interpreterDecodeZSCII(abbrAddr * 2, 1, buf, &abbrDecodedChars); + decodedChars += abbrDecodedChars; + + bufLenTemp = *bufLen - bufPtr; + bufPtr += (bufLenTemp < abbrDecodedChars) ? bufLenTemp : abbrDecodedChars; + bufLenTemp = (bufLenTemp < abbrDecodedChars) ? 0 : (bufLenTemp - abbrDecodedChars); + *bufLen = *bufLen - bufLenTemp; + + useAbbrTable = 0; + alphabet = 0; //***FIXME*** Version 3+ has no shift-lock but V1 needs it. + + } // useAbbrTable + + switch (ch) { + case 0: + printVal = ' '; + break; + + case 1: + if (storyVersion() == 1) { + printVal = '\n'; + } else { + useAbbrTable = 1; + } + break; + + case 2: + case 3: + if (storyVersion() <= 2) { + portDie(MSG_INT_V12_SHIFT); + } else { + useAbbrTable = ch; + } + break; + + case 4: + case 5: + if (storyVersion() <= 2) { + portDie(MSG_INT_V12_SHIFT_LOCK); + } else { + newShift = 1; + alphabet = ch - 3; + } + break; + + default: + if ((ch == 6) && (alphabet == 2)) { + zsciiCollector = 2; + } else { + printVal = __state.alphabetTable[(alphabet * 26) + (ch - 6)]; + } + break; + } + + if (printVal) { + decodedChars++; + if (bufPtr < *bufLen) { + buf[bufPtr++] = printVal; + } + } + + if (alphabet && !newShift) alphabet = 0; + + } // for + + // There is no NULL terminator, you look for a word with the top bit set. + } while ((code & (1 << 15)) == 0); + + *bufLen = decodedChars; + + return pc; +} + + +char interpreterDecodeZSCIIChar(uint16_t z) { + char c = 0; + + //***TODO*** V6+ has more codes. + + if ((z >= 32) && (z <= 126)) { + c = z; // ASCII + } else if (z == 13) { + c = '\n'; + } else if ((z >= 155) && (z <= 251)) { + c = '?'; //***TODO*** Extended ZSCII + } + + return c; +} + + +void interpreterDoBranch(int32_t b) { + uint8_t branch = __state.pc++; + int32_t farJump = (branch & (1 << 6)) == 0; + int32_t onTruth = (branch & (1 << 7)) ? 1 : 0; + uint8_t byte2 = farJump ? __state.pc++ : 0; + int16_t offset; + + if (onTruth == b) { + offset = (int16_t)(branch & 0x3f); + if (farJump) { + if (offset & (1 << 5)) offset |= 0xc0; + offset = (offset << 8) | ((int16_t)byte2); + } + if (offset == 0) { + interpreterDoReturn(0); + } else if (offset == 1) { + interpreterDoReturn(1); + } else { + __state.pc = (__state.pc + offset) - 2; + } + } +} + + +void interpreterDoReturn(uint16_t r) { + uint8_t storeId; + uint8_t store; + + // Newer versions start in a real routine, but still aren't allowed to return from it. + if (__state.bp == 0) portDie(MSG_INT_STACK_UNDERFLOW); + + // Dump all locals and data pushed on stack during the routine. + __state.sp = __state.bp; + // Dump our copy of numLocals. + __state.sp--; + // Restore previous frame's base pointer. + __state.bp = __state.stack[--__state.sp]; + + // Point to start of saved program counter; + __state.sp -= 2; + __state.pc = ((uint32_t)__state.stack[__state.sp]) | (((uint32_t)__state.stack[__state.sp + 1]) << 16); + + // Pop the result storage location. + storeId = (uint8_t)__state.stack[--__state.sp]; + // Store the routine result. + store = interpreterVariableAddress(storeId, 1); + POKEW(store, r); +} + + +uint8_t interpreterGetObjectPointer(uint16_t objectId) { + uint8_t ptr; + + if (objectId == 0) portDie(MSG_INT_OBJECT0_REF); + if ((storyVersion() <= 3) && (objectId > 255)) portDie(MSG_INT_OBJECT_BAD_ID); + + ptr = storyObjectTableAddress(); + ptr += 31 * 2; // Skip properties defaults table. + ptr += 9 * (objectId - 1); // Find object in table. + + return ptr; +} + + +uint8_t interpreterGetObjectProperty(uint16_t objectId, uint32_t propertyId, uint8_t *size) { + uint8_t ptr = interpreterGetObjectPointer(objectId); + uint16_t addr; + uint8_t info; + uint16_t num; + uint8_t s; + + if (storyVersion() <= 3) { + ptr += 7; // Skip to properties address field. + addr = PEEKW(ptr); + ptr = addr; + ptr += PEEK(ptr) * 2 + 1; // Skip object name to start of properties. + while (1) { + info = PEEK(ptr++); + num = (info & 0x1f); // 5 bits for property ID. + s = ((info >> 5) & 0x7) + 1; // 3 bits for property size. + + // These go in descending numeric order, and should fail the interpreter if missing. + // We use 0xFFFFFFFF internally to mean "first property". + if ((num == propertyId) || (propertyId == 0xFFFFFFFF)) { // found it? + if (size) *size = s; + return ptr; + } else if (num < propertyId) // we're past it. + break; + + // Try the next property. + ptr += s; + } + } else { + portDie(MSG_UNIMPLEMENTED); + } + + return 0; +} + + +bool interpreterParseOperand(uint8_t opType, uint8_t operandId) { + uint8_t addr; + + switch(opType) { + // Large constant. + case 0: + __state.operands[operandId] = PEEKW(__state.pc); + __state.pc += 2; + return true; + + // Small constant. + case 1: + __state.operands[operandId] = PEEK(__state.pc++); + return true; + + // Variable. + case 2: + addr = interpreterVariableAddress(PEEK(__state.pc++), 0); + __state.operands[operandId] = PEEKW(addr); + return true; + } + + return false; +} + + +uint8_t interpreterParseVariableOperands(uint8_t index) { + uint8_t operandTypes = PEEK(__state.pc++); + uint8_t shifter = 6; + uint8_t i; + uint8_t opType; + + for (i=0; i<4; i++) { + opType = (operandTypes >> shifter) & 0x3; + shifter -= 2; + if (!interpreterParseOperand(opType, index + i)) break; + } + + return i; +} + + +void interpreterPrintChars(char *chars, uint16_t len) { + //***TODO*** This is where we need to deal with windows and screen + // splits and other stuff. + portPrintChars(chars, len); +} + + +uint32_t interpreterPrintZSCII(uint32_t pc, bool abbr) { + uint32_t decodedChars = __memoryBufferLen; + uint32_t ret = interpreterDecodeZSCII(pc, abbr, __memoryBuffer, &decodedChars); + + if (decodedChars > __memoryBufferLen) { + memoryEnsureBuffer(decodedChars); + ret = interpreterDecodeZSCII(pc, abbr, __memoryBuffer, &decodedChars); + } + + interpreterPrintChars(__memoryBuffer, decodedChars); + + return ret; +} + + +void interpreterRun(void) { + uint8_t opcode; + bool extended; + opcodeT op; + + while (__state.quit == 0) { + opcode = PEEK(__state.pc++); + extended = ((opcode == 190) && (storyVersion() >= 5)) ? true : false; + + if (extended) { + + opcode = PEEK(__state.pc++); + if (opcode >= (sizeof(__state.extOpcodes) / sizeof(__state.extOpcodes[0]))) portDie(MSG_INT_INVALID_EXT_OPCODE); + __state.operandCount = interpreterParseVariableOperands(0); + op = __state.extOpcodes[opcode]; + + } else { + + if (opcode <= 127) { + // 2OP + __state.operandCount = 2; + interpreterParseOperand(((opcode >> 6) & 0x1) ? 2 : 1, 0); + interpreterParseOperand(((opcode >> 5) & 0x1) ? 2 : 1, 1); + } else if (opcode <= 175) { + // 1OP + __state.operandCount = 1; + interpreterParseOperand((opcode >> 4) & 0x3, 0); + } else if (opcode <= 191) { + // 0OP + __state.operandCount = 0; + } else if (opcode > 191) { + // Variable Opcodes + if ((opcode == 236) || (opcode == 250)) { + // call_vs2 and call_vn2 take up to EIGHT arguments! + __state.operandCount = interpreterParseVariableOperands(0); + } else { + __state.operandCount = interpreterParseVariableOperands(0); + if (__state.operandCount == 4) { + __state.operandCount += interpreterParseVariableOperands(4); + } else { + __state.pc++; + } + } + } + + op = __state.opcodes[opcode]; + + } // extended + + if (op == 0) portDie(MSG_INT_UNIMPLEMENTED_OPCODE, extended ? "ext" : "", opcode); + +#ifdef DEBUGGING + printf("pc=%X %sopcode=%u ('%s') [", (unsigned int)__state.pc, extended ? "ext " : "", opcode, extended ? __state.extOpcodesName[opcode] : __state.opcodesName[opcode]); + if (__state.operandCount) { + uint8_t i; + for (i=0; i<__state.operandCount - 1; i++) + printf("%X,", (unsigned int)__state.operands[i]); + printf("%X", (unsigned int)__state.operands[i]); + } + printf("]\n"); +#endif + + op(); + } +} + + +uint8_t interpreterVariableAddress(uint8_t var, bool writing) { + uint16_t numLocals; + + if (var == 0) { // top of stack + if (writing) { + if (__state.sp >= sizeof(__state.stack)) portDie(MSG_INT_STACK_OVERFLOW); + return __state.sp++; + } else { + if (__state.sp == 0) portDie(MSG_INT_STACK_UNDERFLOW); + numLocals = __state.bp ? __state.stack[__state.bp - 1] : 0; + if ((__state.bp + numLocals) >= sizeof(__state.stack)) portDie(MSG_INT_STACK_UNDERFLOW); + return --__state.sp; + } // writing + } // var + + if ((var >= 0x1) && (var <= 0xf)) { // Local var. + if (__state.stack[__state.bp - 1] <= (var - 1)) portDie(MSG_INT_REF_UNALLOCATED_LOCAL); + return __state.bp + (var - 1); + } + + return storyGlobalVariableTableAddress() + ((var - 0x10) * 2); +} diff --git a/src/main.c b/src/main.c index fc8feeb..f79359c 100644 --- a/src/main.c +++ b/src/main.c @@ -35,20 +35,22 @@ */ -#include - #include "stddclmr.h" #include "memory.h" -#include "story.h" -#include "state.h" #include "opcodes.h" +#include "state.h" +#include "interpreter.h" int main(void) { - memoryLoadStory(); + memoryStartup(); + stateReset(); opcodesSetup(); + memoryLoadStory(); + interpreterRun(); + memoryShutdown(); return 0; } diff --git a/src/memory.c b/src/memory.c index ebf47ba..ac096e0 100644 --- a/src/memory.c +++ b/src/memory.c @@ -22,14 +22,22 @@ #include +#include #include "memory.h" #include "story.h" #include "state.h" +#include "portme.h" +#include "messages.h" #include "zork1.h" +// Generic buffer for whatever. +char *__memoryBuffer = 0; +uint16_t __memoryBufferLen = 0; + + // The F256 has 512k of RAM. We use everything except the lower 64k. static uint8_t _RAM[1024 * (512 - 64)]; @@ -39,17 +47,88 @@ uint8_t memoryByte(uint16_t address) { } +void memoryEnsureBuffer(uint32_t newSize) { + if (__memoryBufferLen >= newSize) return; + free(__memoryBuffer); + while (__memoryBufferLen < newSize) { + __memoryBufferLen += 512; + } + __memoryBuffer = (char *)malloc(__memoryBufferLen); + if (__memoryBuffer == 0) portDie(MSG_MEM_BUFFER); +} + + +void memoryLoadStory(void) { + char *p; + uint16_t i; + + // For now, we just copy an embedded Zork 1 into RAM. + memcpy(_RAM, zork1, zork1_len); + + // Later, we probably want to see if the user has a memory expansion + // installed. If they do, we can use it and then use the lower memory + // for graphics and sound. + + // Currently no status bar. + POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) | STORY_FLAG_V3_STATUS_LINE_NOT_AVAILABLE); + + // Uncomment for status bar and window splitting. + //POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) & ~STORY_FLAG_V3_STATUS_LINE_NOT_AVAILABLE); + //POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) | STORY_FLAG_V3_SCREEN_SPLITTING); + + // V6+ this is the address of main(), not just a raw starting address. + __state.pc = storyInitialPC(); + __state.sp = 0; + + // Set up alphabet tables. ***TODO*** V5+ has alternate tables in header. + p = __state.alphabetTable; + for (i=0; i<26; i++) *(p++) = 'a' + i; // Alphabet A0 + for (i=0; i<26; i++) *(p++) = 'A' + i; // Alphabet A1 + // Alphabet A2 + *(p++) = 0; + if (storyVersion() != 1) *(p++) = '\n'; + for (i=0; i<10; i++) *(p++) = 'o' + i; + *(p++) = '.'; + *(p++) = ','; + *(p++) = '!'; + *(p++) = '?'; + *(p++) = '_'; + *(p++) = '#'; + *(p++) = '\''; + *(p++) = '"'; + *(p++) = '/'; + *(p++) = '\\'; + if (storyVersion() == 1) *(p++) = '<'; + *(p++) = '-'; + *(p++) = ':'; + *(p++) = '('; + *(p++) = ')'; +} + + void memorySetByte(uint16_t address, uint8_t value) { _RAM[address] = value; } void memorySetWord(uint16_t address, uint16_t value) { - _RAM[address] = (value & 0xff00) << 8; // MSB first. + _RAM[address] = (value & 0xff00) >> 8; // MSB first. _RAM[address + 1] = value & 0x00ff; } +void memoryShutdown(void) { + free(__memoryBuffer); + __memoryBuffer = 0; + __memoryBufferLen = 0; +} + + +void memoryStartup(void) { + memoryEnsureBuffer(512); +} + + uint32_t memoryUnpackAddress(uint16_t address, uint8_t type) { switch (storyVersion()) { case 1: @@ -76,27 +155,3 @@ uint32_t memoryUnpackAddress(uint16_t address, uint8_t type) { uint16_t memoryWord(uint16_t address) { return ((uint16_t)_RAM[address] << 8) | ((uint16_t)_RAM[address + 1]); } - - -void memoryLoadStory(void) { - // For now, we just copy an embedded Zork 1 into RAM. - memcpy(_RAM, zork1, zork1_len); - - // Later, we probably want to see if the user has a memory expansion - // installed. If they do, we can use it and then use the lower memory - // for graphics and sound. - - // Zero out all state data. - memset(&__state, 0, sizeof(stateT)); - - // Currently no status bar. - POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) | STORY_FLAG_V3_STATUS_LINE_NOT_AVAILABLE); - - // Uncomment for status bar and window splitting. - //POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) & ~STORY_FLAG_V3_STATUS_LINE_NOT_AVAILABLE); - //POKE(STORY_FLAG_V3, PEEK(STORY_FLAG_V3) | STORY_FLAG_V3_SCREEN_SPLITTING); - - // V6+ this is the address of main(), not just a raw starting address. - __state.pc = storyInitialPC(); - __state.sp = 0; -} diff --git a/src/oc_0op.c b/src/oc_0op.c index 8d9681e..82dbd89 100644 --- a/src/oc_0op.c +++ b/src/oc_0op.c @@ -22,14 +22,15 @@ #include "oc_0op.h" +#include "interpreter.h" +#include "state.h" void opcodes_new_line(void) { - + interpreterPrintChars("\n", 1); } - void opcodes_print(void) { - + __state.pc += interpreterPrintZSCII(__state.pc, 0); } diff --git a/src/oc_1op.c b/src/oc_1op.c index c437546..1cd4903 100644 --- a/src/oc_1op.c +++ b/src/oc_1op.c @@ -22,20 +22,22 @@ #include "oc_1op.h" +#include "state.h" +#include "interpreter.h" void opcodes_jump(void) { - + __state.pc = __state.pc + ((int16_t)__state.operands[0]) - 2; } void opcodes_jz(void) { - + interpreterDoBranch((__state.operands[0] == 0) ? 1 : 0); } void opcodes_ret(void) { - + interpreterDoReturn(__state.operands[0]); } diff --git a/src/oc_2op.c b/src/oc_2op.c index 20cc50d..3dd774e 100644 --- a/src/oc_2op.c +++ b/src/oc_2op.c @@ -22,33 +22,60 @@ #include "oc_2op.h" +#include "interpreter.h" +#include "state.h" +#include "memory.h" +#include "story.h" +#include "portme.h" +#include "messages.h" void opcodes_add(void) { - + uint8_t store = interpreterVariableAddress(PEEK(__state.pc++), 1); + POKEW(store, ((int16_t)__state.operands[0]) + ((int16_t)__state.operands[1])); } void opcodes_je(void) { + uint16_t a = __state.operands[0]; + int8_t i; + for (i=1; i<__state.operandCount; i++) { + if (a == __state.operands[i]) interpreterDoBranch(1); + return; + } + + interpreterDoBranch(0); } void opcodes_loadw(void) { - + uint16_t store = interpreterVariableAddress(PEEK(__state.pc++), 1); + uint16_t offset = __state.operands[0] + (__state.operands[1] * 2); + POKEW(store, PEEKW(offset)); } void opcodes_store(void) { - + uint8_t store = interpreterVariableAddress((uint8_t)(__state.operands[0] & 0xFF), 1); + POKEW(store, __state.operands[1]); } void opcodes_sub(void) { - + uint8_t store = interpreterVariableAddress(PEEK(__state.pc++), 1); + POKEW(store, ((int16_t)__state.operands[0]) - ((int16_t)__state.operands[1])); } void opcodes_test_attr(void) { + uint8_t ptr = interpreterGetObjectPointer(__state.operands[0]); + uint16_t attrId = __state.operands[1]; + if (storyVersion() <= 3) { + ptr += (attrId / 8); + interpreterDoBranch((ptr & (0x80 >> (attrId & 7))) ? 1 : 0); + } else { + portDie(MSG_UNIMPLEMENTED); + } } diff --git a/src/oc_var_op.c b/src/oc_var_op.c index f652d74..9c00252 100644 --- a/src/oc_var_op.c +++ b/src/oc_var_op.c @@ -22,18 +22,87 @@ #include "oc_var_op.h" +#include "state.h" +#include "memory.h" +#include "messages.h" +#include "portme.h" +#include "interpreter.h" +#include "story.h" void opcodes_call(void) { + uint8_t args = __state.operandCount; + uint8_t storeId = PEEK(__state.pc++); + uint32_t routine; + uint8_t numLocals; + int8_t i; + uint16_t src; + uint8_t dst; + if ((args == 0) && (__state.operands[0] == 0)) { + // Legal no-op; store 0 to return value and bounce. + POKEW(interpreterVariableAddress(storeId, 1), 0); + } else { + routine = memoryUnpackAddress(__state.operands[0], MEMORY_ROUTINE); + numLocals = PEEK(routine++); + if (numLocals > 15) portDie(MSG_OP_VAR_TOO_MANY_LOCALS); + + if (__state.sp + 5 + numLocals >= sizeof(__state.stack)) portDie(MSG_OP_VAR_STACK_OVERFLOW); + + // Save where we should store the call's result. + __state.stack[__state.sp++] = storeId; + + // Next instruction to run upon return. + __state.stack[__state.sp++] = __state.pc & 0xffff; + __state.stack[__state.sp++] = ((__state.pc >> 16) & 0xffff); + + // Current base pointer. + __state.stack[__state.sp++] = __state.bp; + + // Number of locals. + __state.stack[__state.sp++] = numLocals; + + // New base pointer. + __state.bp = __state.sp; + + if (storyVersion() <= 4) { + for (i=0; i numLocals) args = numLocals; + + // Copy arguments into local variables on stack. + src = 1; + dst = __state.bp; + for (i=0; i #include -#include "text.h" +#include + +#include "portme.h" -void textPrint(char *what) { - printf("%s", what); +void portDie(char *fmt, ...) { + va_list ap; + + printf("\n"); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); + + exit(1); +} + + +void portPrintChars(char *chars, uint16_t len) { + uint16_t x; + for (x=0; x + #include "state.h" stateT __state; + + +void stateReset(void) { + // Zero out all state data. + memset(&__state, 0, sizeof(stateT)); +}