summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.DS_Storebin0 -> 8196 bytes
-rw-r--r--.config/dotnet-tools.json36
-rw-r--r--.fleet/run.json11
-rw-r--r--.gitattributes2
-rw-r--r--.gitignore5
-rw-r--r--.idea/.idea.Chip8Emu2/.idea/.gitignore13
-rw-r--r--.idea/.idea.Chip8Emu2/.idea/.name1
-rw-r--r--.idea/.idea.Chip8Emu2/.idea/encodings.xml4
-rw-r--r--.idea/.idea.Chip8Emu2/.idea/indexLayout.xml8
-rw-r--r--.idea/.idea.Chip8Emu2/.idea/vcs.xml6
-rw-r--r--.idea/.idea.Q1Emu/.idea/.gitignore13
-rw-r--r--.idea/.idea.Q1Emu/.idea/encodings.xml4
-rw-r--r--.idea/.idea.Q1Emu/.idea/indexLayout.xml8
-rw-r--r--.idea/.idea.Q1Emu/.idea/misc.xml10
-rw-r--r--.idea/.idea.Q1Emu/.idea/vcs.xml6
-rw-r--r--Assembler/Assembler.cs106
-rw-r--r--Chip/Bus.cs55
-rw-r--r--Chip/BusDevice.cs50
-rw-r--r--Chip/DisplayDevice.cs76
-rw-r--r--Chip/Q1Cpu.cs152
-rw-r--r--Chip/Q1CpuAddressingModes.cs150
-rw-r--r--Chip/Q1CpuInstructions.cs225
-rw-r--r--Chip/RamDevice.cs49
-rw-r--r--Chip8Emu2.sln.DotSettings.user2
-rw-r--r--Content/Content.mgcb15
-rw-r--r--DisplayEngine.cs72
-rw-r--r--GlobalUsings.cs12
-rw-r--r--Icon.bmpbin0 -> 262282 bytes
-rw-r--r--Icon.icobin0 -> 147541 bytes
-rw-r--r--Make.cs48
-rw-r--r--Program.cs76
-rw-r--r--Q1Emu.csproj38
-rw-r--r--Q1Emu.sln16
-rw-r--r--Q1Emu.sln.DotSettings.user2
-rw-r--r--ROMs/.DS_Storebin0 -> 6148 bytes
-rw-r--r--ROMs/test.q1bin0 -> 10 bytes
-rw-r--r--app.manifest43
37 files changed, 1314 insertions, 0 deletions
diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000..7ae4f61
--- /dev/null
+++ b/.DS_Store
Binary files differ
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..84ff03d
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,36 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "dotnet-mgcb": {
+ "version": "3.8.2.1105",
+ "commands": [
+ "mgcb"
+ ]
+ },
+ "dotnet-mgcb-editor": {
+ "version": "3.8.2.1105",
+ "commands": [
+ "mgcb-editor"
+ ]
+ },
+ "dotnet-mgcb-editor-linux": {
+ "version": "3.8.2.1105",
+ "commands": [
+ "mgcb-editor-linux"
+ ]
+ },
+ "dotnet-mgcb-editor-windows": {
+ "version": "3.8.2.1105",
+ "commands": [
+ "mgcb-editor-windows"
+ ]
+ },
+ "dotnet-mgcb-editor-mac": {
+ "version": "3.8.2.1105",
+ "commands": [
+ "mgcb-editor-mac"
+ ]
+ }
+ }
+} \ No newline at end of file
diff --git a/.fleet/run.json b/.fleet/run.json
new file mode 100644
index 0000000..5ae6c96
--- /dev/null
+++ b/.fleet/run.json
@@ -0,0 +1,11 @@
+{
+ "configurations": [
+ {
+ "type": "dotnet",
+ "name": "Dotnet configuration",
+ "projectPath": "$PROJECT_DIR$",
+ "solutionPath": "$PROJECT_DIR$",
+ "args": []
+ }
+ ]
+} \ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..add57be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+bin/
+obj/
+/packages/
+riderModule.iml
+/_ReSharper.Caches/ \ No newline at end of file
diff --git a/.idea/.idea.Chip8Emu2/.idea/.gitignore b/.idea/.idea.Chip8Emu2/.idea/.gitignore
new file mode 100644
index 0000000..f0180a3
--- /dev/null
+++ b/.idea/.idea.Chip8Emu2/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/contentModel.xml
+/projectSettingsUpdater.xml
+/.idea.Chip8Emu2.iml
+/modules.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.Chip8Emu2/.idea/.name b/.idea/.idea.Chip8Emu2/.idea/.name
new file mode 100644
index 0000000..95e8088
--- /dev/null
+++ b/.idea/.idea.Chip8Emu2/.idea/.name
@@ -0,0 +1 @@
+Chip8Emu2 \ No newline at end of file
diff --git a/.idea/.idea.Chip8Emu2/.idea/encodings.xml b/.idea/.idea.Chip8Emu2/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.Chip8Emu2/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Chip8Emu2/.idea/indexLayout.xml b/.idea/.idea.Chip8Emu2/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.Chip8Emu2/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="UserContentModel">
+ <attachedFolders />
+ <explicitIncludes />
+ <explicitExcludes />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Chip8Emu2/.idea/vcs.xml b/.idea/.idea.Chip8Emu2/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/.idea.Chip8Emu2/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Q1Emu/.idea/.gitignore b/.idea/.idea.Q1Emu/.idea/.gitignore
new file mode 100644
index 0000000..99ea8f4
--- /dev/null
+++ b/.idea/.idea.Q1Emu/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/.idea.Q1Emu.iml
+/projectSettingsUpdater.xml
+/modules.xml
+/contentModel.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.Q1Emu/.idea/encodings.xml b/.idea/.idea.Q1Emu/.idea/encodings.xml
new file mode 100644
index 0000000..df87cf9
--- /dev/null
+++ b/.idea/.idea.Q1Emu/.idea/encodings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Q1Emu/.idea/indexLayout.xml b/.idea/.idea.Q1Emu/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.Q1Emu/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="UserContentModel">
+ <attachedFolders />
+ <explicitIncludes />
+ <explicitExcludes />
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Q1Emu/.idea/misc.xml b/.idea/.idea.Q1Emu/.idea/misc.xml
new file mode 100644
index 0000000..5207dea
--- /dev/null
+++ b/.idea/.idea.Q1Emu/.idea/misc.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="MaterialThemeProjectNewConfig">
+ <option name="metadata">
+ <MTProjectMetadataState>
+ <option name="userId" value="2ed817c6:198067820d1:-7fff" />
+ </MTProjectMetadataState>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.Q1Emu/.idea/vcs.xml b/.idea/.idea.Q1Emu/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.Q1Emu/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/Assembler/Assembler.cs b/Assembler/Assembler.cs
new file mode 100644
index 0000000..14a33fd
--- /dev/null
+++ b/Assembler/Assembler.cs
@@ -0,0 +1,106 @@
+namespace Q1Emu.Assembler;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+public class Assembler
+{
+ public readonly BinaryWriter Writer;
+ public readonly StringReader Reader;
+
+ public Assembler(StringReader reader, BinaryWriter writer)
+ {
+ this.Reader = reader;
+ this.Writer = writer;
+ }
+
+ public void Assemble()
+ {
+ while (this.Reader.ReadLine() is { } line)
+ {
+ string trimmedLine = line.Trim();
+ if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith(';'))
+ continue;
+
+ this.ParseInstruction(trimmedLine);
+ }
+ }
+
+ private void ParseInstruction(string instruction)
+ {
+ string[] parts = instruction.Split(' ', 2);
+ string opcode = parts[0].ToUpperInvariant();
+
+ parts = parts.Length > 1 ? parts[1].Split(',') : [];
+ if (parts.Length > 2)
+ throw new ArgumentException($"Invalid instruction format: {instruction}");
+
+ u8 m1 = 0, m2 = 0;
+ if (parts.Length > 0)
+ {
+ m1 = this.ParseOperand(parts[0]);
+ if (parts.Length > 1)
+ m2 = this.ParseOperand(parts[1]);
+ }
+
+ switch (opcode)
+ {
+ case "NOP":
+ this.Writer.Write((u8) 0x00);
+ break;
+
+ default:
+ throw new NotSupportedException($"Unsupported opcode: {opcode}");
+ }
+ }
+
+ private u8 ParseOperand(string operand)
+ {
+ operand = operand.Trim();
+
+ bool IsRegister(string text)
+ {
+ if (text.Length > 1 && text[0] == 'V')
+ return true;
+
+ string[] hardcodedRegisters =
+ [
+ "AX",
+ "AH",
+ "AL",
+ "DX",
+ "DH",
+ "DL",
+ "LX",
+ "PC",
+ "SP",
+ ];
+
+ return hardcodedRegisters.Contains(text);
+ }
+
+ if (operand.StartsWith("@"))
+ {
+ return 0x03; // Indirect
+ }
+
+ if (operand.StartsWith("[") && operand.EndsWith("]"))
+ {
+ string inner = operand[1..^1].Trim();
+ if (inner.Contains("+"))
+ return 0x07; // Register indirect with offset
+
+ if (IsRegister(inner))
+ return 0x06; // Register indirect
+
+ return 0x02; // Direct
+ }
+
+ if (IsRegister(operand))
+ return 0x05; // Register
+
+ return 0x01; // Immediate
+ }
+} \ No newline at end of file
diff --git a/Chip/Bus.cs b/Chip/Bus.cs
new file mode 100644
index 0000000..e8cf5ad
--- /dev/null
+++ b/Chip/Bus.cs
@@ -0,0 +1,55 @@
+namespace Q1Emu.Chip;
+
+using System.Collections.Generic;
+using System.Linq;
+
+public class Bus : BusDevice
+{
+ public List<BusDevice> Devices;
+
+ public Bus(u16 addressableStart, u16 addressableEnd) : base(addressableStart, addressableEnd)
+ {
+ this.Devices = [];
+ }
+ private BusDevice? GetDevice(u16 address)
+ {
+ BusDevice? device = this.Devices.FirstOrDefault(x => address >= x.AddressableStart && address < x.AddressableEnd);
+ return device;
+ }
+
+ public override u8 Read(u16 address)
+ {
+ BusDevice? device = GetDevice(address);
+ if (device == null)
+ return 0;
+
+ return device.Read(address);
+ }
+
+ public override void Write(u16 address, u8 value)
+ {
+ BusDevice? device = GetDevice(address);
+ if (device == null)
+ return;
+
+ device.Write(address, value);
+ }
+
+ public override ref8 ReadSeq(u16 address, u16 length)
+ {
+ BusDevice? device = this.GetDevice(address);
+ if (device == null)
+ return [];
+
+ return device.ReadSeq(address, length);
+ }
+
+ public override void WriteSeq(u16 address, u16 length, ref8 value)
+ {
+ BusDevice? device = this.GetDevice(address);
+ if (device == null)
+ return;
+
+ device.WriteSeq(address, length, value);
+ }
+} \ No newline at end of file
diff --git a/Chip/BusDevice.cs b/Chip/BusDevice.cs
new file mode 100644
index 0000000..e1f559c
--- /dev/null
+++ b/Chip/BusDevice.cs
@@ -0,0 +1,50 @@
+namespace Q1Emu.Chip;
+
+using System.Runtime.CompilerServices;
+
+public abstract class BusDevice
+{
+ public BusDevice(u16 addressableStart, u16 addressableEnd)
+ {
+ this.AddressableStart = addressableStart;
+ this.AddressableEnd = addressableEnd;
+ }
+
+ public Bus Bus { get; set; }
+ public abstract u8 Read(u16 address);
+ public abstract void Write(u16 address, u8 value);
+
+ public abstract ref8 ReadSeq(u16 address, u16 length);
+ public abstract void WriteSeq(u16 address, u16 length, ref8 value);
+
+ public u16 ReadWord(u16 address)
+ {
+ ref8 data = this.ReadSeq(address, 2);
+ u16 value = Make.u16(data);
+ return value;
+ }
+
+ public void WriteWord(u16 address, u16 value)
+ {
+ ref8 data = Make.ref8(value);
+ this.WriteSeq(address, 2, data);
+ }
+
+ public virtual void Clock() { }
+ public virtual void Reset() { }
+
+ public void AttachToBus(Bus bus)
+ {
+ this.Bus = bus;
+ bus.Devices.Add(this);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected u16 GetRelativeAddress(u16 address)
+ {
+ return (u16) (address - this.AddressableStart);
+ }
+
+ public u16 AddressableStart { get; }
+ public u16 AddressableEnd { get; }
+} \ No newline at end of file
diff --git a/Chip/DisplayDevice.cs b/Chip/DisplayDevice.cs
new file mode 100644
index 0000000..ea34cfb
--- /dev/null
+++ b/Chip/DisplayDevice.cs
@@ -0,0 +1,76 @@
+namespace Q1Emu.Chip;
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+public class DisplayDevice : RamDevice
+{
+ public Texture2D? OutputTexture;
+ private readonly u16 _paletteSize;
+ private readonly u16 _displayWidth;
+ private readonly u16 _displayHeight;
+
+ public DisplayDevice(u16 paletteSize, u16 displayWidth, u16 displayHeight, u16 addressableStart)
+ : base(addressableStart, DisplayDevice.DetermineSize(paletteSize, displayWidth, displayHeight))
+ {
+ this._paletteSize = paletteSize;
+ this._displayWidth = displayWidth;
+ this._displayHeight = displayHeight;
+ }
+
+ private static u16 DetermineSize(u16 paletteSize, u16 displayWidth, u16 displayHeight)
+ {
+ return (u16) ((paletteSize * 3) + (displayWidth * displayHeight));
+ }
+
+ public override void Clock()
+ {
+ Color[] palette = this.ReadPalette();
+ ref8 pixelData = this.ReadSeq((u16) (this.AddressableStart + this._paletteSize * 3), (u16) (this._displayWidth * this._displayHeight));
+
+ Color[] textureData = new Color[this._displayWidth * this._displayHeight];
+ for (u16 i = 0; i < textureData.Length; i++)
+ {
+ u8 pixelValue = pixelData[i];
+ textureData[i] = palette[pixelValue];
+ }
+
+ if (this.OutputTexture != null)
+ this.OutputTexture.SetData(textureData);
+ }
+
+ private Color[] ReadPalette()
+ {
+ ref8 paletteData = this.ReadSeq(this.AddressableStart, (u16) (this._paletteSize * 3));
+ Color[] colors = new Color[this._paletteSize + 1];
+ colors[0] = Color.Transparent;
+
+ for (u16 i = 0; i < this._paletteSize; i++)
+ {
+ u16 offset = (u16) (i * 3);
+ byte r = paletteData[offset];
+ byte g = paletteData[offset + 1];
+ byte b = paletteData[offset + 2];
+ colors[(u16) (i + 1)] = new Color(r, g, b);
+ }
+
+ return colors;
+ }
+
+ public void SetPalette(Color[] palette)
+ {
+ if (palette.Length != this._paletteSize)
+ throw new ArgumentException($"Palette must have {this._paletteSize} colors.");
+
+ ref8 paletteData = this.ReadSeq(this.AddressableStart, (u16) (this._paletteSize * 3));
+ for (u16 i = 0; i < this._paletteSize; i++)
+ {
+ u16 offset = (u16) (i * 3);
+ paletteData[offset] = palette[i].R;
+ paletteData[offset + 1] = palette[i].G;
+ paletteData[offset + 2] = palette[i].B;
+ }
+ }
+} \ No newline at end of file
diff --git a/Chip/Q1Cpu.cs b/Chip/Q1Cpu.cs
new file mode 100644
index 0000000..1124578
--- /dev/null
+++ b/Chip/Q1Cpu.cs
@@ -0,0 +1,152 @@
+using System;
+
+namespace Q1Emu.Chip;
+
+using System.Collections.Generic;
+
+public partial class Q1Cpu
+{
+ public const u16 StackStart = 0x30;
+ public const u16 StackLength = 16 * 2; // 16 16-bit values
+
+ public Random Random;
+
+ public u16 AX = 0; // Accumulator register
+ public u16 DX = 0; // Data register
+ public u16 LX = 0; // Logical register
+
+ public u16 SP { get; set; } // Stack pointer
+ public u16 PC { get; set; } // Program counter
+
+ public u16[] V { get; set; } // Variable registers (64 total)
+ public u16 I { get; set; } // Index register
+ public u16 Cycles { get; set; } // Number of cycles
+
+ public Bus Bus { get; set; }
+
+ public Q1Cpu()
+ {
+ this.LoadInstructionLookup();
+
+ this.Reset();
+ }
+
+ public void Reset()
+ {
+ this.V = new u16[16];
+
+ this.SP = StackStart;
+
+ this.I = 0;
+ }
+
+ public void Clock()
+ {
+ u16 instruction = this.FetchInstruction(out u8 op, out u8 m1, out u8 m2, out _);
+
+ bool ex = m2 > 0;
+ switch (m1, ex)
+ {
+ case (0x00, false):
+ this.InstructionLookup[op].Item1(); // Implicit
+ break;
+ case (> 0x00, false):
+ this.InstructionLookup[op].Item2(m1); // Simple addressing mode
+ break;
+ case (0x00, true):
+ this.InstructionLookup[op].Item3(); // Extended implicit
+ break;
+ case (> 0x00, true):
+ this.InstructionLookup[op].Item4(m1, m2); // Extended addressing mode
+ break;
+ }
+
+ this.Cycles++;
+ }
+ public u16 FetchInstruction(out u8 op, out u8 m1, out u8 m2, out u8 length)
+ {
+ u16 instruction = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ op = (u8) ((instruction >> 8) & 0xFF); // 256 opcodes
+ m1 = (u8) ((instruction >> 4) & 0x0F); // 16 modes
+ m2 = (u8) (instruction & 0x0F); // 16 modes
+
+ length = (u8) (this.AddressingModeLength(m1) + this.AddressingModeLength(m2));
+ return instruction;
+ }
+
+ private void PushStack(u16 value)
+ {
+ if (this.SP + 2 >= StackStart + StackLength)
+ throw new InvalidOperationException("Stack overflow");
+
+ this.Bus.WriteSeq(this.SP, 2, Make.ref8(value));
+ this.SP += 2;
+ }
+
+ private u16 PopStack()
+ {
+ if (this.SP - 2 < StackStart)
+ throw new InvalidOperationException("Stack underflow");
+
+ this.SP -= 2;
+ return Make.u16(this.Bus.ReadSeq(this.SP, 2));
+ }
+
+ private u16 FetchRegister(u8 regIndex)
+ {
+ return regIndex switch
+ {
+ <= 0x3F => this.V[regIndex],
+
+ 0x40 => this.AX,
+ 0x41 => (u16) (this.AX >> 8), // High byte of AX
+ 0x42 => (u16) (this.AX & 0xFF), // Low byte of AX
+
+ 0x43 => this.DX,
+ 0x44 => (u16) (this.DX >> 8), // High byte of DX
+ 0x45 => (u16) (this.DX & 0xFF), // Low byte of DX
+
+ 0x46 => this.LX,
+ 0x50 => this.PC,
+ 0x51 => this.SP,
+
+ _ => throw new InvalidOperationException($"Unknown register index: {regIndex:X2}"),
+ };
+ }
+
+ private void StoreRegister(u8 regIndex, u16 value)
+ {
+ switch (regIndex)
+ {
+ case <= 0x3F:
+ this.V[regIndex] = value;
+ break;
+
+ case 0x40:
+ this.AX = value; break;
+ case 0x41:
+ this.AX = (u16) ((this.AX & 0x00FF) | (value << 8)); break; // Set high byte
+ case 0x42:
+ this.AX = (u16) ((this.AX & 0xFF00) | (value & 0x00FF)); break; // Set low byte
+
+ case 0x43:
+ this.DX = value; break;
+ case 0x44:
+ this.DX = (u16) ((this.DX & 0x00FF) | (value << 8)); break; // Set high byte
+ case 0x45:
+ this.DX = (u16) ((this.DX & 0xFF00) | (value & 0x00FF)); break; // Set low byte
+
+ case 0x46:
+ this.LX = value; break;
+ case 0x50:
+ this.PC = value; break;
+ case 0x51:
+ this.SP = value; break;
+
+ default:
+ throw new InvalidOperationException($"Unknown register index: {regIndex:X2}");
+ }
+ }
+} \ No newline at end of file
diff --git a/Chip/Q1CpuAddressingModes.cs b/Chip/Q1CpuAddressingModes.cs
new file mode 100644
index 0000000..0d664f7
--- /dev/null
+++ b/Chip/Q1CpuAddressingModes.cs
@@ -0,0 +1,150 @@
+namespace Q1Emu.Chip;
+
+using System;
+using System.Collections.Generic;
+
+public partial class Q1Cpu
+{
+ public u16 FetchData(u8 mode)
+ {
+ return mode switch
+ {
+ 0x0 => this.AddressingMode_Implicit(),
+ 0x1 => this.AddressingMode_Immediate(),
+ 0x2 => this.AddressingMode_Direct(),
+ 0x3 => this.AddressingMode_Indirect(),
+ 0x4 => this.AddressingMode_Relative(),
+ 0x5 => this.AddressingMode_Register(),
+ 0x6 => this.AddressingMode_RegisterIndirect(),
+ 0x7 => this.AddressingMode_RegisterIndirectOffset(),
+ _ => throw new InvalidOperationException($"Unknown addressing mode: {mode:X2}"),
+ };
+ }
+
+ public void StoreData(u8 mode, u16 value)
+ {
+ Console.WriteLine($"StoreData: mode={mode:X8}, value={value:X16}");
+ if (mode != 0x05)
+ {
+ u16 address = this.FetchData(mode);
+ Console.WriteLine($"StoreData: address={address:X16}");
+ this.Bus.WriteWord(address, value);
+ }
+ else
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ this.StoreRegister(regIndex, value);
+ }
+ }
+
+ public void TransformData(u8 mode, Func<u16, u16> transform)
+ {
+ if (mode != 0x05)
+ {
+ u16 address = this.FetchData(mode);
+ u16 value = this.Bus.ReadWord(address);
+ u16 transformedValue = transform(value);
+ this.Bus.WriteWord(address, transformedValue);
+ }
+ else
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 value = this.FetchRegister(regIndex);
+ u16 transformedValue = transform(value);
+ this.StoreRegister(regIndex, transformedValue);
+ }
+ }
+
+ public u8 AddressingModeLength(u8 mode)
+ {
+ return mode switch
+ {
+ 0x0 => 0, // Implicit
+ 0x1 => 2, // Immediate
+ 0x2 => 2, // Direct
+ 0x3 => 2, // Indirect
+ 0x4 => 2, // Relative
+ 0x5 => 1, // Register
+ 0x6 => 1, // Register indirect
+ 0x7 => 3, // Register indirect with offset
+ _ => throw new InvalidOperationException($"Unknown addressing mode: {mode:X2}"),
+ };
+ }
+
+ public u16 AddressingMode_Implicit()
+ {
+ return 0;
+ }
+
+
+ public u16 AddressingMode_Immediate()
+ {
+ u16 value = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ return value;
+ }
+
+ public u16 AddressingMode_Direct()
+ {
+ u16 address = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 value = this.Bus.ReadWord(address);
+ return value;
+ }
+
+ public u16 AddressingMode_Indirect()
+ {
+ u16 address = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 indirectAddress = this.Bus.ReadWord(address);
+ u16 value = this.Bus.ReadWord(indirectAddress);
+ return value;
+ }
+
+ public u16 AddressingMode_Relative()
+ {
+ u16 offset = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 address = (u16) (this.PC + offset);
+ return address;
+ }
+
+ public u16 AddressingMode_Register()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ return this.FetchRegister(regIndex);
+ }
+
+ public u16 AddressingMode_RegisterIndirect()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 address = this.FetchRegister(regIndex);
+ u16 value = this.Bus.ReadWord(address);
+ return value;
+ }
+
+ public u16 AddressingMode_RegisterIndirectOffset()
+ {
+ u8 regIndex = this.Bus.Read(this.PC);
+ this.PC += 1;
+
+ u16 offset = this.Bus.ReadWord(this.PC);
+ this.PC += 2;
+
+ u16 address = (u16) (this.FetchRegister(regIndex) + offset);
+ u16 value = this.Bus.ReadWord(address);
+ return value;
+ }
+} \ No newline at end of file
diff --git a/Chip/Q1CpuInstructions.cs b/Chip/Q1CpuInstructions.cs
new file mode 100644
index 0000000..8b79a1d
--- /dev/null
+++ b/Chip/Q1CpuInstructions.cs
@@ -0,0 +1,225 @@
+namespace Q1Emu.Chip;
+
+using System;
+using System.Collections.Generic;
+
+public partial class Q1Cpu
+{
+ public Dictionary<u8, (Action, Action<u8>, Action, Action<u8, u8>)> InstructionLookup;
+ private void LoadInstructionLookup()
+ {
+ this.InstructionLookup = new Dictionary<u8, (Action, Action<u8>, Action, Action<u8, u8>)>
+ {
+ { 0x00, (this.Instruction_NOP, this.Instruction_JMP, this.Instruction_HALT, this.Instruction_MOV) },
+ { 0x01, (this.Instruction_RET, this.Instruction_CALL, this.Instruction_SUS, this.Instruction_CMP) },
+ { 0x02, (this.Instruction_BR, this.Instruction_BZ, this.Instruction_BZ_LX, this.Instruction_LT) },
+ { 0x03, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_GT) },
+
+ { 0x04, (this.Instruction_NOT_LX, this.Instruction_NOT, this.Instruction_NOP, this.Instruction_AND) },
+ { 0x05, (this.Instruction_INV_DX, this.Instruction_INV, this.Instruction_NOP, this.Instruction_OR) },
+ { 0x06, (this.Instruction_SHL_DX, this.Instruction_SHL, this.Instruction_NOP, this.Instruction_XOR) },
+ { 0x07, (this.Instruction_SHR_DX, this.Instruction_SHR, this.Instruction_NOP, this.Instruction_NOP) },
+
+ { 0x08, (this.Instruction_INC_AX, this.Instruction_INC, this.Instruction_NOP, this.Instruction_ADD) },
+ { 0x09, (this.Instruction_DEC_AX, this.Instruction_DEC, this.Instruction_NOP, this.Instruction_SUB) },
+ { 0x0A, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_DIV) },
+ { 0x0B, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_MUL) },
+ { 0x0C, (this.Instruction_NOP, this.Instruction_NOP, this.Instruction_NOP, this.Instruction_MOD) },
+ };
+ }
+ private void Instruction_ADD(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16) (v1 + v2);
+ this.AX = result;
+ }
+ private void Instruction_SUB(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16)(v1 - v2);
+ this.AX = result;
+ }
+ private void Instruction_MUL(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16) (v1 * v2);
+ this.AX = result;
+ }
+ private void Instruction_DIV(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ if (v2 == 0)
+ throw new DivideByZeroException("Cannot perform division by zero.");
+ u16 result = (u16)(v1 / v2);
+ this.AX = result;
+ }
+ private void Instruction_MOD(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ if (v2 == 0)
+ throw new DivideByZeroException("Cannot perform modulo operation with divisor zero.");
+ u16 result = (u16)(v1 % v2);
+ this.AX = result;
+ }
+ private void Instruction_INC(u8 obj)
+ {
+ this.TransformData(obj, value => (u16)(value + 1));
+ }
+ private void Instruction_DEC(u8 obj)
+ {
+ this.TransformData(obj, value => (u16)(value - 1));
+ }
+ private void Instruction_INC_AX()
+ {
+ this.AX = (u16)(this.AX + 1);
+ }
+ private void Instruction_DEC_AX()
+ {
+ this.AX = (u16)(this.AX - 1);
+ }
+ private void Instruction_SHL(u8 obj)
+ {
+ this.TransformData(obj, value => (u16)(value << 1));
+ }
+ private void Instruction_SHR(u8 obj)
+ {
+ this.TransformData(obj, value => (u16)(value >> 1));
+ }
+ private void Instruction_SHL_DX()
+ {
+ this.DX = (u16)(this.DX << 1);
+ }
+ private void Instruction_SHR_DX()
+ {
+ this.DX = (u16)(this.DX >> 1);
+ }
+ private void Instruction_XOR(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16)(v1 ^ v2);
+ this.DX = result;
+ }
+ private void Instruction_OR(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16)(v1 | v2);
+ this.DX = result;
+ }
+ private void Instruction_INV(u8 obj)
+ {
+ u16 value = this.FetchData(obj);
+ u16 result = (u16)~value;
+ this.DX = result;
+ }
+ private void Instruction_INV_DX()
+ {
+ this.DX = (u16)~this.DX;
+ }
+ private void Instruction_AND(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ u16 result = (u16)(v1 & v2);
+ this.DX = result;
+ }
+ private void Instruction_NOT(u8 obj)
+ {
+ u16 value = this.FetchData(obj);
+ u16 result = value == 0 ? (u16)1 : (u16)0;
+ this.LX = result;
+ }
+ private void Instruction_NOT_LX()
+ {
+ this.LX = this.LX == 0 ? (u16)1 : (u16)0;
+ }
+ private void Instruction_GT(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ this.LX = (u16)(v1 > v2 ? 1 : 0);
+ }
+ private void Instruction_LT(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ this.LX = (u16)(v1 < v2 ? 1 : 0);
+ }
+
+ private void Instruction_BR()
+ {
+ this.FetchInstruction(out _, out _, out _, out u8 length);
+ this.PC += length;
+ }
+ private void Instruction_BZ_LX()
+ {
+ if (this.LX == 0)
+ this.Instruction_BR();
+ }
+ private void Instruction_BZ(u8 obj)
+ {
+ u16 value = this.FetchData(obj);
+ if (value == 0)
+ this.Instruction_BR();
+ }
+ private void Instruction_CMP(u8 m1, u8 m2)
+ {
+ u16 v1 = this.FetchData(m1);
+ u16 v2 = this.FetchData(m2);
+
+ // Set LX based on comparison
+ this.LX = (u16)(v1 == v2 ? 1 : 0);
+ }
+ private void Instruction_MOV(u8 m1, u8 m2)
+ {
+ u16 value = this.FetchData(m1);
+ this.StoreData(m2, value);
+ }
+
+ public void Instruction_NOP() { }
+ public void Instruction_NOP(u8 m1) { }
+ public void Instruction_NOP(u8 m1, u8 m2) { }
+
+ public void Instruction_JMP(u8 m1)
+ {
+ this.PC = this.FetchData(m1);
+ }
+
+ public void Instruction_RET()
+ {
+ this.PC = this.PopStack();
+ }
+
+ public void Instruction_CALL(u8 m1)
+ {
+ u16 address = this.FetchData(m1);
+ this.PushStack(this.PC);
+ this.PC = address;
+ }
+
+ public void Instruction_HALT()
+ {
+ throw new NotImplementedException("Halt instruction not implemented yet.");
+ }
+
+ public void Instruction_SUS()
+ {
+ this.PC -= 2;
+ }
+} \ No newline at end of file
diff --git a/Chip/RamDevice.cs b/Chip/RamDevice.cs
new file mode 100644
index 0000000..6811d22
--- /dev/null
+++ b/Chip/RamDevice.cs
@@ -0,0 +1,49 @@
+namespace Q1Emu.Chip;
+
+using System;
+using System.Runtime.CompilerServices;
+
+public class RamDevice : BusDevice
+{
+ public u8[] Memory;
+
+ public RamDevice(u16 start, u16 size) : base(start, (u16) (start + size))
+ {
+ this.Memory = new u8[size];
+ }
+
+ public override void Reset()
+ {
+ this.Memory = new u8[this.AddressableEnd - this.AddressableStart];
+ }
+
+ public override void Clock()
+ {
+ // No clocking needed for RAM
+ }
+
+ public override u8 Read(u16 address)
+ {
+ u16 relativeAddress = this.GetRelativeAddress(address);
+ return this.Memory[relativeAddress];
+ }
+
+ public override void Write(u16 address, u8 value)
+ {
+ u16 relativeAddress = this.GetRelativeAddress(address);
+ this.Memory[relativeAddress] = value;
+ }
+
+ public override ref8 ReadSeq(u16 address, u16 length)
+ {
+ u16 relativeAddress = this.GetRelativeAddress(address);
+ // Console.WriteLine($"{address:X4} {relativeAddress:X4} {length:X4}");
+ return new ref8(this.Memory, relativeAddress, length);
+ }
+
+ public override void WriteSeq(u16 address, u16 length, ref8 value)
+ {
+ ref8 destination = this.ReadSeq(address, length);
+ value.CopyTo(destination);
+ }
+} \ No newline at end of file
diff --git a/Chip8Emu2.sln.DotSettings.user b/Chip8Emu2.sln.DotSettings.user
new file mode 100644
index 0000000..d66483f
--- /dev/null
+++ b/Chip8Emu2.sln.DotSettings.user
@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc06673f4846f4984af370f3a7898b653d83800_003F85_003F06ad3dad_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/Content/Content.mgcb b/Content/Content.mgcb
new file mode 100644
index 0000000..ddc4c36
--- /dev/null
+++ b/Content/Content.mgcb
@@ -0,0 +1,15 @@
+
+#----------------------------- Global Properties ----------------------------#
+
+/outputDir:bin/$(Platform)
+/intermediateDir:obj/$(Platform)
+/platform:DesktopGL
+/config:
+/profile:Reach
+/compress:False
+
+#-------------------------------- References --------------------------------#
+
+
+#---------------------------------- Content ---------------------------------#
+
diff --git a/DisplayEngine.cs b/DisplayEngine.cs
new file mode 100644
index 0000000..f77598f
--- /dev/null
+++ b/DisplayEngine.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using Q1Emu.Chip;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Q1Emu;
+
+public class DisplayEngine : Game
+{
+ private readonly Action<Texture2D> _onScreenTextureLoaded;
+ private GraphicsDeviceManager _graphics;
+ private SpriteBatch _spriteBatch;
+
+ private Texture2D _texture;
+ public int Scaling { get; }
+ public int Width { get; }
+ public int Height { get; }
+
+ public DisplayEngine(int width, int height, int scaling, Action<Texture2D> onScreenTextureLoaded)
+ {
+ this.Width = width;
+ this.Height = height;
+ this.Scaling = scaling;
+ this._onScreenTextureLoaded = onScreenTextureLoaded;
+
+ _graphics = new GraphicsDeviceManager(this);
+ Content.RootDirectory = "Content";
+ IsMouseVisible = true;
+ }
+
+ protected override void Initialize()
+ {
+ this._texture = new Texture2D(GraphicsDevice, Width, Height);
+ this._onScreenTextureLoaded?.Invoke(this._texture);
+ // set window size
+ _graphics.PreferredBackBufferWidth = Width * Scaling;
+ _graphics.PreferredBackBufferHeight = Height * Scaling;
+ _graphics.ApplyChanges();
+
+ base.Initialize();
+ }
+
+ protected override void LoadContent()
+ {
+ _spriteBatch = new SpriteBatch(GraphicsDevice);
+ }
+
+ protected override void Update(GameTime gameTime)
+ {
+ var state = Keyboard.GetState();
+
+ if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || state.IsKeyDown(Keys.Escape))
+ Exit();
+
+ base.Update(gameTime);
+ }
+
+ protected override void Draw(GameTime gameTime)
+ {
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone);
+ // Draw the texture to the screen
+ _spriteBatch.Draw(_texture, new Rectangle(0, 0, Width * Scaling, Height * Scaling), Color.White);
+
+ _spriteBatch.End();
+
+ base.Draw(gameTime);
+ }
+} \ No newline at end of file
diff --git a/GlobalUsings.cs b/GlobalUsings.cs
new file mode 100644
index 0000000..ff38a75
--- /dev/null
+++ b/GlobalUsings.cs
@@ -0,0 +1,12 @@
+// Integer type aliases
+global using i8 = sbyte; // -128 to 127
+global using u8 = byte; // 0 to 255
+global using i16 = short; // -32,768 to 32,767
+global using u16 = ushort; // 0 to 65,535
+global using i32 = int; // -2,147,483,648 to 2,147,483,647
+global using u32 = uint; // 0 to 4,294,967,295
+global using i64 = long; // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
+global using u64 = ulong; // 0 to 18,446,744,073,709,551,615
+
+global using ref8 = System.Span<byte>;
+global using data8 = byte[];
diff --git a/Icon.bmp b/Icon.bmp
new file mode 100644
index 0000000..2b48165
--- /dev/null
+++ b/Icon.bmp
Binary files differ
diff --git a/Icon.ico b/Icon.ico
new file mode 100644
index 0000000..7d9dec1
--- /dev/null
+++ b/Icon.ico
Binary files differ
diff --git a/Make.cs b/Make.cs
new file mode 100644
index 0000000..6999c4d
--- /dev/null
+++ b/Make.cs
@@ -0,0 +1,48 @@
+namespace Q1Emu;
+
+using System;
+
+public static class Make
+{
+ public static u16 u16(u8 high, u8 low)
+ {
+ return (u16) ((high << 8) | low);
+ }
+
+ public static u16 u16(ref8 data)
+ {
+ if (data.Length < 2)
+ throw new ArgumentException("Data must contain at least 2 bytes to form a u16.");
+
+ return u16(data[0], data[1]);
+ }
+
+ public static u32 u32(u8 b1, u8 b2, u8 b3, u8 b4)
+ {
+ return (u32) ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4);
+ }
+
+ public static u32 u32(ref8 data)
+ {
+ if (data.Length < 4)
+ throw new ArgumentException("Data must contain at least 4 bytes to form a u32.");
+
+ return u32(data[0], data[1], data[2], data[3]);
+ }
+
+ public static ref8 ref8(u16 value)
+ {
+ u8 high = (u8) (value >> 8);
+ u8 low = (u8) (value & 0xFF);
+ return new ref8([high, low], 0, 2);
+ }
+
+ public static ref8 ref8(u32 value)
+ {
+ u8 b1 = (u8) (value >> 24);
+ u8 b2 = (u8) (value >> 16);
+ u8 b3 = (u8) (value >> 8);
+ u8 b4 = (u8) (value & 0xFF);
+ return new ref8([b1, b2, b3, b4], 0, 4);
+ }
+} \ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..fab84f7
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using Microsoft.Xna.Framework;
+using Q1Emu.Chip;
+using Q1Emu;
+
+internal class Program
+{
+ public static void Main(string[] args)
+ {
+ if (args.Length > 0)
+ {
+ string filePath = args[0];
+ if (filePath.EndsWith(".q1"))
+ {
+ Run(filePath);
+ }
+ else
+ {
+ Program.AssembleAndRun(filePath);
+ }
+ }
+ else
+ {
+ Console.WriteLine("Usage: Q1Emu <file.q1|file.asm>");
+ Console.WriteLine("Example: Q1Emu game.q1");
+ Console.WriteLine("Example: Q1Emu game.asm");
+ }
+ }
+ private static void AssembleAndRun(string filePath)
+ {
+
+ }
+ private static void Run(string filePath)
+ {
+ Q1Cpu cpu = new();
+ Bus bus = new(u16.MinValue, u16.MaxValue);
+ RamDevice ram = new(0x1FFF, 1024 * 8);
+ File.ReadAllBytes(filePath).CopyTo(ram.Memory, 0);
+
+ cpu.PC = ram.AddressableStart;
+
+ DisplayDevice display = new(16, 64, 32, 0x3FFF);
+ display.SetPalette([
+ Color.Red, Color.Green, Color.Blue, Color.Yellow,
+ Color.Cyan, Color.Magenta, Color.White, Color.Black,
+ Color.Gray, Color.Orange, Color.Purple, Color.Brown,
+ Color.LightGray, Color.DarkGray, Color.LightBlue, Color.LightGreen]);
+
+ cpu.Bus = bus;
+ ram.AttachToBus(bus);
+
+ int frameRate = 600;
+ Thread t = new(() =>
+ {
+ while (true)
+ {
+ cpu.Clock();
+ display.Clock();
+ Thread.Sleep(1000 / frameRate);
+ }
+ });
+ t.IsBackground = true;
+ t.Start();
+
+ using DisplayEngine game = new(64, 32, 10,
+ tex =>
+ {
+ display.OutputTexture = tex;
+ Console.WriteLine("Display texture initialized.");
+ });
+ game.Run();
+ }
+} \ No newline at end of file
diff --git a/Q1Emu.csproj b/Q1Emu.csproj
new file mode 100644
index 0000000..f57964b
--- /dev/null
+++ b/Q1Emu.csproj
@@ -0,0 +1,38 @@
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>WinExe</OutputType>
+ <TargetFramework>net8.0</TargetFramework>
+ <RollForward>Major</RollForward>
+ <PublishReadyToRun>false</PublishReadyToRun>
+ <TieredCompilation>false</TieredCompilation>
+ <RootNamespace>Chip8Emu2</RootNamespace>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+ <PropertyGroup>
+ <ApplicationManifest>app.manifest</ApplicationManifest>
+ <ApplicationIcon>Icon.ico</ApplicationIcon>
+ </PropertyGroup>
+ <ItemGroup>
+ <None Remove="Icon.ico"/>
+ <None Remove="Icon.bmp"/>
+ </ItemGroup>
+ <ItemGroup>
+ <EmbeddedResource Include="Icon.ico">
+ <LogicalName>Icon.ico</LogicalName>
+ </EmbeddedResource>
+ <EmbeddedResource Include="Icon.bmp">
+ <LogicalName>Icon.bmp</LogicalName>
+ </EmbeddedResource>
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.2.1105"/>
+ <PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.2.1105"/>
+ </ItemGroup>
+ <ItemGroup>
+ <Folder Include="ROMs\" />
+ </ItemGroup>
+ <Target Name="RestoreDotnetTools" BeforeTargets="Restore">
+ <Message Text="Restoring dotnet tools" Importance="High"/>
+ <Exec Command="dotnet tool restore"/>
+ </Target>
+</Project> \ No newline at end of file
diff --git a/Q1Emu.sln b/Q1Emu.sln
new file mode 100644
index 0000000..801cf19
--- /dev/null
+++ b/Q1Emu.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Q1Emu", "Q1Emu.csproj", "{04CE77A9-E0DC-4CAB-9382-5F84B4275B6B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {04CE77A9-E0DC-4CAB-9382-5F84B4275B6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {04CE77A9-E0DC-4CAB-9382-5F84B4275B6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {04CE77A9-E0DC-4CAB-9382-5F84B4275B6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {04CE77A9-E0DC-4CAB-9382-5F84B4275B6B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Q1Emu.sln.DotSettings.user b/Q1Emu.sln.DotSettings.user
new file mode 100644
index 0000000..1f447ab
--- /dev/null
+++ b/Q1Emu.sln.DotSettings.user
@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGame_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F49bab08708434a08802510b75a8ea3691b3400_003F26_003F86c71a55_003FGame_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/ROMs/.DS_Store b/ROMs/.DS_Store
new file mode 100644
index 0000000..28ca9c8
--- /dev/null
+++ b/ROMs/.DS_Store
Binary files differ
diff --git a/ROMs/test.q1 b/ROMs/test.q1
new file mode 100644
index 0000000..d0c089f
--- /dev/null
+++ b/ROMs/test.q1
Binary files differ
diff --git a/app.manifest b/app.manifest
new file mode 100644
index 0000000..d9c2d8d
--- /dev/null
+++ b/app.manifest
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+ <assemblyIdentity version="1.0.0.0" name="Chip8Emu2"/>
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+ <security>
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- A list of the Windows versions that this application has been tested on and is
+ is designed to work with. Uncomment the appropriate elements and Windows will
+ automatically selected the most compatible environment. -->
+
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
+
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
+
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
+
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
+
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+
+ </application>
+ </compatibility>
+
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
+ <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness>
+ </windowsSettings>
+ </application>
+
+</assembly>