initial commit

This commit is contained in:
baldeau 2025-04-28 19:10:40 +02:00
commit 546800f344
33 changed files with 966 additions and 0 deletions

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# chip8_rust
Full chip8 implementation in Rust.
For learning purposes only.

1
chip8_core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

157
chip8_core/Cargo.lock generated Normal file
View File

@ -0,0 +1,157 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chip8_core"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

7
chip8_core/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "chip8_core"
version = "0.1.0"
edition = "2024"
[dependencies]
rand = "0.9.1"

445
chip8_core/src/lib.rs Normal file
View File

@ -0,0 +1,445 @@
use rand::random;
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
const FONTSET_SIZE: usize = 80;
const FONTSET: [u8; FONTSET_SIZE] = [
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
0x20, 0x60, 0x20, 0x20, 0x70, // 1
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
0xF0, 0x80, 0xF0, 0x80, 0x80, // F
];
pub const SCREEN_WIDTH: usize = 64;
pub const SCREEN_HEIGHT: usize = 32;
const RAM_SIZE: usize = 4096;
const NUM_REGS: usize = 16;
const STACK_SIZE: usize = 16;
const NUM_KEYS: usize = 16;
pub struct Emu {
// Program Counter
pc: u16,
ram: [u8; RAM_SIZE],
screen: [bool; SCREEN_WIDTH * SCREEN_HEIGHT],
// V 8-bit registers
v_reg: [u8; NUM_REGS],
// I 16-bit register
i_reg: u16,
// stack pointer
sp: u16,
// 16-bit stack
stack: [u16; STACK_SIZE],
keys: [bool; NUM_KEYS],
// delay timer
dt: u8,
// sound timer
st: u8,
}
// Chip-8 programs have to start at 0x200
const START_ADDR: u16 = 0x200;
impl Emu {
pub fn new() -> Self {
let mut new_emu = Self {
pc: START_ADDR,
ram: [0; RAM_SIZE],
screen: [false; SCREEN_WIDTH * SCREEN_HEIGHT],
v_reg: [0; NUM_REGS],
i_reg: 0,
sp: 0,
stack: [0; STACK_SIZE],
keys: [false; NUM_KEYS],
dt: 0,
st: 0,
};
new_emu.ram[..FONTSET_SIZE].copy_from_slice(&FONTSET);
new_emu
}
pub fn reset(&mut self) {
self.pc = START_ADDR;
self.ram = [0; RAM_SIZE];
self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT];
self.v_reg = [0; NUM_REGS];
self.i_reg = 0;
self.sp = 0;
self.stack = [0; STACK_SIZE];
self.keys = [false; NUM_KEYS];
self.dt = 0;
self.st = 0;
self.ram[..FONTSET_SIZE].copy_from_slice(&FONTSET);
}
// push to the stack
fn push(&mut self, val: u16) {
self.stack[self.sp as usize] = val;
self.sp += 1;
}
// pop from the stack
fn pop(&mut self) -> u16 {
self.sp -= 1;
self.stack[self.sp as usize]
}
// CPU tick
pub fn tick(&mut self) {
// Fetch
let op = self.fetch();
// Decode & Execute
self.execute(op);
}
fn execute(&mut self, op: u16) {
let digit1 = (op & 0xF000) >> 12;
let digit2 = (op & 0x0F00) >> 8;
let digit3 = (op & 0x00F0) >> 4;
let digit4 = op & 0x000F;
match (digit1, digit2, digit3, digit4) {
// Nop
(0, 0, 0, 0) => return,
// clear screen
(0, 0, 0xE, 0) => self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT],
// RET: return from subroutine
(0, 0, 0xE, 0xE) => {
let ret_addr = self.pop();
self.pc = ret_addr
}
// JMP NNN: jump instruction
(1, _, _, _) => {
let nnn = op & 0xFFF;
self.pc = nnn;
}
// CALL NNN: call subroutine
(2, _, _, _) => {
let nnn = op & 0xFFF;
self.push(self.pc);
self.pc = nnn;
}
// 3XNN: skip next if VX == NN
(3, _, _, _) => {
let x = digit2 as usize;
let nn = (op & 0xFF) as u8;
if self.v_reg[x] == nn {
self.pc += 2;
}
}
// 4XNN: skip next if VX != NN
(4, _, _, _) => {
let x = digit2 as usize;
let nn = (op & 0xFF) as u8;
if self.v_reg[x] != nn {
self.pc += 2;
}
}
// 5XY0: skip next if VX == VY
(5, _, _, 0) => {
let x = digit2 as usize;
let y = digit3 as usize;
if self.v_reg[x] == self.v_reg[y] {
self.pc += 2;
}
}
// 6XNN: VX = NN
// set the V register specified by the second digit
// to the value given
(6, _, _, _) => {
let x = digit2 as usize;
let nn = (op & 0xFF) as u8;
self.v_reg[x] = nn;
}
// 7XNN: VX += NN
// adds the given number to the specified V register
(7, _, _, _) => {
let x = digit2 as usize;
let nn = (op & 0xFF) as u8;
self.v_reg[x] = self.v_reg[x].wrapping_add(nn);
}
// 8XY0: VX = VY
(8, _, _, 0) => {
let x = digit2 as usize;
let y = digit3 as usize;
self.v_reg[x] = self.v_reg[y];
}
// 8XY1, 8XY2, 8XY3: bitwise operations
// VX |= VY
(8, _, _, 1) => {
let x = digit2 as usize;
let y = digit3 as usize;
self.v_reg[x] |= self.v_reg[y];
}
// VX &= VY
(8, _, _, 2) => {
let x = digit2 as usize;
let y = digit3 as usize;
self.v_reg[x] &= self.v_reg[y];
}
// VX ^= VY
(8, _, _, 3) => {
let x = digit2 as usize;
let y = digit3 as usize;
self.v_reg[x] ^= self.v_reg[y];
}
// 8XY4: VX += VY
(8, _, _, 4) => {
let x = digit2 as usize;
let y = digit3 as usize;
let (new_vx, carry) = self.v_reg[x].overflowing_add(self.v_reg[y]);
// set 16th V register to 1 if overflow
// the 16th V register handles over- and underflows
let new_vf = if carry { 1 } else { 0 };
self.v_reg[x] = new_vx;
self.v_reg[0xF] = new_vf;
}
// 8XY5: VX -= VY
(8, _, _, 5) => {
let x = digit2 as usize;
let y = digit3 as usize;
let (new_vx, borrow) = self.v_reg[x].overflowing_sub(self.v_reg[y]);
let new_vf = if borrow { 0 } else { 1 };
self.v_reg[x] = new_vx;
self.v_reg[0xF] = new_vf;
}
// 8XY6: VX >>= 1
// single right shift on value VX
(8, _, _, 6) => {
let x = digit2 as usize;
let lsb = self.v_reg[x] & 1;
self.v_reg[x] >>= 1;
self.v_reg[0xF] = lsb;
}
// 8XY7: VX = VY - VX
(8, _, _, 7) => {
let x = digit2 as usize;
let y = digit3 as usize;
let (new_vx, borrow) = self.v_reg[y].overflowing_sub(self.v_reg[x]);
let new_vf = if borrow { 0 } else { 1 };
self.v_reg[x] = new_vx;
self.v_reg[0xF] = new_vf;
}
// 8XYE: VX <<= 1
// single left shift on value VX
(8, _, _, 0xE) => {
let x = digit2 as usize;
let msb = (self.v_reg[x] >> 7) & 1;
self.v_reg[x] <<= 1;
self.v_reg[0xF] = msb;
}
// 9XY0: skip if VX != VY
(9, _, _, 0) => {
let x = digit2 as usize;
let y = digit3 as usize;
if self.v_reg[x] != self.v_reg[y] {
self.pc += 2;
}
}
// ANNN: I = NNN
// set I register to the 0xNNN value encoded in this opcode
(0xA, _, _, _) => {
let nnn = op & 0xFFF;
self.i_reg = nnn;
}
// BNNN: jump to V0 + NNN
(0xB, _, _, _) => {
let nnn = op & 0xFFF;
self.pc = (self.v_reg[0] as u16) + nnn;
}
// CXNN: VX = rand() & NN
(0xC, _, _, _) => {
let x = digit2 as usize;
let nn = (op & 0xFF) as u8;
let rng: u8 = random();
self.v_reg[x] = rng & nn;
}
// DRAW
(0xD, _, _, _) => {
// Get the (x, y) coords for our sprite
let x_coord = self.v_reg[digit2 as usize] as u16;
let y_coord = self.v_reg[digit3 as usize] as u16;
// The last digit determines how many rows high our sprite is
let num_rows = digit4;
// Keep track if any pixels were flipped
let mut flipped = false;
// Iterate over each row of our sprite
for y_line in 0..num_rows {
// Determine which memory address our row's data is stored
let addr = self.i_reg + y_line as u16;
let pixels = self.ram[addr as usize];
// Iterate over each column in our row
for x_line in 0..8 {
if (pixels & (0b1000_0000 >> x_line)) != 0 {
// Sprites should wrap around screen, so apply modulo
let x = (x_coord + x_line) as usize % SCREEN_WIDTH;
let y = (y_coord + y_line) as usize % SCREEN_HEIGHT;
// Get our pixel's index for our 1D screen array
let idx = x + SCREEN_WIDTH * y;
// Check if we're about to flip the pixel and set
flipped |= self.screen[idx];
self.screen[idx] ^= true;
}
}
}
// Populate VF register
if flipped {
self.v_reg[0xF] = 1;
} else {
self.v_reg[0xF] = 0;
}
}
// EX9E: skip if key pressed
(0xE, _, 9, 0xE) => {
let x = digit2 as usize;
let vx = self.v_reg[x];
let key = self.keys[vx as usize];
if key {
self.pc += 2;
}
}
// EXA1: skip if key not pressed
(0xE, _, 0xA, 1) => {
let x = digit2 as usize;
let vx = self.v_reg[x];
let key = self.keys[vx as usize];
if !key {
self.pc += 2;
}
}
// VX = DT
(0xF, _, 0, 7) => {
let x = digit2 as usize;
self.v_reg[x] = self.dt;
}
// FX0A: wait for key press
(0xF, _, 0, 0xA) => {
let x = digit2 as usize;
let mut pressed = false;
for i in 0..self.keys.len() {
if self.keys[i] {
self.v_reg[x] = i as u8;
pressed = true;
break;
}
}
if !pressed {
// Redo opcode
self.pc -= 2;
}
}
// FX15: DT = VX
(0xF, _, 1, 5) => {
let x = digit2 as usize;
self.dt = self.v_reg[x];
}
// FX18: ST = VX
(0xF, _, 1, 8) => {
let x = digit2 as usize;
self.st = self.v_reg[x];
}
// FX1E: I += VX
(0xF, _, 1, 0xE) => {
let x = digit2 as usize;
let vx = self.v_reg[x] as u16;
self.i_reg = self.i_reg.wrapping_add(vx);
}
// FX29: set I to font address
(0xF, _, 2, 9) => {
let x = digit2 as usize;
let c = self.v_reg[x] as u16;
self.i_reg = c * 5;
}
// FX33: | = BCD of VX
// storing the binary-coded decimal of
// a number stored in the V registers into the I register
(0xF, _, 3, 3) => {
let x = digit2 as usize;
let vx = self.v_reg[x] as f32;
// Fetch the hundreds digit by dividing by 100 and tossing the decimal
let hundreds = (vx / 100.0).floor() as u8;
// Fetch the tens digit by dividing by 10, tossing the ones digit and the decimal
let tens = ((vx / 10.0) % 10.0).floor() as u8;
// Fetch the ones digit by tossing the hundreds and the tens
let ones = (vx % 10.0) as u8;
self.ram[self.i_reg as usize] = hundreds;
self.ram[(self.i_reg + 1) as usize] = tens;
self.ram[(self.i_reg + 2) as usize] = ones;
}
// FX55: Store V0 - VX into I
(0xF, _, 5, 5) => {
let x = digit2 as usize;
let i = self.i_reg as usize;
for idx in 0..=x {
self.ram[i + idx] = self.v_reg[idx];
}
}
// FX65: load I into V0 - VX
(0xF, _, 6, 5) => {
let x = digit2 as usize;
let i = self.i_reg as usize;
for idx in 0..=x {
self.v_reg[idx] = self.ram[i + idx];
}
}
(_, _, _, _) => unimplemented!("Unimplemented opcode: {}", op),
}
}
// grab the instruction we are about to execute (opcode)
pub fn fetch(&mut self) -> u16 {
let higher_byte = self.ram[self.pc as usize] as u16;
let lower_byte = self.ram[(self.pc + 1) as usize] as u16;
let op = (higher_byte << 8) | lower_byte;
self.pc += 2;
op
}
pub fn tick_timers(&mut self) {
if self.dt > 0 {
self.dt -= 1;
}
if self.st > 0 {
if self.st == 1 {
// BEEP
}
self.st -= 1;
}
}
pub fn get_display(&self) -> &[bool] {
&self.screen
}
pub fn keypress(&mut self, idx: usize, pressed: bool) {
self.keys[idx] = pressed;
}
pub fn load(&mut self, data: &[u8]) {
let start = START_ADDR as usize;
let end = (START_ADDR as usize) + data.len();
self.ram[start..end].copy_from_slice(data);
}
}

1
desktop/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

206
desktop/Cargo.lock generated Normal file
View File

@ -0,0 +1,206 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chip8_core"
version = "0.1.0"
dependencies = [
"rand",
]
[[package]]
name = "desktop"
version = "0.1.0"
dependencies = [
"chip8_core",
"sdl2",
]
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom",
]
[[package]]
name = "sdl2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3"
dependencies = [
"cfg-if",
"libc",
"version-compare",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

8
desktop/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "desktop"
version = "0.1.0"
edition = "2024"
[dependencies]
chip8_core = { path = "../chip8_core" }
sdl2 = "0.37.0"

BIN
desktop/c8games/15PUZZLE Normal file

Binary file not shown.

BIN
desktop/c8games/BLINKY Normal file

Binary file not shown.

BIN
desktop/c8games/BLITZ Normal file

Binary file not shown.

BIN
desktop/c8games/BRIX Normal file

Binary file not shown.

BIN
desktop/c8games/CONNECT4 Normal file

Binary file not shown.

BIN
desktop/c8games/GUESS Normal file

Binary file not shown.

BIN
desktop/c8games/HIDDEN Normal file

Binary file not shown.

BIN
desktop/c8games/INVADERS Normal file

Binary file not shown.

BIN
desktop/c8games/KALEID Normal file

Binary file not shown.

BIN
desktop/c8games/MAZE Normal file

Binary file not shown.

BIN
desktop/c8games/MERLIN Normal file

Binary file not shown.

BIN
desktop/c8games/MISSILE Normal file

Binary file not shown.

BIN
desktop/c8games/PONG Normal file

Binary file not shown.

BIN
desktop/c8games/PONG2 Normal file

Binary file not shown.

BIN
desktop/c8games/PUZZLE Normal file

Binary file not shown.

BIN
desktop/c8games/SYZYGY Normal file

Binary file not shown.

BIN
desktop/c8games/TANK Normal file

Binary file not shown.

BIN
desktop/c8games/TETRIS Normal file

Binary file not shown.

BIN
desktop/c8games/TICTAC Normal file

Binary file not shown.

BIN
desktop/c8games/UFO Normal file

Binary file not shown.

BIN
desktop/c8games/VBRIX Normal file

Binary file not shown.

BIN
desktop/c8games/VERS Normal file

Binary file not shown.

BIN
desktop/c8games/WIPEOFF Normal file

Binary file not shown.

4
desktop/macos.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
export LIBRARY_PATH="$LIBRARY_PATH:/opt/homebrew/lib"
cargo run $1

133
desktop/src/main.rs Normal file
View File

@ -0,0 +1,133 @@
use chip8_core::*;
use std::env;
use std::fs::File;
use std::io::Read;
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::rect::Rect;
use sdl2::render::Canvas;
use sdl2::video::Window;
const SCALE: u32 = 15;
const WINDOW_WIDTH: u32 = (SCREEN_WIDTH as u32) * SCALE;
const WINDOW_HEIGHT: u32 = (SCREEN_HEIGHT as u32) * SCALE;
const TICKS_PER_FRAME: usize = 10;
fn main() {
let args: Vec<_> = env::args().collect();
if args.len() != 2 {
println!("Usage: cargo run path/to/game");
return;
}
// Setup SDL
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem
.window("Chip-8 Emulator", WINDOW_WIDTH, WINDOW_HEIGHT)
.position_centered()
.opengl()
.build()
.unwrap();
let mut canvas = window.into_canvas().present_vsync().build().unwrap();
canvas.clear();
canvas.present();
let mut event_pump = sdl_context.event_pump().unwrap();
let mut chip8 = Emu::new();
let mut rom = File::open(&args[1]).expect("Unable to open file");
let mut buffer = Vec::new();
rom.read_to_end(&mut buffer).unwrap();
chip8.load(&buffer);
'gameloop: loop {
for evt in event_pump.poll_iter() {
match evt {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
break 'gameloop;
}
Event::KeyDown {
keycode: Some(key), ..
} => {
if let Some(k) = key2btn(key) {
chip8.keypress(k, true);
}
}
Event::KeyUp {
keycode: Some(key), ..
} => {
if let Some(k) = key2btn(key) {
chip8.keypress(k, false);
}
}
_ => (),
}
}
for _ in 0..TICKS_PER_FRAME {
chip8.tick();
}
chip8.tick_timers();
draw_screen(&chip8, &mut canvas);
}
}
fn draw_screen(emu: &Emu, canvas: &mut Canvas<Window>) {
// Clear canvas as black
canvas.set_draw_color(Color::RGB(0, 0, 0));
canvas.clear();
let screen_buf = emu.get_display();
// Now set draw color to white, iterate through each point and see if it should be drawn
canvas.set_draw_color(Color::RGB(255, 255, 255));
for (i, pixel) in screen_buf.iter().enumerate() {
if *pixel {
// Convert our 1D array's index into a 2D (x,y) position
let x = (i % SCREEN_WIDTH) as u32;
let y = (i / SCREEN_WIDTH) as u32;
// Draw rectangle at (x,y), scaled up by our SCALE value
let rect = Rect::new((x * SCALE) as i32, (y * SCALE) as i32, SCALE, SCALE);
canvas.fill_rect(rect).unwrap();
}
}
canvas.present();
}
/*
Keyboard Chip-8
+---+---+---+---+ +---+---+---+---+
| 1 | 2 | 3 | 4 | | 1 | 2 | 3 | C |
+---+---+---+---+ +---+---+---+---+
| Q | W | E | R | | 4 | 5 | 6 | D |
+---+---+---+---+ => +---+---+---+---+
| A | S | D | F | | 7 | 8 | 9 | E |
+---+---+---+---+ +---+---+---+---+
| Z | X | C | V | | A | 0 | B | F |
+---+---+---+---+ +---+---+---+---+
*/
fn key2btn(key: Keycode) -> Option<usize> {
match key {
Keycode::Num1 => Some(0x1),
Keycode::Num2 => Some(0x2),
Keycode::Num3 => Some(0x3),
Keycode::Num4 => Some(0xC),
Keycode::Q => Some(0x4),
Keycode::W => Some(0x5),
Keycode::E => Some(0x6),
Keycode::R => Some(0xD),
Keycode::A => Some(0x7),
Keycode::S => Some(0x8),
Keycode::D => Some(0x9),
Keycode::F => Some(0xE),
Keycode::Z => Some(0xA),
Keycode::X => Some(0x0),
Keycode::C => Some(0xB),
Keycode::V => Some(0xF),
_ => None,
}
}