├── .gitignore ├── ConsoleZMachine ├── App.config ├── ConsoleIO.cs ├── ConsoleZMachine.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── README.md ├── ZMachineLib.sln └── ZMachineLib ├── IZMachineIO.cs ├── Log.cs ├── Opcode.cs ├── OperandType.cs ├── ZColor.cs ├── ZMachine.cs ├── ZMachineLib.csproj └── ZStackFrame.cs /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /ConsoleZMachine/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ConsoleZMachine/ConsoleIO.cs: -------------------------------------------------------------------------------- 1 | #define SIMPLE_IO 2 | 3 | using System; 4 | using System.IO; 5 | using ZMachineLib; 6 | 7 | namespace ConsoleZMachine 8 | { 9 | public class ConsoleIO : IZMachineIO 10 | { 11 | private int _lines; 12 | private readonly ConsoleColor _defaultFore; 13 | private readonly ConsoleColor _defaultBack; 14 | 15 | public ConsoleIO() 16 | { 17 | Console.SetBufferSize(Console.WindowWidth, Console.WindowHeight); 18 | Console.SetCursorPosition(0, Console.WindowHeight-1); 19 | _defaultFore = Console.ForegroundColor; 20 | _defaultBack = Console.BackgroundColor; 21 | } 22 | 23 | public void Print(string s) 24 | { 25 | for(int i = 0; i < s.Length; i++) 26 | { 27 | if(s[i] == ' ') 28 | { 29 | int next = s.IndexOf(' ', i+1); 30 | if(next == -1) 31 | next = s.Length; 32 | if(next >= 0) 33 | { 34 | if(Console.CursorLeft + (next - i) >= Console.WindowWidth) 35 | { 36 | Console.MoveBufferArea(0, 0, Console.WindowWidth, _lines, 0, 1); 37 | Console.WriteLine(""); 38 | 39 | i++; 40 | } 41 | } 42 | } 43 | 44 | if(i < s.Length && s[i] == Environment.NewLine[0]) 45 | Console.MoveBufferArea(0, 0, Console.WindowWidth, _lines, 0, 1); 46 | 47 | if(i < s.Length) 48 | Console.Write(s[i]); 49 | } 50 | } 51 | 52 | public string Read(int max) 53 | { 54 | #if SIMPLE_IO 55 | string s = Console.ReadLine(); 56 | Console.MoveBufferArea(0, 0, Console.WindowWidth, _lines, 0, 1); 57 | return s?.Substring(0, Math.Min(s.Length, max)); 58 | #else 59 | string s = string.Empty; 60 | ConsoleKeyInfo key = new ConsoleKeyInfo(); 61 | 62 | do 63 | { 64 | if(Console.KeyAvailable) 65 | { 66 | key = Console.ReadKey(true); 67 | switch(key.Key) 68 | { 69 | case ConsoleKey.Backspace: 70 | if(s.Length > 0) 71 | { 72 | s = s.Remove(s.Length-1, 1); 73 | Console.Write(key.KeyChar); 74 | } 75 | break; 76 | case ConsoleKey.Enter: 77 | break; 78 | default: 79 | s += key.KeyChar; 80 | Console.Write(key.KeyChar); 81 | break; 82 | } 83 | } 84 | } 85 | while(key.Key != ConsoleKey.Enter); 86 | 87 | Console.MoveBufferArea(0, 0, Console.WindowWidth, _lines, 0, 1); 88 | Console.WriteLine(string.Empty); 89 | return s; 90 | #endif 91 | } 92 | 93 | public char ReadChar() 94 | { 95 | return Console.ReadKey(true).KeyChar; 96 | } 97 | 98 | public void SetCursor(ushort line, ushort column, ushort window) 99 | { 100 | Console.SetCursorPosition(column-1, line-1); 101 | } 102 | 103 | public void SetWindow(ushort window) 104 | { 105 | if(window == 0) 106 | Console.SetCursorPosition(0, Console.WindowHeight-1); 107 | } 108 | 109 | public void EraseWindow(ushort window) 110 | { 111 | ConsoleColor c = Console.BackgroundColor; 112 | Console.BackgroundColor = _defaultBack; 113 | Console.Clear(); 114 | Console.BackgroundColor = c; 115 | } 116 | 117 | public void BufferMode(bool buffer) 118 | { 119 | } 120 | 121 | public void SplitWindow(ushort lines) 122 | { 123 | _lines = lines; 124 | } 125 | 126 | public void ShowStatus() 127 | { 128 | } 129 | 130 | public void SetTextStyle(TextStyle textStyle) 131 | { 132 | switch(textStyle) 133 | { 134 | case TextStyle.Roman: 135 | Console.ResetColor(); 136 | break; 137 | case TextStyle.Reverse: 138 | ConsoleColor temp = Console.BackgroundColor; 139 | Console.BackgroundColor = Console.ForegroundColor; 140 | Console.ForegroundColor = temp; 141 | break; 142 | case TextStyle.Bold: 143 | break; 144 | case TextStyle.Italic: 145 | break; 146 | case TextStyle.FixedPitch: 147 | break; 148 | default: 149 | throw new ArgumentOutOfRangeException(nameof(textStyle), textStyle, null); 150 | } 151 | } 152 | 153 | public void SetColor(ZColor foreground, ZColor background) 154 | { 155 | Console.ForegroundColor = ZColorToConsoleColor(foreground, true); 156 | Console.BackgroundColor = ZColorToConsoleColor(background, false); 157 | } 158 | 159 | public void SoundEffect(ushort number) 160 | { 161 | if(number == 1) 162 | Console.Beep(2000, 300); 163 | else if(number == 2) 164 | Console.Beep(250, 300); 165 | else 166 | throw new Exception("Sound > 2"); 167 | } 168 | 169 | public void Quit() 170 | { 171 | } 172 | 173 | private ConsoleColor ZColorToConsoleColor(ZColor c, bool fore) 174 | { 175 | switch(c) 176 | { 177 | case ZColor.PixelUnderCursor: 178 | case ZColor.Current: 179 | return fore ? Console.ForegroundColor : Console.BackgroundColor; 180 | case ZColor.Default: 181 | return fore ? _defaultFore : _defaultBack; 182 | case ZColor.Black: 183 | return ConsoleColor.Black; 184 | case ZColor.Red: 185 | return ConsoleColor.Red; 186 | case ZColor.Green: 187 | return ConsoleColor.Green; 188 | case ZColor.Yellow: 189 | return ConsoleColor.Yellow; 190 | case ZColor.Blue: 191 | return ConsoleColor.Blue; 192 | case ZColor.Magenta: 193 | return ConsoleColor.Magenta; 194 | case ZColor.Cyan: 195 | return ConsoleColor.Cyan; 196 | case ZColor.White: 197 | return ConsoleColor.White; 198 | case ZColor.DarkishGrey: 199 | return ConsoleColor.DarkGray; 200 | case ZColor.LightGrey: 201 | return ConsoleColor.Gray; 202 | case ZColor.MediumGrey: 203 | return ConsoleColor.Gray; 204 | case ZColor.DarkGrey: 205 | return ConsoleColor.DarkGray; 206 | } 207 | return Console.ForegroundColor; 208 | } 209 | 210 | public bool Save(Stream s) 211 | { 212 | FileStream fs = File.Create(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "save")); 213 | s.CopyTo(fs); 214 | fs.Close(); 215 | return true; 216 | } 217 | 218 | public Stream Restore() 219 | { 220 | FileStream fs = File.OpenRead(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "save")); 221 | return fs; 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /ConsoleZMachine/ConsoleZMachine.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D85E0558-A1C2-4B81-9718-7F371F844DD4} 8 | Exe 9 | Properties 10 | ConsoleZMachine 11 | ConsoleZMachine 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {8130ab87-4ad2-4025-bbe8-fa1d06f6df9e} 56 | ZMachineLib 57 | 58 | 59 | 60 | 67 | -------------------------------------------------------------------------------- /ConsoleZMachine/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using ZMachineLib; 3 | 4 | namespace ConsoleZMachine 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | ZMachine zMachine = new ZMachine(new ConsoleIO()); 11 | 12 | FileStream fs = File.OpenRead(@"zork1.dat"); 13 | zMachine.LoadFile(fs); 14 | zMachine.Run(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ConsoleZMachine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ConsoleZMachine")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConsoleZMachine")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("d85e0558-a1c2-4b81-9718-7f371f844dd4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Brian Peek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZMachineLib 2 | 3 | An incomplete ZMachine interpreter written in C#. This library supports v1-v5, but v5 is not complete. The code is a bit rough around the edges in places but I will continue working on it. See the ConsoleZMachine project for info on how to use the library. 4 | -------------------------------------------------------------------------------- /ZMachineLib.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ZMachineLib", "ZMachineLib\ZMachineLib.csproj", "{8130AB87-4AD2-4025-BBE8-FA1D06F6DF9E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleZMachine", "ConsoleZMachine\ConsoleZMachine.csproj", "{D85E0558-A1C2-4B81-9718-7F371F844DD4}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {8130AB87-4AD2-4025-BBE8-FA1D06F6DF9E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {8130AB87-4AD2-4025-BBE8-FA1D06F6DF9E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {8130AB87-4AD2-4025-BBE8-FA1D06F6DF9E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {8130AB87-4AD2-4025-BBE8-FA1D06F6DF9E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D85E0558-A1C2-4B81-9718-7F371F844DD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D85E0558-A1C2-4B81-9718-7F371F844DD4}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D85E0558-A1C2-4B81-9718-7F371F844DD4}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D85E0558-A1C2-4B81-9718-7F371F844DD4}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {E70C3CF7-E1F5-431F-95F8-2762437B6AC9} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ZMachineLib/IZMachineIO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace ZMachineLib 5 | { 6 | [Flags] 7 | public enum TextStyle 8 | { 9 | Roman = 0, 10 | Reverse = 1, 11 | Bold = 2, 12 | Italic = 4, 13 | FixedPitch = 8 14 | } 15 | 16 | public interface IZMachineIO 17 | { 18 | void Print(string s); 19 | string Read(int max); 20 | char ReadChar(); 21 | void SetCursor(ushort line, ushort column, ushort window); 22 | void SetWindow(ushort window); 23 | void EraseWindow(ushort window); 24 | void BufferMode(bool buffer); 25 | void SplitWindow(ushort lines); 26 | void ShowStatus(); 27 | void SetTextStyle(TextStyle textStyle); 28 | bool Save(Stream stream); 29 | Stream Restore(); 30 | void SetColor(ZColor foreground, ZColor background); 31 | void SoundEffect(ushort number); 32 | void Quit(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ZMachineLib/Log.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text; 3 | 4 | namespace ZMachineLib 5 | { 6 | internal static class Log 7 | { 8 | private static readonly StringBuilder _output = new StringBuilder(); 9 | 10 | public static void Write(string s) 11 | { 12 | _output.Append(s); 13 | } 14 | 15 | public static void WriteLine(string s) 16 | { 17 | _output.AppendLine(s); 18 | } 19 | 20 | public static void Flush() 21 | { 22 | Print(); 23 | _output.Clear(); 24 | } 25 | 26 | public static void Print() 27 | { 28 | Debug.WriteLine(_output.ToString()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ZMachineLib/Opcode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace ZMachineLib 4 | { 5 | internal delegate void OpcodeHandler(List args); 6 | 7 | internal struct Opcode 8 | { 9 | public string Name { get; set; } 10 | public OpcodeHandler Handler { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /ZMachineLib/OperandType.cs: -------------------------------------------------------------------------------- 1 | namespace ZMachineLib 2 | { 3 | internal enum OperandType 4 | { 5 | LargeConstant = 0, 6 | SmallConstant, 7 | Variable 8 | } 9 | } -------------------------------------------------------------------------------- /ZMachineLib/ZColor.cs: -------------------------------------------------------------------------------- 1 | namespace ZMachineLib 2 | { 3 | public enum ZColor 4 | { 5 | PixelUnderCursor = -1, 6 | Current = 0, 7 | Default, 8 | Black, 9 | Red, 10 | Green, 11 | Yellow, 12 | Blue, 13 | Magenta, 14 | Cyan, 15 | White, 16 | DarkishGrey, 17 | LightGrey, 18 | MediumGrey, 19 | DarkGrey 20 | } 21 | } -------------------------------------------------------------------------------- /ZMachineLib/ZMachine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Runtime.Serialization.Json; 5 | using System.Text; 6 | 7 | namespace ZMachineLib 8 | { 9 | public class ZMachine 10 | { 11 | private const int ParentOffsetV3 = 4; 12 | private const int SiblingOffsetV3 = 5; 13 | private const int ChildOffsetV3 = 6; 14 | private const int PropertyOffsetV3 = 7; 15 | private const int ObjectSizeV3 = 9; 16 | private const int PropertyDefaultTableSizeV3 = 62; 17 | 18 | private const int ParentOffsetV5 = 6; 19 | private const int SiblingOffsetV5 = 8; 20 | private const int ChildOffsetV5 = 10; 21 | private const int PropertyOffsetV5 = 12; 22 | private const int ObjectSizeV5 = 14; 23 | private const int PropertyDefaultTableSizeV5 = 126; 24 | 25 | private int ParentOffset; 26 | private int SiblingOffset; 27 | private int ChildOffset; 28 | private int PropertyOffset; 29 | private int ObjectSize; 30 | private int PropertyDefaultTableSize; 31 | 32 | private const int Version = 0x00; 33 | private const int InitialPC = 0x06; 34 | private const int DictionaryOffset = 0x08; 35 | private const int ObjectTableOffset = 0x0a; 36 | private const int GlobalVarOffset = 0x0c; 37 | private const int StaticMemoryOffset = 0x0e; 38 | private const int AbbreviationTableOffset = 0x18; 39 | 40 | private readonly IZMachineIO _io; 41 | private byte[] _memory; 42 | private Stream _file; 43 | private bool _running; 44 | private Random _random = new Random(); 45 | private string[] _dictionaryWords; 46 | 47 | private byte _version; 48 | private ushort _pc; 49 | private ushort _globals; 50 | private ushort _objectTable; 51 | private ushort _abbreviationsTable; 52 | private ushort _dictionary; 53 | private ushort _dynamicMemory; 54 | private ushort _readTextAddr; 55 | private ushort _readParseAddr; 56 | private bool _terminateOnInput; 57 | 58 | private Stack _stack = new Stack(); 59 | private readonly Opcode[] _0Opcodes = new Opcode[0x10]; 60 | private readonly Opcode[] _1Opcodes = new Opcode[0x10]; 61 | private readonly Opcode[] _2Opcodes = new Opcode[0x20]; 62 | private readonly Opcode[] _varOpcodes = new Opcode[0x20]; 63 | private readonly Opcode[] _extOpcodes = new Opcode[0x20]; 64 | 65 | private readonly Opcode UnknownOpcode = new Opcode { Handler = delegate { Log.Flush(); throw new Exception("Unknown opcode"); }, Name = "UNKNOWN" }; 66 | 67 | private readonly string _table = @" ^0123456789.,!?_#'""/\-:()"; 68 | private byte _entryLength; 69 | private ushort _wordStart; 70 | 71 | public ZMachine(IZMachineIO io) 72 | { 73 | _io = io; 74 | 75 | InitOpcodes(_0Opcodes); 76 | InitOpcodes(_1Opcodes); 77 | InitOpcodes(_2Opcodes); 78 | InitOpcodes(_varOpcodes); 79 | InitOpcodes(_extOpcodes); 80 | } 81 | 82 | private void InitOpcodes(Opcode[] opcodes) 83 | { 84 | for(int i = 0; i < opcodes.Length; i++) 85 | opcodes[i] = UnknownOpcode; 86 | } 87 | 88 | private void SetupOpcodes() 89 | { 90 | _0Opcodes[0x00] = new Opcode { Handler = RTrue, Name = "RTRUE" }; 91 | _0Opcodes[0x01] = new Opcode { Handler = RFalse, Name = "RFALSE" }; 92 | _0Opcodes[0x02] = new Opcode { Handler = Print, Name = "PRINT" }; 93 | _0Opcodes[0x03] = new Opcode { Handler = PrintRet, Name = "PRINT_RET" }; 94 | _0Opcodes[0x04] = new Opcode { Handler = Nop, Name = "NOP" }; 95 | _0Opcodes[0x05] = new Opcode { Handler = Save, Name = "SAVE" }; 96 | _0Opcodes[0x06] = new Opcode { Handler = Restore, Name = "RESTORE" }; 97 | _0Opcodes[0x07] = new Opcode { Handler = Restart, Name = "RESTART" }; 98 | _0Opcodes[0x08] = new Opcode { Handler = RetPopped, Name = "RET_POPPED" }; 99 | _0Opcodes[0x09] = new Opcode { Handler = Pop, Name = "POP" }; 100 | _0Opcodes[0x0a] = new Opcode { Handler = Quit, Name = "QUIT" }; 101 | _0Opcodes[0x0b] = new Opcode { Handler = NewLine, Name = "NEW_LINE" }; 102 | _0Opcodes[0x0c] = new Opcode { Handler = ShowStatus, Name = "SHOW_STATUS" }; 103 | _0Opcodes[0x0d] = new Opcode { Handler = Verify, Name = "VERIFY" }; 104 | _0Opcodes[0x0f] = new Opcode { Handler = Piracy, Name = "PIRACY" }; 105 | 106 | _1Opcodes[0x00] = new Opcode { Handler = Jz, Name = "JZ" }; 107 | _1Opcodes[0x01] = new Opcode { Handler = GetSibling, Name = "GET_SIBLING" }; 108 | _1Opcodes[0x02] = new Opcode { Handler = GetChild, Name = "GET_CHILD" }; 109 | _1Opcodes[0x03] = new Opcode { Handler = GetParent, Name = "GET_PARENT" }; 110 | _1Opcodes[0x04] = new Opcode { Handler = GetPropLen, Name = "GET_PROP_LEN" }; 111 | _1Opcodes[0x05] = new Opcode { Handler = Inc, Name = "INC" }; 112 | _1Opcodes[0x06] = new Opcode { Handler = Dec, Name = "DEC" }; 113 | _1Opcodes[0x07] = new Opcode { Handler = PrintAddr, Name = "PRINT_ADDR" }; 114 | _1Opcodes[0x08] = new Opcode { Handler = Call1S, Name = "CALL_1S" }; 115 | _1Opcodes[0x09] = new Opcode { Handler = RemoveObj, Name = "REMOVE_OBJ" }; 116 | _1Opcodes[0x0a] = new Opcode { Handler = PrintObj, Name = "PRINT_OBJ" }; 117 | _1Opcodes[0x0b] = new Opcode { Handler = Ret, Name = "RET" }; 118 | _1Opcodes[0x0c] = new Opcode { Handler = Jump, Name = "JUMP" }; 119 | _1Opcodes[0x0d] = new Opcode { Handler = PrintPAddr, Name = "PRINT_PADDR" }; 120 | _1Opcodes[0x0e] = new Opcode { Handler = Load, Name = "LOAD" }; 121 | if(_version <= 4) 122 | _1Opcodes[0x0f] = new Opcode { Handler = Not, Name = "NOT" }; 123 | else 124 | _1Opcodes[0x0f] = new Opcode { Handler = Call1N, Name = "CALL_1N" }; 125 | 126 | _2Opcodes[0x01] = new Opcode { Handler = Je, Name = "JE" }; 127 | _2Opcodes[0x02] = new Opcode { Handler = Jl, Name = "JL" }; 128 | _2Opcodes[0x03] = new Opcode { Handler = Jg, Name = "JG" }; 129 | _2Opcodes[0x04] = new Opcode { Handler = DecCheck, Name = "DEC_CHECK" }; 130 | _2Opcodes[0x05] = new Opcode { Handler = IncCheck, Name = "INC_CHECK" }; 131 | _2Opcodes[0x06] = new Opcode { Handler = Jin, Name = "JIN" }; 132 | _2Opcodes[0x07] = new Opcode { Handler = Test, Name = "TEST" }; 133 | _2Opcodes[0x08] = new Opcode { Handler = Or, Name = "OR" }; 134 | _2Opcodes[0x09] = new Opcode { Handler = And, Name = "AND" }; 135 | _2Opcodes[0x0a] = new Opcode { Handler = TestAttribute, Name = "TEST_ATTR" }; 136 | _2Opcodes[0x0b] = new Opcode { Handler = SetAttribute, Name = "SET_ATTR" }; 137 | _2Opcodes[0x0c] = new Opcode { Handler = ClearAttribute, Name = "CLEAR_ATTR" }; 138 | _2Opcodes[0x0d] = new Opcode { Handler = Store, Name = "STORE" }; 139 | _2Opcodes[0x0e] = new Opcode { Handler = InsertObj, Name = "INSERT_OBJ" }; 140 | _2Opcodes[0x0f] = new Opcode { Handler = LoadW, Name = "LOADW" }; 141 | _2Opcodes[0x10] = new Opcode { Handler = LoadB, Name = "LOADB" }; 142 | _2Opcodes[0x11] = new Opcode { Handler = GetProp, Name = "GET_PROP" }; 143 | _2Opcodes[0x12] = new Opcode { Handler = GetPropAddr, Name = "GET_PROP_ADDR" }; 144 | _2Opcodes[0x13] = new Opcode { Handler = GetNextProp, Name = "GET_NEXT_PROP" }; 145 | _2Opcodes[0x14] = new Opcode { Handler = Add, Name = "ADD" }; 146 | _2Opcodes[0x15] = new Opcode { Handler = Sub, Name = "SUB" }; 147 | _2Opcodes[0x16] = new Opcode { Handler = Mul, Name = "MUL" }; 148 | _2Opcodes[0x17] = new Opcode { Handler = Div, Name = "DIV" }; 149 | _2Opcodes[0x18] = new Opcode { Handler = Mod, Name = "MOD" }; 150 | _2Opcodes[0x19] = new Opcode { Handler = Call2S, Name = "CALL_2S" }; 151 | _2Opcodes[0x1a] = new Opcode { Handler = Call2N, Name = "CALL_2N" }; 152 | _2Opcodes[0x1b] = new Opcode { Handler = SetColor, Name = "SET_COLOR" }; 153 | 154 | _varOpcodes[0x00] = new Opcode { Handler = Call, Name = "CALL(_VS)" }; 155 | _varOpcodes[0x01] = new Opcode { Handler = StoreW, Name = "STOREW" }; 156 | _varOpcodes[0x02] = new Opcode { Handler = StoreB, Name = "STOREB" }; 157 | _varOpcodes[0x03] = new Opcode { Handler = PutProp, Name = "PUT_PROP" }; 158 | _varOpcodes[0x04] = new Opcode { Handler = Read, Name = "READ" }; 159 | _varOpcodes[0x05] = new Opcode { Handler = PrintChar, Name = "PRINT_CHAR" }; 160 | _varOpcodes[0x06] = new Opcode { Handler = PrintNum, Name = "PRINT_NUM" }; 161 | _varOpcodes[0x07] = new Opcode { Handler = Random, Name = "RANDOM" }; 162 | _varOpcodes[0x08] = new Opcode { Handler = Push, Name = "PUSH" }; 163 | _varOpcodes[0x09] = new Opcode { Handler = Pull, Name = "PULL" }; 164 | _varOpcodes[0x0a] = new Opcode { Handler = SplitWindow, Name = "SPLIT_WINDOW" }; 165 | _varOpcodes[0x0b] = new Opcode { Handler = SetWindow, Name = "SET_WINDOW" }; 166 | _varOpcodes[0x0c] = new Opcode { Handler = CallVS2, Name = "CALL_VS2" }; 167 | _varOpcodes[0x0d] = new Opcode { Handler = EraseWindow, Name = "ERASE_WINDOW" }; 168 | _varOpcodes[0x0f] = new Opcode { Handler = SetCursor, Name = "SET_CURSOR" }; 169 | _varOpcodes[0x11] = new Opcode { Handler = SetTextStyle, Name = "SET_TEXT_STYLE" }; 170 | _varOpcodes[0x12] = new Opcode { Handler = BufferMode, Name = "BUFFER_MODE" }; 171 | _varOpcodes[0x13] = new Opcode { Handler = OutputStream, Name = "OUTPUT_STREAM" }; 172 | _varOpcodes[0x15] = new Opcode { Handler = SoundEffect, Name = "SOUND_EFFECT" }; 173 | _varOpcodes[0x16] = new Opcode { Handler = ReadChar, Name = "READ_CHAR" }; 174 | _varOpcodes[0x17] = new Opcode { Handler = ScanTable, Name = "SCAN_TABLE" }; 175 | _varOpcodes[0x18] = new Opcode { Handler = Not, Name = "NOT" }; 176 | _varOpcodes[0x19] = new Opcode { Handler = CallVN, Name = "CALL_VN" }; 177 | _varOpcodes[0x1a] = new Opcode { Handler = CallVN2, Name = "CALL_VN2" }; 178 | _varOpcodes[0x1d] = new Opcode { Handler = CopyTable, Name = "COPY_TABLE" }; 179 | _varOpcodes[0x1e] = new Opcode { Handler = PrintTable, Name = "PRINT_TABLE" }; 180 | _varOpcodes[0x1f] = new Opcode { Handler = CheckArgCount, Name = "CHECK_ARG_COUNT" }; 181 | 182 | _extOpcodes[0x00] = new Opcode { Handler = Save, Name = "SAVE" }; 183 | _extOpcodes[0x01] = new Opcode { Handler = Restore, Name = "RESTORE" }; 184 | _extOpcodes[0x02] = new Opcode { Handler = LogShift, Name = "LOG_SHIFT" }; 185 | _extOpcodes[0x03] = new Opcode { Handler = ArtShift, Name = "ART_SHIFT" }; 186 | _extOpcodes[0x04] = new Opcode { Handler = SetFont, Name = "SET_FONT" }; 187 | } 188 | 189 | public void LoadFile(Stream stream) 190 | { 191 | _file = stream; 192 | 193 | _memory = new byte[stream.Length]; 194 | stream.Seek(0, SeekOrigin.Begin); 195 | stream.Read(_memory, 0, (int)stream.Length); 196 | 197 | _version = _memory[Version]; 198 | _pc = GetWord(InitialPC); 199 | _dictionary = GetWord(DictionaryOffset); 200 | _objectTable = GetWord(ObjectTableOffset); 201 | _globals = GetWord(GlobalVarOffset); 202 | _abbreviationsTable = GetWord(AbbreviationTableOffset); 203 | _dynamicMemory = GetWord(StaticMemoryOffset); 204 | 205 | // TODO: set these via IZMachineIO 206 | _memory[0x01] = 0x01; 207 | _memory[0x20] = 25; 208 | _memory[0x21] = 80; 209 | 210 | SetupOpcodes(); 211 | ParseDictionary(); 212 | 213 | if(_version <= 3) 214 | { 215 | ParentOffset = ParentOffsetV3; 216 | SiblingOffset = SiblingOffsetV3; 217 | ChildOffset = ChildOffsetV3; 218 | PropertyOffset = PropertyOffsetV3; 219 | ObjectSize = ObjectSizeV3; 220 | PropertyDefaultTableSize = PropertyDefaultTableSizeV3; 221 | } 222 | else if (_version <= 5) 223 | { 224 | ParentOffset = ParentOffsetV5; 225 | SiblingOffset = SiblingOffsetV5; 226 | ChildOffset = ChildOffsetV5; 227 | PropertyOffset = PropertyOffsetV5; 228 | ObjectSize = ObjectSizeV5; 229 | PropertyDefaultTableSize = PropertyDefaultTableSizeV5; 230 | } 231 | 232 | ZStackFrame zsf = new ZStackFrame { PC = _pc }; 233 | _stack.Push(zsf); 234 | } 235 | 236 | public void Run(bool terminateOnInput = false) 237 | { 238 | _terminateOnInput = terminateOnInput; 239 | 240 | _running = true; 241 | 242 | while(_running) 243 | { 244 | Opcode? opcode; 245 | 246 | Log.Write($"PC: {_stack.Peek().PC:X5}"); 247 | byte o = _memory[_stack.Peek().PC++]; 248 | if(o == 0xbe) 249 | { 250 | o = _memory[_stack.Peek().PC++]; 251 | opcode = _extOpcodes?[o & 0x1f]; 252 | // TODO: hack to make this a VAR opcode... 253 | o |= 0xc0; 254 | } 255 | else if(o < 0x80) 256 | opcode = _2Opcodes?[o & 0x1f]; 257 | else if (o < 0xb0) 258 | opcode = _1Opcodes?[o & 0x0f]; 259 | else if (o < 0xc0) 260 | opcode = _0Opcodes?[o & 0x0f]; 261 | else if (o < 0xe0) 262 | opcode = _2Opcodes?[o & 0x1f]; 263 | else 264 | opcode = _varOpcodes?[o & 0x1f]; 265 | Log.Write($" Op ({o:X2}): {opcode?.Name} "); 266 | var args = GetOperands(o); 267 | opcode?.Handler(args); 268 | Log.Flush(); 269 | } 270 | } 271 | 272 | private void Nop(List args) 273 | { 274 | } 275 | 276 | private void Save(List args) 277 | { 278 | Stream s = SaveState(); 279 | bool val = _io.Save(s); 280 | 281 | if(_version < 5) 282 | Jump(val); 283 | else 284 | { 285 | byte dest = _memory[_stack.Peek().PC++]; 286 | StoreWordInVariable(dest, (ushort)(val ? 1 : 0)); 287 | } 288 | } 289 | 290 | public Stream SaveState() 291 | { 292 | MemoryStream ms = new MemoryStream(); 293 | BinaryWriter bw = new BinaryWriter(ms); 294 | bw.Write(_readParseAddr); 295 | bw.Write(_readTextAddr); 296 | bw.Write(_memory, 0, _dynamicMemory - 1); 297 | DataContractJsonSerializer dcs = new DataContractJsonSerializer(typeof(Stack)); 298 | dcs.WriteObject(ms, _stack); 299 | ms.Position = 0; 300 | return ms; 301 | } 302 | 303 | private void Restore(List args) 304 | { 305 | Stream stream = _io.Restore(); 306 | if(stream != null) 307 | RestoreState(stream); 308 | 309 | if(_version < 5) 310 | Jump(stream != null); 311 | else 312 | { 313 | byte dest = _memory[_stack.Peek().PC++]; 314 | StoreWordInVariable(dest, (ushort)(stream != null ? 1 : 0)); 315 | } 316 | } 317 | 318 | public void RestoreState(Stream stream) 319 | { 320 | BinaryReader br = new BinaryReader(stream); 321 | stream.Position = 0; 322 | _readParseAddr = br.ReadUInt16(); 323 | _readTextAddr = br.ReadUInt16(); 324 | stream.Read(_memory, 0, _dynamicMemory - 1); 325 | DataContractJsonSerializer dcs = new DataContractJsonSerializer(typeof(Stack)); 326 | _stack = (Stack)dcs.ReadObject(stream); 327 | stream.Dispose(); 328 | } 329 | 330 | private void Restart(List args) 331 | { 332 | LoadFile(_file); 333 | } 334 | 335 | private void Quit(List args) 336 | { 337 | _running = false; 338 | _io.Quit(); 339 | } 340 | 341 | private void Verify(List args) 342 | { 343 | // TODO: checksum 344 | Jump(true); 345 | } 346 | 347 | private void Piracy(List args) 348 | { 349 | Jump(true); 350 | } 351 | 352 | private void ScanTable(List args) 353 | { 354 | byte dest = _memory[_stack.Peek().PC++]; 355 | byte len = 0x02; 356 | 357 | if(args.Count == 4) 358 | len = (byte)(args[3] & 0x7f); 359 | 360 | for(int i = 0; i < args[2]; i++) 361 | { 362 | ushort addr = (ushort)(args[1] + i*len); 363 | ushort val; 364 | 365 | if(args.Count == 3 || (args[3] & 0x80) == 0x80) 366 | val = GetWord(addr); 367 | else 368 | val = _memory[addr]; 369 | 370 | if(val == args[0]) 371 | { 372 | StoreWordInVariable(dest, addr); 373 | Jump(true); 374 | return; 375 | } 376 | } 377 | 378 | StoreWordInVariable(dest, 0); 379 | Jump(false); 380 | } 381 | 382 | private void CopyTable(List args) 383 | { 384 | if(args[1] == 0) 385 | { 386 | for(int i = 0; i < args[2]; i++) 387 | _memory[args[0] + i] = 0; 388 | } 389 | else if((short)args[1] < 0) 390 | { 391 | for(int i = 0; i < Math.Abs(args[2]); i++) 392 | _memory[args[1] + i] = _memory[args[0] + i]; 393 | } 394 | else 395 | { 396 | for(int i = Math.Abs(args[2])-1; i >=0 ; i--) 397 | _memory[args[1] + i] = _memory[args[0] + i]; 398 | } 399 | } 400 | 401 | private void PrintTable(List args) 402 | { 403 | // TODO: print properly 404 | 405 | List chars = GetZsciiChars(args[0]); 406 | string s = DecodeZsciiChars(chars); 407 | _io.Print(s); 408 | Log.Write($"[{s}]"); 409 | } 410 | 411 | private void Print(List args) 412 | { 413 | List chars = GetZsciiChars(_stack.Peek().PC); 414 | _stack.Peek().PC += (ushort)(chars.Count/3*2); 415 | string s = DecodeZsciiChars(chars); 416 | _io.Print(s); 417 | Log.Write($"[{s}]"); 418 | } 419 | 420 | private string DecodeZsciiChars(List chars) 421 | { 422 | StringBuilder sb = new StringBuilder(); 423 | for(int i = 0; i < chars.Count; i++) 424 | { 425 | if(chars[i] == 0x00) 426 | sb.Append(" "); 427 | else if(chars[i] >= 0x01 && chars[i] <= 0x03) 428 | { 429 | ushort offset = (ushort)(32*(chars[i]-1) + chars[++i]); 430 | ushort lookup = (ushort)(_abbreviationsTable + (offset*2)); 431 | ushort wordAddr = GetWord(lookup); 432 | List abbrev = GetZsciiChars((ushort)(wordAddr*2)); 433 | sb.Append(DecodeZsciiChars(abbrev)); 434 | } 435 | else if(chars[i] == 0x04) 436 | sb.Append(Convert.ToChar((chars[++i]-6)+'A')); 437 | else if(chars[i] == 0x05) 438 | { 439 | if(i == chars.Count-1 || chars[i+1] == 0x05) 440 | break; 441 | 442 | if(chars[i+1] == 0x06) 443 | { 444 | ushort x = (ushort)(chars[i+2] << 5 | chars[i+3]); 445 | i+= 3; 446 | sb.Append(Convert.ToChar(x)); 447 | } 448 | else if(chars[i+1] == 0x07) 449 | { 450 | sb.AppendLine(""); 451 | i++; 452 | } 453 | else 454 | sb.Append(_table[chars[++i]-6]); 455 | } 456 | else 457 | sb.Append(Convert.ToChar((chars[i]-6)+'a')); 458 | } 459 | return sb.ToString(); 460 | } 461 | 462 | private List GetZsciiChars(uint address) 463 | { 464 | List chars = new List(); 465 | ushort word; 466 | do 467 | { 468 | word = GetWord(address); 469 | chars.AddRange(GetZsciiChar(address)); 470 | address += 2; 471 | } 472 | while((word & 0x8000) != 0x8000); 473 | 474 | return chars; 475 | } 476 | 477 | private List GetZsciiChar(uint address) 478 | { 479 | List chars = new List(); 480 | 481 | var word = GetWord(address); 482 | 483 | byte c = (byte)(word >> 10 & 0x1f); 484 | chars.Add(c); 485 | c = (byte)(word >> 5 & 0x1f); 486 | chars.Add(c); 487 | c = (byte)(word >> 0 & 0x1f); 488 | chars.Add(c); 489 | 490 | return chars; 491 | } 492 | 493 | private void NewLine(List args) 494 | { 495 | _io.Print(Environment.NewLine); 496 | } 497 | 498 | private void PrintNum(List args) 499 | { 500 | string s = args[0].ToString(); 501 | _io.Print(s); 502 | Log.Write($"[{s}]"); 503 | } 504 | 505 | private void PrintChar(List args) 506 | { 507 | string s = Convert.ToChar(args[0]).ToString(); 508 | _io.Print(s); 509 | Log.Write($"[{s}]"); 510 | } 511 | 512 | private void PrintRet(List args) 513 | { 514 | List chars = GetZsciiChars(_stack.Peek().PC); 515 | _stack.Peek().PC += (ushort)(chars.Count/3*2); 516 | string s = DecodeZsciiChars(chars); 517 | _io.Print(s + Environment.NewLine); 518 | Log.Write($"[{s}]"); 519 | RTrue(null); 520 | } 521 | 522 | private void PrintObj(List args) 523 | { 524 | ushort addr = GetPropertyHeaderAddress(args[0]); 525 | var chars = GetZsciiChars((ushort)(addr+1)); 526 | string s = DecodeZsciiChars(chars); 527 | _io.Print(s); 528 | Log.Write($"[{s}]"); 529 | } 530 | 531 | private void PrintAddr(List args) 532 | { 533 | List chars = GetZsciiChars(args[0]); 534 | string s = DecodeZsciiChars(chars); 535 | _io.Print(s); 536 | Log.Write($"[{s}]"); 537 | } 538 | 539 | private void PrintPAddr(List args) 540 | { 541 | List chars = GetZsciiChars(GetPackedAddress(args[0])); 542 | string s = DecodeZsciiChars(chars); 543 | _io.Print(s); 544 | Log.Write($"[{s}]"); 545 | } 546 | 547 | private void ShowStatus(List args) 548 | { 549 | _io.ShowStatus(); 550 | } 551 | 552 | private void SplitWindow(List args) 553 | { 554 | _io.SplitWindow(args[0]); 555 | } 556 | 557 | private void SetWindow(List args) 558 | { 559 | _io.SetWindow(args[0]); 560 | } 561 | 562 | private void EraseWindow(List args) 563 | { 564 | _io.EraseWindow(args[0]); 565 | } 566 | 567 | private void SetCursor(List args) 568 | { 569 | _io.SetCursor(args[0], args[1], (ushort)(args.Count == 3 ? args[2] : 0)); 570 | } 571 | 572 | private void SetTextStyle(List args) 573 | { 574 | _io.SetTextStyle((TextStyle)args[0]); 575 | } 576 | 577 | private void SetFont(List args) 578 | { 579 | // TODO 580 | 581 | byte dest = _memory[_stack.Peek().PC++]; 582 | StoreWordInVariable(dest, 0); 583 | } 584 | 585 | private void SetColor(List args) 586 | { 587 | _io.SetColor((ZColor)args[0], (ZColor)args[1]); 588 | } 589 | 590 | private void SoundEffect(List args) 591 | { 592 | // TODO - the rest of the params 593 | 594 | _io.SoundEffect(args[0]); 595 | } 596 | 597 | private void BufferMode(List args) 598 | { 599 | _io.BufferMode(args[0] == 1); 600 | } 601 | 602 | private void OutputStream(List args) 603 | { 604 | // TODO 605 | } 606 | 607 | private void Read(List args) 608 | { 609 | _readTextAddr = args[0]; 610 | _readParseAddr = args[1]; 611 | 612 | if(_terminateOnInput) 613 | _running = false; 614 | else 615 | { 616 | byte max = _memory[_readTextAddr]; 617 | string input = _io.Read(max); 618 | FinishRead(input); 619 | } 620 | } 621 | 622 | public void FinishRead(string input) 623 | { 624 | if(input != null && _readTextAddr != 0 && _readParseAddr != 0) 625 | { 626 | int textMax = _memory[_readTextAddr]; 627 | int wordMax = _memory[_readParseAddr]; 628 | 629 | input = input.ToLower().Substring(0, Math.Min(input.Length, textMax)); 630 | Log.Write($"[{input}]"); 631 | 632 | int ix = 1; 633 | 634 | if(_version >= 5) 635 | _memory[_readTextAddr + ix++] = (byte)input.Length; 636 | 637 | for(int j = 0; j < input.Length; j++, ix++) 638 | _memory[_readTextAddr + ix] = (byte)input[j]; 639 | 640 | if(_version < 5) 641 | _memory[_readTextAddr + ++ix] = 0; 642 | 643 | string[] tokenised = input.Split(' '); 644 | 645 | _memory[_readParseAddr + 1] = (byte)tokenised.Length; 646 | 647 | int len = (_version <= 3) ? 6 : 9; 648 | int last = 0; 649 | int max = Math.Min(tokenised.Length, wordMax); 650 | 651 | for(int i = 0; i < max; i++) 652 | { 653 | if(tokenised[i].Length > len) 654 | tokenised[i] = tokenised[i].Substring(0, len); 655 | 656 | ushort wordIndex = (ushort)(Array.IndexOf(_dictionaryWords, tokenised[i])); 657 | ushort addr = (ushort)(wordIndex == 0xffff ? 0 : _wordStart + wordIndex * _entryLength); 658 | StoreWord((ushort)(_readParseAddr + 2 + i*4), addr); 659 | _memory[_readParseAddr + 4 + i*4] = (byte)tokenised[i].Length; 660 | int index = input.IndexOf(tokenised[i], last, StringComparison.Ordinal); 661 | _memory[_readParseAddr + 5 + i*4] = (byte)(index + (_version < 5 ? 1 : 2)); 662 | last = index + tokenised[i].Length; 663 | } 664 | 665 | if(_version >= 5) 666 | { 667 | byte dest = _memory[_stack.Peek().PC++]; 668 | StoreByteInVariable(dest, 10); 669 | } 670 | 671 | _readTextAddr = 0; 672 | _readParseAddr = 0; 673 | } 674 | } 675 | 676 | private void ReadChar(List args) 677 | { 678 | char key = _io.ReadChar(); 679 | 680 | byte dest = _memory[_stack.Peek().PC++]; 681 | StoreByteInVariable(dest, (byte)key); 682 | } 683 | 684 | private void InsertObj(List args) 685 | { 686 | if(args[0] == 0 || args[1] == 0) 687 | return; 688 | 689 | Log.Write($"[{GetObjectName(args[0])}] [{GetObjectName(args[1])}] "); 690 | 691 | ushort obj1 = args[0]; 692 | ushort obj2 = args[1]; 693 | 694 | ushort obj1Addr = GetObjectAddress(args[0]); 695 | ushort obj2Addr = GetObjectAddress(args[1]); 696 | 697 | ushort parent1 = GetObjectNumber((ushort)(obj1Addr+ParentOffset)); 698 | ushort sibling1 = GetObjectNumber((ushort)(obj1Addr+SiblingOffset)); 699 | ushort child2 = GetObjectNumber((ushort)(obj2Addr+ChildOffset)); 700 | 701 | ushort parent1Addr = GetObjectAddress(parent1); 702 | 703 | ushort parent1Child = GetObjectNumber((ushort)(parent1Addr+ChildOffset)); 704 | ushort parent1ChildAddr = GetObjectAddress(parent1Child); 705 | ushort parent1ChildSibling = GetObjectNumber((ushort)(parent1ChildAddr+SiblingOffset)); 706 | 707 | if(parent1 == obj2 && child2 == obj1) 708 | return; 709 | 710 | // if parent1's child is obj1 we need to assign the sibling 711 | if(parent1Child == obj1) 712 | { 713 | // set parent1's child to obj1's sibling 714 | SetObjectNumber((ushort)(parent1Addr+ChildOffset), sibling1); 715 | } 716 | else // else if I'm not the child but there is a child, we need to link the broken sibling chain 717 | { 718 | ushort addr = parent1ChildAddr; 719 | ushort currentSibling = parent1ChildSibling; 720 | 721 | // while sibling of parent1's child has siblings 722 | while(currentSibling != 0) 723 | { 724 | // if obj1 is the sibling of the current object 725 | if(currentSibling == obj1) 726 | { 727 | // set the current object's sibling to the next sibling 728 | SetObjectNumber((ushort)(addr+SiblingOffset), sibling1); 729 | break; 730 | } 731 | 732 | addr = GetObjectAddress(currentSibling); 733 | currentSibling = GetObjectNumber((ushort)(addr+SiblingOffset)); 734 | } 735 | } 736 | 737 | // set obj1's parent to obj2 738 | SetObjectNumber((ushort)(obj1Addr+ParentOffset), obj2); 739 | 740 | // set obj2's child to obj1 741 | SetObjectNumber((ushort)(obj2Addr+ChildOffset), obj1); 742 | 743 | // set obj1's sibling to obj2's child 744 | SetObjectNumber((ushort)(obj1Addr+SiblingOffset), child2); 745 | } 746 | 747 | private void RemoveObj(List args) 748 | { 749 | if(args[0] == 0) 750 | return; 751 | 752 | Log.Write($"[{GetObjectName(args[0])}] "); 753 | ushort objAddr = GetObjectAddress(args[0]); 754 | ushort parent = GetObjectNumber((ushort)(objAddr+ParentOffset)); 755 | ushort parentAddr = GetObjectAddress(parent); 756 | ushort parentChild = GetObjectNumber((ushort)(parentAddr+ChildOffset)); 757 | ushort sibling = GetObjectNumber((ushort)(objAddr+SiblingOffset)); 758 | 759 | // if object is the first child, set first child to the sibling 760 | if(parent == args[0]) 761 | SetObjectNumber((ushort)(parentAddr+ChildOffset), sibling); 762 | else if(parentChild != 0) 763 | { 764 | ushort addr = GetObjectAddress(parentChild); 765 | ushort currentSibling = GetObjectNumber((ushort)(addr+SiblingOffset)); 766 | 767 | // while sibling of parent1's child has siblings 768 | while(currentSibling != 0) 769 | { 770 | // if obj1 is the sibling of the current object 771 | if(currentSibling == args[0]) 772 | { 773 | // set the current object's sibling to the next sibling 774 | SetObjectNumber((ushort)(addr+SiblingOffset), sibling); 775 | break; 776 | } 777 | 778 | addr = GetObjectAddress(currentSibling); 779 | currentSibling = GetObjectNumber((ushort)(addr+SiblingOffset)); 780 | } 781 | } 782 | 783 | // set the object's parent to nothing 784 | SetObjectNumber((ushort)(objAddr+ParentOffset), 0); 785 | } 786 | 787 | private void GetProp(List args) 788 | { 789 | Log.Write($"[{GetObjectName(args[0])}] "); 790 | 791 | byte dest = _memory[_stack.Peek().PC++]; 792 | ushort val = 0; 793 | 794 | ushort addr = GetPropertyAddress(args[0], (byte)args[1]); 795 | if(addr > 0) 796 | { 797 | byte propInfo = _memory[addr++]; 798 | byte len; 799 | 800 | if(_version > 3 && (propInfo & 0x80) == 0x80) 801 | len = (byte)(_memory[addr++] & 0x3f); 802 | else 803 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 804 | 805 | for(int i = 0; i < len; i++) 806 | val |= (ushort)(_memory[addr+i] << (len-1-i)*8); 807 | } 808 | else 809 | val = GetWord((ushort)(_objectTable + (args[1]-1)*2)); 810 | 811 | StoreWordInVariable(dest, val); 812 | } 813 | 814 | private void GetPropAddr(List args) 815 | { 816 | Log.Write($"[{GetObjectName(args[0])}] "); 817 | 818 | byte dest = _memory[_stack.Peek().PC++]; 819 | ushort addr = GetPropertyAddress(args[0], (byte)args[1]); 820 | 821 | if(addr > 0) 822 | { 823 | byte propInfo = _memory[addr+1]; 824 | 825 | if(_version > 3 && (propInfo & 0x80) == 0x80) 826 | addr+=2; 827 | else 828 | addr+=1; 829 | } 830 | 831 | StoreWordInVariable(dest, addr); 832 | } 833 | 834 | private void GetNextProp(List args) 835 | { 836 | Log.Write($"[{GetObjectName(args[0])}] "); 837 | 838 | bool next = false; 839 | 840 | byte dest = _memory[_stack.Peek().PC++]; 841 | if(args[1] == 0) 842 | next = true; 843 | 844 | ushort propHeaderAddr = GetPropertyHeaderAddress(args[0]); 845 | byte size = _memory[propHeaderAddr]; 846 | propHeaderAddr += (ushort)(size * 2+1); 847 | 848 | while(_memory[propHeaderAddr] != 0x00) 849 | { 850 | byte propInfo = _memory[propHeaderAddr]; 851 | byte len; 852 | if(_version > 3 && (propInfo & 0x80) == 0x80) 853 | { 854 | len = (byte)(_memory[++propHeaderAddr] & 0x3f); 855 | if(len == 0) 856 | len = 64; 857 | } 858 | else 859 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 860 | byte propNum = (byte)(propInfo & (_version <= 3 ? 0x1f : 0x3f)); 861 | 862 | if(next) 863 | { 864 | StoreByteInVariable(dest, propNum); 865 | return; 866 | } 867 | 868 | if(propNum == args[1]) 869 | next = true; 870 | 871 | propHeaderAddr += (ushort)(len+1); 872 | } 873 | 874 | StoreByteInVariable(dest, 0); 875 | } 876 | 877 | private void GetPropLen(List args) 878 | { 879 | byte dest = _memory[_stack.Peek().PC++]; 880 | byte propInfo = _memory[args[0]-1]; 881 | byte len; 882 | if(_version > 3 && (propInfo & 0x80) == 0x80) 883 | { 884 | len = (byte)(_memory[args[0]-1] & 0x3f); 885 | if(len == 0) 886 | len = 64; 887 | } 888 | else 889 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 890 | 891 | StoreByteInVariable(dest, len); 892 | } 893 | 894 | private void PutProp(List args) 895 | { 896 | Log.Write($"[{GetObjectName(args[0])}] "); 897 | 898 | ushort prop = GetPropertyHeaderAddress(args[0]); 899 | byte size = _memory[prop]; 900 | prop += (ushort)(size * 2+1); 901 | 902 | while(_memory[prop] != 0x00) 903 | { 904 | byte propInfo = _memory[prop++]; 905 | byte len; 906 | if(_version > 3 && (propInfo & 0x80) == 0x80) 907 | { 908 | len = (byte)(_memory[prop++] & 0x3f); 909 | if(len == 0) 910 | len = 64; 911 | } 912 | else 913 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 914 | byte propNum = (byte)(propInfo & (_version <= 3 ? 0x1f : 0x3f)); 915 | if(propNum == args[1]) 916 | { 917 | if(len == 1) 918 | _memory[prop+1] = (byte)args[2]; 919 | else 920 | StoreWord(prop, args[2]); 921 | 922 | break; 923 | } 924 | prop += len; 925 | } 926 | } 927 | 928 | private void TestAttribute(List args) 929 | { 930 | Log.Write($"[{GetObjectName(args[0])}] "); 931 | PrintObjectInfo(args[0], false); 932 | 933 | ushort objectAddr = GetObjectAddress(args[0]); 934 | ulong attributes; 935 | ulong flag; 936 | 937 | if(_version <= 3) 938 | { 939 | attributes = GetUint(objectAddr); 940 | flag = 0x80000000 >> args[1]; 941 | } 942 | else 943 | { 944 | attributes = (ulong)GetUint(objectAddr) << 16 | GetWord((uint)(objectAddr+4)); 945 | flag = (ulong)(0x800000000000 >> args[1]); 946 | } 947 | 948 | bool branch = (flag & attributes) == flag; 949 | Jump(branch); 950 | } 951 | 952 | private void SetAttribute(List args) 953 | { 954 | if(args[0] == 0) 955 | return; 956 | 957 | Log.Write($"[{GetObjectName(args[0])}] "); 958 | 959 | ushort objectAddr = GetObjectAddress(args[0]); 960 | ulong attributes; 961 | ulong flag; 962 | 963 | if(_version <= 3) 964 | { 965 | attributes = GetUint(objectAddr); 966 | flag = 0x80000000 >> args[1]; 967 | attributes |= flag; 968 | StoreUint(objectAddr, (uint)attributes); 969 | } 970 | else 971 | { 972 | attributes = (ulong)GetUint(objectAddr) << 16 | GetWord((uint)(objectAddr+4)); 973 | flag = (ulong)(0x800000000000 >> args[1]); 974 | attributes |= flag; 975 | StoreUint(objectAddr, (uint)(attributes >> 16)); 976 | StoreWord((ushort)(objectAddr+4), (ushort)attributes); 977 | } 978 | } 979 | 980 | private void ClearAttribute(List args) 981 | { 982 | Log.Write($"[{GetObjectName(args[0])}] "); 983 | 984 | ushort objectAddr = GetObjectAddress(args[0]); 985 | ulong attributes; 986 | ulong flag; 987 | 988 | if(_version <= 3) 989 | { 990 | attributes = GetUint(objectAddr); 991 | flag = 0x80000000 >> args[1]; 992 | attributes &= ~flag; 993 | StoreUint(objectAddr, (uint)attributes); 994 | } 995 | else 996 | { 997 | attributes = (ulong)GetUint(objectAddr) << 16 | GetWord((uint)(objectAddr+4)); 998 | flag = (ulong)(0x800000000000 >> args[1]); 999 | attributes &= ~flag; 1000 | StoreUint(objectAddr, (uint)attributes >> 16); 1001 | StoreWord((ushort)(objectAddr+4), (ushort)attributes); 1002 | } 1003 | } 1004 | 1005 | private void GetParent(List args) 1006 | { 1007 | Log.Write($"[{GetObjectName(args[0])}] "); 1008 | 1009 | ushort addr = GetObjectAddress(args[0]); 1010 | ushort parent = GetObjectNumber((ushort)(addr + ParentOffset)); 1011 | 1012 | Log.Write($"[{GetObjectName(parent)}] "); 1013 | 1014 | byte dest = _memory[_stack.Peek().PC++]; 1015 | 1016 | if(_version <= 3) 1017 | StoreByteInVariable(dest, (byte)parent); 1018 | else 1019 | StoreWordInVariable(dest, parent); 1020 | } 1021 | 1022 | private void GetChild(List args) 1023 | { 1024 | Log.Write($"[{GetObjectName(args[0])}] "); 1025 | 1026 | ushort addr = GetObjectAddress(args[0]); 1027 | ushort child = GetObjectNumber((ushort)(addr + ChildOffset)); 1028 | 1029 | Log.Write($"[{GetObjectName(child)}] "); 1030 | 1031 | byte dest = _memory[_stack.Peek().PC++]; 1032 | 1033 | if(_version <= 3) 1034 | StoreByteInVariable(dest, (byte)child); 1035 | else 1036 | StoreWordInVariable(dest, child); 1037 | 1038 | Jump(child != 0); 1039 | } 1040 | 1041 | private void GetSibling(List args) 1042 | { 1043 | Log.Write($"[{GetObjectName(args[0])}] "); 1044 | 1045 | ushort addr = GetObjectAddress(args[0]); 1046 | ushort sibling = GetObjectNumber((ushort)(addr + SiblingOffset)); 1047 | 1048 | Log.Write($"[{GetObjectName(sibling)}] "); 1049 | 1050 | byte dest = _memory[_stack.Peek().PC++]; 1051 | 1052 | if(_version <= 3) 1053 | StoreByteInVariable(dest, (byte)sibling); 1054 | else 1055 | StoreWordInVariable(dest, sibling); 1056 | 1057 | Jump(sibling != 0); 1058 | } 1059 | 1060 | private void Load(List args) 1061 | { 1062 | byte dest = _memory[_stack.Peek().PC++]; 1063 | ushort val = GetVariable((byte)args[0], false); 1064 | StoreByteInVariable(dest, (byte)val); 1065 | } 1066 | 1067 | private void Store(List args) 1068 | { 1069 | StoreWordInVariable((byte)args[0], args[1], false); 1070 | } 1071 | 1072 | private void StoreB(List args) 1073 | { 1074 | ushort addr = (ushort)(args[0] + args[1]); 1075 | _memory[addr] = (byte)args[2]; 1076 | } 1077 | 1078 | private void StoreW(List args) 1079 | { 1080 | ushort addr = (ushort)(args[0] + 2 * args[1]); 1081 | StoreWord(addr, args[2]); 1082 | } 1083 | 1084 | private void LoadB(List args) 1085 | { 1086 | ushort addr = (ushort)(args[0] + args[1]); 1087 | byte b = _memory[addr]; 1088 | byte dest = _memory[_stack.Peek().PC++]; 1089 | StoreByteInVariable(dest, b); 1090 | } 1091 | 1092 | private void LoadW(List args) 1093 | { 1094 | ushort addr = (ushort)(args[0] + 2 * args[1]); 1095 | ushort word = GetWord(addr); 1096 | byte dest = _memory[_stack.Peek().PC++]; 1097 | StoreWordInVariable(dest, word); 1098 | } 1099 | 1100 | private void Jump(List args) 1101 | { 1102 | _stack.Peek().PC = (uint)(_stack.Peek().PC + (short)(args[0] - 2)); 1103 | Log.Write($"-> {_stack.Peek().PC:X5}"); 1104 | } 1105 | 1106 | private void Je(List args) 1107 | { 1108 | bool equal = false; 1109 | for(int i = 1; i < args.Count; i++) 1110 | { 1111 | if(args[0] == args[i]) 1112 | { 1113 | equal = true; 1114 | break; 1115 | } 1116 | } 1117 | 1118 | Jump(equal); 1119 | } 1120 | 1121 | private void Jz(List args) 1122 | { 1123 | Jump(args[0] == 0); 1124 | } 1125 | 1126 | private void Jl(List args) 1127 | { 1128 | Jump((short)args[0] < (short)args[1]); 1129 | } 1130 | 1131 | private void Jg(List args) 1132 | { 1133 | Jump((short)args[0] > (short)args[1]); 1134 | } 1135 | 1136 | private void Jin(List args) 1137 | { 1138 | Log.Write($"C[{GetObjectName(args[0])}] P[{GetObjectName(args[1])}] "); 1139 | 1140 | ushort addr = GetObjectAddress(args[0]); 1141 | ushort parent = GetObjectNumber((ushort)(addr+ParentOffset)); 1142 | Jump(parent == args[1]); 1143 | } 1144 | 1145 | private void Jump(bool flag) 1146 | { 1147 | bool branch; 1148 | 1149 | byte offset = _memory[_stack.Peek().PC++]; 1150 | short newOffset; 1151 | 1152 | if((offset & 0x80) == 0x80) 1153 | { 1154 | Log.Write(" [TRUE] "); 1155 | branch = true; 1156 | } 1157 | else 1158 | { 1159 | Log.Write(" [FALSE] "); 1160 | branch = false; 1161 | } 1162 | 1163 | bool executeBranch = branch && flag || !branch && !flag; 1164 | 1165 | if((offset & 0x40) == 0x40) 1166 | { 1167 | offset = (byte)(offset & 0x3f); 1168 | 1169 | if(offset == 0 && executeBranch) 1170 | { 1171 | Log.Write(" RFALSE "); 1172 | RFalse(null); 1173 | return; 1174 | } 1175 | 1176 | if(offset == 1 && executeBranch) 1177 | { 1178 | Log.Write(" RTRUE "); 1179 | RTrue(null); 1180 | return; 1181 | } 1182 | 1183 | newOffset = (short)(offset - 2); 1184 | } 1185 | else 1186 | { 1187 | byte offset2 = _memory[_stack.Peek().PC++]; 1188 | ushort final = (ushort)((offset & 0x3f) << 8 | offset2); 1189 | 1190 | // this is a 14-bit number, so set the sign bit properly because we can jump backwards 1191 | if((final & 0x2000) == 0x2000) 1192 | final |= 0xc000; 1193 | 1194 | newOffset = (short)(final - 2); 1195 | } 1196 | 1197 | if(executeBranch) 1198 | _stack.Peek().PC += (uint)newOffset; 1199 | 1200 | Log.Write($"-> {_stack.Peek().PC:X5}"); 1201 | } 1202 | 1203 | private void Add(List args) 1204 | { 1205 | short val = (short)(args[0] + args[1]); 1206 | byte dest = _memory[_stack.Peek().PC++]; 1207 | StoreWordInVariable(dest, (ushort)val); 1208 | } 1209 | 1210 | private void Sub(List args) 1211 | { 1212 | short val = (short)(args[0] - args[1]); 1213 | byte dest = _memory[_stack.Peek().PC++]; 1214 | StoreWordInVariable(dest, (ushort)val); 1215 | } 1216 | 1217 | private void Mul(List args) 1218 | { 1219 | short val = (short)(args[0] * args[1]); 1220 | byte dest = _memory[_stack.Peek().PC++]; 1221 | StoreWordInVariable(dest, (ushort)val); 1222 | } 1223 | 1224 | private void Div(List args) 1225 | { 1226 | byte dest = _memory[_stack.Peek().PC++]; 1227 | 1228 | if(args[1] == 0) 1229 | return; 1230 | 1231 | short val = (short)((short)args[0] / (short)args[1]); 1232 | StoreWordInVariable(dest, (ushort)val); 1233 | } 1234 | 1235 | private void Mod(List args) 1236 | { 1237 | short val = (short)((short)args[0] % (short)args[1]); 1238 | byte dest = _memory[_stack.Peek().PC++]; 1239 | StoreWordInVariable(dest, (ushort)val); 1240 | } 1241 | 1242 | private void Inc(List args) 1243 | { 1244 | short val = (short)(GetVariable((byte)args[0])+1); 1245 | StoreWordInVariable((byte)args[0], (ushort)val); 1246 | } 1247 | 1248 | private void Dec(List args) 1249 | { 1250 | short val = (short)(GetVariable((byte)args[0])-1); 1251 | StoreWordInVariable((byte)args[0], (ushort)val); 1252 | } 1253 | 1254 | private void ArtShift(List args) 1255 | { 1256 | // keep the sign bit, so make it a short 1257 | short val = (short)args[0]; 1258 | if((short)args[1] > 0) 1259 | val <<= args[1]; 1260 | else if((short)args[1] < 0) 1261 | val >>= -args[1]; 1262 | 1263 | byte dest = _memory[_stack.Peek().PC++]; 1264 | StoreWordInVariable(dest, (ushort)val); 1265 | } 1266 | 1267 | private void LogShift(List args) 1268 | { 1269 | // kill the sign bit, so make it a ushort 1270 | ushort val = args[0]; 1271 | if((short)args[1] > 0) 1272 | val <<= args[1]; 1273 | else if((short)args[1] < 0) 1274 | val >>= -args[1]; 1275 | 1276 | byte dest = _memory[_stack.Peek().PC++]; 1277 | StoreWordInVariable(dest, (ushort)val); 1278 | } 1279 | 1280 | private void Random(List args) 1281 | { 1282 | ushort val = 0; 1283 | 1284 | if((short)args[0] <= 0) 1285 | _random = new Random(-args[0]); 1286 | else 1287 | val = (ushort)(_random.Next(0, args[0])+1); 1288 | 1289 | byte dest = _memory[_stack.Peek().PC++]; 1290 | StoreWordInVariable(dest, val); 1291 | } 1292 | 1293 | private void Or(List args) 1294 | { 1295 | ushort or = (ushort)(args[0] | args[1]); 1296 | byte dest = _memory[_stack.Peek().PC++]; 1297 | StoreWordInVariable(dest, or); 1298 | } 1299 | 1300 | private void And(List args) 1301 | { 1302 | ushort and = (ushort)(args[0] & args[1]); 1303 | byte dest = _memory[_stack.Peek().PC++]; 1304 | StoreWordInVariable(dest, and); 1305 | } 1306 | 1307 | private void Not(List args) 1308 | { 1309 | byte dest = _memory[_stack.Peek().PC++]; 1310 | StoreWordInVariable(dest, (ushort)~args[0]); 1311 | } 1312 | 1313 | private void Test(List args) 1314 | { 1315 | Jump((args[0] & args[1]) == args[1]); 1316 | } 1317 | 1318 | private void DecCheck(List args) 1319 | { 1320 | short val = (short)GetVariable((byte)args[0]); 1321 | val--; 1322 | StoreWordInVariable((byte)args[0], (ushort)val); 1323 | Jump(val < (short)args[1]); 1324 | } 1325 | 1326 | private void IncCheck(List args) 1327 | { 1328 | short val = (short)GetVariable((byte)args[0]); 1329 | val++; 1330 | StoreWordInVariable((byte)args[0], (ushort)val); 1331 | Jump(val > (short)args[1]); 1332 | } 1333 | 1334 | private void Call(List args) 1335 | { 1336 | Call(args, true); 1337 | } 1338 | 1339 | private void Call(List args, bool storeResult) 1340 | { 1341 | if(args[0] == 0) 1342 | { 1343 | if(storeResult) 1344 | { 1345 | byte dest = _memory[_stack.Peek().PC++]; 1346 | StoreWordInVariable(dest, 0); 1347 | } 1348 | return; 1349 | } 1350 | 1351 | uint pc = GetPackedAddress(args[0]); 1352 | Log.Write($"New PC: {pc:X5}"); 1353 | 1354 | ZStackFrame zsf = new ZStackFrame { PC = pc, StoreResult = storeResult }; 1355 | _stack.Push(zsf); 1356 | 1357 | byte count = _memory[_stack.Peek().PC++]; 1358 | 1359 | if(_version <= 4) 1360 | { 1361 | for(int i = 0; i < count; i++) 1362 | { 1363 | zsf.Variables[i] = GetWord(_stack.Peek().PC); 1364 | _stack.Peek().PC += 2; 1365 | } 1366 | } 1367 | 1368 | for(int i = 0; i < args.Count-1; i++) 1369 | zsf.Variables[i] = args[i+1]; 1370 | 1371 | zsf.ArgumentCount = args.Count-1; 1372 | } 1373 | 1374 | private void Call1N(List args) 1375 | { 1376 | Call(args, false); 1377 | } 1378 | 1379 | private void Call1S(List args) 1380 | { 1381 | Call(args, true); 1382 | } 1383 | 1384 | private void Call2S(List args) 1385 | { 1386 | Call(args, true); 1387 | } 1388 | 1389 | private void Call2N(List args) 1390 | { 1391 | Call(args, false); 1392 | } 1393 | 1394 | private void CallVN(List args) 1395 | { 1396 | Call(args, false); 1397 | } 1398 | 1399 | private void CallVN2(List args) 1400 | { 1401 | Call(args, false); 1402 | } 1403 | 1404 | private void CallVS2(List args) 1405 | { 1406 | Call(args, true); 1407 | } 1408 | 1409 | private void Ret(List args) 1410 | { 1411 | ZStackFrame sf = _stack.Pop(); 1412 | if(sf.StoreResult) 1413 | { 1414 | byte dest = _memory[_stack.Peek().PC++]; 1415 | StoreWordInVariable(dest, args[0]); 1416 | } 1417 | } 1418 | 1419 | private void RetPopped(List args) 1420 | { 1421 | ushort val = _stack.Peek().RoutineStack.Pop(); 1422 | ZStackFrame sf = _stack.Pop(); 1423 | if(sf.StoreResult) 1424 | { 1425 | byte dest = _memory[_stack.Peek().PC++]; 1426 | StoreWordInVariable(dest, val); 1427 | } 1428 | } 1429 | 1430 | private void Pop(List args) 1431 | { 1432 | if(_stack.Peek().RoutineStack.Count > 0) 1433 | _stack.Peek().RoutineStack.Pop(); 1434 | else 1435 | _stack.Pop(); 1436 | } 1437 | 1438 | private void RTrue(List args) 1439 | { 1440 | ZStackFrame sf = _stack.Pop(); 1441 | if(sf.StoreResult) 1442 | { 1443 | byte dest = _memory[_stack.Peek().PC++]; 1444 | StoreWordInVariable(dest, 1); 1445 | } 1446 | } 1447 | 1448 | private void RFalse(List args) 1449 | { 1450 | ZStackFrame sf = _stack.Pop(); 1451 | if(sf.StoreResult) 1452 | { 1453 | byte dest = _memory[_stack.Peek().PC++]; 1454 | StoreWordInVariable(dest, 0); 1455 | } 1456 | } 1457 | 1458 | private void CheckArgCount(List args) 1459 | { 1460 | Jump(args[0] <= _stack.Peek().ArgumentCount); 1461 | } 1462 | 1463 | private void Push(List args) 1464 | { 1465 | _stack.Peek().RoutineStack.Push(args[0]); 1466 | } 1467 | 1468 | private void Pull(List args) 1469 | { 1470 | ushort val = _stack.Peek().RoutineStack.Pop(); 1471 | StoreWordInVariable((byte)args[0], val, false); 1472 | } 1473 | 1474 | private List GetOperands(byte opcode) 1475 | { 1476 | List args = new List(); 1477 | ushort arg; 1478 | 1479 | // Variable 1480 | if((opcode & 0xc0) == 0xc0) 1481 | { 1482 | byte types = _memory[_stack.Peek().PC++]; 1483 | byte types2 = 0; 1484 | 1485 | if(opcode == 0xec || opcode == 0xfa) 1486 | types2 = _memory[_stack.Peek().PC++]; 1487 | 1488 | GetVariableOperands(types, args); 1489 | if(opcode == 0xec || opcode == 0xfa) 1490 | GetVariableOperands(types2, args); 1491 | } 1492 | // Short 1493 | else if((opcode & 0x80) == 0x80) 1494 | { 1495 | byte type = (byte)(opcode >> 4 & 0x03); 1496 | arg = GetOperand((OperandType)type); 1497 | args.Add(arg); 1498 | } 1499 | // Long 1500 | else 1501 | { 1502 | arg = GetOperand((opcode & 0x40) == 0x40 ? OperandType.Variable : OperandType.SmallConstant); 1503 | args.Add(arg); 1504 | 1505 | arg = GetOperand((opcode & 0x20) == 0x20 ? OperandType.Variable : OperandType.SmallConstant); 1506 | args.Add(arg); 1507 | } 1508 | 1509 | return args; 1510 | } 1511 | 1512 | private void GetVariableOperands(byte types, List args) 1513 | { 1514 | for(int i = 6; i >= 0; i -= 2) 1515 | { 1516 | byte type = (byte)((types >> i) & 0x03); 1517 | 1518 | // omitted 1519 | if(type == 0x03) 1520 | break; 1521 | 1522 | ushort arg = GetOperand((OperandType)type); 1523 | args.Add(arg); 1524 | } 1525 | } 1526 | 1527 | private ushort GetOperand(OperandType type) 1528 | { 1529 | ushort arg = 0; 1530 | 1531 | switch(type) 1532 | { 1533 | case OperandType.LargeConstant: 1534 | arg = GetWord(_stack.Peek().PC); 1535 | _stack.Peek().PC+=2; 1536 | Log.Write($"#{arg:X4}, "); 1537 | break; 1538 | case OperandType.SmallConstant: 1539 | arg = _memory[_stack.Peek().PC++]; 1540 | Log.Write($"#{arg:X2}, "); 1541 | break; 1542 | case OperandType.Variable: 1543 | byte b = _memory[_stack.Peek().PC++]; 1544 | arg = GetVariable(b); 1545 | break; 1546 | } 1547 | 1548 | return arg; 1549 | } 1550 | 1551 | private void StoreByteInVariable(byte dest, byte value) 1552 | { 1553 | if(dest == 0) 1554 | { 1555 | Log.Write($"-> SP ({value:X4}), "); 1556 | _stack.Peek().RoutineStack.Push(value); 1557 | } 1558 | else if(dest < 0x10) 1559 | { 1560 | Log.Write($"-> L{dest-1:X2} ({value:X4}), "); 1561 | _stack.Peek().Variables[dest-1] = value; 1562 | } 1563 | else 1564 | { 1565 | // this still gets written as a word...write the byte to addr+1 1566 | Log.Write($"-> G{dest - 0x10:X2} ({value:X4}), "); 1567 | _memory[_globals + 2 * (dest - 0x10)] = 0; 1568 | _memory[_globals + 2 * (dest - 0x10)+1] = value; 1569 | } 1570 | } 1571 | 1572 | private void StoreWordInVariable(byte dest, ushort value, bool push=true) 1573 | { 1574 | if(dest == 0) 1575 | { 1576 | Log.Write($"-> SP ({value:X4}), "); 1577 | if(!push) 1578 | _stack.Peek().RoutineStack.Pop(); 1579 | _stack.Peek().RoutineStack.Push(value); 1580 | } 1581 | else if(dest < 0x10) 1582 | { 1583 | Log.Write($"-> L{dest-1:X2} ({value:X4}), "); 1584 | _stack.Peek().Variables[dest-1] = value; 1585 | } 1586 | else 1587 | { 1588 | Log.Write($"-> G{dest - 0x10:X2} ({value:X4}), "); 1589 | StoreWord((ushort)(_globals + 2 * (dest - 0x10)), value); 1590 | } 1591 | } 1592 | 1593 | private ushort GetVariable(byte variable, bool pop=true) 1594 | { 1595 | ushort val; 1596 | 1597 | if(variable == 0) 1598 | { 1599 | if(pop) 1600 | val = _stack.Peek().RoutineStack.Pop(); 1601 | else 1602 | val = _stack.Peek().RoutineStack.Peek(); 1603 | Log.Write($"SP ({val:X4}), "); 1604 | } 1605 | else if(variable < 0x10) 1606 | { 1607 | val = _stack.Peek().Variables[variable-1]; 1608 | Log.Write($"L{variable-1:X2} ({val:X4}), "); 1609 | } 1610 | else 1611 | { 1612 | val = GetWord((ushort)(_globals + 2 * (variable - 0x10))); 1613 | Log.Write($"G{variable - 0x10:X2} ({val:X4}), "); 1614 | } 1615 | return val; 1616 | } 1617 | 1618 | private ushort GetWord(uint address) 1619 | { 1620 | return (ushort)(_memory[address] << 8 | _memory[address+1]); 1621 | } 1622 | 1623 | private void StoreWord(ushort address, ushort value) 1624 | { 1625 | _memory[address+0] = (byte)(value >> 8); 1626 | _memory[address+1] = (byte)value; 1627 | } 1628 | 1629 | private uint GetUint(uint address) 1630 | { 1631 | return (uint)(_memory[address] << 24 | _memory[address+1] << 16 | _memory[address+2] << 8 | _memory[address+3]); 1632 | } 1633 | 1634 | private void StoreUint(uint address, uint val) 1635 | { 1636 | _memory[address+0] = (byte)(val >> 24); 1637 | _memory[address+1] = (byte)(val >> 16); 1638 | _memory[address+2] = (byte)(val >> 8); 1639 | _memory[address+3] = (byte)(val >> 0); 1640 | } 1641 | 1642 | private uint GetPackedAddress(ushort address) 1643 | { 1644 | if(_version <= 3) 1645 | return (uint)(address * 2); 1646 | if(_version <= 5) 1647 | return (uint)(address * 4); 1648 | 1649 | return 0; 1650 | } 1651 | 1652 | private ushort GetObjectAddress(ushort obj) 1653 | { 1654 | ushort objectAddr = (ushort)(_objectTable + PropertyDefaultTableSize + (obj-1) * ObjectSize); 1655 | return objectAddr; 1656 | } 1657 | 1658 | private ushort GetObjectNumber(ushort objectAddr) 1659 | { 1660 | if(_version <= 3) 1661 | return _memory[objectAddr]; 1662 | return GetWord(objectAddr); 1663 | } 1664 | 1665 | private void SetObjectNumber(ushort objectAddr, ushort obj) 1666 | { 1667 | if(_version <= 3) 1668 | _memory[objectAddr] = (byte)obj; 1669 | else 1670 | StoreWord(objectAddr, obj); 1671 | } 1672 | 1673 | private ushort GetPropertyHeaderAddress(ushort obj) 1674 | { 1675 | ushort objectAddr = GetObjectAddress(obj); 1676 | ushort propAddr = (ushort)(objectAddr + PropertyOffset); 1677 | ushort prop = GetWord(propAddr); 1678 | return prop; 1679 | } 1680 | 1681 | private ushort GetPropertyAddress(ushort obj, byte prop) 1682 | { 1683 | ushort propHeaderAddr = GetPropertyHeaderAddress(obj); 1684 | 1685 | // skip past text 1686 | byte size = _memory[propHeaderAddr]; 1687 | propHeaderAddr += (ushort)(size * 2+1); 1688 | 1689 | while(_memory[propHeaderAddr] != 0x00) 1690 | { 1691 | byte propInfo = _memory[propHeaderAddr]; 1692 | byte propNum = (byte)(propInfo & (_version <= 3 ? 0x1f : 0x3f)); 1693 | 1694 | if(propNum == prop) 1695 | return propHeaderAddr; 1696 | 1697 | byte len; 1698 | 1699 | if(_version > 3 && (propInfo & 0x80) == 0x80) 1700 | { 1701 | len = (byte)(_memory[++propHeaderAddr] & 0x3f); 1702 | if(len == 0) 1703 | len = 64; 1704 | } 1705 | else 1706 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 1707 | 1708 | propHeaderAddr += (ushort)(len+1); 1709 | } 1710 | 1711 | return 0; 1712 | } 1713 | 1714 | private string GetObjectName(ushort obj) 1715 | { 1716 | string s = string.Empty; 1717 | 1718 | if(obj != 0) 1719 | { 1720 | ushort addr = GetPropertyHeaderAddress(obj); 1721 | if(_memory[addr] != 0) 1722 | { 1723 | List chars = GetZsciiChars((uint)(addr+1)); 1724 | s = DecodeZsciiChars(chars); 1725 | } 1726 | } 1727 | 1728 | return s; 1729 | } 1730 | 1731 | private void PrintObjects() 1732 | { 1733 | ushort lowest = 0xffff; 1734 | 1735 | for(ushort i = 1; i < 255 && (_objectTable + i*ObjectSize) < lowest; i++) 1736 | { 1737 | ushort addr = PrintObjectInfo(i, true); 1738 | if(addr < lowest) 1739 | lowest = addr; 1740 | } 1741 | } 1742 | 1743 | private void PrintObjectTree() 1744 | { 1745 | for(ushort i = 1; i < 255; i++) 1746 | { 1747 | ushort addr = GetObjectAddress(i); 1748 | ushort parent = GetObjectNumber((ushort)(addr+ParentOffset)); 1749 | if(parent == 0) 1750 | PrintTree(i, 0); 1751 | } 1752 | } 1753 | 1754 | private void PrintTree(ushort obj, int depth) 1755 | { 1756 | while(obj != 0) 1757 | { 1758 | for(int i = 0; i < depth; i++) 1759 | Log.Write(" . "); 1760 | 1761 | PrintObjectInfo(obj, false); 1762 | ushort addr = GetObjectAddress(obj); 1763 | ushort child = GetObjectNumber((ushort)(addr+ChildOffset)); 1764 | obj = GetObjectNumber((ushort)(addr+SiblingOffset)); 1765 | if(child != 0) 1766 | PrintTree(child, depth + 1); 1767 | } 1768 | } 1769 | 1770 | private ushort PrintObjectInfo(ushort obj, bool properties) 1771 | { 1772 | if(obj == 0) 1773 | return 0; 1774 | 1775 | ushort startAddr = GetObjectAddress(obj); 1776 | 1777 | ulong attributes = (ulong)GetUint(startAddr) << 16 | GetWord((uint)(startAddr+4)); 1778 | ushort parent = GetObjectNumber((ushort)(startAddr+ParentOffset)); 1779 | ushort sibling = GetObjectNumber((ushort)(startAddr+SiblingOffset)); 1780 | ushort child = GetObjectNumber((ushort)(startAddr+ChildOffset)); 1781 | ushort propAddr = GetWord((uint)(startAddr+PropertyOffset)); 1782 | 1783 | Log.Write($"{obj} ({obj:X2}) at {propAddr:X5}: "); 1784 | 1785 | byte size = _memory[propAddr++]; 1786 | string s = string.Empty; 1787 | if(size > 0) 1788 | { 1789 | var name = GetZsciiChars(propAddr); 1790 | s = DecodeZsciiChars(name); 1791 | } 1792 | 1793 | propAddr += (ushort)(size*2); 1794 | 1795 | Log.WriteLine($"[{s}] A:{attributes:X12} P:{parent}({parent:X2}) S:{sibling}({sibling:X2}) C:{child}({child:X2})"); 1796 | 1797 | if(properties) 1798 | { 1799 | string ss = string.Empty; 1800 | for(int i = 47; i >= 0; i--) 1801 | { 1802 | if(((attributes >> i) & 0x01) == 0x01) 1803 | { 1804 | ss += 47-i + ", "; 1805 | } 1806 | } 1807 | 1808 | Log.WriteLine("Attributes: " + ss); 1809 | 1810 | while(_memory[propAddr] != 0x00) 1811 | { 1812 | byte propInfo = _memory[propAddr]; 1813 | byte len; 1814 | if(_version > 3 && (propInfo & 0x80) == 0x80) 1815 | len = (byte)(_memory[propAddr+1] & 0x3f); 1816 | else 1817 | len = (byte)((propInfo >> (_version <= 3 ? 5 : 6)) + 1); 1818 | byte propNum = (byte)(propInfo & (_version <= 3 ? 0x1f : 0x3f)); 1819 | 1820 | Log.Write($" P:{propNum:X2} at {propAddr:X4}: "); 1821 | for(int i = 0; i < len; i++) 1822 | Log.Write($"{_memory[propAddr++]:X2} "); 1823 | Log.WriteLine(""); 1824 | propAddr++; 1825 | } 1826 | } 1827 | 1828 | return propAddr; 1829 | } 1830 | 1831 | private void ParseDictionary() 1832 | { 1833 | ushort address = _dictionary; 1834 | 1835 | byte len = _memory[address++]; 1836 | address += len; 1837 | 1838 | _entryLength = _memory[address++]; 1839 | ushort numEntries = GetWord(address); 1840 | address+=2; 1841 | 1842 | _wordStart = address; 1843 | 1844 | _dictionaryWords = new string[numEntries]; 1845 | 1846 | for(int i = 0; i < numEntries; i++) 1847 | { 1848 | ushort wordAddress = (ushort)(address + i*_entryLength); 1849 | var chars = GetZsciiChar(wordAddress); 1850 | chars.AddRange(GetZsciiChar((uint)(wordAddress+2))); 1851 | if(_entryLength == 9) 1852 | chars.AddRange(GetZsciiChar((uint)(wordAddress+4))); 1853 | string s = DecodeZsciiChars(chars); 1854 | _dictionaryWords[i] = s; 1855 | } 1856 | } 1857 | 1858 | private void PrintDictionary() 1859 | { 1860 | ushort address = _dictionary; 1861 | 1862 | byte len = _memory[address++]; 1863 | 1864 | Log.Write("Separators: [" ); 1865 | for(int i = 0; i < len; i++) 1866 | Log.Write(Convert.ToChar(_memory[address++]).ToString()); 1867 | Log.WriteLine("]"); 1868 | 1869 | byte entryLength = _memory[address++]; 1870 | ushort numEntries = GetWord(address); 1871 | address+=2; 1872 | Log.WriteLine($"Entry Length: {entryLength}, Num Entries: {numEntries}"); 1873 | 1874 | for(int i = 0; i < numEntries; i++) 1875 | { 1876 | ushort wordAddress = (ushort)(address + i*entryLength); 1877 | var chars = GetZsciiChar(wordAddress); 1878 | chars.AddRange(GetZsciiChar((uint)(wordAddress+2))); 1879 | if(_entryLength == 9) 1880 | chars.AddRange(GetZsciiChar((uint)(wordAddress+4))); 1881 | string s = DecodeZsciiChars(chars); 1882 | Log.WriteLine($"{i+1} ({wordAddress:X4}): {s}"); 1883 | } 1884 | 1885 | Log.Flush(); 1886 | } 1887 | } 1888 | } 1889 | -------------------------------------------------------------------------------- /ZMachineLib/ZMachineLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.2 5 | true 6 | Brian Peek 7 | Brian Peek 8 | A ZMachine interpreter supporting v1-v5 games written in C#. 9 | Copyright © 2017 Brian Peek 10 | https://github.com/BrianPeek/ZMachineLib/blob/master/LICENSE 11 | https://github.com/BrianPeek/ZMachineLib 12 | zmachine z-machine infocom if interactive fiction text adventure 13 | 1.0.0 14 | 1.0.0 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ZMachineLib/ZStackFrame.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.Serialization; 3 | 4 | namespace ZMachineLib 5 | { 6 | [DataContract] 7 | internal class ZStackFrame 8 | { 9 | [DataMember] 10 | public uint PC { get; set; } 11 | [DataMember] 12 | public Stack RoutineStack { get; set; } 13 | [DataMember] 14 | public ushort[] Variables { get; set; } 15 | [DataMember] 16 | public bool StoreResult { get; set; } 17 | [DataMember] 18 | public int ArgumentCount { get; set; } 19 | 20 | public ZStackFrame() 21 | { 22 | Variables = new ushort[0x10]; 23 | RoutineStack = new Stack(); 24 | StoreResult = true; 25 | } 26 | } 27 | } --------------------------------------------------------------------------------