From 4f2d873f58709a3ba7d39905f95f333f71dcd552 Mon Sep 17 00:00:00 2001 From: Samuel Perrouault Date: Tue, 25 Mar 2025 22:06:05 +0100 Subject: initial commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ Readme.md | 1 + rust-toolchain.toml | 2 + shell.nix | 11 ++++ src/chip8.rs | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 21 ++++++++ 8 files changed, 193 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Readme.md create mode 100644 rust-toolchain.toml create mode 100644 shell.nix create mode 100644 src/chip8.rs create mode 100644 src/main.rs 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 { 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(&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(()) +} -- cgit v1.2.3