chip8-emulator/src/machine.rs
rick 56237065d4
update: change graphics and rom load
ROM load by argument

Graphics are manage by main and not machine
2022-02-16 00:38:26 +01:00

323 lines
11 KiB
Rust

/*
* Chip8 emulator
*
* Copyright (C) 2022 rick G. <rick@gnous.eu>
*
* 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 <https://www.gnu.org/licenses/>.
*/
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<u16>,
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<String, io::Error> {
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::<u8>() & 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),
}
}
}