/* * Chip8 emulator * * Copyright (C) 2022 rick G. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ const FONT: [u8; 80] = [ 0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0, 0xF0, 0x10, 0xF0, 0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0, 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0, 0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0, 0xF0, 0x80, 0x80, 0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80, 0xF0, 0x80, 0xF0, 0xF0, 0x80, 0xF0, 0x80, 0x80 ]; pub const HEIGHT: usize = 32; pub const WIDTH: usize = 64; extern crate sdl2; extern crate rand; pub use sdl2::event::Event; pub use sdl2::EventPump; pub use sdl2::keyboard::Keycode; pub use std::fs::File; pub use std::path::Path; pub use std::io; pub use std::io::Read; pub struct Machine { memory: [u8; 4096], pub key_inputs: [u8; 16], pub display_buffer: [[i32; WIDTH]; HEIGHT], gpio: [u8; 16], index: u16, pc: usize, stack: Vec, dt: u8, st: u8, } impl Machine { /// Initialise une machine en mettant tout à 0 (RAM, PC...) pub fn new() -> Self { Self { memory: [0; 4096], key_inputs: [0; 16], display_buffer: [[0; WIDTH]; HEIGHT], gpio: [0; 16], index: 0, pc: 0, stack: Vec::new(), dt: 0, st: 0, } } pub fn init(&mut self) { for i in 0..FONT.len() { self.memory[i] = FONT[i]; } self.pc = 0x200; } /// charge une ROM en mémoire //pub fn load_rom(&mut self, p: &Path) -> Result { pub fn load_rom(&mut self, p: &Path) -> Result<(), io::Error> { let mut f = File::open(p)?; let mut buffer = Vec::new(); f.read_to_end(&mut buffer)?; for i in 0..buffer.len() { self.memory[i + 0x200] = buffer[i]; } Ok(()) //Ok(String::from(p.to_str().unwrap())) } pub fn cycle(&mut self, events: &mut EventPump) { let opcode = ((self.memory[self.pc] as u16) << 8) + (self.memory[self.pc + 1] as u16); let vx: usize = ((opcode & 0x0f00) >> 8) as usize; let vy: usize = ((opcode & 0x00f0) >> 4) as usize; let k: u8 = (opcode & 0x00ff) as u8; self.pc += 2; if self.dt > 0 { self.dt -= 1; } if self.st > 0 { self.st -= 1; if self.st == 0 { println!("BUZZZ!!"); } } match (opcode & 0xf000) >> 12 { 0 => match opcode { 0x00E0 => self.display_buffer = [[0; 64]; 32], 0x00EE => self.pc = self.stack.pop().unwrap() as usize, _ => println!("SYS"), } 1 => self.pc = (opcode & 0x0fff) as usize, 2 => { self.stack.push(self.pc as u16); self.pc = (opcode & 0x0fff) as usize }, 3 => if self.gpio[vx] == k { self.pc += 2; }, 4 => if self.gpio[vx] != k { self.pc += 2; }, 5 => if self.gpio[vx] == self.gpio[vy] { self.pc += 2; } 6 => self.gpio[vx] = k, 7 => self.gpio[vx] = self.gpio[vx].saturating_add(k), 8 => { match opcode & 0x000f { 0 => self.gpio[vx] = self.gpio[vy], 1 => self.gpio[vx] |= self.gpio[vy], 2 => self.gpio[vx] &= self.gpio[vy], 3 => self.gpio[vx] ^= self.gpio[vy], 4 => { /* let tx = self.gpio[vx] as u16; let ty = self.gpio[vy] as u16; let result = tx + ty; self.gpio[vx] = result; self */ self.gpio[vx] = self.gpio[vx].saturating_add(self.gpio[vy]); let (_, overflow) = self.gpio[vx].overflowing_add(self.gpio[vy]); self.gpio[0x0f] = if overflow { 1 } else { 0 }; }, 5 => { self.gpio[0x0f] = if self.gpio[vx] > self.gpio[vy] { 1 } else { 0 }; //let (t, _) = self.gpio[vx].overflowing_sub(self.gpio[vy]); //self.gpio[vx] = t; self.gpio[vx] = self.gpio[vx].wrapping_sub(self.gpio[vy]); }, 6 => { self.gpio[0x0f] = self.gpio[vx] & 0x01; self.gpio[vx] >>= 1; }, 7 => { self.gpio[0x0f] = if self.gpio[vx] < self.gpio[vy] { 1 } else { 0 }; self.gpio[vx] = self.gpio[vy].wrapping_sub(self.gpio[vx]); }, 0xE => { if self.gpio[vx].leading_ones() > 0 { self.gpio[0x0f] = 1; } else { self.gpio[0x0f] = 0; } self.gpio[vx] <<= 1; }, _ => println!("error 8") } } 9 => if self.gpio[vx] != self.gpio[vy] { self.pc += 2; }, 0xA => self.index = opcode & 0x0fff, 0xB => self.pc = ((self.gpio[0]) as u16 + (opcode & 0x0fff)) as usize, 0xC => self.gpio[vx] = rand::random::() & k, 0xD => { let x = self.gpio[vx] as u16; let y = self.gpio[vy] as u16; for i in 0..(opcode & 0x000f) { //println!("{:?}", self.memory); let mut sprite = self.memory[(self.index + i as u16) as usize]; for j in 0..8 { let mut case = 0; if sprite.leading_ones() > 0 { case = 1; } //println!("I :: {}", self.index + i); //println!("D :: {} - {}", sprite, case); sprite = sprite << 1; let ty: usize = (i + y) as usize % HEIGHT; let tx: usize = (j + x) as usize % WIDTH; let tmp = self.display_buffer[ty][tx]; self.display_buffer[ty][tx] ^= case; if self.display_buffer[ty][tx] == 0 && tmp == 1 { self.gpio[0xf] = 1; } } } }, 0xE => { match opcode & 0x00ff { 0x9E => { if self.key_inputs[self.gpio[vx] as usize] == 1 { self.pc += 2; } }, 0xA1 => { if self.key_inputs[self.gpio[vx] as usize] == 0 { self.pc += 2; } }, _ => println!("error E") } } 0xF => { match opcode & 0x00ff { 0x07 => self.gpio[vx] = self.dt, 0x0A => self.check_touch(vx, events), 0x15 => self.dt = self.gpio[vx], 0x18 => self.st = self.gpio[vx], 0x1E => self.index += self.gpio[vx] as u16, 0x29 => self.index = (self.gpio[vx] as u16) * 5, 0x33 => { self.memory[self.index as usize] = self.gpio[vx] / 100 as u8; self.memory[(self.index + 1) as usize] = (self.gpio[vx] % 100) /10 as u8; self.memory[(self.index + 2) as usize] = self.gpio[vx] % 10 as u8; }, 0x55 => { for i in 0..(vx + 1) { self.memory[(self.index as usize) + i] = self.gpio[i]; } }, 0x65 => { for i in 0..(vx + 1) { self.gpio[i] = self.memory[(self.index as usize) + i]; } }, _ => println!("error F") } } _ => println!("error") } } fn check_touch(&mut self, vx: usize, events: &mut EventPump) { let touch = events.wait_event(); match touch { Event::KeyDown { keycode: Some(Keycode::Num0), .. } => { self.gpio[vx] = 0; }, Event::KeyDown { keycode: Some(Keycode::Num1), .. } => { self.gpio[vx] = 1; }, Event::KeyDown { keycode: Some(Keycode::Num2), .. } => { self.gpio[vx] = 2; }, Event::KeyDown { keycode: Some(Keycode::Num3), .. } => { self.gpio[vx] = 3; }, Event::KeyDown { keycode: Some(Keycode::Num4), .. } => { self.gpio[vx] = 4; }, Event::KeyDown { keycode: Some(Keycode::Num5), .. } => { self.gpio[vx] = 5; }, Event::KeyDown { keycode: Some(Keycode::Num6), .. } => { self.gpio[vx] = 6; }, Event::KeyDown { keycode: Some(Keycode::Num7), .. } => { self.gpio[vx] = 7; }, Event::KeyDown { keycode: Some(Keycode::Num8), .. } => { self.gpio[vx] = 8; }, Event::KeyDown { keycode: Some(Keycode::Num9), .. } => { self.gpio[vx] = 9; }, Event::KeyDown { keycode: Some(Keycode::A), .. } => { self.gpio[vx] = 0xa; }, Event::KeyDown { keycode: Some(Keycode::B), .. } => { self.gpio[vx] = 0xb; }, Event::KeyDown { keycode: Some(Keycode::C), .. } => { self.gpio[vx] = 0xc; }, Event::KeyDown { keycode: Some(Keycode::D), .. } => { self.gpio[vx] = 0xd; }, Event::KeyDown { keycode: Some(Keycode::E), .. } => { self.gpio[vx] = 0xe; }, Event::KeyDown { keycode: Some(Keycode::F), .. } => { self.gpio[vx] = 0xf; }, _ => self.check_touch(vx, events), } } }