diff --git a/src/interpreter/chip8.rs b/src/interpreter/chip8.rs index 39477ca..46e3ea0 100644 --- a/src/interpreter/chip8.rs +++ b/src/interpreter/chip8.rs @@ -75,7 +75,7 @@ where 'cpu: loop { let next_instruction = self.next_instruction()?; - self.execute_instruction(next_instruction); + self.execute_instruction(next_instruction)?; self.window .update_buffer(&self.display, DISPLAY_WIDTH, DISPLAY_HEIGHT); @@ -136,67 +136,79 @@ where Instruction::new(opcode).ok_or(anyhow!("Cannot decode opcode {:#06x}", opcode)) } - fn execute_instruction(&mut self, instruction: Instruction) { + fn execute_instruction(&mut self, instruction: Instruction) -> Result<()> { use Instruction::*; match instruction { - ClearScreen => self.display.fill(0), - Jump { address } => self.pc = address, + ClearScreen => Ok(self.display.fill(0)), + Jump { address } => Ok(self.pc = address), JumpOffset { address, offset_register: _, } => { - self.pc = address; + self.pc = address + self.variables[0] as usize; + Ok(()) } - SetLiteral { dest, value } => self.variables[dest] = value, + SetLiteral { dest, value } => Ok(self.variables[dest] = value), AddLiteral { dest, value } => { - self.variables[dest] = self.variables[dest].wrapping_add(value) + self.variables[dest] = self.variables[dest].wrapping_add(value); + Ok(()) } - SetIndex { src } => self.index = src, - SetIndexFont { src } => { + SetIndex { src } => Ok(self.index = src), + SetIndexFont { src, big: _ } => { let character = (self.variables[src] & 0x0F) as usize; self.index = FONT_ADDRESS + 5 * character; + Ok(()) } AddIndex { src } => { let (res, overflow) = self.index.overflowing_add(self.variables[src] as usize); self.index = res; self.variables[0xF] = overflow as u8; + Ok(()) } Call { address } => { self.stack.push(self.pc); self.pc = address; + Ok(()) } Return => { self.pc = self.stack.pop().expect("Don't pop out of main"); + Ok(()) } SkipEq { x, y } => { if self.variables[x] == self.variables[y] { self.pc += 2; } + Ok(()) } SkipNotEq { x, y } => { if self.variables[x] != self.variables[y] { self.pc += 2; } + Ok(()) } SkipEqLiteral { x, value } => { if self.variables[x] == value { self.pc += 2; } + Ok(()) } SkipNotEqLiteral { x, value } => { if self.variables[x] != value { self.pc += 2; } + Ok(()) } SkipIfKey { key_register } => { if self.window.is_key_down(self.variables[key_register]) { self.pc += 2; } + Ok(()) } SkipIfNotKey { key_register } => { if !self.window.is_key_down(self.variables[key_register]) { self.pc += 2; } + Ok(()) } GetKey { dest } => { if let Some(key) = self.window.get_key() { @@ -204,48 +216,61 @@ where } else { self.pc -= 2; } + Ok(()) } Set { dest, src } => { self.variables[dest] = self.variables[src]; + Ok(()) } Or { lhs, rhs } => { self.variables[lhs] |= self.variables[rhs]; + Ok(()) } And { lhs, rhs } => { self.variables[lhs] &= self.variables[rhs]; + Ok(()) } Xor { lhs, rhs } => { self.variables[lhs] ^= self.variables[rhs]; + Ok(()) } Add { lhs, rhs } => { let (res, overflow) = self.variables[lhs].overflowing_add(self.variables[rhs]); self.variables[lhs] = res; self.variables[0xF] = overflow as u8; + Ok(()) } Sub { lhs, rhs, dest } => { let (res, overflow) = self.variables[lhs].overflowing_sub(self.variables[rhs]); self.variables[dest] = res; self.variables[0xF] = !overflow as u8; + Ok(()) } - // TODO: Make ambiguity configurable LeftShift { lhs, rhs } => { + self.variables[lhs] = self.variables[rhs]; let flag = self.variables[lhs] >> 7; self.variables[lhs] <<= 1; self.variables[0xF] = flag; + Ok(()) } RightShift { lhs, rhs } => { + self.variables[lhs] = self.variables[rhs]; let flag = self.variables[lhs] & 1; self.variables[lhs] >>= 1; self.variables[0xF] = flag; + Ok(()) } GetDelay { dest } => { self.variables[dest] = self.delay_timer; + Ok(()) } SetDelay { src } => { self.delay_timer = self.variables[src]; + Ok(()) } SetSound { src } => { self.sound_timer = self.variables[src]; + Ok(()) } Draw { x, @@ -271,9 +296,11 @@ where self.display[buffer_index] ^= pixel; } } + Ok(()) } Random { x, mask } => { self.variables[x] = rand::random::() & mask; + Ok(()) } DecimalConversion { src } => { let mut n = self.variables[src]; @@ -282,17 +309,26 @@ where self.memory[self.index + i] = n % 10; n /= 10; } + Ok(()) } StoreMemory { registers } => { for i in 0..=registers { self.memory[self.index + i] = self.variables[i]; + // self.index = i; } + Ok(()) } LoadMemory { registers } => { for i in 0..=registers { self.variables[i] = self.memory[self.index + i]; + // self.index = i; } + Ok(()) } + _ => Err(anyhow!( + "Instruction {:?} not in Chip8 instruction set.", + instruction + )), } } } diff --git a/src/interpreter/instruction.rs b/src/interpreter/instruction.rs index 03f7674..1f0bc7d 100644 --- a/src/interpreter/instruction.rs +++ b/src/interpreter/instruction.rs @@ -1,5 +1,6 @@ #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum Instruction { + // Chip8 instructions ClearScreen, Draw { x: usize, @@ -126,6 +127,7 @@ pub enum Instruction { SetIndexFont { src: usize, + big: bool, }, DecimalConversion { @@ -144,6 +146,25 @@ pub enum Instruction { x: usize, mask: u8, }, + + // Schip extension + Hires, + Lores, + + ScrollRight, + ScrollLeft, + ScrollDown { + amount: usize, + }, + + SaveFlags { + x: usize, + }, + LoadFlags { + x: usize, + }, + + Exit, } impl Instruction { @@ -155,7 +176,17 @@ impl Instruction { 0x0000 => match opcode { 0x00E0 => Some(ClearScreen), 0x00EE => Some(Return), - _ => None, + 0x00FF => Some(Hires), + 0x00FE => Some(Lores), + 0x00FB => Some(ScrollRight), + 0x00FC => Some(ScrollLeft), + 0x00FD => Some(Exit), + _ => match opcode & 0x00F0 { + 0x00C0 => Some(ScrollDown { + amount: xyn(opcode).2 as usize, + }), + _ => None, + }, }, 0x1000 => { let address = nnn(opcode); @@ -215,11 +246,9 @@ impl Instruction { } } 0xA000 => Some(SetIndex { src: nnn(opcode) }), - - // TODO: Make this op's behaviour configurable. 0xB000 => Some(JumpOffset { address: nnn(opcode), - offset_register: 0, + offset_register: xnn(opcode).0, }), 0xC000 => { @@ -253,10 +282,13 @@ impl Instruction { 0x18 => Some(SetSound { src: x }), 0x1E => Some(AddIndex { src: x }), 0x0A => Some(GetKey { dest: x }), - 0x29 => Some(SetIndexFont { src: x }), + 0x29 => Some(SetIndexFont { src: x, big: false }), + 0x30 => Some(SetIndexFont { src: x, big: true }), 0x33 => Some(DecimalConversion { src: x }), 0x55 => Some(StoreMemory { registers: x }), 0x65 => Some(LoadMemory { registers: x }), + 0x75 => Some(SaveFlags { x }), + 0x85 => Some(LoadFlags { x }), _ => None, } } diff --git a/src/interpreter/schip.rs b/src/interpreter/schip.rs index 2ad7def..4ff9b71 100644 --- a/src/interpreter/schip.rs +++ b/src/interpreter/schip.rs @@ -4,8 +4,14 @@ use anyhow::{anyhow, Result}; use std::time::{Duration, Instant}; const PROGRAM_ADDRESS: usize = 0x200; + const FONT_ADDRESS: usize = 0x50; -const FONT: [u8; 80] = [ +const BIGFONT_ADDRESS: usize = FONT_ADDRESS + 160; + +const FONT_WIDTH: usize = 5; +const BIGFONT_WIDTH: usize = 10; + +const FONT: [u8; 240] = [ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 @@ -22,10 +28,26 @@ const FONT: [u8; 80] = [ 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80, // F + 0xff, 0xff, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xff, // 0 + 0x18, 0x78, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0xff, 0xff, // 1 + 0xff, 0xff, 0x03, 0x03, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, // 2 + 0xff, 0xff, 0x03, 0x03, 0xff, 0xff, 0x03, 0x03, 0xff, 0xff, // 3 + 0xc3, 0xc3, 0xc3, 0xc3, 0xff, 0xff, 0x03, 0x03, 0x03, 0x03, // 4 + 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, 0x03, 0x03, 0xff, 0xff, // 5 + 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, 0xc3, 0xc3, 0xff, 0xff, // 6 + 0xff, 0xff, 0x03, 0x03, 0x06, 0x0c, 0x18, 0x18, 0x18, 0x18, // 7 + 0xff, 0xff, 0xc3, 0xc3, 0xff, 0xff, 0xc3, 0xc3, 0xff, 0xff, // 8 + 0xff, 0xff, 0xc3, 0xc3, 0xff, 0xff, 0x03, 0x03, 0xff, 0xff, // 9 + 0x7e, 0xff, 0xc3, 0xc3, 0xc3, 0xff, 0xff, 0xc3, 0xc3, 0xc3, // A + 0xfc, 0xfc, 0xc3, 0xc3, 0xfc, 0xfc, 0xc3, 0xc3, 0xfc, 0xfc, // B + 0x3c, 0xff, 0xc3, 0xc0, 0xc0, 0xc0, 0xc0, 0xc3, 0xff, 0x3c, // C + 0xfc, 0xfe, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xfe, 0xfc, // D + 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, // E + 0xff, 0xff, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, // F ]; -const DISPLAY_WIDTH: usize = 64; -const DISPLAY_HEIGHT: usize = 32; +const DISPLAY_WIDTH: usize = 128; +const DISPLAY_HEIGHT: usize = 64; const DEFAULT_SPEED: u64 = 700; @@ -40,7 +62,9 @@ where delay_timer: u8, sound_timer: u8, variables: [u8; 16], + hires: bool, display: [u8; DISPLAY_WIDTH * DISPLAY_HEIGHT], + running: bool, speed: u64, window: W, } @@ -61,7 +85,7 @@ where W: Display + Input, { fn display_open(&self) -> bool { - self.window.is_open() + self.running && self.window.is_open() } fn tick(&mut self) -> Result<()> { @@ -74,8 +98,12 @@ where self.update_timers(); 'cpu: loop { - let next_instruction = self.next_instruction()?; - self.execute_instruction(next_instruction); + if let Ok(next_instruction) = self.next_instruction() { + self.execute_instruction(next_instruction); + } else { + println!("Unknown instruction"); + } + self.window .update_buffer(&self.display, DISPLAY_WIDTH, DISPLAY_HEIGHT); @@ -118,7 +146,9 @@ where delay_timer: 0, sound_timer: 0, variables: [0; 16], + hires: false, display: [0; DISPLAY_WIDTH * DISPLAY_HEIGHT], + running: true, speed, window: display, } @@ -143,18 +173,22 @@ where Jump { address } => self.pc = address, JumpOffset { address, - offset_register: _, + offset_register, } => { - self.pc = address; + self.pc = address + self.variables[offset_register] as usize; } SetLiteral { dest, value } => self.variables[dest] = value, AddLiteral { dest, value } => { self.variables[dest] = self.variables[dest].wrapping_add(value) } SetIndex { src } => self.index = src, - SetIndexFont { src } => { + SetIndexFont { src, big } => { let character = (self.variables[src] & 0x0F) as usize; - self.index = FONT_ADDRESS + 5 * character; + if big { + self.index = BIGFONT_ADDRESS + character * BIGFONT_WIDTH; + } else { + self.index = FONT_ADDRESS + character * FONT_WIDTH; + } } AddIndex { src } => { let (res, overflow) = self.index.overflowing_add(self.variables[src] as usize); @@ -227,13 +261,12 @@ where self.variables[dest] = res; self.variables[0xF] = !overflow as u8; } - // TODO: Make ambiguity configurable - LeftShift { lhs, rhs } => { + LeftShift { lhs, rhs: _ } => { let flag = self.variables[lhs] >> 7; self.variables[lhs] <<= 1; self.variables[0xF] = flag; } - RightShift { lhs, rhs } => { + RightShift { lhs, rhs: _ } => { let flag = self.variables[lhs] & 1; self.variables[lhs] >>= 1; self.variables[0xF] = flag; @@ -252,25 +285,113 @@ where y, sprite_height, } => { - let x = self.variables[x] as usize % DISPLAY_WIDTH; - let y = self.variables[y] as usize % DISPLAY_HEIGHT; - self.variables[0xF] = 0; + if self.hires { + let x = self.variables[x] as usize % DISPLAY_WIDTH; + let y = self.variables[y] as usize % DISPLAY_HEIGHT; + self.variables[0xF] = 0; - for y_offset in 0..sprite_height { - if y + y_offset >= DISPLAY_HEIGHT { - break; + if sprite_height == 0 { + for y_offset in 0..16 { + if y + y_offset >= DISPLAY_HEIGHT { + break; + } + let sprite_row = u16::from_be_bytes([ + self.memory[self.index + y_offset], + self.memory[self.index + y_offset + 1], + ]); + for x_offset in 0..16 { + if x + x_offset >= DISPLAY_WIDTH { + break; + } + let pixel = ((sprite_row >> (15 - x_offset)) & 1) as u8; + let buffer_index = (y + y_offset) * DISPLAY_WIDTH + (x + x_offset); + self.variables[0xF] |= self.display[buffer_index] & pixel; + self.display[buffer_index] ^= pixel; + } + } + } else { + for y_offset in 0..sprite_height { + if y + y_offset >= DISPLAY_HEIGHT { + break; + } + let sprite_row = self.memory[self.index + y_offset]; + for x_offset in 0..8 { + if x + x_offset >= DISPLAY_WIDTH { + break; + } + let pixel = (sprite_row >> (7 - x_offset)) & 1; + let buffer_index = (y + y_offset) * DISPLAY_WIDTH + (x + x_offset); + self.variables[0xF] |= self.display[buffer_index] & pixel; + self.display[buffer_index] ^= pixel; + } + } } - let sprite_row = self.memory[self.index + y_offset]; - for x_offset in 0..8 { - if x + x_offset >= DISPLAY_WIDTH { + } else if !self.hires { + let x = (self.variables[x] as usize * 2) % DISPLAY_WIDTH; + let y = (self.variables[y] as usize * 2) % DISPLAY_HEIGHT; + self.variables[0xF] = 0; + + for y_offset in 0..sprite_height * 2 { + if y + y_offset >= DISPLAY_HEIGHT { break; } - let pixel = (sprite_row >> (7 - x_offset)) & 1; - let buffer_index = (y + y_offset) * DISPLAY_WIDTH + (x + x_offset); - self.variables[0xF] |= self.display[buffer_index] & pixel; - self.display[buffer_index] ^= pixel; + let sprite_row = self.memory[self.index + y_offset / 2]; + for x_offset in 0..16 { + if x + x_offset >= DISPLAY_WIDTH { + break; + } + let pixel = (sprite_row >> (7 - x_offset / 2)) & 1; + let buffer_index = (y + y_offset) * DISPLAY_WIDTH + (x + x_offset); + self.variables[0xF] |= self.display[buffer_index] & pixel; + self.display[buffer_index] ^= pixel; + } + } + } + } + ScrollRight => { + let amount = match self.hires { + true => 4, + false => 2 * 4, + }; + for row in 0..DISPLAY_HEIGHT { + for col in (amount..DISPLAY_WIDTH).rev() { + self.display[row * DISPLAY_WIDTH + col] = + self.display[row * DISPLAY_WIDTH + col - amount] } } + + for row in 0..DISPLAY_HEIGHT { + for col in 0..amount { + self.display[row * DISPLAY_WIDTH + col] = 0; + } + } + } + ScrollLeft => { + let amount = match self.hires { + true => 4, + false => 2 * 4, + }; + + for row in 0..DISPLAY_HEIGHT { + for col in 0..DISPLAY_WIDTH - amount { + self.display[row * DISPLAY_WIDTH + col] = + self.display[row * DISPLAY_WIDTH + col + amount] + } + } + + for row in 0..DISPLAY_HEIGHT { + for col in (DISPLAY_WIDTH - amount)..DISPLAY_WIDTH { + self.display[row * DISPLAY_WIDTH + col] = 0; + } + } + } + ScrollDown { amount } => { + let amount = match self.hires { + true => amount, + false => 2 * amount, + }; + self.display.rotate_right(amount * DISPLAY_WIDTH); + self.display[0..amount * DISPLAY_WIDTH].fill(0); } Random { x, mask } => { self.variables[x] = rand::random::() & mask; @@ -293,6 +414,11 @@ where self.variables[i] = self.memory[self.index + i]; } } + Hires => self.hires = true, + Lores => self.hires = false, + SaveFlags { x: _ } => (), + LoadFlags { x: _ } => (), + Exit => self.running = false, } } }