diff --git a/CMakeLists.txt b/CMakeLists.txt index 0734a50..3c83290 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,16 +7,24 @@ set(HEADERS interpreter.h memory.h messages.h - oc_0op.h - oc_1op.h - oc_2op.h - oc_var_op.h - oc_ext.h + object.h + oc_call.h + oc_compare.h + oc_input.h + oc_math.h + oc_memory.h + oc_misc.h + oc_object.h + oc_output.h + oc_save.h + oc_window.h opcodes.h portme.h state.h stddclmr.h story.h + variable.h + zscii.h zork1.h ) list(TRANSFORM HEADERS PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/include/") @@ -25,15 +33,23 @@ set(SOURCE interpreter.c main.c memory.c - oc_0op.c - oc_1op.c - oc_2op.c - oc_var_op.c - oc_ext.c + object.c + oc_call.c + oc_compare.c + oc_input.c + oc_math.c + oc_memory.c + oc_misc.c + oc_object.c + oc_output.c + oc_save.c + oc_window.c opcodes.c portme.c state.c story.c + variable.c + zscii.c ) list(TRANSFORM SOURCE PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/src/") diff --git a/include/common.h b/include/common.h index 6b284b8..0fe1a8a 100644 --- a/include/common.h +++ b/include/common.h @@ -25,7 +25,23 @@ #define COMMON_H +#include + + #define DEBUGGING +#ifdef DEBUGGING +#include +#endif + + +#define true 1 +#define false 0 + + +typedef unsigned char byte; +typedef unsigned char bool; + + #endif // COMMON_H diff --git a/include/interpreter.h b/include/interpreter.h index e09742f..b55e338 100644 --- a/include/interpreter.h +++ b/include/interpreter.h @@ -25,25 +25,14 @@ #define INTERPRETER_H -#include #include "common.h" -#include "memory.h" -uint32_t interpreterDecodeZSCII(uint32_t zstr, bool abbr, char *buf, uint32_t *bufLenP); -char interpreterDecodeZSCIIChar(uint16_t z); + void interpreterDoBranch(int32_t truth); void interpreterDoReturn(uint16_t r); -uint32_t interpreterGetObjectPointer(uint16_t objectId); -uint8_t interpreterGetObjectProperty(uint16_t objectId, uint32_t propertyId, uint8_t *size); -uint32_t interpreterGlobalVariableAddress(uint16_t var); -uint16_t interpreterLoadVariable(uint16_t var); 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); -void interpreterStoreVariable(uint16_t var, uint16_t value); -uint8_t interpreterVariableAddress(uint8_t var, bool writing); #endif // INTERPRETER_H diff --git a/include/memory.h b/include/memory.h index 4e12271..ac2a5f5 100644 --- a/include/memory.h +++ b/include/memory.h @@ -25,40 +25,29 @@ #define MEMORY_H -#include #include "common.h" +#include "portme.h" #define MEMORY_ROUTINE 0 #define MEMORY_PRINT 1 -#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) - - -typedef unsigned char byte; -typedef unsigned char bool; +#define ZPEEK(a) portByteGet(a) +#define ZPOKE(a,v) portByteSet(a,v) +#define ZPEEKW(a) portWordGet(a) +#define ZPOKEW(a,v) portWordSet(a,v) 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); #endif // MEMORY_H diff --git a/include/object.h b/include/object.h new file mode 100644 index 0000000..ca51fc9 --- /dev/null +++ b/include/object.h @@ -0,0 +1,35 @@ +/* + * 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 OBJECT_H +#define OBJECT_H + + +#include "common.h" + + +uint32_t objectPointerGet(uint16_t objectId); +uint8_t objectPropertyGet(uint16_t objectId, uint32_t propertyId, uint8_t *size); + + +#endif // OBJECT_H diff --git a/include/oc_call.h b/include/oc_call.h new file mode 100644 index 0000000..9cc9aaa --- /dev/null +++ b/include/oc_call.h @@ -0,0 +1,35 @@ +/* + * 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 OC_CALL_H +#define OC_CALL_H + + +#include "common.h" + + +void opcodes_call(void); +void opcodes_ret(void); + + +#endif // OC_CALL_H diff --git a/include/oc_1op.h b/include/oc_compare.h similarity index 93% rename from include/oc_1op.h rename to include/oc_compare.h index 4f9a6ca..e0bcb31 100644 --- a/include/oc_1op.h +++ b/include/oc_compare.h @@ -21,15 +21,16 @@ */ -#ifndef OC_1OP_H -#define OC_1OP_H +#ifndef OC_COMPARE_H +#define OC_COMPARE_H #include "common.h" +void opcodes_je(void); void opcodes_jump(void); void opcodes_jz(void); -void opcodes_ret(void); -#endif // OC_1OP_H + +#endif // OC_COMPARE_H diff --git a/include/oc_input.h b/include/oc_input.h new file mode 100644 index 0000000..286f02c --- /dev/null +++ b/include/oc_input.h @@ -0,0 +1,31 @@ +/* + * 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 OC_INPUT_H +#define OC_INPUT_H + + +#include "common.h" + + +#endif // OC_INPUT_H diff --git a/include/oc_math.h b/include/oc_math.h new file mode 100644 index 0000000..cbe910c --- /dev/null +++ b/include/oc_math.h @@ -0,0 +1,35 @@ +/* + * 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 OC_MATH_H +#define OC_MATH_H + + +#include "common.h" + + +void opcodes_add(void); +void opcodes_sub(void); + + +#endif // OC_MATH_H diff --git a/include/oc_memory.h b/include/oc_memory.h new file mode 100644 index 0000000..0b33c3e --- /dev/null +++ b/include/oc_memory.h @@ -0,0 +1,36 @@ +/* + * 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 OC_MEMORY_H +#define OC_MEMORY_H + + +#include "common.h" + + +void opcodes_loadw(void); +void opcodes_store(void); +void opcodes_storew(void); + + +#endif // OC_MEMORY_H diff --git a/include/oc_ext.h b/include/oc_misc.h similarity index 95% rename from include/oc_ext.h rename to include/oc_misc.h index c6bd870..73230d4 100644 --- a/include/oc_ext.h +++ b/include/oc_misc.h @@ -21,11 +21,11 @@ */ -#ifndef OC_EXT_H -#define OC_EXT_H +#ifndef OC_MISC_H +#define OC_MISC_H #include "common.h" -#endif // OC_EXT_H +#endif // OC_MISC_H diff --git a/include/oc_var_op.h b/include/oc_object.h similarity index 91% rename from include/oc_var_op.h rename to include/oc_object.h index d768fec..6766aa8 100644 --- a/include/oc_var_op.h +++ b/include/oc_object.h @@ -21,16 +21,15 @@ */ -#ifndef OC_VAR_OP_H -#define OC_VAR_OP_H +#ifndef OC_OBJECT_H +#define OC_OBJECT_H #include "common.h" -void opcodes_call(void); void opcodes_put_prop(void); -void opcodes_storew(void); +void opcodes_test_attr(void); -#endif // OC_VAR_OP_H +#endif // OC_OBJECT_H diff --git a/include/oc_0op.h b/include/oc_output.h similarity index 95% rename from include/oc_0op.h rename to include/oc_output.h index 2effa65..ba53d85 100644 --- a/include/oc_0op.h +++ b/include/oc_output.h @@ -21,8 +21,8 @@ */ -#ifndef OC_0OP_H -#define OC_0OP_H +#ifndef OC_OUTPUT_H +#define OC_OUTPUT_H #include "common.h" @@ -32,4 +32,4 @@ void opcodes_new_line(void); void opcodes_print(void); -#endif // OC_0OP_H +#endif // OC_OUTPUT_H diff --git a/include/oc_save.h b/include/oc_save.h new file mode 100644 index 0000000..4cbfe64 --- /dev/null +++ b/include/oc_save.h @@ -0,0 +1,31 @@ +/* + * 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 OC_SAVE_H +#define OC_SAVE_H + + +#include "common.h" + + +#endif // OC_SAVE_H diff --git a/include/oc_window.h b/include/oc_window.h new file mode 100644 index 0000000..2a6d0b0 --- /dev/null +++ b/include/oc_window.h @@ -0,0 +1,31 @@ +/* + * 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 OC_WINDOW_H +#define OC_WINDOW_H + + +#include "common.h" + + +#endif // OC_WINDOW_H diff --git a/include/portme.h b/include/portme.h index 1ffd63d..c82eee5 100644 --- a/include/portme.h +++ b/include/portme.h @@ -25,13 +25,16 @@ #define PORTME_H -#include - #include "common.h" -void portDie(char *fmt, ...); -void portPrintChars(char *chars, uint16_t len); +uint8_t portByteGet(uint16_t address); +void portByteSet(uint16_t address, uint8_t value); +void portDie(char *fmt, ...); +void portPrintChars(char *chars, uint16_t len); +void portStoryLoad(void); +uint16_t portWordGet(uint16_t address); +void portWordSet(uint16_t address, uint16_t value); #endif // PORTME_H diff --git a/include/state.h b/include/state.h index f53dad4..9323774 100644 --- a/include/state.h +++ b/include/state.h @@ -26,7 +26,6 @@ #include "common.h" -#include "memory.h" #include "opcodes.h" diff --git a/include/story.h b/include/story.h index 316f904..d708f7c 100644 --- a/include/story.h +++ b/include/story.h @@ -26,7 +26,7 @@ #include "common.h" -#include "memory.h" +#include "portme.h" #define STORY_STATUS_SCORE_TURNS 0 @@ -74,46 +74,47 @@ #define STORY_FLAG_3_USE_TRANSPARENCY (1 << 0) -#define storyVersion() memoryByte(0x0) -#define storyFlags() memoryByte(STORY_FLAG_V3) -#define storyHightMemory() memoryWord(0x4) -#define storyInitialPC() memoryWord(0x6) // V1-5 -#define storyInitialPackedPC() memoryUnpackAddress(memoryWord(0x6), MEMORY_ROUTINE) // V6 -#define storyDictionaryAddress() memoryWord(0x8) -#define storyObjectTableAddress() memoryWord(0xa) -#define storyGlobalVariableTableAddress() memoryWord(0xc) -#define storyStaticMemoryBaseAddress() memoryWord(0xe) -#define storyFlags2() memoryWord(0x10) -#define storyAbbreviationTableAddress() memoryWord(0x18) -#define storyChecksum() memoryWord(0x1c) -#define storyInterpreterNumber() memoryByte(0x1e) -#define storyInterpreterVersion() memoryByte(0x1f) -#define storyScreenHeightLines() memoryByte(0x20) -#define storyScreenWidthCharacters() memoryByte(0x21) -#define storyScreenHeightUnits() memoryWord(0x22) -#define storyScreenWidthUnits() memoryWord(0x24) -#define storyFontHeightUnits() (storyVersion() == 5 ? memoryByte(0x27) ? memoryByte(0x26)) // WTF Infocom? -#define storyFontWidthUnits() (storyVersion() == 5 ? memoryByte(0x26) ? memoryByte(0x27)) -#define storyRoutinesOffset() memoryWord(0x28) -#define storyStringsOffset() memoryWord(0x2a) -#define storyDefaultBackgroundColor() memoryByte(0x2c) -#define storyDefaultForegroundColor() memoryByte(0x2d) -#define storyTerminatingCharactersTableAddress() memoryWord(0x2e) -#define storyWidthInPixelsToOutputStream3() memoryByte(0x30) -#define storyStandardRevisionNumber() memoryByte(0x32) -#define storyAlphabetTableAddress() memoryWord(0x34) -#define storyHeaderExtensionTableAddress() memoryWord(0x36) +#define storyVersion() portByteGet(0x0) +#define storyFlags() portByteGet(STORY_FLAG_V3) +#define storyHightMemory() portWordGet(0x4) +#define storyInitialPC() portWordGet(0x6) // V1-5 +#define storyInitialPackedPC() memoryUnpackAddress(portWordGet(0x6), MEMORY_ROUTINE) // V6 +#define storyDictionaryAddress() portWordGet(0x8) +#define storyObjectTableAddress() portWordGet(0xa) +#define storyGlobalVariableTableAddress() portWordGet(0xc) +#define storyStaticMemoryBaseAddress() portWordGet(0xe) +#define storyFlags2() portWordGet(0x10) +#define storyAbbreviationTableAddress() portWordGet(0x18) +#define storyChecksum() portWordGet(0x1c) +#define storyInterpreterNumber() portByteGet(0x1e) +#define storyInterpreterVersion() portByteGet(0x1f) +#define storyScreenHeightLines() portByteGet(0x20) +#define storyScreenWidthCharacters() portByteGet(0x21) +#define storyScreenHeightUnits() portWordGet(0x22) +#define storyScreenWidthUnits() portWordGet(0x24) +#define storyFontHeightUnits() (storyVersion() == 5 ? portByteGet(0x27) ? portByteGet(0x26)) // WTF Infocom? +#define storyFontWidthUnits() (storyVersion() == 5 ? portByteGet(0x26) ? portByteGet(0x27)) +#define storyRoutinesOffset() portWordGet(0x28) +#define storyStringsOffset() portWordGet(0x2a) +#define storyDefaultBackgroundColor() portByteGet(0x2c) +#define storyDefaultForegroundColor() portByteGet(0x2d) +#define storyTerminatingCharactersTableAddress() portWordGet(0x2e) +#define storyWidthInPixelsToOutputStream3() portByteGet(0x30) +#define storyStandardRevisionNumber() portByteGet(0x32) +#define storyAlphabetTableAddress() portWordGet(0x34) +#define storyHeaderExtensionTableAddress() portWordGet(0x36) -#define storyHXWordsInTable() memoryWord(storyHeaderExtensionTableAddress()) -#define storyHXMouseXClick() memoryWord(storyHeaderExtensionTableAddress() + 2) -#define storyHXMouseYClick() memoryWord(storyHeaderExtensionTableAddress() + 4) -#define storyHXUnicodeTranslationTableAddress() memoryWord(storyHeaderExtensionTableAddress() + 6) -#define storyHXFlags3() memoryWord(storyHeaderExtensionTableAddress() + 8) -#define storyHXTrueDefaultForegroundColor() memoryWord(storyHeaderExtensionTableAddress() + 10) -#define storyHXTrueDefaultBackgroundColor() memoryWord(storyHeaderExtensionTableAddress() + 12) +#define storyHXWordsInTable() portWordGet(storyHeaderExtensionTableAddress()) +#define storyHXMouseXClick() portWordGet(storyHeaderExtensionTableAddress() + 2) +#define storyHXMouseYClick() portWordGet(storyHeaderExtensionTableAddress() + 4) +#define storyHXUnicodeTranslationTableAddress() portWordGet(storyHeaderExtensionTableAddress() + 6) +#define storyHXFlags3() portWordGet(storyHeaderExtensionTableAddress() + 8) +#define storyHXTrueDefaultForegroundColor() portWordGet(storyHeaderExtensionTableAddress() + 10) +#define storyHXTrueDefaultBackgroundColor() portWordGet(storyHeaderExtensionTableAddress() + 12) uint32_t storyLength(); +void storySetup(void); #endif // STORY_H diff --git a/include/variable.h b/include/variable.h new file mode 100644 index 0000000..751bf34 --- /dev/null +++ b/include/variable.h @@ -0,0 +1,37 @@ +/* + * 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 VARIABLE_H +#define VARIABLE_H + + +#include "common.h" + + +uint32_t variableAddressGlobal(uint16_t var); +uint16_t variableLoad(uint16_t var); +void variableStore(uint16_t var, uint16_t value); +uint8_t variableAddress(uint8_t var, bool writing); + + +#endif // VARIABLE_H diff --git a/include/oc_2op.h b/include/zscii.h similarity index 84% rename from include/oc_2op.h rename to include/zscii.h index 0637e1f..71b7726 100644 --- a/include/oc_2op.h +++ b/include/zscii.h @@ -21,19 +21,16 @@ */ -#ifndef OC_2OP_H -#define OC_2OP_H +#ifndef ZSCII_H +#define ZSCII_H #include "common.h" -void opcodes_add(void); -void opcodes_je(void); -void opcodes_loadw(void); -void opcodes_store(void); -void opcodes_sub(void); -void opcodes_test_attr(void); +uint32_t zsciiDecode(uint32_t zstr, bool abbr, char *buf, uint32_t *bufLenP); +char zsciiDecodeChar(uint16_t z); +uint32_t zsciiPrint(uint32_t pc, bool abbr); -#endif // OC_2OP_H +#endif // ZSCII_H diff --git a/src/interpreter.c b/src/interpreter.c index 89fe27f..5e62fab 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -26,172 +26,15 @@ #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 *bufLenP) { - uint32_t bufLen = *bufLenP; - 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 pc = zstr; - char printVal = 0; - 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) { - - newShift = 0; - printVal = 0; - ch = ((code >> i) & 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 (bufLen) { - *(buf++) = printVal; - bufLen--; - } - } - 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); - ptr += 2; - - abbrDecodedChars = bufLen; - interpreterDecodeZSCII(abbrAddr * 2, 1, buf, &abbrDecodedChars); - decodedChars += abbrDecodedChars; - buf += (bufLen < abbrDecodedChars) ? bufLen : abbrDecodedChars; - bufLen = (bufLen < abbrDecodedChars) ? 0 : (bufLen - abbrDecodedChars); - - useAbbrTable = 0; - alphabet = 0; //***FIXME*** Version 3+ has no shift-lock but V1 needs it. - - continue; - - } // 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 (bufLen) { - *(buf++) = printVal; - bufLen--; - } - } - - 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); - - *bufLenP = decodedChars; - - return pc - zstr; -} - - -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; -} +#include "variable.h" +#include "memory.h" void interpreterDoBranch(int32_t truth) { - uint8_t branch = PEEK(__state.pc++); + uint8_t branch = ZPEEK(__state.pc++); int32_t farJump = (branch & (1 << 6)) == 0; int32_t onTruth = (branch & (1 << 7)) ? 1 : 0; - uint8_t byte2 = farJump ? PEEK(__state.pc++) : 0; + uint8_t byte2 = farJump ? ZPEEK(__state.pc++) : 0; int16_t offset; if (onTruth == truth) { @@ -241,70 +84,7 @@ void interpreterDoReturn(uint16_t r) { #endif // Store the routine result. - interpreterStoreVariable(storeId, r); -} - - -uint32_t interpreterGetObjectPointer(uint16_t objectId) { - uint32_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) { - uint32_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; -} - - -uint32_t interpreterGlobalVariableAddress(uint16_t var) { - return storyGlobalVariableTableAddress() + ((var - 0x10) * 2); -} - - -uint16_t interpreterLoadVariable(uint16_t var) { - // Is it on the stack? - if (var < 16) return __state.stack[interpreterVariableAddress(var, 0)]; - // Nope, global. - return PEEKW(interpreterGlobalVariableAddress(var)); + variableStore(storeId, r); } @@ -312,18 +92,18 @@ bool interpreterParseOperand(uint8_t opType, uint8_t operandId) { switch(opType) { // Large constant. case 0: - __state.operands[operandId] = PEEKW(__state.pc); + __state.operands[operandId] = ZPEEKW(__state.pc); __state.pc += 2; return true; // Small constant. case 1: - __state.operands[operandId] = PEEK(__state.pc++); + __state.operands[operandId] = ZPEEK(__state.pc++); return true; // Variable. case 2: - __state.operands[operandId] = interpreterLoadVariable(PEEK(__state.pc++)); + __state.operands[operandId] = variableLoad(ZPEEK(__state.pc++)); return true; } @@ -332,7 +112,7 @@ bool interpreterParseOperand(uint8_t opType, uint8_t operandId) { uint8_t interpreterParseVariableOperands(uint8_t index) { - uint8_t operandTypes = PEEK(__state.pc++); + uint8_t operandTypes = ZPEEK(__state.pc++); uint8_t shifter = 6; uint8_t i; uint8_t opType; @@ -347,28 +127,6 @@ uint8_t interpreterParseVariableOperands(uint8_t index) { } -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; @@ -376,12 +134,12 @@ void interpreterRun(void) { bool eight; while (__state.quit == 0) { - opcode = PEEK(__state.pc++); + opcode = ZPEEK(__state.pc++); extended = ((opcode == 190) && (storyVersion() >= 5)) ? true : false; if (extended) { - opcode = PEEK(__state.pc++); + opcode = ZPEEK(__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]; @@ -437,47 +195,3 @@ void interpreterRun(void) { } } - -void interpreterStoreVariable(uint16_t var, uint16_t value) { - uint32_t addr; - - if (var < 16) { - // On stack. - addr = interpreterVariableAddress(var, 1); - __state.stack[addr] = value; - } else { - // Global. - addr = interpreterGlobalVariableAddress(var); - POKEW(addr, value); - } -} - - -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); -#ifdef DEBUGGING - printf("Push stack\n"); -#endif - 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); -#ifdef DEBUGGING - printf("Pop stack\n"); -#endif - 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 f79359c..20636f9 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,7 @@ #include "opcodes.h" #include "state.h" #include "interpreter.h" +#include "story.h" int main(void) { @@ -48,7 +49,8 @@ int main(void) { memoryStartup(); stateReset(); opcodesSetup(); - memoryLoadStory(); + portStoryLoad(); + storySetup(); interpreterRun(); memoryShutdown(); diff --git a/src/memory.c b/src/memory.c index 231addd..16698c7 100644 --- a/src/memory.c +++ b/src/memory.c @@ -21,32 +21,19 @@ */ -#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)]; - - -uint8_t memoryByte(uint16_t address) { - return _RAM[address]; -} - - void memoryEnsureBuffer(uint32_t newSize) { if (__memoryBufferLen >= newSize) return; free(__memoryBuffer); @@ -58,65 +45,6 @@ void memoryEnsureBuffer(uint32_t newSize) { } -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++) = '0' + 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 >> 8) & 0xff; // MSB first. - _RAM[address + 1] = value & 0xff; -} - - void memoryShutdown(void) { free(__memoryBuffer); __memoryBuffer = 0; @@ -125,7 +53,7 @@ void memoryShutdown(void) { void memoryStartup(void) { - memoryEnsureBuffer(512); + memoryEnsureBuffer(1024); } @@ -148,10 +76,5 @@ uint32_t memoryUnpackAddress(uint16_t address, uint8_t type) { return (uint32_t)address * 8; } - return ((uint16_t)_RAM[address] << 8) | ((uint16_t)_RAM[address + 1]); -} - - -uint16_t memoryWord(uint16_t address) { - return ((uint16_t)_RAM[address] << 8) | ((uint16_t)_RAM[address + 1]); + return ((uint16_t)ZPEEK(address) << 8) | ((uint16_t)ZPEEK(address + 1)); } diff --git a/src/object.c b/src/object.c new file mode 100644 index 0000000..0540b1d --- /dev/null +++ b/src/object.c @@ -0,0 +1,80 @@ +/* + * 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 "object.h" +#include "story.h" +#include "portme.h" +#include "messages.h" +#include "memory.h" + + +uint32_t objectPointerGet(uint16_t objectId) { + uint32_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 objectPropertyGet(uint16_t objectId, uint32_t propertyId, uint8_t *size) { + uint32_t ptr = objectPointerGet(objectId); + uint16_t addr; + uint8_t info; + uint16_t num; + uint8_t s; + + if (storyVersion() <= 3) { + ptr += 7; // Skip to properties address field. + addr = ZPEEKW(ptr); + ptr = addr; + ptr += ZPEEK(ptr) * 2 + 1; // Skip object name to start of properties. + while (1) { + info = ZPEEK(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; +} + + diff --git a/src/oc_var_op.c b/src/oc_call.c similarity index 82% rename from src/oc_var_op.c rename to src/oc_call.c index 9c00252..5ad3697 100644 --- a/src/oc_var_op.c +++ b/src/oc_call.c @@ -21,18 +21,19 @@ */ -#include "oc_var_op.h" +#include "oc_call.h" #include "state.h" -#include "memory.h" +#include "interpreter.h" #include "messages.h" #include "portme.h" -#include "interpreter.h" #include "story.h" +#include "variable.h" +#include "memory.h" void opcodes_call(void) { uint8_t args = __state.operandCount; - uint8_t storeId = PEEK(__state.pc++); + uint8_t storeId = ZPEEK(__state.pc++); uint32_t routine; uint8_t numLocals; int8_t i; @@ -41,10 +42,10 @@ void opcodes_call(void) { if ((args == 0) && (__state.operands[0] == 0)) { // Legal no-op; store 0 to return value and bounce. - POKEW(interpreterVariableAddress(storeId, 1), 0); + ZPOKEW(variableAddress(storeId, 1), 0); } else { routine = memoryUnpackAddress(__state.operands[0], MEMORY_ROUTINE); - numLocals = PEEK(routine++); + numLocals = ZPEEK(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); @@ -66,7 +67,7 @@ void opcodes_call(void) { __state.bp = __state.sp; if (storyVersion() <= 4) { - for (i=0; i> (attrId & 7))) ? 1 : 0); + interpreterDoBranch((ZPEEK(ptr) & (0x80 >> (attrId & 7))) ? 1 : 0); } else { portDie(MSG_UNIMPLEMENTED); } diff --git a/src/oc_0op.c b/src/oc_output.c similarity index 90% rename from src/oc_0op.c rename to src/oc_output.c index 82dbd89..aa4a866 100644 --- a/src/oc_0op.c +++ b/src/oc_output.c @@ -21,16 +21,17 @@ */ -#include "oc_0op.h" -#include "interpreter.h" +#include "oc_output.h" #include "state.h" +#include "portme.h" +#include "zscii.h" void opcodes_new_line(void) { - interpreterPrintChars("\n", 1); + portPrintChars("\n", 1); } void opcodes_print(void) { - __state.pc += interpreterPrintZSCII(__state.pc, 0); + __state.pc += zsciiPrint(__state.pc, 0); } diff --git a/src/oc_save.c b/src/oc_save.c new file mode 100644 index 0000000..c2ed1d4 --- /dev/null +++ b/src/oc_save.c @@ -0,0 +1,24 @@ +/* + * 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 "oc_save.h" diff --git a/src/oc_window.c b/src/oc_window.c new file mode 100644 index 0000000..0762a46 --- /dev/null +++ b/src/oc_window.c @@ -0,0 +1,24 @@ +/* + * 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 "oc_window.h" diff --git a/src/opcodes.c b/src/opcodes.c index 2f77ab5..e7867e1 100644 --- a/src/opcodes.c +++ b/src/opcodes.c @@ -25,11 +25,16 @@ #include "story.h" #include "state.h" #include "opcodes.h" -#include "oc_0op.h" -#include "oc_1op.h" -#include "oc_2op.h" -#include "oc_var_op.h" -#include "oc_ext.h" +#include "oc_call.h" +#include "oc_compare.h" +#include "oc_input.h" +#include "oc_math.h" +#include "oc_memory.h" +#include "oc_misc.h" +#include "oc_object.h" +#include "oc_output.h" +#include "oc_save.h" +#include "oc_window.h" // Macros to declare implemented opcodes. diff --git a/src/portme.c b/src/portme.c index cf04566..2afacdf 100644 --- a/src/portme.c +++ b/src/portme.c @@ -24,8 +24,26 @@ #include #include #include +#include #include "portme.h" +#include "story.h" + +#include "zork1.h" + + +// The F256 has 512k of RAM. We use everything except the lower 64k. +static uint8_t _RAM[1024 * (512 - 64)]; + + +uint8_t portByteGet(uint16_t address) { + return _RAM[address]; +} + + +void portByteSet(uint16_t address, uint8_t value) { + _RAM[address] = value; +} void portDie(char *fmt, ...) { @@ -47,3 +65,33 @@ void portPrintChars(char *chars, uint16_t len) { printf("%c", chars[x]); } } + + +void portStoryLoad(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. + + // Currently no status bar. + ZPOKE(STORY_FLAG_V3, ZPEEK(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); +} + + +uint16_t portWordGet(uint16_t address) { + return ((uint16_t)_RAM[address] << 8) | ((uint16_t)_RAM[address + 1]); +} + + +void portWordSet(uint16_t address, uint16_t value) { + _RAM[address] = (value >> 8) & 0xff; // MSB first. + _RAM[address + 1] = value & 0xff; +} + diff --git a/src/story.c b/src/story.c index 9409b55..94e09af 100644 --- a/src/story.c +++ b/src/story.c @@ -1,8 +1,32 @@ +/* + * 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 "story.h" +#include "state.h" uint32_t storyLength() { - uint32_t m = (uint32_t)memoryWord(0x1a); + uint32_t m = (uint32_t)portWordGet(0x1a); switch (storyVersion()) { case 1: @@ -21,3 +45,37 @@ uint32_t storyLength() { return m; } + + +void storySetup(void) { + char *p; + uint16_t i; + + // 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++) = '0' + i; + *(p++) = '.'; + *(p++) = ','; + *(p++) = '!'; + *(p++) = '?'; + *(p++) = '_'; + *(p++) = '#'; + *(p++) = '\''; + *(p++) = '"'; + *(p++) = '/'; + *(p++) = '\\'; + if (storyVersion() == 1) *(p++) = '<'; + *(p++) = '-'; + *(p++) = ':'; + *(p++) = '('; + *(p++) = ')'; +} diff --git a/src/variable.c b/src/variable.c new file mode 100644 index 0000000..391b505 --- /dev/null +++ b/src/variable.c @@ -0,0 +1,87 @@ +/* + * 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 "variable.h" +#include "state.h" +#include "portme.h" +#include "messages.h" +#include "story.h" +#include "memory.h" + + +uint32_t variableAddressGlobal(uint16_t var) { + return storyGlobalVariableTableAddress() + ((var - 0x10) * 2); +} + + +uint16_t variableLoad(uint16_t var) { + // Is it on the stack? + if (var < 16) return __state.stack[variableAddress(var, 0)]; + // Nope, global. + return ZPEEKW(variableAddressGlobal(var)); +} + + +void variableStore(uint16_t var, uint16_t value) { + uint32_t addr; + + if (var < 16) { + // On stack. + addr = variableAddress(var, 1); + __state.stack[addr] = value; + } else { + // Global. + addr = variableAddressGlobal(var); + ZPOKEW(addr, value); + } +} + + +uint8_t variableAddress(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); +#ifdef DEBUGGING + printf("Push stack\n"); +#endif + 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); +#ifdef DEBUGGING + printf("Pop stack\n"); +#endif + 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/zscii.c b/src/zscii.c new file mode 100644 index 0000000..a83aca4 --- /dev/null +++ b/src/zscii.c @@ -0,0 +1,199 @@ +/* + * 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 "zscii.h" +#include "portme.h" +#include "messages.h" +#include "story.h" +#include "state.h" +#include "memory.h" + + +uint32_t zsciiDecode(uint32_t zstr, bool abbr, char *buf, uint32_t *bufLenP) { + uint32_t bufLen = *bufLenP; + 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 pc = zstr; + char printVal = 0; + uint32_t index; + uint32_t ptr; + uint16_t abbrAddr; + uint32_t abbrDecodedChars; + int8_t i; + uint8_t ch; + int16_t newShift; + + do { + code = ZPEEKW(pc); + pc += 2; + + // Characters are 5 bits each, packed three to a 16-bit word. + for (i=10; i>=0; i-=5) { + + newShift = 0; + printVal = 0; + ch = ((code >> i) & 0x1f); + + if (zsciiCollector) { + if (zsciiCollector == 2) { + zsciiCode |= ((uint16_t)ch) << 5; + } else { + zsciiCode |= ((uint16_t)ch); + } + + zsciiCollector--; + + if (!zsciiCollector) { + printVal = zsciiDecodeChar(zsciiCode); + if (printVal) { + decodedChars++; + if (bufLen) { + *(buf++) = printVal; + bufLen--; + } + } + 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 = ZPEEKW(ptr); + ptr += 2; + + abbrDecodedChars = bufLen; + zsciiDecode(abbrAddr * 2, 1, buf, &abbrDecodedChars); + decodedChars += abbrDecodedChars; + buf += (bufLen < abbrDecodedChars) ? bufLen : abbrDecodedChars; + bufLen = (bufLen < abbrDecodedChars) ? 0 : (bufLen - abbrDecodedChars); + + useAbbrTable = 0; + alphabet = 0; //***FIXME*** Version 3+ has no shift-lock but V1 needs it. + + continue; + + } // 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 (bufLen) { + *(buf++) = printVal; + bufLen--; + } + } + + 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); + + *bufLenP = decodedChars; + + return pc - zstr; +} + + +char zsciiDecodeChar(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; +} + + +uint32_t zsciiPrint(uint32_t pc, bool abbr) { + uint32_t decodedChars = __memoryBufferLen; + uint32_t ret = zsciiDecode(pc, abbr, __memoryBuffer, &decodedChars); + + if (decodedChars > __memoryBufferLen) { + memoryEnsureBuffer(decodedChars); + ret = zsciiDecode(pc, abbr, __memoryBuffer, &decodedChars); + } + + portPrintChars(__memoryBuffer, decodedChars); + + return ret; +}