Chip8-em init commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
1469
Cargo.lock
generated
Normal file
1469
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "chip-8-em"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5", features = ["derive"]}
|
||||
raylib = "5.5.1"
|
||||
rand = "0.9"
|
||||
rodio = "0.21"
|
||||
BIN
assets/pluck.wav
Normal file
BIN
assets/pluck.wav
Normal file
Binary file not shown.
BIN
programs/1-chip8-logo.ch8
Normal file
BIN
programs/1-chip8-logo.ch8
Normal file
Binary file not shown.
BIN
programs/2-ibm-logo.ch8
Normal file
BIN
programs/2-ibm-logo.ch8
Normal file
Binary file not shown.
BIN
programs/4-flags.ch8
Normal file
BIN
programs/4-flags.ch8
Normal file
Binary file not shown.
BIN
programs/5-quirks.ch8
Normal file
BIN
programs/5-quirks.ch8
Normal file
Binary file not shown.
BIN
programs/6-keypad.ch8
Normal file
BIN
programs/6-keypad.ch8
Normal file
Binary file not shown.
BIN
programs/7-beep.ch8
Normal file
BIN
programs/7-beep.ch8
Normal file
Binary file not shown.
BIN
programs/Airplane.ch8
Normal file
BIN
programs/Airplane.ch8
Normal file
Binary file not shown.
BIN
programs/Nim [Carmelo Cortez, 1978].ch8
Normal file
BIN
programs/Nim [Carmelo Cortez, 1978].ch8
Normal file
Binary file not shown.
BIN
programs/br8kout.ch8
Normal file
BIN
programs/br8kout.ch8
Normal file
Binary file not shown.
BIN
programs/corax.ch8
Normal file
BIN
programs/corax.ch8
Normal file
Binary file not shown.
BIN
programs/snake.ch8
Normal file
BIN
programs/snake.ch8
Normal file
Binary file not shown.
BIN
programs/test.ch8
Normal file
BIN
programs/test.ch8
Normal file
Binary file not shown.
149
src/chip8.rs
Normal file
149
src/chip8.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
mod cpu;
|
||||
mod debug;
|
||||
mod gpu;
|
||||
mod input;
|
||||
mod memory;
|
||||
mod renderer;
|
||||
|
||||
use rodio::source::{SineWave, Source};
|
||||
|
||||
static MEMORY_LIMIT: i32 = 4096;
|
||||
static STACK_LIMIT: i32 = 16;
|
||||
static VARIABLE_REGISTER_COUNT: i32 = 16;
|
||||
static TIMER_TICK_RATE: u32 = 60;
|
||||
static DESIRED_FPS: u32 = 165;
|
||||
static CYCLES_PER_FRAME: u32 = 10;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Chip8State {
|
||||
// Flags
|
||||
eti_600_flag: bool,
|
||||
|
||||
// Memory
|
||||
mem: [u8; MEMORY_LIMIT as usize],
|
||||
stack: [u16; STACK_LIMIT as usize],
|
||||
|
||||
// Registers
|
||||
r_v: [u8; VARIABLE_REGISTER_COUNT as usize], // General Purpose
|
||||
r_i: u16, // Memory Addresses
|
||||
r_dt: u8, // Delay Timer
|
||||
r_st: u8, // Sound Timer
|
||||
r_pc: u16, // Program Counter
|
||||
r_sp: u8, // Stack Pointer
|
||||
|
||||
// Display
|
||||
display: [[bool; gpu::CHIP8_DISPLAY_WIDTH as usize]; gpu::CHIP8_DISPLAY_HEIGHT as usize],
|
||||
|
||||
// Input
|
||||
input: [bool; 16],
|
||||
}
|
||||
|
||||
pub fn run<S: AsRef<str>>(chip8_executable_filepath: S, debug_mode: bool) {
|
||||
let mut state = Chip8State {
|
||||
eti_600_flag: false,
|
||||
mem: [0; 4096],
|
||||
stack: [0; 16],
|
||||
r_v: [0; 16],
|
||||
r_i: 0,
|
||||
r_dt: 2,
|
||||
r_st: 0,
|
||||
r_pc: 0,
|
||||
r_sp: 0,
|
||||
display: [[false; 64]; 32],
|
||||
input: [false; 16],
|
||||
};
|
||||
|
||||
if !state.eti_600_flag {
|
||||
state.r_pc = 0x200;
|
||||
} else {
|
||||
state.r_pc = 0x600;
|
||||
}
|
||||
|
||||
// Load Program
|
||||
let _ = memory::load_file_to_memory(&mut state, chip8_executable_filepath.as_ref());
|
||||
|
||||
// Run Program
|
||||
start(&mut state, debug_mode);
|
||||
}
|
||||
|
||||
fn start(state: &mut Chip8State, debug_mode: bool) {
|
||||
// TODO rip out as much RL stuff from here and put into renderer
|
||||
// Init Rendering Pipeline
|
||||
let (mut rl, thread) = raylib::init()
|
||||
.size(renderer::DISPLAY_WIDTH, renderer::DISPLAY_HEIGHT)
|
||||
.title("Chip8 Emu")
|
||||
.build();
|
||||
rl.set_target_fps(DESIRED_FPS); // Should see if i can get the users hz
|
||||
if !debug_mode {
|
||||
rl.set_trace_log(raylib::ffi::TraceLogLevel::LOG_NONE);
|
||||
}
|
||||
|
||||
// initialize timer
|
||||
let mut timer_accumulator: f32 = 0.0f32;
|
||||
let timer_increment: f32 = TIMER_TICK_RATE as f32 / DESIRED_FPS as f32;
|
||||
|
||||
// initialize builtin sprites
|
||||
gpu::load_builtin_sprites(state);
|
||||
|
||||
// initialize sound system look into struct and impl for functions
|
||||
let stream_handle =
|
||||
rodio::OutputStreamBuilder::open_default_stream().expect("open default audio stream");
|
||||
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
|
||||
|
||||
let source = SineWave::new(440.0)
|
||||
.amplify(0.2) // Volume (0.0 to 1.0)
|
||||
.repeat_infinite();
|
||||
|
||||
sink.append(source);
|
||||
sink.play();
|
||||
|
||||
while !rl.window_should_close() {
|
||||
if state.r_pc > 4095 {
|
||||
break;
|
||||
}
|
||||
|
||||
let sound_volume = sink.volume();
|
||||
if state.r_st > 0 {
|
||||
if sound_volume < 1.0f32 {
|
||||
sink.set_volume(0.2f32);
|
||||
}
|
||||
} else {
|
||||
sink.set_volume(0.0f32);
|
||||
}
|
||||
|
||||
input::handle_input(state, &mut rl);
|
||||
|
||||
for _ in 0..CYCLES_PER_FRAME {
|
||||
let instruction_bytes =
|
||||
memory::read_n_bytes(&state.mem, state.mem.len(), state.r_pc as usize, 2);
|
||||
let instruction: u16 =
|
||||
((instruction_bytes[0] as u16) << 8) | instruction_bytes[1] as u16;
|
||||
state.r_pc += 2;
|
||||
|
||||
if debug_mode {
|
||||
debug::print_debug(state, instruction);
|
||||
}
|
||||
|
||||
cpu::execute_instruction(state, instruction);
|
||||
}
|
||||
|
||||
// move to timers.rs
|
||||
// timers::tick();
|
||||
if (state.r_dt | state.r_st) > 0 {
|
||||
timer_accumulator += timer_increment;
|
||||
while timer_accumulator >= 1.0f32 {
|
||||
if state.r_dt > 0 {
|
||||
state.r_dt -= 1;
|
||||
};
|
||||
if state.r_st > 0 {
|
||||
state.r_st -= 1;
|
||||
}
|
||||
timer_accumulator -= 1.0f32;
|
||||
}
|
||||
} else {
|
||||
timer_accumulator = 0.0f32;
|
||||
}
|
||||
|
||||
renderer::render(&state, &mut rl, &thread);
|
||||
}
|
||||
}
|
||||
159
src/chip8/cpu.rs
Normal file
159
src/chip8/cpu.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use crate::chip8::gpu;
|
||||
|
||||
use super::Chip8State;
|
||||
use super::memory::read_n_bytes;
|
||||
|
||||
// Need to come back and add good comments for each of the match patterns
|
||||
pub fn execute_instruction(state: &mut Chip8State, instruction: u16) {
|
||||
let c = ((instruction & 0xF000) >> 12) as u8;
|
||||
let x = ((instruction & 0x0F00) >> 8) as u8;
|
||||
let y = ((instruction & 0x00F0) >> 4) as u8;
|
||||
let d = (instruction & 0x000F) as u8;
|
||||
let n = d;
|
||||
let kk = (instruction & 0x00FF) as u8;
|
||||
let nnn = (instruction & 0x0FFF) as u16;
|
||||
|
||||
match (c, x, y, d) {
|
||||
(0x0, _, 0xE, 0x0) => {
|
||||
for row in state.display.iter_mut() {
|
||||
for col in row {
|
||||
*col = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
(0x0, _, 0xE, 0xE) => {
|
||||
state.r_sp -= 1;
|
||||
state.r_pc = state.stack[state.r_sp as usize];
|
||||
}
|
||||
(0x1, _, _, _) => state.r_pc = nnn,
|
||||
(0x2, _, _, _) => {
|
||||
state.stack[state.r_sp as usize] = state.r_pc;
|
||||
state.r_sp += 1;
|
||||
state.r_pc = nnn;
|
||||
}
|
||||
(0x3, _, _, _) => {
|
||||
if state.r_v[x as usize] == kk {
|
||||
state.r_pc += 2;
|
||||
}
|
||||
}
|
||||
(0x4, _, _, _) => {
|
||||
if state.r_v[x as usize] != kk {
|
||||
state.r_pc += 2;
|
||||
}
|
||||
}
|
||||
(0x5, _, _, _) => {
|
||||
if state.r_v[x as usize] == state.r_v[y as usize] {
|
||||
state.r_pc += 2;
|
||||
}
|
||||
}
|
||||
(0x6, _, _, _) => state.r_v[x as usize] = kk,
|
||||
(0x7, _, _, _) => state.r_v[x as usize] = state.r_v[x as usize].wrapping_add(kk),
|
||||
(0x8, _, _, 0x0) => state.r_v[x as usize] = state.r_v[y as usize],
|
||||
(0x8, _, _, 0x1) => state.r_v[x as usize] |= state.r_v[y as usize],
|
||||
(0x8, _, _, 0x2) => state.r_v[x as usize] &= state.r_v[y as usize],
|
||||
(0x8, _, _, 0x3) => state.r_v[x as usize] ^= state.r_v[y as usize],
|
||||
(0x8, _, _, 0x4) => {
|
||||
let val: u16 = state.r_v[x as usize] as u16 + state.r_v[y as usize] as u16;
|
||||
if val > u8::MAX as u16 {
|
||||
state.r_v[x as usize] = (val & 0xFFFF) as u8;
|
||||
state.r_v[0xF] = 1;
|
||||
} else {
|
||||
state.r_v[x as usize] = val as u8;
|
||||
state.r_v[0xF] = 0;
|
||||
}
|
||||
}
|
||||
(0x8, _, _, 0x5) => {
|
||||
let flag = state.r_v[x as usize] > state.r_v[y as usize];
|
||||
state.r_v[x as usize] = state.r_v[x as usize].wrapping_sub(state.r_v[y as usize]);
|
||||
state.r_v[0xF] = if flag { 1 } else { 0 };
|
||||
}
|
||||
(0x8, _, _, 0x6) => {
|
||||
let flag = (state.r_v[x as usize] & 0b00000001) == 1;
|
||||
state.r_v[0xF] = if flag { 1 } else { 0 };
|
||||
state.r_v[x as usize] = state.r_v[x as usize] / 2;
|
||||
}
|
||||
(0x8, _, _, 0x7) => {
|
||||
let flag = state.r_v[x as usize] < state.r_v[y as usize];
|
||||
state.r_v[x as usize] = state.r_v[y as usize].wrapping_sub(state.r_v[x as usize]);
|
||||
state.r_v[0xF] = if flag { 1 } else { 0 };
|
||||
}
|
||||
(0x8, _, _, 0xE) => {
|
||||
let flag = ((state.r_v[x as usize] & 0b10000000) >> 7) == 1;
|
||||
state.r_v[0xF] = if flag { 1 } else { 0 };
|
||||
state.r_v[x as usize] = state.r_v[x as usize].wrapping_mul(2);
|
||||
}
|
||||
(0x9, _, _, _) => {
|
||||
if state.r_v[x as usize] != state.r_v[y as usize] {
|
||||
state.r_pc += 2
|
||||
}
|
||||
}
|
||||
(0xA, _, _, _) => state.r_i = nnn,
|
||||
(0xB, _, _, _) => state.r_pc = nnn + state.r_v[0] as u16,
|
||||
(0xC, _, _, _) => {
|
||||
let rng = rand::random_range(0..256) as u8;
|
||||
let result = rng & kk;
|
||||
state.r_v[x as usize] = result;
|
||||
}
|
||||
(0xD, _, _, _) => {
|
||||
state.r_v[0xF] = 0;
|
||||
let bytes = read_n_bytes(&state.mem, state.mem.len(), state.r_i as usize, n as usize);
|
||||
gpu::draw(state, x, y, &bytes, n);
|
||||
}
|
||||
(0xE, _, _, 0xE) => {
|
||||
let key_index = state.r_v[x as usize];
|
||||
if state.input[key_index as usize] {
|
||||
state.r_pc += 2;
|
||||
}
|
||||
}
|
||||
(0xE, _, _, 0x1) => {
|
||||
let key_index = state.r_v[x as usize];
|
||||
if !state.input[key_index as usize] {
|
||||
state.r_pc += 2;
|
||||
}
|
||||
}
|
||||
(0xF, _, 0x0, 0x7) => state.r_v[x as usize] = state.r_dt,
|
||||
(0xF, _, 0x0, 0xA) => {
|
||||
let mut is_key_pressed = false;
|
||||
for i in 0..state.input.len() {
|
||||
if state.input[i] {
|
||||
state.r_v[x as usize] = i as u8;
|
||||
is_key_pressed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !is_key_pressed {
|
||||
state.r_pc -= 2; // set the pc back to this instruction, otherwise can't handle input
|
||||
}
|
||||
}
|
||||
(0xF, _, 0x1, 0x5) => state.r_dt = state.r_v[x as usize],
|
||||
(0xF, _, 0x1, 0x8) => state.r_st = state.r_v[x as usize],
|
||||
(0xF, _, 0x1, 0xE) => state.r_i = state.r_i + state.r_v[x as usize] as u16,
|
||||
(0xF, _, 0x2, 0x9) => state.r_i = gpu::get_builtin_sprite_addr(x) as u16,
|
||||
(0xF, _, 0x3, 0x3) => {
|
||||
let mut decimal = state.r_v[x as usize];
|
||||
let mut i = 3;
|
||||
loop {
|
||||
i = i - 1;
|
||||
state.mem[(state.r_i + i) as usize] = decimal % 10;
|
||||
decimal = decimal / 10;
|
||||
|
||||
if i == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
(0xF, _, _, 0x5) => {
|
||||
let mut i = 0;
|
||||
while i <= x {
|
||||
match y {
|
||||
0x5 => state.mem[(state.r_i + i as u16) as usize] = state.r_v[i as usize],
|
||||
0x6 => state.r_v[i as usize] = state.mem[(state.r_i + i as u16) as usize],
|
||||
_ => panic!("Unmatched OPCODE 0xFx{}5", y),
|
||||
}
|
||||
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
27
src/chip8/debug.rs
Normal file
27
src/chip8/debug.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::io::{self, Read};
|
||||
|
||||
use super::Chip8State;
|
||||
|
||||
pub fn print_debug(state: &Chip8State, current_instruction: u16) {
|
||||
print!("\x1b[H\x1b[J");
|
||||
println!("---- DEBUG ----");
|
||||
println!("PC: {:04X}", state.r_pc - 2); // -2 because of where i put this log in the main loop
|
||||
println!("SP: {}", state.r_sp);
|
||||
println!("I: {:04X}", state.r_i);
|
||||
println!("DT: {}", state.r_dt);
|
||||
println!("ST: {}", state.r_st);
|
||||
for x in 0..state.r_v.len() {
|
||||
if state.r_v[x] > 0 {
|
||||
println!("V{}: {}", x, state.r_v[x]);
|
||||
}
|
||||
}
|
||||
for x in 0..state.input.len() {
|
||||
if state.input[x] == true {
|
||||
println!("Pressed: {}", x);
|
||||
}
|
||||
}
|
||||
println!("Current Instruction: {:04X}", current_instruction);
|
||||
println!("----------------");
|
||||
println!("Press Enter to continue...");
|
||||
let _ = io::stdin().read(&mut [0u8]).unwrap();
|
||||
}
|
||||
135
src/chip8/gpu.rs
Normal file
135
src/chip8/gpu.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use crate::chip8::memory;
|
||||
|
||||
use super::Chip8State;
|
||||
|
||||
pub static CHIP8_DISPLAY_WIDTH: i32 = 64;
|
||||
pub static CHIP8_DISPLAY_HEIGHT: i32 = 32;
|
||||
pub static SPRITE_WIDTH: u8 = 8;
|
||||
|
||||
// I probably don't need state here
|
||||
// refactor to just receive a mutatable buffer
|
||||
pub fn draw(state: &mut Chip8State, vx: u8, vy: u8, bytes_to_draw: &[u8], bytes_to_draw_len: u8) {
|
||||
let mut bytes_idx = 0;
|
||||
let start_x = state.r_v[vx as usize];
|
||||
let start_y = state.r_v[vy as usize];
|
||||
for y in start_y..start_y + bytes_to_draw_len {
|
||||
if (y as i32) < CHIP8_DISPLAY_HEIGHT {
|
||||
// 8 is the hardcoded sprite width, has to atleast have 1 8 bit value to display
|
||||
let mut bit_idx = 0;
|
||||
for x in start_x..start_x + SPRITE_WIDTH {
|
||||
if (x as i32) < CHIP8_DISPLAY_WIDTH {
|
||||
let sprite_pixel = ((bytes_to_draw[bytes_idx] >> (7 - bit_idx)) & 1) == 1;
|
||||
let current_pixel = state.display[y as usize][x as usize];
|
||||
let new_pixel = current_pixel ^ sprite_pixel;
|
||||
|
||||
state.display[y as usize][x as usize] = new_pixel;
|
||||
|
||||
if new_pixel != current_pixel {
|
||||
state.r_v[0xF] = 1;
|
||||
}
|
||||
|
||||
bit_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
bytes_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_builtin_sprites(state: &mut Chip8State) {
|
||||
memory::load_bytes(
|
||||
state,
|
||||
&BUILTIN_SPIRTES,
|
||||
BUILTIN_SPIRTES.len(),
|
||||
BUILTIN_SPRITES_ADDR as usize,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn get_builtin_sprite_addr(sprite_index: u8) -> u8 {
|
||||
return BUILTIN_SPRITES_ADDR + (sprite_index * BUILTIN_SPRITES_SIZE);
|
||||
}
|
||||
|
||||
static BUILTIN_SPRITES_ADDR: u8 = 0;
|
||||
static BUILTIN_SPRITES_SIZE: u8 = 5;
|
||||
static BUILTIN_SPIRTES: [u8; 80] = [
|
||||
0xF0, // 0
|
||||
0x90, // 0
|
||||
0x90, // 0
|
||||
0x90, // 0
|
||||
0xF0, // 0
|
||||
0x20, // 1
|
||||
0x60, // 1
|
||||
0x20, // 1
|
||||
0x20, // 1
|
||||
0x70, // 1
|
||||
0xF0, // 2
|
||||
0x10, // 2
|
||||
0xF0, // 2
|
||||
0x80, // 2
|
||||
0xF0, // 2
|
||||
0xF0, // 3
|
||||
0x10, // 3
|
||||
0xF0, // 3
|
||||
0x10, // 3
|
||||
0xF0, // 3
|
||||
0x90, // 4
|
||||
0x90, // 4
|
||||
0xF0, // 4
|
||||
0x10, // 4
|
||||
0x10, // 4
|
||||
0xF0, // 5
|
||||
0x80, // 5
|
||||
0xF0, // 5
|
||||
0x10, // 5
|
||||
0xF0, // 5
|
||||
0xF0, // 6
|
||||
0x80, // 6
|
||||
0xF0, // 6
|
||||
0x90, // 6
|
||||
0xF0, // 6
|
||||
0xF0, // 7
|
||||
0x10, // 7
|
||||
0x20, // 7
|
||||
0x40, // 7
|
||||
0x40, // 7
|
||||
0xF0, // 8
|
||||
0x90, // 8
|
||||
0xF0, // 8
|
||||
0x90, // 8
|
||||
0xF0, // 8
|
||||
0xF0, // 9
|
||||
0x90, // 9
|
||||
0xF0, // 9
|
||||
0x10, // 9
|
||||
0xF0, // 9
|
||||
0xF0, // A
|
||||
0x90, // A
|
||||
0xF0, // A
|
||||
0x90, // A
|
||||
0x90, // A
|
||||
0xE0, // B
|
||||
0x90, // B
|
||||
0xE0, // B
|
||||
0x90, // B
|
||||
0xE0, // B
|
||||
0xF0, // C
|
||||
0x80, // C
|
||||
0x80, // C
|
||||
0x80, // C
|
||||
0xF0, // C
|
||||
0xE0, // D
|
||||
0x90, // D
|
||||
0x90, // D
|
||||
0x90, // D
|
||||
0xE0, // D
|
||||
0xF0, // E
|
||||
0x80, // E
|
||||
0xF0, // E
|
||||
0x80, // E
|
||||
0xF0, // E
|
||||
0xF0, // F
|
||||
0x80, // F
|
||||
0xF0, // F
|
||||
0x80, // F
|
||||
0x80, // F
|
||||
];
|
||||
78
src/chip8/input.rs
Normal file
78
src/chip8/input.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use raylib::{RaylibHandle, ffi::KeyboardKey};
|
||||
|
||||
use super::Chip8State;
|
||||
|
||||
static RL_KEY_LAYOUT: [KeyboardKey; 16] = [
|
||||
KeyboardKey::KEY_ONE,
|
||||
KeyboardKey::KEY_TWO,
|
||||
KeyboardKey::KEY_THREE,
|
||||
KeyboardKey::KEY_FOUR,
|
||||
KeyboardKey::KEY_Q,
|
||||
KeyboardKey::KEY_W,
|
||||
KeyboardKey::KEY_E,
|
||||
KeyboardKey::KEY_R,
|
||||
KeyboardKey::KEY_A,
|
||||
KeyboardKey::KEY_S,
|
||||
KeyboardKey::KEY_D,
|
||||
KeyboardKey::KEY_F,
|
||||
KeyboardKey::KEY_Z,
|
||||
KeyboardKey::KEY_X,
|
||||
KeyboardKey::KEY_C,
|
||||
KeyboardKey::KEY_V,
|
||||
];
|
||||
|
||||
pub fn handle_input(state: &mut Chip8State, rl_hdl: &mut RaylibHandle) {
|
||||
for key in RL_KEY_LAYOUT {
|
||||
let mapped_key = qwerty_to_chip8(key);
|
||||
// let chip8_input_index = chip8_to_index(mapped_key);
|
||||
if rl_hdl.is_key_down(key) {
|
||||
state.input[mapped_key as usize] = true;
|
||||
} else {
|
||||
state.input[mapped_key as usize] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn qwerty_to_chip8(keycode: KeyboardKey) -> u8 {
|
||||
match keycode {
|
||||
KeyboardKey::KEY_ONE => return 0x1,
|
||||
KeyboardKey::KEY_TWO => return 0x2,
|
||||
KeyboardKey::KEY_THREE => return 0x3,
|
||||
KeyboardKey::KEY_FOUR => return 0xC,
|
||||
KeyboardKey::KEY_Q => return 0x4,
|
||||
KeyboardKey::KEY_W => return 0x5,
|
||||
KeyboardKey::KEY_E => return 0x6,
|
||||
KeyboardKey::KEY_R => return 0xD,
|
||||
KeyboardKey::KEY_A => return 0x7,
|
||||
KeyboardKey::KEY_S => return 0x8,
|
||||
KeyboardKey::KEY_D => return 0x9,
|
||||
KeyboardKey::KEY_F => return 0xE,
|
||||
KeyboardKey::KEY_Z => return 0xA,
|
||||
KeyboardKey::KEY_X => return 0x0,
|
||||
KeyboardKey::KEY_C => return 0xB,
|
||||
KeyboardKey::KEY_V => return 0xF,
|
||||
_ => return 0,
|
||||
}
|
||||
}
|
||||
|
||||
// fn chip8_to_index(chip8_key: u8) -> u8 {
|
||||
// match chip8_key {
|
||||
// 0x1 => return 0x0,
|
||||
// 0x2 => return 0x1,
|
||||
// 0x3 => return 0x2,
|
||||
// 0xC => return 0x3,
|
||||
// 0x4 => return 0x4,
|
||||
// 0x5 => return 0x5,
|
||||
// 0x6 => return 0x6,
|
||||
// 0xD => return 0x7,
|
||||
// 0x7 => return 0x8,
|
||||
// 0x8 => return 0x9,
|
||||
// 0x9 => return 0xA,
|
||||
// 0xE => return 0xB,
|
||||
// 0xA => return 0xC,
|
||||
// 0x0 => return 0xD,
|
||||
// 0xB => return 0xE,
|
||||
// 0xF => return 0xF,
|
||||
// _ => panic!("Unknown Chip8 Key {}", chip8_key),
|
||||
// }
|
||||
// }
|
||||
45
src/chip8/memory.rs
Normal file
45
src/chip8/memory.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use super::Chip8State;
|
||||
use std::{io, path::Path};
|
||||
|
||||
pub fn read_n_bytes(
|
||||
buffer: &[u8],
|
||||
buffer_len: usize,
|
||||
start_addr: usize,
|
||||
n_bytes: usize,
|
||||
) -> Vec<u8> {
|
||||
let mut addr = start_addr;
|
||||
let mut bytes = Vec::new();
|
||||
while addr != start_addr + n_bytes {
|
||||
if addr >= buffer_len {
|
||||
panic!(
|
||||
"Couldn't read from Address {} exceeds buffer length {}",
|
||||
addr, buffer_len
|
||||
) // nice error handling
|
||||
}
|
||||
bytes.push(buffer[addr]);
|
||||
|
||||
addr += 1;
|
||||
}
|
||||
|
||||
return bytes as Vec<u8>;
|
||||
}
|
||||
|
||||
pub fn load_bytes(state: &mut Chip8State, data: &[u8], data_len: usize, start_addr: usize) {
|
||||
for i in 0..data_len {
|
||||
state.mem[start_addr + i] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_file_to_memory<P: AsRef<Path>>(state: &mut Chip8State, filepath: P) -> io::Result<()> {
|
||||
let fp = filepath.as_ref();
|
||||
|
||||
// read file to Vec(u8)
|
||||
let program = std::fs::read(fp)?;
|
||||
|
||||
for i in 0..program.len() {
|
||||
state.mem[state.r_pc as usize + i] = program[i];
|
||||
}
|
||||
|
||||
// Should return Ok or Err
|
||||
Ok(())
|
||||
}
|
||||
28
src/chip8/renderer.rs
Normal file
28
src/chip8/renderer.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use super::Chip8State;
|
||||
use super::gpu::{CHIP8_DISPLAY_HEIGHT, CHIP8_DISPLAY_WIDTH};
|
||||
use raylib::{RaylibHandle, RaylibThread, color::Color, prelude::RaylibDraw};
|
||||
|
||||
pub static DISPLAY_WIDTH: i32 = 640;
|
||||
pub static DISPLAY_HEIGHT: i32 = 480;
|
||||
|
||||
pub fn render(state: &Chip8State, rl_handle: &mut RaylibHandle, rl_thread: &RaylibThread) {
|
||||
let mut d = rl_handle.begin_drawing(&rl_thread);
|
||||
|
||||
// d.clear_background(Color::BLACK);
|
||||
|
||||
let scale_x = DISPLAY_WIDTH / CHIP8_DISPLAY_WIDTH;
|
||||
let scale_y = DISPLAY_HEIGHT / CHIP8_DISPLAY_HEIGHT;
|
||||
|
||||
for y in 0..CHIP8_DISPLAY_HEIGHT {
|
||||
for x in 0..CHIP8_DISPLAY_WIDTH {
|
||||
// fix to render color based on exact bit for pixels
|
||||
let color: Color;
|
||||
if state.display[y as usize][x as usize] {
|
||||
color = Color::WHITE;
|
||||
} else {
|
||||
color = Color::GREEN;
|
||||
}
|
||||
d.draw_rectangle(x * scale_x, y * scale_y, scale_x, scale_y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/main.rs
Normal file
27
src/main.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use clap::Parser;
|
||||
|
||||
mod chip8;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about = "Chip 8 Emulator")]
|
||||
struct Args {
|
||||
/// Turn on debug mode
|
||||
#[arg(short, long, action = clap::ArgAction::SetTrue)]
|
||||
debug: bool,
|
||||
/// Set path for chip8 binary file to run
|
||||
#[arg(short, long)]
|
||||
file: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
// TODO: should define my raylib/sound/etc handles here eventually and pass them down
|
||||
|
||||
if let Some(filepath) = args.file {
|
||||
if args.debug {
|
||||
chip8::run(filepath, true);
|
||||
} else {
|
||||
chip8::run(filepath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user