use std::{
sync::{
Arc,
atomic::{AtomicU16, Ordering},
},
time::{Duration, SystemTime, UNIX_EPOCH},
};
use crate::{
ApplicationError, BASE_SCREEN_HEIGHT, BASE_SCREEN_WIDTH, Draw, ReadInputState,
emulator::{
fonts::{BIG_FONT, BIG_FONT_ADDR, FONT, FONT_ADDR},
instructions::{Instruction, KeyStateToCheck},
logical_operator::{Direction, LogicalOperator},
quirks::{QuirksFields, QuirksMode},
sub_commands::{FontVariant, SubCommand},
},
twister_rand::MarsenneTwister32,
utils::u8_to_arr,
};
#[derive(Debug, Clone)]
pub struct Emulator<T: Draw, P: ReadInputState> {
memory: [u8; 0x1000],
stack: Vec<usize>,
variable_registers: [u8; 16],
screen_buffer: Vec<u8>,
index_register: usize,
program_counter: usize,
delay_timer: Arc<AtomicU16>,
sound_timer: Arc<AtomicU16>,
pub drawer: T,
pub input_provider: P,
max_draw_delay: Duration,
last_draw: SystemTime,
quirks: QuirksFields,
screen_width: usize,
screen_height: usize,
rng: MarsenneTwister32,
}
impl<T: Draw, P: ReadInputState> Emulator<T, P> {
pub fn init(program: Vec<u8>, drawer: T, input_provider: P) -> Result<Self, ApplicationError> {
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Now is always after UNIX Epoch");
let mut emulator = Self {
memory: [0; 0x1000],
stack: vec![],
variable_registers: [0; 16],
screen_buffer: vec![0; BASE_SCREEN_WIDTH * BASE_SCREEN_HEIGHT],
index_register: 0,
program_counter: 0x200,
delay_timer: Arc::new(AtomicU16::new(0)),
sound_timer: Arc::new(AtomicU16::new(0)),
drawer,
input_provider,
max_draw_delay: Duration::from_millis(7),
last_draw: SystemTime::now(),
quirks: QuirksMode::Chip8.into(),
screen_width: 64,
screen_height: 32,
rng: MarsenneTwister32::new((seed.as_micros() % u32::MAX as u128) as u32),
};
emulator.set_mem_block(&FONT, FONT_ADDR)?;
emulator.set_mem_block(&BIG_FONT, BIG_FONT_ADDR)?;
emulator.set_mem_block(&program, 0x200)?;
let delay_timer = emulator.delay_timer.clone();
let sound_timer = emulator.sound_timer.clone();
std::thread::spawn(move || {
loop {
let old_val = delay_timer.load(Ordering::Relaxed);
if old_val > 0 {
delay_timer.store(old_val - 1, Ordering::Relaxed);
}
let old_val = sound_timer.load(Ordering::Relaxed);
if old_val > 0 {
sound_timer.store(old_val - 1, Ordering::Relaxed);
}
std::thread::sleep(Duration::from_millis(6));
}
});
Ok(emulator)
}
pub fn run_blocking(&mut self) {
loop {
let instruction = self.fetch();
self.execute(instruction);
}
}
pub fn reset(&mut self, program: Vec<u8>) -> Result<(), ApplicationError> {
self.memory = [0; 0x1000];
self.stack = vec![];
self.variable_registers = [0; 16];
self.screen_buffer = vec![0; BASE_SCREEN_WIDTH * BASE_SCREEN_HEIGHT];
self.index_register = 0;
self.program_counter = 0x200;
self.max_draw_delay = Duration::from_millis(7);
self.last_draw = SystemTime::now();
self.quirks = QuirksMode::Chip8.into();
self.screen_width = 64;
self.screen_height = 32;
self.set_mem_block(&FONT, FONT_ADDR)?;
self.set_mem_block(&BIG_FONT, BIG_FONT_ADDR)?;
self.set_mem_block(&program, 0x200)?;
Ok(())
}
pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) -> &mut Self {
self.quirks = quirks_mode.into();
self
}
pub fn with_custom_quirks(&mut self, quirks: QuirksFields) -> &mut Self {
self.quirks = quirks;
self
}
pub fn set_max_draw_delay(&mut self, rate: Duration) -> &mut Self {
self.max_draw_delay = rate;
self
}
fn fetch(&mut self) -> Instruction {
let tophalf = self.memory[self.program_counter];
let bothalf = self.memory[self.program_counter + 1];
self.program_counter = (self.program_counter + 2) % self.memory.len();
(((tophalf as u16) << 8) | bothalf as u16).into()
}
fn set_high_res(&mut self) {
self.screen_width = BASE_SCREEN_WIDTH << 1;
self.screen_height = BASE_SCREEN_HEIGHT << 1;
}
fn set_low_res(&mut self) {
self.screen_width = BASE_SCREEN_WIDTH;
self.screen_height = BASE_SCREEN_HEIGHT;
}
fn execute(&mut self, instruction: Instruction) {
match instruction {
Instruction::ClearScreen => {
self.clear_screen();
}
Instruction::Draw {
x_register,
y_register,
height,
} => {
self.draw(x_register, y_register, height);
}
Instruction::Jump(address) => self.program_counter = address as usize,
Instruction::SetIndexRegister(address) => self.index_register = address as usize,
Instruction::SetGeneralRegister { register, value } => {
self.variable_registers[register] = value
}
Instruction::AddToRegister { register, value } => {
let val: u16 = value as u16 + self.variable_registers[register] as u16;
self.variable_registers[register] = (val & 0xFF) as u8;
}
Instruction::SkipEqValueWithRegisterContents { register, value } => {
let vx_value = self.variable_registers[register];
if vx_value == value {
self.program_counter += 2;
}
}
Instruction::SkipNotEqValueWithRegisterContents { register, value } => {
let vx_value = self.variable_registers[register];
if vx_value != value {
self.program_counter += 2;
}
}
Instruction::SkipEqRegisters {
register_x,
register_y,
} => {
let vx_value = self.variable_registers[register_x];
let vy_value = self.variable_registers[register_y];
if vx_value == vy_value {
self.program_counter += 2;
}
}
Instruction::SkipNotEqRegisters {
register_x,
register_y,
} => {
let vx_value = self.variable_registers[register_x];
let vy_value = self.variable_registers[register_y];
if vx_value != vy_value {
self.program_counter += 2;
}
}
Instruction::LogicalOperator {
operator,
register_x,
register_y,
} => {
self.perform_logical_operator(register_x, register_y, operator);
}
Instruction::JumpWithOffset {
register_x,
address,
} => {
if self.quirks.jumping {
self.program_counter = address + self.variable_registers[register_x] as usize;
} else {
self.program_counter = address + self.variable_registers[0] as usize;
}
}
Instruction::Random {
register_x,
val_to_and,
} => {
let randint = (self.rng.generate() % 255) as u8;
self.variable_registers[register_x] = randint & val_to_and;
}
Instruction::Return => {
if let Some(val) = self.stack.pop() {
self.program_counter = val;
};
}
Instruction::Call(memory_addr) => {
self.stack.push(self.program_counter);
self.program_counter = memory_addr;
}
Instruction::SubCommand { register, command } => {
self.perform_sub_command(command, register)
}
Instruction::SkipIfKey {
register,
state_to_check,
} => {
self.skip_if_key(state_to_check, register);
}
Instruction::SetHiRes => {
self.set_high_res();
self.screen_buffer
.resize(self.screen_width * self.screen_height, 0);
self.wait_to_display();
self.drawer
.draw_buffer(&self.screen_buffer, self.screen_width, self.screen_height);
}
Instruction::SetLoRes => {
self.set_low_res();
self.screen_buffer
.resize(self.screen_width * self.screen_height, 0);
self.wait_to_display();
self.drawer
.draw_buffer(&self.screen_buffer, self.screen_width, self.screen_height);
}
Instruction::ScrollDown { amount } => {
let px_to_shift = self.screen_width * amount as usize;
let mut new_buffer = vec![0; px_to_shift];
new_buffer.extend(&self.screen_buffer);
new_buffer.resize(self.screen_width * self.screen_height, 0);
self.screen_buffer = new_buffer;
}
Instruction::ScrollRight => {
let new_buffer: Vec<u8> = self
.screen_buffer
.chunks(self.screen_width)
.flat_map(|x| {
let mut row = vec![0, 0, 0, 0];
row.extend(x);
row.resize(self.screen_width, 0);
row
})
.collect();
self.screen_buffer = new_buffer;
}
Instruction::ScrollLeft => {
let new_buffer: Vec<u8> = self
.screen_buffer
.chunks(self.screen_width)
.flat_map(|x| {
let mut row = x[4..].to_vec();
row.resize(self.screen_width, 0);
row
})
.collect();
self.screen_buffer = new_buffer;
}
Instruction::Unimplemented(val) => {
eprintln!("UNIMPLEMENTED: {}", val)
}
}
}
fn skip_if_key(&mut self, state_to_check: KeyStateToCheck, register: usize) {
if let Ok(keys) = self.input_provider.read_keys_state() {
let current_key_state = keys[(self.variable_registers[register] & 0xF) as usize];
match state_to_check {
KeyStateToCheck::IsPressed => {
if current_key_state > 0 {
self.program_counter += 2;
}
}
KeyStateToCheck::NotPressed => {
if current_key_state == 0 {
self.program_counter += 2;
}
}
KeyStateToCheck::Invalid => {}
}
};
}
fn perform_sub_command(&mut self, command: SubCommand, register: usize) {
match command {
SubCommand::ReadDelayTimer => {
self.variable_registers[register] = self.delay_timer.load(Ordering::Relaxed) as u8;
}
SubCommand::SetDelayTimer => {
self.delay_timer
.store(self.variable_registers[register] as u16, Ordering::Relaxed);
}
SubCommand::SetSoundTimer => {
self.sound_timer
.store(self.variable_registers[register] as u16, Ordering::Relaxed);
}
SubCommand::AddToIndexRegister => {
self.index_register += self.variable_registers[register] as usize;
}
SubCommand::GetFontCharacter(FontVariant::Small) => {
self.index_register =
FONT_ADDR + (self.variable_registers[register] & 0xF) as usize * 5;
}
SubCommand::GetFontCharacter(FontVariant::Big) => {
self.index_register =
BIG_FONT_ADDR + (self.variable_registers[register]) as usize * 10;
}
SubCommand::DecimalConversion => {
let val = self.variable_registers[register];
let val = u8_to_arr(val);
for (i, val) in val.iter().enumerate() {
let wrapped = (self.index_register + i) % self.memory.len();
if let Some(x) = self.memory.get_mut(wrapped) {
*x = *val;
};
}
}
SubCommand::LoadFrom => {
for (i, reg) in self.variable_registers[0..=register].iter_mut().enumerate() {
let wrapped = (self.index_register + i) % self.memory.len();
if let Some(x) = self.memory.get(wrapped) {
*reg = *x;
}
}
if self.quirks.memory {
self.index_register += register + 1;
}
}
SubCommand::StoreTo => {
for (i, reg) in self.variable_registers[0..=register].iter().enumerate() {
let wrapped = (self.index_register + i) % self.memory.len();
if let Some(x) = self.memory.get_mut(wrapped) {
*x = *reg;
}
}
if self.quirks.memory {
self.index_register += register + 1;
}
}
SubCommand::GetKey => {
let mut key_pressed = false;
if let Ok(keys) = self.input_provider.read_keys_state() {
for (i, key) in keys.iter().enumerate() {
if *key > 0 {
self.variable_registers[register] = i as u8;
key_pressed = true;
break;
}
}
}
if !key_pressed {
self.program_counter -= 2;
} else {
self.input_provider.reset_keys_state();
};
}
SubCommand::Unimplemented(_value) => {}
}
}
fn perform_logical_operator(
&mut self,
register_x: usize,
register_y: usize,
operator: LogicalOperator,
) {
match operator {
LogicalOperator::Set => {
self.variable_registers[register_x] = self.variable_registers[register_y];
}
LogicalOperator::BinaryOr => {
self.variable_registers[register_x] |= self.variable_registers[register_y];
if self.quirks.vf_reset {
self.variable_registers[0xF] = 0;
}
}
LogicalOperator::BinaryAnd => {
self.variable_registers[register_x] &= self.variable_registers[register_y];
if self.quirks.vf_reset {
self.variable_registers[0xF] = 0;
}
}
LogicalOperator::LogicalXor => {
self.variable_registers[register_x] ^= self.variable_registers[register_y];
if self.quirks.vf_reset {
self.variable_registers[0xF] = 0;
}
}
LogicalOperator::AddAffectingCarry => {
let res = self.variable_registers[register_x] as u16
+ self.variable_registers[register_y] as u16;
self.variable_registers[register_x] = (res & 0xFF) as u8;
self.variable_registers[0xF] = if res > 255 { 1 } else { 0 };
}
LogicalOperator::Subtract => {
let res = self.variable_registers[register_x] as i16
- self.variable_registers[register_y] as i16;
self.variable_registers[register_x] = (res & 0xFF) as u8;
self.variable_registers[0xF] = if res >= 0 { 1 } else { 0 };
}
LogicalOperator::SubtractReverse => {
let res = self.variable_registers[register_y] as i16
- self.variable_registers[register_x] as i16;
self.variable_registers[register_x] = (res & 0xFF) as u8;
self.variable_registers[0xF] = if res >= 0 { 1 } else { 0 };
}
LogicalOperator::Shift(direction) => {
if !self.quirks.shifting {
self.variable_registers[register_x] = self.variable_registers[register_y];
}
match direction {
Direction::Left => {
let top = self.variable_registers[register_x] & 0b1000_0000;
let res = self.variable_registers[register_x] << 1;
self.variable_registers[register_x] = res;
if top > 0 {
self.variable_registers[0xF] = 1
} else {
self.variable_registers[0xF] = 0
};
}
Direction::Right => {
let bot = self.variable_registers[register_x] & 0b1;
let res = self.variable_registers[register_x] >> 1;
self.variable_registers[register_x] = res;
if bot > 0 {
self.variable_registers[0xF] = 1
} else {
self.variable_registers[0xF] = 0
};
}
}
}
LogicalOperator::Invalid => {}
}
}
fn draw(&mut self, x_register: usize, y_register: usize, height: u8) {
let (x_value, y_value) = (
(self.variable_registers[x_register] % self.screen_width as u8) as u16,
(self.variable_registers[y_register] % self.screen_height as u8) as u16,
);
self.variable_registers[0xF] = 0;
let start_loc = y_value * self.screen_width as u16 + x_value;
let (width, height) = { if height == 0 { (16, 16) } else { (8, height) } };
for i in 0..height {
let mut overdrawn_y = false;
let sprite_row = if width == 8 {
let wrapped_mem_index = (self.index_register + i as usize) % self.memory.len();
let Some(sprite_row) = self.memory.get(wrapped_mem_index) else {
break;
};
*sprite_row as u16
} else {
let wrapped_mem_index_first_half =
(self.index_register + (i * 2) as usize) % self.memory.len();
let Some(sprite_left_half) = self.memory.get(wrapped_mem_index_first_half) else {
break;
};
let wrapped_mem_index_second_half =
(self.index_register + (i * 2 + 1) as usize) % self.memory.len();
let Some(sprite_right_half) = self.memory.get(wrapped_mem_index_second_half) else {
break;
};
(*sprite_left_half as u16) << 8 | *sprite_right_half as u16
};
let current_loc = start_loc + self.screen_width as u16 * i as u16;
if y_value + i as u16 > (self.screen_height - 1) as u16 {
if self.quirks.clipping {
continue;
} else {
overdrawn_y = true;
}
}
for j in 0..width {
let mut overdrawn_x = false;
let mask: u16 = 0b1 << (width - j - 1);
if (x_value + j) > (self.screen_width - 1) as u16 {
if self.quirks.clipping {
continue;
} else {
overdrawn_x = true;
}
}
let mut to_get_index = current_loc + j;
if overdrawn_x {
to_get_index -= self.screen_width as u16;
}
if overdrawn_y {
to_get_index -= (self.screen_width * self.screen_height) as u16;
}
if let Some(x) = self.screen_buffer.get_mut((to_get_index) as usize) {
let pixel = if (mask & sprite_row) > 0 { 1 } else { 0 };
if pixel > 0 && *x > 0 {
self.variable_registers[0xF] = 1;
}
*x ^= pixel;
}
}
}
self.wait_to_display();
self.drawer
.draw_buffer(&self.screen_buffer, self.screen_width, self.screen_height);
}
fn wait_to_display(&mut self) {
let now = SystemTime::now();
let time_since_last_draw = now
.duration_since(self.last_draw)
.expect("Earlier is before now.");
if time_since_last_draw < self.max_draw_delay {
std::thread::sleep(self.max_draw_delay - time_since_last_draw);
}
self.last_draw = SystemTime::now();
}
fn clear_screen(&mut self) {
for i in self.screen_buffer.iter_mut() {
*i = 0;
}
self.drawer.clear_screen();
}
fn set_mem_block(&mut self, set: &[u8], start_addr: usize) -> Result<(), ApplicationError> {
let end_addr = start_addr + set.len();
if end_addr > self.memory.len() {
return Err(ApplicationError::MemoryLocationOutOfRange {
max_addr: self.memory.len() - set.len(),
});
}
let x = &mut self.memory[start_addr..end_addr];
for (i, item) in x.iter_mut().enumerate() {
*item = set[i];
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct EmulatorState {
pub memory: [u8; 0x1000],
pub stack: Vec<usize>,
pub variable_registers: [u8; 16],
pub screen_buffer: Vec<u8>,
pub index_register: usize,
pub program_counter: usize,
pub last_instruction: Instruction,
pub delay_timer: u16,
pub sound_timer: u16,
pub width: usize,
pub height: usize,
}
impl Default for EmulatorState {
fn default() -> Self {
Self {
memory: [0; 0x1000],
last_instruction: Instruction::ClearScreen,
stack: Default::default(),
variable_registers: Default::default(),
screen_buffer: Default::default(),
index_register: Default::default(),
program_counter: Default::default(),
delay_timer: Default::default(),
sound_timer: Default::default(),
width: BASE_SCREEN_WIDTH,
height: BASE_SCREEN_HEIGHT,
}
}
}
impl<T: Draw, P: ReadInputState> Iterator for Emulator<T, P> {
type Item = EmulatorState;
fn next(&mut self) -> Option<Self::Item> {
let instruction = self.fetch();
self.execute(instruction.clone());
Some(EmulatorState {
last_instruction: instruction,
memory: self.memory,
stack: self.stack.clone(),
variable_registers: self.variable_registers,
screen_buffer: self.screen_buffer.clone(),
index_register: self.index_register,
program_counter: self.program_counter,
delay_timer: self.delay_timer.load(Ordering::Relaxed),
sound_timer: self.sound_timer.load(Ordering::Relaxed),
width: self.screen_width,
height: self.screen_height,
})
}
}
#[cfg(test)]
mod tests {
struct DummyInput;
struct DebugDrawer;
impl Draw for DebugDrawer {
fn draw_buffer(&mut self, _: &[u8], _: usize, _: usize) {}
fn clear_screen(&mut self) {}
}
impl ReadInputState for DummyInput {
fn read_keys_state(&self) -> Result<[u8; 16], String> {
Ok([0; 16])
}
fn reset_keys_state(&mut self) {}
}
use super::*;
#[test]
fn it_can_fetch_an_instruction() {
let mut emulator =
Emulator::<DebugDrawer, DummyInput>::init(vec![], DebugDrawer, DummyInput)
.expect("All initial memory is in range");
emulator
.set_mem_block(&FONT, FONT_ADDR)
.expect("Should be able to set font");
emulator.memory[0x200] = 0x00;
emulator.memory[0x201] = 0xE0;
emulator.memory[0x202] = 0xDE;
emulator.memory[0x203] = 0xF5;
emulator.memory[0x204] = 0x6E;
emulator.memory[0x205] = 0xAB;
emulator.memory[0x206] = 0x7E;
emulator.memory[0x207] = 0xAB;
emulator.memory[0x208] = 0xA1;
emulator.memory[0x209] = 0x23;
emulator.memory[0x20A] = 0x11;
emulator.memory[0x20B] = 0x23;
emulator.memory[0x20C] = 0x31;
emulator.memory[0x20D] = 0x23;
emulator.memory[0x20E] = 0x41;
emulator.memory[0x20F] = 0x23;
emulator.memory[0x210] = 0x51;
emulator.memory[0x211] = 0x20;
emulator.memory[0x212] = 0x91;
emulator.memory[0x213] = 0x20;
emulator.program_counter = 0x200;
let instruction = emulator.fetch();
assert!(matches!(instruction, Instruction::ClearScreen));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::Draw {
x_register: 0xE,
y_register: 0xF,
height: 0x5
}
));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::SetGeneralRegister {
register: 0xE,
value: 0xAB
}
));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::AddToRegister {
register: 0xE,
value: 0xAB
}
));
let instruction = emulator.fetch();
assert!(matches!(instruction, Instruction::SetIndexRegister(0x123)));
let instruction = emulator.fetch();
assert!(matches!(instruction, Instruction::Jump(0x123)));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::SkipEqValueWithRegisterContents {
register: 0x1,
value: 0x23
}
));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::SkipNotEqValueWithRegisterContents {
register: 0x1,
value: 0x23
}
));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::SkipEqRegisters {
register_x: 0x1,
register_y: 0x2
}
));
let instruction = emulator.fetch();
assert!(matches!(
instruction,
Instruction::SkipNotEqRegisters {
register_x: 0x1,
register_y: 0x2
}
));
}
}