From 5fdde760ce6252ec5b8950a60333af8bc497f286 Mon Sep 17 00:00:00 2001 From: BayThylacine Date: Sun, 16 Feb 2025 17:27:43 +1100 Subject: [PATCH] Buggy and sound unimplemented but most opcodes should work --- .gitignore | 2 +- Cargo.lock | 102 +++++++++++++- Cargo.toml | 3 +- src/main.rs | 376 +++++++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 460 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index b888ad8..84c2b40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -/IBMLogo.ch8 +/roms /.idea diff --git a/Cargo.lock b/Cargo.lock index 5d1ea55..2700d23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,7 @@ dependencies = [ "clap", "clap_derive", "pixels", + "rand", "tao", "tracing", "tracing-subscriber", @@ -23,7 +24,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -189,6 +190,12 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.0" @@ -717,6 +724,18 @@ dependencies = [ "x11", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets 0.52.6", +] + [[package]] name = "gio" version = "0.18.4" @@ -1526,6 +1545,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy 0.7.35", +] + [[package]] name = "presser" version = "0.3.1" @@ -1600,6 +1628,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy 0.8.18", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom", + "zerocopy 0.8.18", +] + [[package]] name = "range-alloc" version = "0.1.4" @@ -2163,6 +2222,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -2702,6 +2770,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -2771,7 +2848,17 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "zerocopy-derive", + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" +dependencies = [ + "zerocopy-derive 0.8.18", ] [[package]] @@ -2785,6 +2872,17 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index 562d7e8..f72f638 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ clap_derive = "4.5.28" pixels = "0.15.0" tao = "0.31.1" tracing = "0.1.41" -tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } \ No newline at end of file +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +rand = "0.9.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 4c0b518..05120fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use std::num::NonZeroU32; use std::rc::Rc; use pixels::{Error, Pixels, SurfaceTexture}; use tao::{ - event::{Event, KeyEvent, WindowEvent}, + event::{Event, KeyEvent, WindowEvent, ElementState}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder, keyboard::KeyCode, @@ -15,7 +15,13 @@ use tao::dpi::LogicalSize; use tracing::{info, error}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use std::sync::Arc; +use rand::prelude::*; +use tao::keyboard::Key; +const MODERNSHIFT: bool = true; +const OLDJUMP: bool = true; + +const MODERNLOADSAVE: bool = true; #[derive(Parser, Debug)] struct Args { @@ -32,8 +38,12 @@ struct Chip8 { delay_timer: u8, ram: [u8; 4096], pc: u16, - stack: [u8; 64], + stack: [u16; 64], stack_pointer: u8, + keys: [bool; 16], + get_key_pause: bool, + keys_released: [bool; 16], + keys_pressed: [bool; 16], } impl Chip8 { @@ -48,6 +58,10 @@ impl Chip8 { pc: 0x200, stack: [0; 64], stack_pointer: 0, + keys: [false; 16], + get_key_pause: false, + keys_released: [false; 16], + keys_pressed: [false; 16], } } @@ -61,7 +75,13 @@ impl Chip8 { self } - fn draw(self, frame: &mut [u8], dimensions: (u32, u32)) -> Self { + fn draw(&mut self, frame: &mut [u8], dimensions: (u32, u32)) -> Self { + if self.sound_timer != 0 { + self.sound_timer -= 1; + } + if self.delay_timer != 0 { + self.delay_timer -= 1; + } for(i, pixel) in frame.chunks_exact_mut(4).enumerate() { let x = i % dimensions.0 as usize; let y = i / dimensions.0 as usize; @@ -78,7 +98,7 @@ impl Chip8 { pixel.copy_from_slice(&rgba); } info!("done"); - self + *self } fn update(&mut self, instructions: u32) -> Self { @@ -102,13 +122,27 @@ impl Chip8 { 0x00 => { self.clear_screen(); }, - _ => {} + 0x0e => { + if self.stack_pointer != 0 { + self.pc = self.stack[self.stack_pointer as usize]; + self.stack_pointer -= 1; + } else { + error!("at root subroutine") + } + }, + _ => { + error!("Does not support machine code"); + } } }, - _ => {} + _ => { + error!("Does not support machine code"); + } } }, - _ => {} + _ => { + error!("Does not support machine code"); + } } }, 0x01 => { @@ -116,16 +150,33 @@ impl Chip8 { self.pc = position; }, 0x02 => { - + if self.stack_pointer != 63 { + self.stack_pointer += 1; + self.stack[self.stack_pointer as usize] = self.pc; + self.pc = ((opcodes[1] as u16) << 8) | ((opcodes[2] as u16) << 4) | opcodes[3] as u16; + } else { + error!("reached the limit of the stack") + } }, 0x03 => { - + if (self.registers[opcodes[1] as usize]) == (((opcodes[2]) << 4) + opcodes[3]) { + self.pc += 2; + } }, 0x04 => { - + if (self.registers[opcodes[1] as usize]) != (((opcodes[2]) << 4) + opcodes[3]) { + self.pc += 2; + } }, 0x05 => { - + match opcodes[3] { + 0x00 => { + if self.registers[opcodes[1] as usize] == self.registers[opcodes[2] as usize] { + self.pc += 2; + } + }, + _ => {error!("unknown opcode")} + } }, 0x06 => { self.registers[opcodes[1] as usize] = opcodes[2] << 4 | opcodes[3]; @@ -135,19 +186,94 @@ impl Chip8 { self.registers[opcodes[1] as usize] = (((self.registers[opcodes[1] as usize] as u16) + value) & 255) as u8; }, 0x08 => { - + match opcodes[3] { + 0x00 => { + self.registers[opcodes[1] as usize] = self.registers[opcodes[2] as usize]; + }, + 0x01 => { + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] | self.registers[opcodes[2] as usize]; + }, + 0x02 => { + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] & self.registers[opcodes[2] as usize]; + }, + 0x03 => { + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] ^ self.registers[opcodes[2] as usize]; + }, + 0x04 => { + let added_base = (self.registers[opcodes[1] as usize] as u16) + (self.registers[opcodes[2] as usize] as u16); + if added_base > 255 { + self.registers[0x0f] = 1; + } else { + self.registers[0x0f] = 0; + } + self.registers[opcodes[1] as usize] = (added_base & 255) as u8; + }, + 0x05 => { + info!("{},{}", self.registers[opcodes[1] as usize], self.registers[opcodes[2] as usize]); + let no_underflow = (self.registers[opcodes[1] as usize] >= self.registers[opcodes[2] as usize]); + self.registers[0x0f] = no_underflow as u8; + if no_underflow { + self.registers[opcodes[1] as usize] -= self.registers[opcodes[2] as usize]; + } else { + self.registers[opcodes[1] as usize] = 255 - ((self.registers[opcodes[2] as usize] - self.registers[opcodes[1] as usize]) - 1); + } + }, + 0x06 => { + if MODERNSHIFT { + self.registers[0x0f] = ((self.registers[opcodes[1] as usize] & 1) != 0) as u8; + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] >> 1; + } else { + self.registers[opcodes[1] as usize] = self.registers[opcodes[2] as usize]; + self.registers[0x0f] = ((self.registers[opcodes[1] as usize] & 1) != 0) as u8; + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] >> 1; + } + }, + 0x07 => { + let no_underflow = (self.registers[opcodes[2] as usize] >= self.registers[opcodes[1] as usize]); + self.registers[0x0f] = no_underflow as u8; + if no_underflow { + self.registers[opcodes[1] as usize] = self.registers[opcodes[2] as usize] - self.registers[opcodes[1] as usize]; + } else { + self.registers[opcodes[1] as usize] = 255 - ((self.registers[opcodes[1] as usize] - self.registers[opcodes[2] as usize]) - 1); + } + }, + 0x0e => { + if MODERNSHIFT { + self.registers[0x0f] = ((self.registers[opcodes[1] as usize] & 128) != 0) as u8; + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] << 1; + } else { + self.registers[opcodes[1] as usize] = self.registers[opcodes[2] as usize]; + self.registers[0x0f] = ((self.registers[opcodes[1] as usize] & 128) != 0) as u8; + self.registers[opcodes[1] as usize] = self.registers[opcodes[1] as usize] << 1; + } + }, + _ => {error!("unknown opcode")} + } }, 0x09 => { - + match opcodes[3] { + 0x00 => { + if self.registers[opcodes[1] as usize] != self.registers[opcodes[2] as usize] { + self.pc += 2; + } + }, + _ => {error!("unknown opcode")} + } }, 0x0a => { self.index_register = (opcodes[1] as u16) << 8 | (opcodes[2] as u16) << 4 | (opcodes[3] as u16); }, 0x0b => { - + if OLDJUMP { + self.pc = ((opcodes[1] as u16) << 8) + ((opcodes[2] as u16) << 4) + (opcodes[3] as u16) + (self.registers[0] as u16); + } else { + self.pc = ((opcodes[1] as u16) << 8) + ((opcodes[2] as u16) << 4) + (opcodes[3] as u16) + (self.registers[opcodes[1] as usize] as u16) + } }, 0x0c => { - + let mut rng = rand::rng(); + let random = rng.random::(); + self.registers[opcodes[1] as usize] = random & (opcodes[2] << 4 | opcodes[3]); }, 0x0d => { let x = self.registers[opcodes[1] as usize] % 64; @@ -159,15 +285,158 @@ impl Chip8 { for i in 0..8 { let bit = get_bit_u8(spritedata, i); checkdata += (bit as u8) << (7 - i); - self.display[(y + n) as usize] = self.display[(y + n) as usize] ^ ((bit as u64) << (x + i)); + if (x+i <= 63) && (y+n <= 31) { + self.display[(y + n) as usize] = self.display[(y + n) as usize] ^ ((bit as u64) << (x + i)); + } } //info!("{},{}", spritedata, checkdata); } }, 0x0e => { - + match opcodes[2] { + 0x09 => { + match opcodes[3] { + 0x0e => { + info!("23: {}", self.registers[opcodes[1] as usize]); + if self.registers[opcodes[1] as usize] < 16{ + if self.keys[(self.registers[opcodes[1] as usize] & 15) as usize] { + self.pc += 2; + } + } else { + error!("invalid keycode"); + } + } + _ => {} + } + }, + 0x0a => { + match opcodes[3] { + 0x01 => { + info!("a1 {}", self.registers[opcodes[1] as usize]); + if self.registers[opcodes[1] as usize] < 16{ + if !self.keys[(self.registers[opcodes[1] as usize] & 15) as usize] { + self.pc += 2; + } + } else { + error!("invalid keycode"); + } + }, + _ => {error!("invalid opcode");} + } + }, + _ => {error!("unknown opcode")} + } }, 0x0f => { + match opcodes[2] { + 0x00 => { + match opcodes[3] { + 0x07 => { + self.registers[opcodes[1] as usize] = self.delay_timer; + }, + 0x0a => { + let mut keypress = false; + if !self.get_key_pause { + self.get_key_pause = true; + for i in 0..16 { + self.keys_released[i] = !self.keys[i]; + self.keys_pressed[i] = false; + } + } else { + for i in 0..16 { + if !keypress { + if self.keys_pressed[i] && !self.keys[i] { + self.registers[opcodes[1] as usize] = i as u8; + self.get_key_pause = false; + keypress = true; + } else if self.keys_released[i] && self.keys[i] { + self.keys_pressed[i] = true; + } else if !self.keys_released[i] && !self.keys[i] { + self.keys_released[i] = true; + } + } + } + } + if !keypress { + self.pc -= 2; + } + }, + _ => { + error!("unknown opcode"); + } + } + } + 0x01 => { + match opcodes[3] { + 0x05 => { + self.delay_timer = self.registers[opcodes[1] as usize]; + }, + 0x08 => { + self.sound_timer = self.registers[opcodes[1] as usize]; + } + 0x0e => { + if self.index_register + self.registers[opcodes[1] as usize] as u16 >= 0x1000 { + self.registers[0x0f] = 1; + } + self.index_register = (self.index_register + self.registers[opcodes[1] as usize] as u16) & 0x0FFF; + }, + _ => {error!("unknown opcode");} + } + }, + 0x02 => { + match opcodes[3] { + 0x09 => { + self.index_register = 0x050 + (((self.registers[opcodes[1] as usize] & 0x0F) as u16) * 5); + }, + _ => {error!("unknown opcode");} + } + }, + 0x03 => { + match opcodes[3] { + 0x03 => { + self.ram[self.index_register as usize] = self.registers[opcodes[1] as usize] / 100; + self.ram[self.index_register as usize + 1] = (self.registers[opcodes[1] as usize] / 10) % 10; + self.ram[self.index_register as usize + 2] = self.registers[opcodes[1] as usize] % 10; + }, + _ => {error!("unknown opcode");} + } + } + 0x05 => { + match opcodes[3] { + 0x05 => { + if MODERNLOADSAVE { + for i in 0..=opcodes[1] { + self.ram[(self.index_register + i as u16) as usize] = self.registers[i as usize]; + } + } else { + for i in 0..=opcodes[1] { + self.ram[self.index_register as usize] = self.registers[i as usize]; + self.index_register += i as u16; + } + } + }, + _ => {error!("unknown opcode");} + } + }, + 0x06 => { + match opcodes[3] { + 0x05 => { + if MODERNLOADSAVE { + for i in 0..=opcodes[1] { + self.registers[i as usize] = self.ram[(self.index_register + i as u16) as usize]; + } + } else { + for i in 0..=opcodes[1] { + self.registers[i as usize] = self.ram[self.index_register as usize]; + self.index_register += 1; + } + } + }, + _ => {error!("unknown opcode");} + } + }, + _ => {error!("unknown opcode")} + } }, _ => { @@ -179,9 +448,9 @@ impl Chip8 { *self } - fn clear_screen(mut self) -> Self { + fn clear_screen(&mut self) -> Self { self.display = [0;32]; - self + *self } } @@ -229,6 +498,67 @@ fn main() { *control_flow = ControlFlow::Exit; } }, + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key, + state, + .. + }, + .. + } => { + match logical_key { + Key::Character("1") => { + set_keystate(&mut chip8.keys, 1, state); + }, + Key::Character("2") => { + set_keystate(&mut chip8.keys, 2, state); + }, + Key::Character("3") => { + set_keystate(&mut chip8.keys, 3, state); + }, + Key::Character("4") => { + set_keystate(&mut chip8.keys, 0x0c, state); + }, + Key::Character("q") => { + set_keystate(&mut chip8.keys, 4, state); + }, + Key::Character("w") => { + set_keystate(&mut chip8.keys, 5, state); + }, + Key::Character("e") => { + set_keystate(&mut chip8.keys, 6, state); + }, + Key::Character("r") => { + set_keystate(&mut chip8.keys, 0x0d, state); + }, + Key::Character("a") => { + set_keystate(&mut chip8.keys, 7, state); + }, + Key::Character("s") => { + set_keystate(&mut chip8.keys, 8, state); + }, + Key::Character("d") => { + set_keystate(&mut chip8.keys, 9, state); + }, + Key::Character("f") => { + set_keystate(&mut chip8.keys, 0x0e, state); + }, + Key::Character("z") => { + set_keystate(&mut chip8.keys, 0x0a, state); + }, + Key::Character("x") => { + set_keystate(&mut chip8.keys, 0, state); + }, + Key::Character("c") => { + set_keystate(&mut chip8.keys, 0x0b, state); + }, + Key::Character("v") => { + set_keystate(&mut chip8.keys, 0x0f, state); + }, + _ => {} + } + } _ => {} }, Event::MainEventsCleared => { @@ -310,4 +640,12 @@ fn get_bit_u8(num: u8, bit: u8) -> bool { } else { false } +} + +fn set_keystate(keys: &mut [bool; 16], key: u8, state: ElementState) { + if state == ElementState::Pressed { + keys[key as usize] = true; + } else if state == ElementState::Released { + keys[key as usize] = false; + } } \ No newline at end of file