private void DispatchOpcode(ParsedOpCode parsedOpCode) { switch (parsedOpCode.Instruction) { case 0x0: // 0xE0 Clear Display if (parsedOpCode.NN == 0xE0) { DisplayModel.Clear(); } // 0xEE Return from Subroutine else if (parsedOpCode.NN == 0xEE) { CPU.Ret(); } break; case 0x01: // 0x1NNN Jump to address NNN CPU.Jump(parsedOpCode.NNN); break; case 0x02: // 0x2NNN Execute subroutine at address NNN CPU.Subroutine(parsedOpCode.NNN); break; case 0x03: // 0x3XNN Skip the next instruction if regoster VX == NN CPU.SkipIfEqual(parsedOpCode.X, parsedOpCode.NN); break; case 0x04: // 0x4XNN Skip the next instruction if register VX != NN CPU.SkipIfNotEqual(parsedOpCode.X, parsedOpCode.NN); break; case 0x05: // 0x5XY0 Skip the next instruction if register VX == VY CPU.SkipIfXEqY(parsedOpCode.X, parsedOpCode.Y); break; case 0x06: // 0x6XNN Store the number NN in register VX CPU.StoreInRegister(parsedOpCode.X, parsedOpCode.NN); break; case 0x07: // 0x7XNN Add the number NN to register VX CPU.AddToRegister(parsedOpCode.X, parsedOpCode.NN); break; case 0x08: // Handle opcodes starting with 0x08 in a separate function Dispatch0x08(parsedOpCode); break; case 0x09: // 0x9XY0 Skip the next instruction if register VX != VY if (parsedOpCode.N == 0) { CPU.SkipNextInstructionIfVXneVY(parsedOpCode.X, parsedOpCode.Y); } break; case 0x0A: // 0xANNN Store memeory at address NNN in register I CPU.Index = parsedOpCode.NNN; break; case 0x0B: // 0xBNNN Jump to address NNN plus V0 CPU.JumpToAddressPlusV0(parsedOpCode.NNN); break; case 0x0C: // 0xCXNN Set VX to a random number with a mask of NN byte number = (byte)(random.Next(parsedOpCode.NN)); CPU.V[parsedOpCode.X] = number; break; case 0x0D: // 0xDXYN Draw sprite at position VX, VY with N bytes of data at the address stored in I // Set VF to 1 if any set pixels become unset. Set VF to 0 otherwise. var unsetPixels = DisplayModel.DrawSprite(CPU.V[parsedOpCode.X], CPU.V[parsedOpCode.Y], Memory, CPU.Index, parsedOpCode.N); CPU.V[0x0F] = (byte)(unsetPixels ? 1 : 0); break; case 0x0E: // Handle opcodes begining with 0xE Dispatch0x0E(parsedOpCode); break; case 0x0F: // Handle opcodes begining with 0xF Dispatch0x0F(parsedOpCode); break; } }
static void Main(string[] args) { if (SDL.SDL_Init(SDL.SDL_INIT_EVERYTHING) < 0) { Console.WriteLine("SDL failed to init."); return; } IntPtr window = SDL.SDL_CreateWindow("Chip-8 Interpreter", 128, 128, 64 * 8, 32 * 8, 0); if (window == IntPtr.Zero) { Console.WriteLine("SDL could not create a window."); return; } IntPtr renderer = SDL.SDL_CreateRenderer(window, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED); if (renderer == IntPtr.Zero) { Console.WriteLine("SDL could not create a valid renderer."); return; } CPU cpu = new CPU(); using (BinaryReader reader = new BinaryReader(new FileStream("../../spaceInvaders.ch8", FileMode.Open))) { List <byte> program = new List <byte>(); while (reader.BaseStream.Position < reader.BaseStream.Length) { program.Add(reader.ReadByte()); } cpu.LoadProgram(program.ToArray()); } SDL.SDL_Event sdlEvent; bool running = true; int sample = 0; int beepSamples = 0; SDL.SDL_AudioSpec audioSpec = new SDL.SDL_AudioSpec(); audioSpec.channels = 1; audioSpec.freq = 44100; audioSpec.samples = 256; audioSpec.format = SDL.AUDIO_S8; audioSpec.callback = new SDL.SDL_AudioCallback((userdata, stream, length) => { if (cpu == null) { return; } sbyte[] waveData = new sbyte[length]; for (int i = 0; i < waveData.Length && cpu.SoundTimer > 0; i++, beepSamples++) { if (beepSamples == 730) { beepSamples = 0; cpu.SoundTimer--; } waveData[i] = (sbyte)(127 * Math.Sin(sample * Math.PI * 2 * 604.1 / 44100)); sample++; } byte[] byteData = (byte[])(Array)waveData; Marshal.Copy(byteData, 0, stream, byteData.Length); }); SDL.SDL_OpenAudio(ref audioSpec, IntPtr.Zero); SDL.SDL_PauseAudio(0); IntPtr sdlSurface, sdlTexture = IntPtr.Zero; Stopwatch frameTimer = Stopwatch.StartNew(); int ticksPer60hz = (int)(Stopwatch.Frequency * 0.016); while (running) { try { if (!cpu.WaitingForKeyPress) { cpu.Step(); } if (frameTimer.ElapsedTicks > ticksPer60hz) { while (SDL.SDL_PollEvent(out sdlEvent) != 0) { if (sdlEvent.type == SDL.SDL_EventType.SDL_QUIT) { running = false; } else if (sdlEvent.type == SDL.SDL_EventType.SDL_KEYDOWN) { var key = KeyCodeToKey((int)sdlEvent.key.keysym.sym); cpu.Keyboard |= (ushort)(1 << key); if (cpu.WaitingForKeyPress) { cpu.KeyPressed((byte)key); } } else if (sdlEvent.type == SDL.SDL_EventType.SDL_KEYUP) { var key = KeyCodeToKey((int)sdlEvent.key.keysym.sym); cpu.Keyboard &= (ushort)~(1 << key); } } var displayHandle = GCHandle.Alloc(cpu.Display, GCHandleType.Pinned); if (sdlTexture != IntPtr.Zero) { SDL.SDL_DestroyTexture(sdlTexture); } sdlSurface = SDL.SDL_CreateRGBSurfaceFrom(displayHandle.AddrOfPinnedObject(), 64, 32, 32, 64 * 4, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); sdlTexture = SDL.SDL_CreateTextureFromSurface(renderer, sdlSurface); displayHandle.Free(); SDL.SDL_RenderClear(renderer); SDL.SDL_RenderCopy(renderer, sdlTexture, IntPtr.Zero, IntPtr.Zero); SDL.SDL_RenderPresent(renderer); frameTimer.Restart(); } Thread.Sleep(1); } catch (Exception e) { Console.WriteLine(e.Message); } } SDL.SDL_DestroyRenderer(renderer); SDL.SDL_DestroyWindow(window); }