├── .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 | }
--------------------------------------------------------------------------------