├── .gitignore ├── Cargo.toml ├── README.md ├── imgs ├── logo_ch8.png ├── pong.jpeg └── tests.jpeg └── src ├── bus.rs ├── busstate.rs ├── cpu.rs ├── keyboard.rs ├── main.rs └── memory.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty_chip_8" 3 | version = "0.1.0" 4 | authors = ["AlexPeixoto "] 5 | edition = "2018" 6 | 7 | 8 | [dependencies] 9 | sfml = "0.15.1" 10 | csfml-audio-sys = "0.5.0" 11 | csfml-graphics-sys = "0.5.0" 12 | csfml-system-sys = "0.5.0" 13 | csfml-window-sys = "0.5.0" 14 | byteorder = "1" 15 | rand = "0.8.0" 16 | bitmatrix = "0.1.0" 17 | bit-vec = "0.6.3" 18 | derive_more = "0.99.16" 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustyChip8 2 | CHIP-8 emulator written in rust 3 | 4 | 5 | This is my very first RUST project and it probably breaks a lot of expectations from what a good RUST code would look like. 6 | So it was used as a learning opportunity (this was literally my hello world). 7 | 8 | I still want to go back and reorder some stuff on the code, move around a little bit of the code and do some cleanup as I feel that I 9 | can improve quite a bit how its organized. 10 | A lot of this came from my learning process I am aware that I can do a few things: 11 | - Improve the code overall (Move around some variables, rename, rethink some of them) 12 | - Remove &mut self in several places 13 | - Perhaps break down the fn main a bit more 14 | - Add some methods to access some of the data structures. 15 | 16 | 17 | The emulator doesn't have sound (YACH8WA), but at this time it was a design choice basically because there are only beeps 18 | which can easily be done with SFML, but its not being done here, even if the sould timer do exist internally. 19 | 20 | The code passes test rom, keyboard rom, PONG, Maze, RNG and probably several others and do not implement Super Chip-48 instructions. 21 | 22 | Below are some images of it running 23 | 24 | ![It works](https://github.com/AlexPeixoto/RustyChip8/blob/main/imgs/logo_ch8.png) 25 | 26 | ![Pong](https://github.com/AlexPeixoto/RustyChip8/blob/main/imgs/pong.jpeg) 27 | 28 | ![Tests](https://github.com/AlexPeixoto/RustyChip8/blob/main/imgs/tests.jpeg) 29 | 30 | -------------------------------------------------------------------------------- /imgs/logo_ch8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPeixoto/RustyChip8/d3316ccaf004dec28926015293483a116b12b0c1/imgs/logo_ch8.png -------------------------------------------------------------------------------- /imgs/pong.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPeixoto/RustyChip8/d3316ccaf004dec28926015293483a116b12b0c1/imgs/pong.jpeg -------------------------------------------------------------------------------- /imgs/tests.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexPeixoto/RustyChip8/d3316ccaf004dec28926015293483a116b12b0c1/imgs/tests.jpeg -------------------------------------------------------------------------------- /src/bus.rs: -------------------------------------------------------------------------------- 1 | extern crate bitmatrix; 2 | 3 | use crate::busstate::BusState; 4 | use crate::cpu::CPU; 5 | use crate::memory::MemoryMap; 6 | use crate::keyboard::Keyboard; 7 | use bitmatrix::BitMatrix; 8 | 9 | /* This is a entirelly public 10 | * state.soundruct so other components can have 11 | * intercomunication via the bus. 12 | * While this is probably not the bestate.sound pattern 13 | * hardware-wise it would be expected that those components 14 | * can directly communicate, so I avoided creating get*** for that 15 | * Memory and keyboard are public as the CPU can directlu 16 | * access it and the GPU can read from it. 17 | * Nobody can access the CPU but the bus via tick, the same for the GPU. 18 | */ 19 | pub struct Bus{ 20 | cpu: CPU, 21 | pub memory: MemoryMap, 22 | pub keyboard: Keyboard, 23 | 24 | pub state: BusState, 25 | } 26 | 27 | impl Bus{ 28 | pub fn new(file: &str) -> Bus { 29 | Bus { 30 | cpu: CPU::new(), 31 | memory: MemoryMap::new(file), 32 | keyboard: Keyboard::new(), 33 | state: BusState { 34 | delay: 0, 35 | sound: 0, 36 | lock_until_pressed: false, 37 | write_to: 0x0, 38 | }, 39 | } 40 | } 41 | 42 | pub fn was_screen_updated(&self) -> bool { 43 | self.memory.was_screen_updated() 44 | } 45 | 46 | pub fn get_vram(&self) -> &BitMatrix { 47 | self.memory.get_full_vram() 48 | } 49 | 50 | pub fn tick_frame_timer(&mut self) { 51 | if self.state.delay > 0 { 52 | self.state.delay = self.state.delay - 1; 53 | } 54 | if self.state.sound > 0 { 55 | self.state.sound = self.state.sound - 1; 56 | } 57 | } 58 | 59 | pub fn tick_frame_cpu(&mut self) { 60 | if self.keyboard.was_any_key_pressed() && self.state.lock_until_pressed { 61 | self.state.lock_until_pressed = false; 62 | 63 | let last_key_pressed = self.keyboard.get_last_pressed_key(); 64 | self.cpu.write_key_to(self.state.write_to as usize, last_key_pressed); 65 | } 66 | 67 | if self.state.lock_until_pressed { 68 | return; 69 | } 70 | //four clocks per frame 71 | //This looks ugly, needs to revisit this once I learn more about the language 72 | self.cpu.execute_next_instruction( 73 | &mut self.memory, 74 | &mut self.keyboard, 75 | &mut self.state 76 | ); 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/busstate.rs: -------------------------------------------------------------------------------- 1 | pub struct BusState{ 2 | //Decrement at 60hz 3 | pub delay:u8, 4 | //Beeps if not 8 5 | pub sound:u8, 6 | 7 | pub write_to: u8, 8 | //stops processing until a key is pressed 9 | pub lock_until_pressed: bool, 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/cpu.rs: -------------------------------------------------------------------------------- 1 | extern crate bit_vec; 2 | 3 | use std::process; 4 | use rand::Rng; 5 | 6 | use crate::busstate::BusState; 7 | use crate::memory::MemoryMap; 8 | use crate::keyboard::Keyboard; 9 | 10 | use bit_vec::BitVec; 11 | 12 | pub struct CPU { 13 | sp:u16, 14 | pc:u16, 15 | 16 | //16 V registers (The doc is confusing 17 | //it states 0..F and talks about VF, so its 17 18 | //to make things easier I will create 17 elements 19 | //instead of 16 20 | v: [u8; 0x10], 21 | //Single I register 22 | i: u16, 23 | //Stack in CHIP-8 is 24 | //limited to 16 elements 25 | stack: [u16; 0x10], 26 | } 27 | 28 | enum PcIncrement { 29 | SINGLE, 30 | SKIP, 31 | NONE, 32 | } 33 | 34 | impl CPU { 35 | pub fn new() -> CPU { 36 | CPU{ 37 | sp: 0, 38 | pc: 0x200, 39 | v: [0; 0x10], 40 | i: 0, 41 | stack: [0; 0x10], 42 | } 43 | } 44 | 45 | fn get_val_from_opcode(opcode : u16, pos : u8) -> usize { 46 | (opcode >> (pos * 4) & 0xF) as usize 47 | } 48 | 49 | fn push_pc_to_stack(&mut self) { 50 | self.stack[self.sp as usize] = self.pc; 51 | self.sp += 1; 52 | } 53 | 54 | fn pop_pc_from_stack(&mut self) { 55 | self.sp -= 1; 56 | self.pc = self.stack[self.sp as usize]; 57 | } 58 | 59 | pub fn execute_next_instruction(&mut self, memory: &mut MemoryMap, keyboard: &mut Keyboard, state: &mut BusState) { 60 | // Opcodes are stored in 2 bytes 61 | let shifted_pc:u16 = (memory[self.pc] as u16) << 8; 62 | let opcode = shifted_pc | (memory[self.pc + 1] as u16); 63 | if opcode == 0x00E0 { 64 | memory.clear_vram(); 65 | 66 | } 67 | else if opcode == 0xEE { 68 | self.pop_pc_from_stack(); 69 | self.pc += 2; 70 | return; 71 | } 72 | 73 | // Most of instructions, beside 74 | // the above ones can be defined 75 | // by its first byte. 76 | let first_nibble:u8 = (opcode >> 12) as u8; 77 | /* Its quite common to have the 2nd nibble as 78 | * a V[x], so to make the code cleaner I do it here 79 | */ 80 | let regs = (CPU::get_val_from_opcode(opcode, 0), 81 | CPU::get_val_from_opcode(opcode, 1), 82 | CPU::get_val_from_opcode(opcode, 2)); 83 | 84 | let mut increment_type = PcIncrement::SINGLE; 85 | 86 | // I can probably do a expression-oriented style 87 | // for this here 88 | // (https://blog.rust-lang.org/2015/04/17/Enums-match-mutation-and-moves.html) 89 | // where each one is inside a function 90 | // Another solution would be to have a "function match" array, like on my GB emulator 91 | // But I avoided it just to do things differently here. 92 | match first_nibble { 93 | 0x1 => { 94 | self.pc = opcode & 0x0FFF; 95 | increment_type = PcIncrement::NONE; 96 | } 97 | 0x2 => { 98 | self.push_pc_to_stack(); 99 | self.pc = opcode & 0x0FFF; 100 | increment_type = PcIncrement::NONE; 101 | }, 102 | 0x3 => { 103 | let value = opcode & 0xFF; 104 | if u16::from(self.v[regs.2]) == value { 105 | increment_type = PcIncrement::SKIP; 106 | } 107 | }, 108 | 0x4 => { 109 | let value = (opcode & 0xFF) as u8; 110 | if self.v[regs.2] != value { 111 | increment_type = PcIncrement::SKIP; 112 | } 113 | }, 114 | 0x5 => { 115 | let last_octal = opcode & 0xF; 116 | if last_octal != 0x0 { 117 | CPU::abort(); 118 | } 119 | 120 | if self.v[regs.1] == self.v[regs.2] { 121 | increment_type = PcIncrement::SKIP; 122 | } 123 | }, 124 | 0x6 => { 125 | let value = (opcode & 0xFF) as u8; 126 | self.v[regs.2] = value; 127 | }, 128 | 0x7 => { 129 | let value = (opcode & 0xFF) as u8; 130 | self.v[regs.2] = self.v[regs.2].wrapping_add(value); 131 | }, 132 | 0x8 => { 133 | CPU::execute_instr_op_8(&mut self.v, opcode); 134 | }, 135 | 0x9 => { 136 | let last_octal = opcode & 0xF; 137 | if last_octal != 0x0 { 138 | CPU::abort(); 139 | } 140 | 141 | if self.v[regs.1] != self.v[regs.2] { 142 | increment_type = PcIncrement::SKIP; 143 | } 144 | }, 145 | 0xA => { 146 | self.i = opcode & 0xFFF; 147 | }, 148 | 0xB => { 149 | self.pc = (self.v[0x0] as u16) + (opcode & 0xFFF); 150 | return; 151 | }, 152 | 0xC => { 153 | let mut rng = rand::thread_rng(); 154 | let val:u8 = rng.gen(); 155 | self.v[regs.2] = val & (opcode & 0xFF) as u8; 156 | }, 157 | 0xD => { 158 | self.render_sprites_x_y(regs.2, regs.1, regs.0, memory); 159 | }, 160 | 0xE => { 161 | self.execute_instr_op_e(&mut increment_type, opcode, keyboard); 162 | }, 163 | 0xF => { 164 | self.execute_instr_op_f(opcode, memory, state); 165 | }, 166 | _ => { 167 | } 168 | } 169 | 170 | // Handle pc increment 171 | match increment_type { 172 | PcIncrement::SINGLE => self.pc += 2, 173 | PcIncrement::SKIP => self.pc += 4, 174 | PcIncrement::NONE => {}, 175 | } 176 | } 177 | 178 | /* Maybe move that into GPU in the future? */ 179 | fn render_sprites_x_y(&mut self, x:usize, y:usize, n:usize, memory: &mut MemoryMap) { 180 | // Initial position warp, but, if it starts at 63 we dont warp 181 | // further pixel writes 182 | let x_pos = (self.v[x] % 64) as usize; 183 | let y_pos = (self.v[y] % 32) as usize; 184 | let height = n; 185 | 186 | self.v[0xF] = 0; 187 | 188 | for y in 0..height { 189 | let byte = memory[self.i + y as u16]; 190 | let pixel_vec = BitVec::from_bytes(&[byte]); 191 | let target_y = y + y_pos; 192 | for x in 0..8 { 193 | let mut bit_goal = false; 194 | let target_x = x + x_pos; 195 | // We only warp at the start (we break the loop and 196 | // avoid warp here. 197 | if target_x >= 64 || target_y >= 32 { 198 | break 199 | } 200 | 201 | let pixel = pixel_vec[x] as bool; 202 | let is_set = memory.get_vram(target_x, target_y); 203 | 204 | /* Weird pixel set behavior 205 | * 206 | * If the current pixel in the sprite row is on and the pixel at 207 | * coordinates X,Y on the screen is also on, turn off the pixel 208 | * and set VF to 1 209 | * 210 | * Or if the current pixel in the sprite row is on and the screen 211 | * pixel is not, draw the pixel at the X and Y coordinates 212 | */ 213 | if pixel { 214 | if is_set { 215 | self.v[0xF] = 1; 216 | } else { 217 | bit_goal = true; 218 | } 219 | } else if is_set { 220 | bit_goal = true; 221 | } 222 | 223 | /* We only redraw the screen if there 224 | * is a pending update. 225 | * While the main code will redraw everything 226 | * and we could improve that, this will be 227 | * left as it is now. 228 | */ 229 | if bit_goal != is_set { 230 | memory.pending_screen_update(true); 231 | } 232 | 233 | /* Set the bit on vram */ 234 | memory.set_vram(target_x, target_y, bit_goal); 235 | } 236 | } 237 | 238 | } 239 | 240 | fn abort() { 241 | println!("Invalid instruction"); 242 | process::abort(); 243 | } 244 | 245 | fn execute_instr_op_8(v: &mut [u8; 0x10], opcode:u16) { 246 | let op8 = CPU::get_val_from_opcode(opcode, 0); 247 | let regs = (CPU::get_val_from_opcode(opcode, 2), 248 | CPU::get_val_from_opcode(opcode, 1)); 249 | match op8 { 250 | 0x0 => v[regs.0] = v[regs.1], 251 | 0x1 => v[regs.0] = v[regs.0] | v[regs.1], 252 | 0x2 => v[regs.0] = v[regs.0] & v[regs.1], 253 | 0x3 => v[regs.0] = v[regs.0] ^ v[regs.1], 254 | 0x4 => { 255 | let tmp_sum = (v[regs.0] as u16 + v[regs.1] as u16) as u16; 256 | v[0xF] = (tmp_sum > 0xFF) as u8; 257 | v[regs.0] = tmp_sum as u8; 258 | }, 259 | 0x5 => { 260 | v[0xF] = (v[regs.0] > v[regs.1]) as u8; 261 | v[regs.0] = v[regs.0].wrapping_sub(v[regs.1]); 262 | }, 263 | 0x6 => { 264 | v[0xF] = (v[regs.0] & 0x1) as u8; 265 | v[regs.0] = v[regs.0] >> 1; 266 | }, 267 | 0x7 => { 268 | v[0xF] = (v[regs.1] > v[regs.0]) as u8; 269 | v[regs.1] = v[regs.1].wrapping_sub(v[regs.0]); 270 | }, 271 | /* No 0x8..0xC */ 272 | 0xE => { 273 | v[0xF] = ((v[regs.0] >> 7) & 0x1) as u8; 274 | v[regs.0] = v[regs.0] << 1; 275 | }, 276 | _ => { 277 | process::abort(); 278 | } 279 | } 280 | } 281 | 282 | fn execute_instr_op_e(&mut self, increment_type: &mut PcIncrement, opcode:u16, keyboard: &mut Keyboard) { 283 | let sub_op_code = opcode & 0xFF; 284 | let reg = CPU::get_val_from_opcode(opcode, 2); 285 | match sub_op_code { 286 | // Self Keyboard 287 | 0x9E => { 288 | if keyboard.is_key_pressed(self.v[reg] as usize) { 289 | *increment_type = PcIncrement::SKIP; 290 | } 291 | }, 292 | 0xA1 => { 293 | if !keyboard.is_key_pressed(self.v[reg] as usize) { 294 | *increment_type = PcIncrement::SKIP; 295 | } 296 | }, 297 | _ => { 298 | CPU::abort(); 299 | } 300 | } 301 | } 302 | 303 | fn execute_instr_op_f(&mut self, opcode:u16, memory: &mut MemoryMap, state: &mut BusState) { 304 | let sub_op_code = opcode & 0xFF; 305 | let reg = CPU::get_val_from_opcode(opcode, 2); 306 | match sub_op_code { 307 | 0x07 => self.v[reg] = state.delay, 308 | 0x0A => { 309 | state.lock_until_pressed = true; 310 | state.write_to = reg as u8; 311 | }, 312 | 0x15 => state.delay = self.v[reg], 313 | 0x18 => state.sound = self.v[reg], 314 | 0x1E => { 315 | let tmp_sum = (u16::from(self.v[reg]) + self.i) as u16; 316 | self.v[0xF] = (tmp_sum > 0xFFF) as u8; 317 | self.i = tmp_sum & 0xFFF; 318 | }, 319 | 0x29 => { 320 | //The opcode contains the memory location for the index of the char 321 | //Each char has 5 bytes, so we get the position and multiply by 5 322 | self.i = u16::from(self.v[reg]) * 5; 323 | }, 324 | 0x33 => { 325 | let mut val = self.v[reg]; 326 | /* 327 | * Run in inverse order 328 | * 156 should be stored, for example 329 | * as 1, 5, 6 ON [2, 1, 0] 330 | */ 331 | for idx in (0..3).rev() { 332 | let current_pos = (self.i + idx) as u16; 333 | memory[current_pos] = val%10; 334 | val = val/10; 335 | } 336 | }, 337 | 0x55 => { 338 | /* 339 | * The interpreter copies the values of registers V0 through Vx into memory, 340 | * starting at the address in I. 341 | */ 342 | let limit:u16 = reg as u16 + 1; 343 | for idx in 0x0..limit { 344 | let current_pos = (self.i + idx) as u16; 345 | memory[current_pos] = self.v[idx as usize]; 346 | } 347 | }, 348 | 0x65 => { 349 | let limit:u16 = reg as u16 + 1; 350 | for idx in 0x0..limit { 351 | let current_pos = (self.i + idx) as u16; 352 | self.v[idx as usize] = memory[current_pos]; 353 | } 354 | 355 | }, 356 | _ => { 357 | CPU::abort(); 358 | } 359 | } 360 | } 361 | 362 | pub fn write_key_to(&mut self, reg: usize, key: u8) { 363 | self.v[reg] = key; 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/keyboard.rs: -------------------------------------------------------------------------------- 1 | pub struct Keyboard { 2 | keys: [State; 0x10], 3 | last_pressed_key: u8, 4 | key_pressed: bool, 5 | } 6 | 7 | #[derive(Clone, Copy, PartialEq)] 8 | pub enum State{ 9 | PRESSED, 10 | RELEASED, 11 | } 12 | 13 | impl Keyboard{ 14 | pub fn new() -> Keyboard { 15 | Keyboard { 16 | keys: [State::RELEASED; 0x10], 17 | key_pressed: false, 18 | last_pressed_key: 0, 19 | } 20 | } 21 | pub fn reset_key_press(&mut self) { 22 | self.key_pressed = false; 23 | } 24 | 25 | pub fn process_key (&mut self, key: usize, state: State) { 26 | if state == State::PRESSED { 27 | self.key_pressed = true; 28 | } 29 | 30 | self.last_pressed_key = key as u8; 31 | self.keys[key] = state; 32 | } 33 | 34 | pub fn was_any_key_pressed(&mut self) -> bool { 35 | self.key_pressed 36 | } 37 | 38 | pub fn is_key_pressed(&mut self, key: usize) -> bool { 39 | self.keys[key] == State::PRESSED 40 | } 41 | 42 | pub fn get_last_pressed_key(&mut self) -> u8 { 43 | self.last_pressed_key 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate sfml; 2 | 3 | mod bus; 4 | mod busstate; 5 | mod cpu; 6 | mod memory; 7 | mod keyboard; 8 | 9 | use keyboard::State; 10 | use sfml::window::{ContextSettings, Event, Style, Key}; 11 | use sfml::graphics::{Color, Image, RenderTarget, RenderWindow, Texture, Sprite}; 12 | use crate::bus::Bus; 13 | 14 | fn main() { 15 | let mut window = RenderWindow::new( 16 | //64 x 32 chip 8 resolution 17 | (64, 32), 18 | "RustyChip8 Emulator", 19 | Style::CLOSE, 20 | &ContextSettings::default(), 21 | ); 22 | let mut image = Image::new(64, 32); 23 | 24 | let mut bus = Bus::new("/Users/alexcpeixoto/developer/pong.ch8"); 25 | let color = Color::rgb(0, 127, 0); 26 | let black = Color::rgb(9, 0, 0); 27 | 28 | window.set_framerate_limit(60); 29 | window.set_size((1280, 640)); 30 | 31 | while window.is_open() { 32 | // Reset key state for this frame 33 | bus.keyboard.reset_key_press(); 34 | 35 | while let Some(event) = window.poll_event() { 36 | // Request closing for the window 37 | if event == Event::Closed { 38 | window.close(); 39 | } 40 | match event { 41 | Event::KeyPressed {code, ..} => process_keys(code, &mut bus.keyboard, State::PRESSED), 42 | Event::KeyReleased{code, ..} => process_keys(code, &mut bus.keyboard, State::RELEASED), 43 | _ => {}, 44 | }; 45 | } 46 | for _tick in 0..5 { 47 | bus.tick_frame_cpu(); 48 | } 49 | let screen_updated = bus.was_screen_updated(); 50 | if screen_updated { 51 | let vram = bus.get_vram(); 52 | for i in 0..64 { 53 | for j in 0..32 { 54 | if vram[(i, j)] { 55 | image.set_pixel(i as u32, j as u32, color); 56 | } else { 57 | image.set_pixel(i as u32, j as u32, black); 58 | } 59 | } 60 | } 61 | } 62 | let texture = Texture::from_image(&image).unwrap(); 63 | let sprite = Sprite::with_texture(&texture); 64 | window.set_active(true); 65 | window.draw(&sprite); 66 | window.display(); 67 | 68 | bus.tick_frame_timer(); 69 | } 70 | } 71 | 72 | fn process_keys(key_ev: Key , keyboard: &mut crate::keyboard::Keyboard, state: State) { 73 | let key_pair = [(Key::Num1, 0x1), (Key::Num2, 0x2), (Key::Num3, 0x3), 74 | (Key::Num4, 0xC), (Key::Q, 0x4), (Key::W, 0x5), 75 | (Key::E, 0x6), (Key::R, 0xD), (Key::A, 0x7), 76 | (Key::S, 0x8), (Key::D, 0x9), (Key::F, 0xE), 77 | (Key::Z, 0xA), (Key::X, 0x0), (Key::C, 0xB), 78 | (Key::V, 0xF)]; 79 | 80 | for (key, target) in key_pair.iter() { 81 | if key_ev == *key { 82 | keyboard.process_key(*target, state) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/memory.rs: -------------------------------------------------------------------------------- 1 | extern crate byteorder; 2 | extern crate bitmatrix; 3 | 4 | use std::fs::File; 5 | use std::io::Read; 6 | use bitmatrix::BitMatrix; 7 | 8 | use std::ops::{Index, IndexMut}; 9 | 10 | pub struct MemoryMap { 11 | /* 12 | Memory Map: 13 | +---------------+= 0xFFF (4095) End of Chip-8 RAM 14 | | | 15 | | | 16 | | | 17 | | | 18 | | | 19 | | 0x200 to 0xFFF| 20 | | Chip-8 | 21 | | Program / Data| 22 | | Space | 23 | | | 24 | | | 25 | | | 26 | +- - - - - - - -+= 0x600 (1536) Start of ETI 660 Chip-8 programs 27 | | | 28 | | | 29 | | | 30 | +---------------+= 0x200 (512) Start of most Chip-8 programs 31 | | 0x000 to 0x1FF| 32 | | Reserved for | 33 | | interpreter | 34 | +---------------+= 0x000 (0) Start of Chip-8 RAM 35 | */ 36 | memory: [u8; 0xFFF], 37 | rom_name: String, 38 | vram: BitMatrix, 39 | vram_changed: bool, 40 | } 41 | 42 | impl Index for MemoryMap { 43 | type Output = u8; 44 | fn index<'a>(&'a self, i: u16) -> &'a u8 { 45 | &self.memory[i as usize] 46 | } 47 | } 48 | 49 | impl IndexMut for MemoryMap{ 50 | fn index_mut<'a>(&'a mut self, i: u16) -> &'a mut u8 { 51 | &mut self.memory[i as usize] 52 | } 53 | } 54 | 55 | impl MemoryMap { 56 | pub fn new(rom_name: &str) -> Self { 57 | //this is actually returning a new instance 58 | let mut to_ret = Self { 59 | memory: [0; 0xFFF], 60 | //To preserve X, Y, "order" the matrix 61 | //is "inverted", just to avoid confusion later 62 | vram: BitMatrix::new(64, 32), 63 | vram_changed: false, 64 | rom_name: rom_name.to_owned(), 65 | }; 66 | 67 | for i in 0..64 { 68 | for j in 0..32 { 69 | to_ret.vram.set((i, j), false); 70 | } 71 | } 72 | 73 | to_ret.init_font(); 74 | to_ret.load_rom(); 75 | to_ret 76 | } 77 | 78 | fn init_font(&mut self) { 79 | let font_default = [ 80 | 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 81 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 82 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 83 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 84 | 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 85 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 86 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 87 | 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 88 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 89 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 90 | 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 91 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 92 | 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 93 | 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 94 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 95 | 0xF0, 0x80, 0xF0, 0x80, 0x80, // F 96 | ]; 97 | for idx in 0..80 { 98 | self.memory[idx] = font_default[idx]; 99 | } 100 | } 101 | 102 | fn load_rom(&mut self) { 103 | let slice = &mut self.memory[0x200..0xFFF]; 104 | 105 | let mut rom = File::open(&self.rom_name).expect("Could not open file"); 106 | 107 | rom.read(&mut slice[..]).expect("Failed to load rom data"); 108 | } 109 | 110 | pub fn get_vram(&self, x: usize, y: usize) -> bool { 111 | self.vram[(x, y)] 112 | } 113 | 114 | pub fn get_full_vram(&self) -> &BitMatrix { 115 | &self.vram 116 | } 117 | 118 | pub fn set_vram(&mut self, x: usize, y: usize, set: bool) { 119 | self.vram.set((x, y), set); 120 | } 121 | 122 | pub fn pending_screen_update(&mut self, updated: bool) { 123 | self.vram_changed = updated; 124 | } 125 | 126 | pub fn was_screen_updated(&self) -> bool { 127 | self.vram_changed.clone() 128 | } 129 | 130 | pub fn clear_vram(&mut self) { 131 | for i in 0..64 { 132 | for j in 0..32 { 133 | self.vram.set((i, j), false); 134 | } 135 | } 136 | self.vram_changed = true; 137 | } 138 | } 139 | --------------------------------------------------------------------------------