/// <summary> /// Skips the next instruction (two bytes) if V[x] != V[y]. /// </summary> void SkipIfXNotEqualY(OpCodeData data) { if (V[data.X] != V[data.Y]) { PC += 2; } }
/// <summary> /// Skips the next instruction (two bytes) if V[x] == nn. /// </summary> void SkipIfXEqual(OpCodeData data) { if (V[data.X] == data.NN) { PC += 2; } }
/// <summary> /// Skips the next instruction (two bytes) if V[x] != nn. /// </summary> void SkipIfXNotEqual(OpCodeData data) { if (V[data.X] != data.NN) { PC += 2; } }
// Misc has its own dictionary because it's full of random stuff. void Misc(OpCodeData data) { if (opCodesMisc.ContainsKey(data.NN)) { opCodesMisc[data.NN](data); } }
/// <summary> /// Draws an n-byte sprite from register I at V[x], V[y]. Sets V[0xF] if it collides. /// </summary> void DrawSprite(OpCodeData data) { var startX = V[data.X]; var startY = V[data.Y]; Debug.WriteLine(string.Format("Drawing {0}-line sprite from {1} at {2}, {3}", data.N, I, startX, startY)); V[0xF] = 0; for (var i = 0; i < data.N; i++) { var spriteLine = RAM[I + i]; // A line of the sprite to render for (var bit = 0; bit < 8; bit++) { var x = (startX + bit) % ScreenWidth; var y = (startY + i) % ScreenHeight; var spriteBit = ((spriteLine >> (7 - bit)) & 1); var oldBit = buffer[x, y] ? 1 : 0; // New bit is XOR of existing and new. var newBit = oldBit ^ spriteBit; buffer[x, y] = newBit != 0; // If we wiped out a pixel, set flag for collission. if (oldBit != 0 && newBit == 0) { V[0xF] = 1; } } } }
/// <summary> /// Saves all registers to the address in register I. /// </summary> void SaveX(OpCodeData data) { for (var i = 0; i <= data.X; i++) { RAM[I + i] = V[i]; } }
/// <summary> /// Loads all registers from the address in register I. /// </summary> void LoadX(OpCodeData data) { for (var i = 0; i <= data.X; i++) { V[i] = RAM[I + i]; } }
/// <summary> /// Skips the next instruction based on the key at V[x] being pressed/not pressed. /// </summary> void SkipOnKey(OpCodeData data) { if ( (data.NN == 0x9E && pressedKeys.Contains(V[data.X])) || // 9E = IfKeyPressed (data.NN == 0xA1 && !pressedKeys.Contains(V[data.X])) // A1 = IfKeyNotPressed ) { PC += 2; } }
/// <summary> /// Waits for a key to be pressed by looping at the current instruction. /// </summary> void WaitForKey(OpCodeData data) { // If we have a key pressed, store it and more on. if (pressedKeys.Count != 0) { V[data.X] = pressedKeys.First(); } else { // Otherwise, wind the PC back so we will keep executing this instruction. PC -= 2; } }
// http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#3.1 /// <summary> /// Handles 0x0... which either clears the screen or returns from a subroutine. /// </summary> void ClearOrReturn(OpCodeData data) { if (data.NN == 0xE0) { for (var x = 0; x < ScreenWidth; x++) { for (var y = 0; y < ScreenHeight; y++) { buffer[x, y] = false; } } } else if (data.NN == 0xEE) { PC = Pop(); } }
/// <summary> /// Sets V[x] to V[y]. /// </summary> void Arithmetic(OpCodeData data) { switch (data.N) { case 0x0: V[data.X] = V[data.Y]; break; case 0x1: V[data.X] |= V[data.Y]; break; case 0x2: V[data.X] &= V[data.Y]; break; case 0x3: V[data.X] ^= V[data.Y]; break; case 0x4: V[0xF] = (byte)(V[data.X] + V[data.Y] > 0xFF ? 1 : 0); // Set flag if we overflowed. V[data.X] += V[data.Y]; break; case 0x5: V[0xF] = (byte)(V[data.X] > V[data.Y] ? 1 : 0); // Set flag if we underflowed. V[data.X] -= V[data.Y]; break; case 0x6: V[0xF] = (byte)((V[data.X] & 0x1) != 0 ? 1 : 0); // Set flag if we shifted a 1 off the end. V[data.X] /= 2; // Shift right. break; case 0x7: // Note: This is Y-X, 5 was X-Y. V[0xF] = (byte)(V[data.Y] > V[data.X] ? 1 : 0); // Set flag if we underflowed. V[data.Y] -= V[data.X]; break; case 0xE: V[0xF] = (byte)((V[data.X] & 0xF) != 0 ? 1 : 0); // Set flag if we shifted a 1 off the end. V[data.X] *= 2; // Shift left. break; } }
public void Tick() { // Read the two bytes of OpCode (big endian). var opCode = (ushort)(RAM[PC++] << 8 | RAM[PC++]); Debug.WriteLine((PC - 2).ToString("X4") + ": " + opCode.ToString("X4")); // Split data into the possible formats the instruction might need. // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table var op = new OpCodeData() { OpCode = opCode, NNN = (ushort)(opCode & 0x0FFF), NN = (byte)(opCode & 0x00FF), N = (byte)(opCode & 0x000F), X = (byte)((opCode & 0x0F00) >> 8), Y = (byte)((opCode & 0x00F0) >> 4), }; // Loop up the OpCode using the first nibble and execute. opCodes[(byte)(opCode >> 12)](op); }
/// <summary> /// Takes the decimal representation of V[x] and puts each character into memory locations /// starting at I (with a maximum of 3). /// </summary> void BinaryCodedDecimal(OpCodeData data) { RAM[I + 0] = (byte)((V[data.X] / 100) % 10); RAM[I + 1] = (byte)((V[data.X] / 10) % 10); RAM[I + 2] = (byte)(V[data.X] % 10); }
/// <summary> /// Sets I to the correct location of the font sprite V[x]. /// Each font sprite is 5 bytes long. /// </summary> void SetIForChar(OpCodeData data) { Debug.WriteLine(string.Format("Setting I to {0} to render a {1}", V[data.X] * 5, V[data.X].ToString("X"))); I = (ushort)(V[data.X] * 5); // 0 is at 0x0, 1 is at 0x5, ... }
/// <summary> /// Adds V[x] to register I. /// </summary> void AddXToI(OpCodeData data) { I += V[data.X]; }
/// <summary> /// Play sound for V[x] 60ths of a second. /// </summary> void SetSound(OpCodeData data) { beep((int)(V[data.X] * (1000f / 60))); }
/// <summary> /// Sets the delay register to V[x]. /// </summary> void SetDelay(OpCodeData data) { Delay = V[data.X]; }
/// <summary> /// Sets V[x] to equal the Delay register. /// </summary> void SetXToDelay(OpCodeData data) { V[data.X] = Delay; }
/// <summary> /// Jumps to location nnn (not a subroutine, so old PC is not pushed to the stack). /// </summary> void Jump(OpCodeData data) { PC = data.NNN; }
/// <summary> /// Jumps to location nnn + v[0] (not a subroutine, so old PC is not pushed to the stack). /// </summary> void JumpWithOffset(OpCodeData data) { PC = (ushort)(data.NNN + V[0]); }
/// <summary> /// Jumps to subroutine nnn (unlike Jump, this pushes the previous PC to the stack to allow return). /// </summary> void CallSubroutine(OpCodeData data) { Push(PC); PC = data.NNN; }
/// <summary> /// ANDs a random number with nn and stores in V[x]. /// </summary> void Rnd(OpCodeData data) { V[data.X] = (byte)(rnd.Next(0, 256) & data.NN); }
/// <summary> /// Sets V[x] == nn. /// </summary> void SetX(OpCodeData data) { V[data.X] = data.NN; }
/// <summary> /// Adds nn to V[x]. /// </summary> void AddX(OpCodeData data) { V[data.X] += data.NN; // TODO: Do we need to handle overflow? }
/// <summary> /// Sets the I register. /// </summary> void SetI(OpCodeData data) { I = data.NNN; }
/// <summary> /// Draws an n-byte sprite from register I at V[x], V[y]. Sets V[0xF] if it collides. /// </summary> void DrawSprite(OpCodeData data) { var startX = V[data.X]; var startY = V[data.Y]; //Debug.WriteLine(string.Format("Drawing {0}-line sprite from {1} at {2}, {3}", data.N, I, startX, startY)); // Write any pending clears for (var x = 0; x < ScreenWidth; x++) { for (var y = 0; y < ScreenHeight; y++) { if (pendingClearBuffer[x, y]) { if (buffer[x, y]) { needsRedraw = true; } pendingClearBuffer[x, y] = false; buffer[x, y] = false; } } } V[0xF] = 0; for (var i = 0; i < data.N; i++) { var spriteLine = RAM[I + i]; // A line of the sprite to render for (var bit = 0; bit < 8; bit++) { var x = (startX + bit) % ScreenWidth; var y = (startY + i) % ScreenHeight; var spriteBit = ((spriteLine >> (7 - bit)) & 1); var oldBit = buffer[x, y] ? 1 : 0; if (oldBit != spriteBit) { needsRedraw = true; } // New bit is XOR of existing and new. var newBit = oldBit ^ spriteBit; if (newBit != 0) { buffer[x, y] = true; } else // Otherwise write a pending clear { pendingClearBuffer[x, y] = true; } // If we wiped out a pixel, set flag for collission. if (oldBit != 0 && newBit == 0) { V[0xF] = 1; } } } }