├── .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 |
--------------------------------------------------------------------------------