├── .gitattributes ├── .gitignore ├── src ├── fontset.bin └── main.rs ├── README.md ├── Cargo.toml ├── LICENSE.txt └── Cargo.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.bin binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /games 2 | 3 | /target 4 | **/*.rs.bk 5 | 6 | /.idea 7 | -------------------------------------------------------------------------------- /src/fontset.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jellysquid3/chip8-rs/HEAD/src/fontset.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | chip8-rs 2 | ======== 3 | 4 | A simple emulator for the [CHIP-8 programming language](https://en.wikipedia.org/wiki/CHIP-8). -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chip8-rs" 3 | version = "0.1.1" 4 | authors = ["JellySquid"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | xorshift = "0.1" 9 | sdl2 = "0.35" 10 | 11 | [profile.release] 12 | lto = true 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "1.0.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 16 | 17 | [[package]] 18 | name = "chip8-rs" 19 | version = "0.1.1" 20 | dependencies = [ 21 | "sdl2", 22 | "xorshift", 23 | ] 24 | 25 | [[package]] 26 | name = "fuchsia-cprng" 27 | version = "0.1.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 30 | 31 | [[package]] 32 | name = "lazy_static" 33 | version = "0.2.11" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" 36 | 37 | [[package]] 38 | name = "lazy_static" 39 | version = "1.4.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 42 | 43 | [[package]] 44 | name = "libc" 45 | version = "0.2.122" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" 48 | 49 | [[package]] 50 | name = "rand" 51 | version = "0.3.23" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 54 | dependencies = [ 55 | "libc", 56 | "rand 0.4.6", 57 | ] 58 | 59 | [[package]] 60 | name = "rand" 61 | version = "0.4.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 64 | dependencies = [ 65 | "fuchsia-cprng", 66 | "libc", 67 | "rand_core 0.3.1", 68 | "rdrand", 69 | "winapi", 70 | ] 71 | 72 | [[package]] 73 | name = "rand_core" 74 | version = "0.3.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 77 | dependencies = [ 78 | "rand_core 0.4.2", 79 | ] 80 | 81 | [[package]] 82 | name = "rand_core" 83 | version = "0.4.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 86 | 87 | [[package]] 88 | name = "rdrand" 89 | version = "0.4.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 92 | dependencies = [ 93 | "rand_core 0.3.1", 94 | ] 95 | 96 | [[package]] 97 | name = "sdl2" 98 | version = "0.35.2" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" 101 | dependencies = [ 102 | "bitflags", 103 | "lazy_static 1.4.0", 104 | "libc", 105 | "sdl2-sys", 106 | ] 107 | 108 | [[package]] 109 | name = "sdl2-sys" 110 | version = "0.35.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" 113 | dependencies = [ 114 | "cfg-if", 115 | "libc", 116 | "version-compare", 117 | ] 118 | 119 | [[package]] 120 | name = "version-compare" 121 | version = "0.1.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "fe88247b92c1df6b6de80ddc290f3976dbdf2f5f5d3fd049a9fb598c6dd5ca73" 124 | 125 | [[package]] 126 | name = "winapi" 127 | version = "0.3.9" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 130 | dependencies = [ 131 | "winapi-i686-pc-windows-gnu", 132 | "winapi-x86_64-pc-windows-gnu", 133 | ] 134 | 135 | [[package]] 136 | name = "winapi-i686-pc-windows-gnu" 137 | version = "0.4.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 140 | 141 | [[package]] 142 | name = "winapi-x86_64-pc-windows-gnu" 143 | version = "0.4.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 146 | 147 | [[package]] 148 | name = "xorshift" 149 | version = "0.1.3" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "da1942554bd45c0beacab23cc6b70dfdc76c308defc4a2519f38449aadeca1ed" 152 | dependencies = [ 153 | "lazy_static 0.2.11", 154 | "rand 0.3.23", 155 | ] 156 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | use xorshift::{Rng, SeedableRng, Xoroshiro128}; 8 | 9 | use sdl2::Sdl; 10 | use sdl2::event::Event; 11 | use sdl2::keyboard::Keycode; 12 | use sdl2::pixels::{PixelFormatEnum}; 13 | use sdl2::render::{TextureAccess, WindowCanvas}; 14 | 15 | const FRAMEBUFFER_WIDTH: usize = 64; 16 | const FRAMEBUFFER_HEIGHT: usize = 32; 17 | const FRAMEBUFFER_SIZE: usize = FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT; 18 | const FRAMEBUFFER_PITCH: usize = FRAMEBUFFER_WIDTH * 4; 19 | 20 | const DISPLAY_SCALE: usize = 16; 21 | 22 | fn main() { 23 | let rom_path = env::args().skip(1).next().expect("Missing path argument"); 24 | 25 | let mut rom: Vec = Vec::new(); 26 | 27 | let mut rom_file = File::open(rom_path) 28 | .expect("Failed to open ROM file"); 29 | rom_file 30 | .read_to_end(&mut rom) 31 | .expect("Failed to read ROM file"); 32 | 33 | let mut app = Application::new(rom); 34 | app.run(); 35 | } 36 | 37 | struct Application { 38 | sdl: Sdl, 39 | cpu: Chip8, 40 | canvas: WindowCanvas 41 | } 42 | 43 | impl Application { 44 | pub fn new(rom: Vec) -> Self { 45 | let sdl = sdl2::init().expect("Failed to initialize SDL2"); 46 | let video_sys = sdl 47 | .video() 48 | .expect("Failed to initialize SDL2 Video"); 49 | 50 | let cpu = Chip8::new(&rom) 51 | .expect("Failed to initialize CHIP-8 CPU"); 52 | 53 | let window = video_sys 54 | .window("chip8-rs", 55 | (FRAMEBUFFER_WIDTH * DISPLAY_SCALE) as u32, 56 | (FRAMEBUFFER_HEIGHT * DISPLAY_SCALE) as u32) 57 | .opengl() 58 | .position_centered() 59 | .build() 60 | .expect("Failed to create SDL2 window"); 61 | 62 | let canvas = window 63 | .into_canvas() 64 | .build() 65 | .expect("Failed to create SDL2 window surface"); 66 | 67 | Application { 68 | sdl, 69 | cpu, 70 | canvas 71 | } 72 | } 73 | 74 | pub fn run(&mut self) { 75 | let mut events = self.sdl 76 | .event_pump() 77 | .expect("Failed to create event pump"); 78 | 79 | let mut close = false; 80 | 81 | let texture_creator = self.canvas.texture_creator(); 82 | 83 | let mut texture = texture_creator 84 | .create_texture(PixelFormatEnum::RGB888, TextureAccess::Streaming, 85 | FRAMEBUFFER_WIDTH as u32, FRAMEBUFFER_HEIGHT as u32) 86 | .expect("Failed to create streaming texture"); 87 | 88 | while !close { 89 | for event in events.poll_iter() { 90 | match event { 91 | Event::Quit { .. } => close = true, 92 | Event::KeyDown { keycode, .. } => { 93 | if let Some(key) = keycode { 94 | self.cpu.set_key_state(key, true); 95 | } 96 | } 97 | Event::KeyUp { keycode, .. } => { 98 | if let Some(key) = keycode { 99 | self.cpu.set_key_state(key, false); 100 | } 101 | } 102 | _ => (), 103 | } 104 | } 105 | 106 | self.cpu.step(); 107 | 108 | texture.update(None, self.cpu.get_framebuffer(), FRAMEBUFFER_PITCH) 109 | .expect("Failed to update texture"); 110 | 111 | self.canvas.copy(&texture, None, None) 112 | .expect("Failed to copy texture"); 113 | 114 | self.canvas.present(); 115 | 116 | if cfg!(debug_assertions) { 117 | print!( 118 | "OP:\t{:04X}\t| PC: \t{:04X}\t| I:\t{:04X}\t| SP:\t{:02X}\t", 119 | self.cpu.get_opcode(), 120 | self.cpu.get_program_counter(), 121 | self.cpu.get_program_index(), 122 | self.cpu.get_stack_pointer() 123 | ); 124 | 125 | print!("\nS: \t"); 126 | self.cpu 127 | .get_stack() 128 | .iter() 129 | .for_each(|b| print!("{:04X} ", b)); 130 | 131 | print!("\nV: \t"); 132 | self.cpu 133 | .get_registers() 134 | .iter() 135 | .for_each(|b| print!("{:02X} ", b)); 136 | 137 | print!("\n\n"); 138 | } 139 | 140 | thread::sleep(Duration::from_millis(16)); 141 | } 142 | } 143 | } 144 | 145 | struct Chip8 { 146 | memory: [u8; 4096], 147 | registers: [u8; 16], 148 | framebuffer: [u32; FRAMEBUFFER_SIZE], 149 | stack: [u16; 16], 150 | keys: [bool; 16], 151 | opcode: u16, 152 | 153 | index: u16, 154 | program_counter: u16, 155 | stack_pointer: usize, 156 | 157 | random: Xoroshiro128, 158 | 159 | delay_timer: u8, 160 | sound_timer: u8, 161 | 162 | beep_flag: bool, 163 | 164 | last_key: Option, 165 | } 166 | 167 | impl Chip8 { 168 | fn new(rom: &[u8]) -> Result { 169 | let mut chip8 = Chip8 { 170 | memory: [0; 4096], 171 | registers: [0; 16], 172 | framebuffer: [0; FRAMEBUFFER_SIZE], 173 | stack: [0; 16], 174 | keys: [false; 16], 175 | opcode: 0, 176 | 177 | index: 0, 178 | program_counter: 0, 179 | stack_pointer: 0, 180 | 181 | random: Xoroshiro128::from_seed(&[0x7020de7ee5e88ab7, 0xe587fbb5ba4fccee]), 182 | 183 | delay_timer: 0, 184 | sound_timer: 0, 185 | 186 | beep_flag: false, 187 | 188 | last_key: None, 189 | }; 190 | 191 | chip8.load_fontset(include_bytes!("fontset.bin"))?; 192 | chip8.load_rom(rom)?; 193 | 194 | Ok(chip8) 195 | } 196 | 197 | fn load_fontset(&mut self, bytes: &[u8]) -> Result<(), String> { 198 | let start = 0x050; 199 | let end = 0x0A0; 200 | let len = end - start; 201 | 202 | if bytes.len() > len { 203 | Err(format!("Fontset ROM exceeds maximum size (cap: {}, len: {})", len, bytes.len())) 204 | } else { 205 | self.memory[start..end] 206 | .copy_from_slice(bytes); 207 | 208 | Ok(()) 209 | } 210 | } 211 | 212 | fn load_rom(&mut self, bytes: &[u8]) -> Result { 213 | let start = 0x200; 214 | let end = self.memory.len(); 215 | 216 | if bytes.len() > end - start { 217 | Err(format!("Game ROM exceeds maximum size (cap: {}, len: {})", end - start, bytes.len())) 218 | } else { 219 | self.program_counter = start as u16; 220 | self.index = 0x0; 221 | 222 | self.stack = [0u16; 16]; 223 | self.stack_pointer = 0; 224 | 225 | for i in 0..bytes.len() { 226 | self.memory[i + start] = bytes[i]; 227 | } 228 | 229 | Ok(self.memory.len()) 230 | } 231 | } 232 | 233 | fn step(&mut self) { 234 | self.opcode = (self.memory[self.program_counter as usize] as u16) << 8 235 | | self.memory[self.program_counter as usize + 1] as u16; 236 | 237 | match self.opcode & 0xF000 { 238 | // 0NNN - Calls RCA 1802 program at address NNN 239 | 0x0000 => { 240 | match self.opcode & 0x0FFF { 241 | 0x0000 => { 242 | self.program_counter += 2; 243 | } 244 | // 00E0 - Clear framebuffer 245 | 0x00E0 => { 246 | self.framebuffer.fill(0); 247 | self.program_counter += 2; 248 | } 249 | // 00EE - Returns from subroutine 250 | 0x00EE => { 251 | if self.stack_pointer <= 0 { 252 | panic!("Couldn't pop from stack (stack is empty)"); 253 | } 254 | 255 | self.stack_pointer -= 1; 256 | 257 | self.program_counter = self.stack[self.stack_pointer]; 258 | self.program_counter += 2; 259 | } 260 | _ => panic!("Unknown instruction ({:04X})", self.opcode), 261 | } 262 | } 263 | // 1NNN - Jumps to address NNN 264 | 0x1000 => { 265 | self.program_counter = self.opcode & 0x0FFF; 266 | } 267 | // 2NNN - Calls subroutine at NNN 268 | 0x2000 => { 269 | if self.stack_pointer >= 15 { 270 | panic!("Couldn't push into stack (stack has exceeded maximum size)"); 271 | } 272 | 273 | self.stack[self.stack_pointer] = self.program_counter; 274 | self.stack_pointer += 1; 275 | 276 | self.program_counter = self.opcode & 0x0FFF; 277 | } 278 | // 3XNN - Skips the next instruction if VX equals NN 279 | 0x3000 => { 280 | if self.registers[(self.opcode as usize & 0x0F00) >> 8] 281 | == (self.opcode & 0x00FF) as u8 282 | { 283 | self.program_counter += 4; 284 | } else { 285 | self.program_counter += 2; 286 | } 287 | } 288 | // 4XNN - Skips the next instruction if VX does not equal NN 289 | 0x4000 => { 290 | if self.registers[(self.opcode as usize & 0x0F00) >> 8] 291 | != (self.opcode & 0x00FF) as u8 292 | { 293 | self.program_counter += 4; 294 | } else { 295 | self.program_counter += 2; 296 | } 297 | } 298 | // 5XY0 - Skips the next instruction if VX equals VY 299 | 0x5000 => { 300 | if self.registers[(self.opcode as usize & 0x0F00) >> 8] 301 | == self.registers[(self.opcode as usize & 0x00F0) >> 4] 302 | { 303 | self.program_counter += 4; 304 | } else { 305 | self.program_counter += 2; 306 | } 307 | } 308 | // 6XNN - Sets VX to NN 309 | 0x6000 => { 310 | self.registers[(self.opcode as usize & 0x0F00) >> 8] = (self.opcode & 0x00FF) as u8; 311 | self.program_counter += 2; 312 | } 313 | // 7XNN - Adds NN to VX (carry flag is not changed) 314 | 0x7000 => { 315 | let (result, _) = self.registers[(self.opcode as usize & 0x0F00) >> 8] 316 | .overflowing_add((self.opcode & 0x00FF) as u8); 317 | 318 | self.registers[(self.opcode as usize & 0x0F00) >> 8] = result; 319 | self.program_counter += 2; 320 | } 321 | // 8XNO - Sets VX to a value calculated from VX and VY 322 | 0x8000 => { 323 | let x = (self.opcode as usize & 0x0F00) >> 8; 324 | let y = (self.opcode as usize & 0x00F0) >> 4; 325 | 326 | match self.opcode & 0x000F { 327 | // 8XY0 - Sets VX to VY 328 | 0x0000 => self.registers[x] = self.registers[y], 329 | // 8XY1 - Sets VX to VX OR VY 330 | 0x0001 => self.registers[x] |= self.registers[y], 331 | // 8XY2 - Sets VX to VX AND VY 332 | 0x0002 => self.registers[x] &= self.registers[y], 333 | // 8XY3 - Sets VX to VX XOR VY 334 | 0x0003 => self.registers[x] ^= self.registers[y], 335 | // 8XY4 - Sets VX to VX + VY (sets VF to 1 if a carry occurs, otherwise 0) 336 | 0x0004 => { 337 | let (result, carry) = self.registers[x].overflowing_add(self.registers[y]); 338 | 339 | self.registers[0xF] = if carry { 1 } else { 0 }; 340 | self.registers[x] = result; 341 | } 342 | // 8XY5 - Sets VX to VX - VY (sets VF to 0 if a borrow occurs, otherwise 1) 343 | 0x0005 => { 344 | let (result, borrow) = self.registers[x].overflowing_sub(self.registers[y]); 345 | 346 | self.registers[0xF] = if borrow { 0 } else { 1 }; 347 | self.registers[x] = result; 348 | } 349 | // 8XY6 - Sets VX to VY >> 1 (sets VF to the least significant bit of VY before the shift) 350 | 0x0006 => { 351 | self.registers[0xF] = self.registers[y] & 0b00000001; 352 | self.registers[x] = self.registers[y] >> 1; 353 | } 354 | // 8XY7 - Sets VX to VY - VX. (sets VF to 0 if a borrow occurs, otherwise 1) 355 | 0x0007 => { 356 | let (result, borrow) = self.registers[y].overflowing_sub(self.registers[x]); 357 | 358 | self.registers[0xF] = if borrow { 0 } else { 1 }; 359 | self.registers[x] = result; 360 | } 361 | // 8XYE - Sets VX to VY << 1 (sets VF to the most significant bit of VY before the shift) 362 | 0x000E => { 363 | self.registers[0xF] = self.registers[y] & 0b10000000; 364 | self.registers[x] = self.registers[y] << 1; 365 | } 366 | _ => panic!("Unknown instruction ({:04X})", self.opcode), 367 | } 368 | 369 | self.program_counter += 2; 370 | } 371 | // 9XY0 - Skips the next instruction if VX doesn't equal VY 372 | 0x9000 => { 373 | if self.registers[(self.opcode as usize & 0x0F00) >> 8] 374 | != self.registers[(self.opcode as usize & 0x00F0) >> 4] 375 | { 376 | self.program_counter += 4; 377 | } else { 378 | self.program_counter += 2; 379 | } 380 | } 381 | // ANNN - Sets I to the address NNN 382 | 0xA000 => { 383 | self.index = self.opcode & 0x0FFF; 384 | self.program_counter += 2; 385 | } 386 | // BNNN - Jumps to the address NNN plus V0 387 | 0xB000 => { 388 | self.program_counter = (self.opcode & 0x0FFF) + self.registers[0x0] as u16; 389 | } 390 | // CXNN - Sets VX to the result of a bitwise and operation on a random number (between 0 and 255) and NN 391 | 0xC000 => { 392 | self.registers[(self.opcode as usize & 0x0F00) >> 8] = 393 | self.rand() & (self.opcode & 0x00FF) as u8; 394 | 395 | self.program_counter += 2; 396 | } 397 | // DXYN - Draws a sprite at coordinates (VX, VY) that has the dimensions of 8xN 398 | 0xD000 => { 399 | let dst_x = self.registers[(self.opcode as usize & 0x0F00) >> 8] as usize; 400 | let dst_y = self.registers[(self.opcode as usize & 0x00F0) >> 4] as usize; 401 | 402 | let width = 8; 403 | let height = (self.opcode & 0x000F) as usize; 404 | 405 | self.registers[0xF] = 0; 406 | 407 | for y in 0..height { 408 | let src_pixel = self.memory[self.index as usize + y]; 409 | 410 | for x in 0..width { 411 | if dst_x + x >= FRAMEBUFFER_WIDTH || dst_y + y >= FRAMEBUFFER_HEIGHT { 412 | continue; 413 | } 414 | 415 | if (src_pixel & (0x80 >> x)) != 0 { 416 | let dst = (dst_x + x) + ((dst_y + y) * FRAMEBUFFER_WIDTH); 417 | 418 | if self.framebuffer[dst] != 0 { 419 | self.registers[0xF] = 1; 420 | } 421 | 422 | self.framebuffer[dst] ^= 0xFFFFFFFF; 423 | } 424 | } 425 | } 426 | 427 | self.program_counter += 2; 428 | } 429 | 0xE000 => { 430 | let x = (self.opcode as usize & 0x0F00) >> 8; 431 | 432 | match self.opcode & 0x00FF { 433 | // EX9E - Skips the next instruction if the key stored in VX is pressed 434 | 0x009E => { 435 | if self.keys[x] { 436 | self.program_counter += 4; 437 | } else { 438 | self.program_counter += 2; 439 | } 440 | } 441 | // EXA1 - Skips the next instruction if the key stored in VX is not pressed 442 | 0x00A1 => { 443 | if !self.keys[x] { 444 | self.program_counter += 4; 445 | } else { 446 | self.program_counter += 2; 447 | } 448 | } 449 | _ => panic!("Unknown instruction ({:04X})", self.opcode), 450 | } 451 | } 452 | 0xF000 => { 453 | let x = (self.opcode as usize & 0x0F00) >> 8; 454 | 455 | match self.opcode & 0x00FF { 456 | // FX07 - Sets VX to the value of the delay timer 457 | 0x0007 => { 458 | self.registers[x] = self.delay_timer; 459 | self.program_counter += 2; 460 | } 461 | // FX0A - Sets VX to the next key press, blocking all other instructions until it is received 462 | 0x000A => { 463 | if let Some(key) = self.last_key { 464 | self.registers[x] = key as u8; 465 | self.program_counter += 2; 466 | } 467 | } 468 | // FX15 - Sets the delay timer to VX 469 | 0x0015 => { 470 | self.delay_timer = self.registers[x]; 471 | self.program_counter += 2; 472 | } 473 | // FX18 - Sets the sound timer to VX 474 | 0x0018 => { 475 | self.sound_timer = self.registers[x]; 476 | self.program_counter += 2; 477 | } 478 | // FX1E - Sets I to VX + I 479 | 0x001E => { 480 | self.index += self.registers[x] as u16; 481 | self.program_counter += 2; 482 | } 483 | // FX29 - Sets I to the location of the sprite for the character in VX 484 | 0x0029 => { 485 | let c = self.registers[x] as u16; 486 | 487 | self.index = 0x050 + (c * 5); 488 | self.program_counter += 2; 489 | } 490 | // FX33 - Sets VX to the binary-coded deciaml representation of I 491 | 0x0033 => { 492 | let x = self.registers[x]; 493 | 494 | self.memory[self.index as usize] = x / 100; 495 | self.memory[self.index as usize + 1] = (x / 10) % 10; 496 | self.memory[self.index as usize + 2] = (x % 100) % 10; 497 | 498 | self.program_counter += 2; 499 | } 500 | // FX55 - Stores V0 to VX (including VX) in memory starting at address I 501 | 0x0055 => { 502 | for x in 0..=x { 503 | self.memory[self.index as usize] = self.registers[x]; 504 | self.index += 1; 505 | } 506 | 507 | self.program_counter += 2; 508 | } 509 | // FX65 - Fills V0 to VX (including VX) with values from memory starting at address I 510 | 0x0065 => { 511 | for x in 0..=x { 512 | self.registers[x] = self.memory[self.index as usize]; 513 | self.index += 1; 514 | } 515 | 516 | self.program_counter += 2; 517 | } 518 | _ => panic!("Unknown instruction ({:04X})", self.opcode), 519 | } 520 | } 521 | _ => panic!("Unknown instruction ({:04X})", self.opcode), 522 | } 523 | 524 | if self.delay_timer > 0 { 525 | self.delay_timer -= 1; 526 | } 527 | 528 | if self.sound_timer > 0 { 529 | if self.sound_timer == 1 { 530 | self.beep_flag = true; 531 | } 532 | 533 | self.sound_timer -= 1; 534 | } 535 | 536 | self.last_key = None; 537 | } 538 | 539 | pub fn get_registers(&self) -> &[u8; 16] { 540 | &self.registers 541 | } 542 | 543 | pub fn get_framebuffer(&self) -> &[u8] { 544 | let len = self.framebuffer.len(); 545 | let ptr = self.framebuffer.as_ptr() as *const u8; 546 | 547 | unsafe { 548 | std::slice::from_raw_parts(ptr, len * 4) 549 | } 550 | } 551 | 552 | pub fn get_stack(&self) -> &[u16; 16] { 553 | &self.stack 554 | } 555 | 556 | pub fn get_opcode(&self) -> u16 { 557 | self.opcode 558 | } 559 | 560 | pub fn get_program_counter(&self) -> u16 { 561 | self.program_counter 562 | } 563 | 564 | pub fn get_program_index(&self) -> u16 { 565 | self.index 566 | } 567 | 568 | pub fn get_stack_pointer(&self) -> usize { 569 | self.stack_pointer 570 | } 571 | 572 | pub fn set_key_state(&mut self, key: Keycode, pressed: bool) { 573 | let i = match key { 574 | Keycode::Num1 => 0x1, 575 | Keycode::Num2 => 0x2, 576 | Keycode::Num3 => 0x3, 577 | Keycode::Num4 => 0xC, 578 | Keycode::Q => 0x4, 579 | Keycode::W => 0x5, 580 | Keycode::E => 0x6, 581 | Keycode::R => 0xD, 582 | Keycode::A => 0x7, 583 | Keycode::S => 0x8, 584 | Keycode::D => 0x9, 585 | Keycode::F => 0xE, 586 | Keycode::Z => 0xA, 587 | Keycode::X => 0x0, 588 | Keycode::C => 0xB, 589 | Keycode::V => 0xF, 590 | _ => return, 591 | }; 592 | 593 | self.keys[i] = pressed; 594 | self.last_key = Some(i); 595 | } 596 | 597 | fn rand(&mut self) -> u8 { 598 | (self.random.next_u32() & 0x000000FF) as u8 599 | } 600 | } 601 | --------------------------------------------------------------------------------