use rand::Rng; use std::fs; use std::io::Read; const w: usize = 64; const h: usize = 32; pub struct Chip8 { opcode: u16, memory: [u8; 4096], v: [u8; 16], index: u16, pc: u16, pub gfx: [u8; w * h], delay_timer: u8, sound_timer: u8, stack: [u16; 16], sp: usize, pub key: [u8; 16], pub draw_flag: bool, } const font_set: [u8; 80] = [ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 0xF0, 0x80, 0xF0, 0x80, 0x80, // F ]; impl Chip8 { pub fn new() -> Chip8 { let mut c = Chip8 { opcode: 0, memory: [0; 4096], v: [0; 16], index: 0, pc: 0x200, gfx: [0; w * h], delay_timer: 0, sound_timer: 0, stack: [0; 16], sp: 0, key: [0; 16], draw_flag: false, }; for i in 0..font_set.len() { c.memory[i] = font_set[i]; } c } pub fn load_game(&mut self, filepath: &str) { let mut f = fs::File::open(filepath).expect("can not load rom file"); let metadata = fs::metadata(filepath).expect("unable to read metadata"); let mut b = vec![0; metadata.len() as usize]; f.read(&mut b).expect("buffer overflow"); for i in 0..b.len() { self.memory[512 + i] = b[i]; } } pub fn emulate_cycle(&mut self) { self.opcode = ((self.memory[self.pc as usize] as u16) << 8) | (self.memory[(self.pc + 1) as usize] as u16); let x: usize = ((self.opcode & 0x0F00) >> 8) as usize; let y: usize = ((self.opcode & 0x00F0) >> 4) as usize; let nn: u8 = (self.opcode & 0x00FF) as u8; let nnn: u16 = (self.opcode & 0x0FFF) as u16; // println!("{:x} {:?}", self.opcode, self.pc); // Decode Opcode // https://en.wikipedia.org/wiki/CHIP-8#Opcode_table match self.opcode & 0xF000 { 0x0000 => { match self.opcode & 0x000F { 0x0000 => { // 00E0 Clear screen for i in 0..self.gfx.len() { self.gfx[i] = 0; } self.pc += 2; self.draw_flag = true; } 0x000E => { // 00EE Returns from a subroutine self.sp -= 1; self.pc = self.stack[self.sp]; self.pc += 2; } _ => println!("Unknown opcode: {:x}", self.opcode), } } 0x1000 => { // 1NNN Jumps to address NNN self.pc = nnn; } 0x2000 => { // 2NNN Calls subroutine at NNN self.stack[self.sp] = self.pc; self.sp += 1; self.pc = nnn; } 0x3000 => { // 3XNN Skips the next instruction if VX equals NN. (Usually // the next instruction is a jump to skip a code block) if self.v[x] == nn { self.pc += 2; } self.pc += 2; } 0x4000 => { // 4XNN Skips the next instruction if VX doesn't equal NN. // (Usually the next instruction is a jump to skip a code // block) if self.v[x] != nn { self.pc += 2; } self.pc += 2; } 0x5000 => { // 5XY0 Skips the next instruction if VX equals VY. (Usually // the next instruction is a jump to skip a code block) if self.v[x] == self.v[y] { self.pc += 2; } self.pc += 2; } 0x6000 => { // 6XNN Sets VX to NN self.v[x] = nn; self.pc += 2; } 0x7000 => { // 7XNN Adds NN to VX. (Carry flag is not changed) self.v[x] = self.v[x].wrapping_add(nn); self.pc += 2; } 0x8000 => { match self.opcode & 0x000F { 0x0000 => { // 0x8XY0 Sets VX to the value of VY self.v[x] = self.v[y]; self.pc += 2; } 0x0001 => { // 0x8XY1 Sets VX to VX or VY. (Bitwise OR operation) self.v[x] = (self.v[x] | self.v[y]); self.pc += 2; } 0x0002 => { // 0x8XY2 Sets VX to VX and VY. (Bitwise AND operation) self.v[x] = (self.v[x] & self.v[y]); self.pc += 2; } 0x0003 => { // 0x8XY3 Sets VX to VX xor VY self.v[x] = (self.v[x] ^ self.v[y]); self.pc += 2; } 0x0004 => { // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a // carry, and to 0 when there isn't if self.v[y] > (0xFF - self.v[x]) { self.v[0xF] = 1; } else { self.v[0xF] = 0; } self.v[x] = self.v[x].wrapping_add(self.v[y]); self.pc += 2; } 0x0005 => { // 0x8XY5 VY is subtracted from VX. VF is set to 0 when // there's a borrow, and 1 when there isn't if self.v[x] > self.v[y] { self.v[0xF] = 1; } else { self.v[0xF] = 0; } self.v[x] = self.v[x].wrapping_sub(self.v[y]); self.pc += 2; } 0x0006 => { // 0x8XY6 Stores the least significant bit of VX in VF // and then shifts VX to the right by 1 if self.opcode & 0x1 >= 1 { self.v[0xF] = 1; } else { self.v[0xF] = 0; } self.v[x] = self.v[x].wrapping_shr(1); self.pc += 2; } 0x0007 => { // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when // there's a borrow, and 1 when there isn't if self.v[y] > self.v[x] { self.v[0xF] = 1; } else { self.v[0xF] = 0; } self.v[x] = self.v[y].wrapping_sub(self.v[x]); self.pc += 2; } 0x000E => { // 0x8XYE Stores the most significant bit of VX in VF // and then shifts VX to the left by 1 if self.opcode & 0x80 == 0x80 { self.v[0xF] = 1; } else { self.v[0xF] = 0; } self.v[x] = self.v[x].wrapping_shl(1); self.pc += 2; } _ => println!("Unknown opcode: {:x}", self.opcode), } } 0x9000 => { // 9XY0 Skips the next instruction if VX doesn't equal VY. // (Usually the next instruction is a jump to skip a code // block) if self.v[x] != self.v[y] { self.pc += 2; } self.pc += 2; } 0xA000 => { // ANNN set index to NNN position self.index = nnn; self.pc += 2; } 0xB000 => { // BNNN Jumps to the address NNN plus V0 self.pc = nnn + self.v[0] as u16; // self.pc += 2; } 0xC000 => { // CXNN Sets VX to the result of a bitwise and operation on a // random number (Typically: 0 to 255) and NN let mut rng = rand::thread_rng(); let r: u8 = rng.gen_range(0, 255); self.v[x] = r & nn; self.pc += 2; } 0xD000 => { // DXYN Draws a sprite at coordinate (VX, VY) that has a width // of 8 pixels and a height of N+1 pixels. Each row of 8 pixels // is read as bit-coded starting from memory location I; I // value doesn’t change after the execution of this // instruction. As described above, VF is set to 1 if any // screen pixels are flipped from set to unset when the sprite // is drawn, and to 0 if that doesn’t happen let heigh = self.opcode & 0x000F; let mut pixel: u8; self.v[0xF] = 0; for yline in 0..heigh { pixel = self.memory[(self.index + yline) as usize]; for xline in 0..8 { if (pixel & (0x80 >> xline)) != 0 { let pos = (self.v[x] as u16 + xline) as usize + (self.v[y] as u16 + yline) as usize * w; if pos >= 2048 { break; } if self.gfx[pos] == 1 { self.v[0xF] = 1; } self.gfx[pos] ^= 1; } } } self.draw_flag = true; self.pc += 2; } 0xE000 => { match self.opcode & 0x00FF { 0x009E => { // EX9E Skips the next instruction if the key stored in // VX is pressed. (Usually the next instruction is a // jump to skip a code block) if self.key[self.v[x] as usize] != 0 { self.pc += 2; } self.pc += 2; } 0x00A1 => { // EXA1 Skips the next instruction if the key stored in // VX isn't pressed. (Usually the next instruction is a // jump to skip a code block) if self.key[self.v[x] as usize] != 1 { self.pc += 2; } self.pc += 2; } _ => println!("Unknown opcode: {:x}", self.opcode), } } 0xF000 => { match self.opcode & 0x00FF { 0x0007 => { // FX07 Sets VX to the value of the delay timer self.v[x] = self.delay_timer; self.pc += 2; } 0x000A => { // FX0A A key press is awaited, and then stored in VX. // (Blocking Operation. All instruction halted until // next key event) let mut pressed: bool = false; for i in 0..16 { if self.key[i] == 1 { self.v[x] = i as u8; pressed = true; } } if pressed { self.pc += 2; } } 0x0015 => { // FX15 Sets the delay timer to VX self.delay_timer = self.v[x]; self.pc += 2; } 0x0018 => { // FX18 Sets the sound timer to VX self.sound_timer = self.v[x]; self.pc += 2; } 0x001E => { // FX1E Adds VX to I. VF is not affected self.index += self.v[x] as u16; self.pc += 2; } 0x0029 => { // FX29 Sets I to the location of the sprite for the // character in VX. Characters 0-F (in hexadecimal) are // represented by a 4x5 font self.index = self.v[x] as u16 * 5; self.pc += 2; } 0x0033 => { self.memory[self.index as usize] = self.v[x] / 100; self.memory[self.index as usize + 1] = (self.v[x] / 10) % 10; self.memory[self.index as usize + 2] = (self.v[x] / 100) % 10; self.pc += 2; } 0x0055 => { // FX55 Stores V0 to VX (including VX) in memory // starting at address I. The offset from I is // increased by 1 for each value written, but I itself // is left unmodified for i in 0..(x + 1) { self.memory[self.index as usize + i] = self.v[i]; } self.pc += 2; } 0x0065 => { // 0xFX65 Fills V0 to VX (including VX) with values // from memory starting at address I. The offset from I // is increased by 1 for each value written, but I // itself is left unmodified for i in 0..(x + 1) { self.v[i] = self.memory[self.index as usize + i]; } self.pc += 2; } _ => println!("Unknown opcode: {:x}", self.opcode), } } _ => println!("Unknown opcode: {:x}", self.opcode), } if self.delay_timer > 0 { self.delay_timer -= 1; } if self.sound_timer > 0 { if self.sound_timer == 1 { println!("Beep"); } self.sound_timer -= 1; } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_load_game() { let mut c = Chip8::new(); c.load_game("Cargo.toml"); c.emulate_cycle(); } }