summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml6
-rw-r--r--Readme.md1
-rw-r--r--rust-toolchain.toml2
-rw-r--r--shell.nix11
-rw-r--r--src/chip8.rs144
-rw-r--r--src/main.rs21
8 files changed, 193 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..0e6cf8f
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "chip8"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..73f1c6d
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "chip8"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..d4e7470
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1 @@
+Following along on https://multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..c5794a6
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "1.85"
diff --git a/shell.nix b/shell.nix
new file mode 100644
index 0000000..7df07fd
--- /dev/null
+++ b/shell.nix
@@ -0,0 +1,11 @@
+let
+ moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz);
+ nixpkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
+in
+ with nixpkgs;
+ stdenv.mkDerivation {
+ name = "moz_overlay_shell";
+ buildInputs = [
+ (nixpkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; }).rust
+ ];
+ }
diff --git a/src/chip8.rs b/src/chip8.rs
new file mode 100644
index 0000000..50b5c12
--- /dev/null
+++ b/src/chip8.rs
@@ -0,0 +1,144 @@
+use std::io::Read;
+use std::io::Write;
+
+const WIDTH: u16 = 64;
+const HEIGHT: u16 = 32;
+const PIXELS: usize = (WIDTH * HEIGHT) as usize;
+
+fn unknown_opcode(opcode: u16) {
+ eprintln!("Unknwown opcode {:#06x}", opcode);
+ std::process::exit(1);
+}
+
+pub struct Chip8 {
+ v: [u8; 4096],
+ i: u16,
+ pc: u16,
+ stack: [u16; 16],
+ sp: u16,
+ memory: [u8; 4096],
+ screen: [u8; PIXELS],
+ delay_timer: u8,
+ sound_timer: u8,
+ key: [u8; 16],
+ draw: bool,
+}
+
+impl Chip8 {
+ pub fn new() -> Self {
+ return Self {
+ v: [0; 4096],
+ i: 0,
+ pc: 0x200,
+ stack: [0; 16],
+ sp: 0,
+ memory: [0; 4096],
+ screen: [0; PIXELS],
+ delay_timer: 0,
+ sound_timer: 0,
+ key: [0; 16],
+ draw: false,
+ };
+ }
+
+ pub fn load_rom<R: Read>(&mut self, mut reader: R) -> std::io::Result<()> {
+ reader.read(&mut self.memory[0x200..])?;
+ Ok(())
+ }
+
+ pub fn cycle(&mut self) {
+ let pc = self.pc as usize;
+ let opcode = (self.memory[pc] as u16) << 8 | self.memory[pc + 1] as u16;
+ match opcode & 0xF000 {
+ 0x0000 => match opcode & 0x000F {
+ 0x0000 => {
+ eprintln!("CLS");
+ self.screen = [0; PIXELS];
+ self.draw = true;
+ self.pc += 2;
+ }
+ 0x000E => {
+ eprintln!("RET");
+ self.pc += 2;
+ }
+ _ => unknown_opcode(opcode),
+ },
+ 0x6000 => {
+ let x = (opcode & 0x0F00) >> 8;
+ let value = opcode & 0x00FF;
+ eprintln!("LD V{}, {}", x, value);
+ self.v[x as usize] = value as u8;
+ self.pc += 2;
+ }
+ 0xA000 => {
+ self.i = opcode & 0x0FFF;
+ self.pc += 2;
+ eprintln!("LD I, {}", self.i);
+ }
+ 0xD000 => {
+ let x = (opcode & 0x0F00) >> 8;
+ let y = (opcode & 0x00F0) >> 4;
+ let nibble = opcode & 0x000F;
+ eprintln!("DRW V{}, V{}, {}", x, y, nibble);
+ self.v[0xF] = 0;
+ for yy in 0..nibble {
+ let pixel = self.memory[(self.i + yy) as usize];
+ for xx in 0..8 {
+ if (pixel & (0x80 >> xx)) != 0 {
+ let si = x + xx + ((y + yy) * WIDTH);
+ if self.screen[si as usize] == 1 {
+ self.v[0xF] = 1;
+ }
+ self.screen[si as usize] ^= 1;
+ }
+ }
+ }
+ self.draw = true;
+ self.pc += 2;
+ }
+ _ => unknown_opcode(opcode),
+ };
+
+ if self.delay_timer > 0 {
+ self.delay_timer -= 1;
+ }
+
+ if self.sound_timer > 0 {
+ if self.sound_timer == 1 {
+ eprintln!("BEEP");
+ }
+ self.sound_timer -= 1;
+ }
+ }
+
+ pub fn display(&mut self) -> std::io::Result<()> {
+ if self.draw {
+ self.draw = false;
+ self.draw()
+ } else {
+ Ok(())
+ }
+ }
+
+ fn draw(&self) -> std::io::Result<()> {
+ let lines: String = std::iter::once("\x1B[2J\n")
+ .chain((0..HEIGHT).flat_map(|y| {
+ (0..WIDTH)
+ .map(move |x| {
+ let si = x + y * WIDTH;
+ if self.screen[si as usize] == 0 {
+ "░"
+ } else {
+ "█"
+ }
+ })
+ .chain(std::iter::once("\n"))
+ }))
+ .collect();
+
+ let stdout = std::io::stdout();
+ let mut handle = stdout.lock();
+ handle.write_all(lines.as_bytes())?;
+ handle.flush()
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..82374b0
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,21 @@
+use std::{fs::File, io::BufReader};
+
+mod chip8;
+
+fn main() -> std::io::Result<()> {
+ if let Some(arg) = std::env::args().skip(1).next() {
+ let mut chip8 = chip8::Chip8::new();
+ {
+ let file = File::open(arg)?;
+ let buf_reader = BufReader::new(file);
+ chip8.load_rom(buf_reader)?;
+ }
+ loop {
+ chip8.cycle();
+ chip8.display()?;
+ }
+ } else {
+ println!("usage: chip8 rompath")
+ }
+ Ok(())
+}