Compare commits

..

23 Commits

Author SHA1 Message Date
0695c23a78 move raylib rs to ack 2026-01-31 19:30:02 -06:00
c3df8672c8 update built with to include raylib rust bindings 2026-01-31 19:28:01 -06:00
47cce19940 acknowledge chip8 archive 2026-01-31 16:43:34 -06:00
d8b3bcae19 fix formatting and spelling issues 2026-01-31 16:40:34 -06:00
7ede5aeafa add program aquasition info 2026-01-31 16:38:26 -06:00
08a2ec4459 fix license URL 2026-01-31 16:26:41 -06:00
58901521e5 readme license shield 2026-01-31 16:25:35 -06:00
fed6762288 more readme changes 2026-01-31 16:20:56 -06:00
342eb4557d Merge branch 'main' of git:/jlanders/chip8-em 2026-01-31 16:07:47 -06:00
63fe937481 maybe it's fixed now :) 2026-01-31 16:07:35 -06:00
73422b4072 maybe it's fixed now :) 2026-01-31 16:06:50 -06:00
efba92b8c7 Merge branch 'main' of git:/jlanders/chip8-em 2026-01-31 16:05:24 -06:00
25c6d9beaa fix screenshot readme 2026-01-31 16:05:06 -06:00
38ec89813d fix screenshot readme 2026-01-31 16:04:39 -06:00
596e60abeb Merge branch 'main' of git:/jlanders/chip8-em 2026-01-31 16:04:18 -06:00
ad50485d37 Merge branch 'main' of git:/jlanders/chip8-em 2026-01-31 16:03:53 -06:00
ce5ff7ee24 Merge branch 'main' of git:/jlanders/chip8-em 2026-01-31 16:03:14 -06:00
124493fd11 pushing readme changes to view 2026-01-31 16:02:42 -06:00
0ff0b6c596 pushing readme changes to view 2026-01-31 16:00:54 -06:00
253095bc6d logic changed after changing to assert 2026-01-31 15:37:11 -06:00
063ca15b37 fix most clippy complaints 2026-01-31 15:32:07 -06:00
266c685d1b change package name 2026-01-31 14:44:48 -06:00
82323846ec clippy fixes 1 manual before auto apply 2026-01-31 14:43:36 -06:00
11 changed files with 202 additions and 272 deletions

2
Cargo.lock generated
View File

@@ -177,7 +177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]] [[package]]
name = "chip-8-em" name = "chip8-em"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "chip-8-em" name = "chip8-em"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

167
README.md
View File

@@ -1,12 +1,4 @@
<!-- Improved compatibility of back to top link: See: https://github.com/othneildrew/Best-README-Template/pull/73 -->
<a id="readme-top"></a> <a id="readme-top"></a>
<!--
*** Thanks for checking out the Best-README-Template. If you have a suggestion
*** that would make this better, please fork the repo and create a pull request
*** or simply open an issue with the tag "enhancement".
*** Don't forget to give the project a star!
*** Thanks again! Now go create something AMAZING! :D
-->
@@ -18,29 +10,25 @@
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use. *** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links *** https://www.markdownguide.org/basic-syntax/#reference-style-links
--> -->
[![project_license][license-shield]][license-url] [![MIT][license-shield]][license-url]
[![LinkedIn][linkedin-shield]][linkedin-url] <!-- [![LinkedIn][linkedin-shield]][linkedin-url] -->
<!-- PROJECT LOGO --> <!-- PROJECT LOGO -->
<br /> <br />
<div align="center"> <div align="center">
<a href="https://github.com/github_username/repo_name"> <!-- <a href="https://git.colelanders.dev/jlanders/chip8-em">
<img src="images/logo.png" alt="Logo" width="80" height="80"> <img src="images/logo.png" alt="Logo" width="80" height="80">
</a> </a> -->
<h3 align="center">Chip8 Emulator</h3> <h3 align="center">Chip8 Emulator</h3>
<p align="center"> <p align="center">
Chip8 Emulator built in Rust. Chip8 Emulator built in Rust.
<br /> <br />
<a href="https://git.colelanders.dev/jlanders/chip8-em"><strong>Explore the docs »</strong></a> <!-- <a href="https://git.colelanders.dev/jlanders/chip8-em"><strong>Explore the docs »</strong></a>
<br /> -->
<br /> <br />
<br /> <a href="https://git.colelanders.dev/jlanders/chip8-em">View Demo</a>
<a href="https://github.com/github_username/repo_name">View Demo</a>
&middot;
<a href="https://github.com/github_username/repo_name/issues/new?labels=bug&template=bug-report---.md">Report Bug</a>
&middot;
<a href="https://github.com/github_username/repo_name/issues/new?labels=enhancement&template=feature-request---.md">Request Feature</a>
</p> </p>
</div> </div>
@@ -53,7 +41,7 @@
<li> <li>
<a href="#about-the-project">About The Project</a> <a href="#about-the-project">About The Project</a>
<ul> <ul>
<li><a href="#built-with">Built With Rust</a></li> <li><a href="#built-with">Built With</a></li>
</ul> </ul>
</li> </li>
<li> <li>
@@ -61,6 +49,7 @@
<ul> <ul>
<li><a href="#prerequisites">Prerequisites</a></li> <li><a href="#prerequisites">Prerequisites</a></li>
<li><a href="#installation">Installation</a></li> <li><a href="#installation">Installation</a></li>
<li><a href="#running">Running</a></li>
</ul> </ul>
</li> </li>
<li><a href="#usage">Usage</a></li> <li><a href="#usage">Usage</a></li>
@@ -77,74 +66,78 @@
<!-- ABOUT THE PROJECT --> <!-- ABOUT THE PROJECT -->
## About The Project ## About The Project
[![Product Name Screen Shot][product-screenshot]](https://example.com) ![Chip8 Emulator Screen Shot][product-screenshot]
Here's a blank template to get started. To avoid retyping too much info, do a search and replace with your text editor for the following: `github_username`, `repo_name`, `twitter_handle`, `linkedin_username`, `email_client`, `email`, `project_title`, `project_description`, `project_license`
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
### Built With ### Built With
* [![Next][Next.js]][Next-url] * [![Rust][Rust]][Rust-url]
* [![React][React.js]][React-url] * [![Raylib][Raylib]][Raylib-url]
* [![Vue][Vue.js]][Vue-url] * [![Clap][Clap]][Clap-url]
* [![Angular][Angular.io]][Angular-url] * [![Rodio][Rodio]][Rodio-url]
* [![Svelte][Svelte.dev]][Svelte-url]
* [![Laravel][Laravel.com]][Laravel-url]
* [![Bootstrap][Bootstrap.com]][Bootstrap-url]
* [![JQuery][JQuery.com]][JQuery-url]
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- GETTING STARTED --> <!-- GETTING STARTED -->
## Getting Started ## Getting Started
This is an example of how you may give instructions on setting up your project locally. You can find pre-built binaries for Windows/MacOS/Linux -> [releases](https://git.colelanders.dev/jlanders/chip8-em/releases)
To get a local copy up and running follow these simple example steps.
To run the code get a local copy up and running follow these simple example steps.
### Prerequisites ### Prerequisites
This is an example of how to list things you need to use the software and how to install them. This is an example of how to list things you need to use the software and how to install them.
* npm * rust - Follow the official install guide [here](https://rust-lang.org/tools/install/)
```sh <!-- ```sh
npm install npm@latest -g npm install npm@latest -g
``` ``` -->
### Installation ### Installation
1. Get a free API Key at [https://example.com](https://example.com) 1. Clone the repo
2. Clone the repo
```sh ```sh
git clone https://github.com/github_username/repo_name.git git clone https://git.colelanders.dev/jlanders/chip8-em.git
``` ```
3. Install NPM packages 2. Navigate to the repo directory
```sh ```sh
npm install cd chip8-em
``` ```
4. Enter your API in `config.js` 3. Build the project
```js
const API_KEY = 'ENTER YOUR API';
```
5. Change git remote url to avoid accidental pushes to base project
```sh ```sh
git remote set-url origin github_username/repo_name cargo build
git remote -v # confirm the changes
``` ```
### Running
Choose the method that best fits your needs. If you just want to use the emulator, I recommend using the **Pre-built Binaries**.
#### Prerequisite - Obtaining a Chip8 Rom
*Note - as of now the project only supports chip8 programs. Chip8 extensions such as SUPER-Chip or XO-Chip will not work with v1*
This emulator requires you either:
* Create your own ROM using a tool like [Octo](https://johnearnest.github.io/Octo/)
* Download a ROM made by the community
* Navigate to [johnearnest.github.io/chip8Archive/](https://johnearnest.github.io/chip8Archive/?sort=platform)
* Select a chip8 ROM that seems interesting
* Click "Download Rom"
#### 🚀 Option 1: Pre-built Binaries (Fastest)
**WIP**
#### 🛠️ Option 2: Built Locally (From Source)
**WIP**
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- USAGE EXAMPLES --> <!-- USAGE EXAMPLES -->
## Usage ## Usage
Use this space to show useful examples of how a project can be used. Additional screenshots, code examples and demos work well in this space. You may also link to more resources. **WIP**
_For more examples, please refer to the [Documentation](https://example.com)_ <!-- Use this space to show useful examples of how a project can be used. Additional screenshots, code examples and demos work well in this space. You may also link to more resources. -->
<!-- _For more examples, please refer to the [Documentation](https://example.com)_ -->
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
@@ -153,40 +146,19 @@ _For more examples, please refer to the [Documentation](https://example.com)_
<!-- ROADMAP --> <!-- ROADMAP -->
## Roadmap ## Roadmap
- [ ] Feature 1 - [ ] Super-Chip8 extension
- [ ] Feature 2 - [ ] XO-CHIP extension
- [ ] Feature 3 - [ ] More Configuration
- [ ] Nested Feature - [ ] Window Resizing
- [ ] Allow rebinding keys via GUI
See the [open issues](https://github.com/github_username/repo_name/issues) for a full list of proposed features (and known issues). - [ ] Replacing default sound via GUI
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- CONTRIBUTING --> <!-- CONTRIBUTING -->
## Contributing ## Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. Since this is a personal project, this specific repo is not open for contributions. However, please feel free to clone the project and use it as you wish!
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
Don't forget to give the project a star! Thanks again!
1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
<p align="right">(<a href="#readme-top">back to top</a>)</p>
### Top contributors:
<a href="https://github.com/github_username/repo_name/graphs/contributors">
<img src="https://contrib.rocks/image?repo=github_username/repo_name" alt="contrib.rocks image" />
</a>
<!-- LICENSE --> <!-- LICENSE -->
## License ## License
@@ -202,7 +174,7 @@ Distributed under the MIT License. See `LICENSE` for more information.
Cole Landers - jclanders@pm.me Cole Landers - jclanders@pm.me
Project Link: [https://github.com/github_username/repo_name](https://github.com/github_username/repo_name) Project Link: [https://git.colelanders.dev/jlanders/chip8-em](https://git.colelanders.dev/jlanders/chip8-em)
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
@@ -211,9 +183,8 @@ Project Link: [https://github.com/github_username/repo_name](https://github.com/
<!-- ACKNOWLEDGMENTS --> <!-- ACKNOWLEDGMENTS -->
## Acknowledgments ## Acknowledgments
* []() * [raylib-rs](https://github.com/raylib-rs/raylib-rs) - For raylib rust bindings
* []() * [John Earnest Chip8 Archive](https://github.com/JohnEarnest/chip8Archive) - For amazing tools and support of the Chip8 ecosystem
* []()
<p align="right">(<a href="#readme-top">back to top</a>)</p> <p align="right">(<a href="#readme-top">back to top</a>)</p>
@@ -222,11 +193,21 @@ Project Link: [https://github.com/github_username/repo_name](https://github.com/
<!-- MARKDOWN LINKS & IMAGES --> <!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links --> <!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
<!-- [license-shield]: https://img.shields.io/github/license/github_username/repo_name.svg?style=for-the-badge <!-- [license-shield]: https://img.shields.io/github/license/github_username/repo_name.svg?style=for-the-badge
[license-url]: https://github.com/github_username/repo_name/blob/master/LICENSE.txt [license-url]: https://git.colelanders.dev/jlanders/chip8-em/blob/master/LICENSE.txt
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
[linkedin-url]: https://linkedin.com/in/linkedin_username [linkedin-url]: https://linkedin.com/in/linkedin_username-->
[product-screenshot]: images/screenshot.png --> [license-shield]: https://img.shields.io/badge/license-MIT-blue
[license-url]: https://git.colelanders.dev/jlanders/chip8-em/src/branch/main/LICENSE
[product-screenshot]: images/screenshot.png
<!-- Shields.io badges. You can a comprehensive list with many more badges at: https://github.com/inttter/md-badges --> <!-- Shields.io badges. You can a comprehensive list with many more badges at: https://github.com/inttter/md-badges -->
<!-- [Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white --> [Rust]: https://img.shields.io/badge/rust-%23E32F26.svg?style=for-the-badge&logo=rust&logoColor=white
<!-- [Next-url]: https://nextjs.org/ --> [Rust-url]: https://www.rust-lang.org/
[Raylib]: https://img.shields.io/badge/raylib-white?style=for-the-badge&logo=target&logoColor=black
[Raylib-url]: https://www.raylib.com/
[Clap]: https://img.shields.io/badge/clap-blue?style=for-the-badge&logo=command-line&logoColor=white
[Clap-url]: https://github.com/clap-rs/clap
[Rodio]: https://img.shields.io/badge/rodio-orange?style=for-the-badge&logo=audio-bus&logoColor=white
[Rodio-url]: https://github.com/RustAudio/rodio

View File

@@ -1,5 +1,4 @@
mod cpu; mod cpu;
mod debug;
mod gpu; mod gpu;
mod input; mod input;
mod memory; mod memory;
@@ -7,14 +6,15 @@ mod renderer;
use rodio::source::{SineWave, Source}; use rodio::source::{SineWave, Source};
static MEMORY_LIMIT: i32 = 4096; static MEMORY_LIMIT: u16 = 4096;
static STACK_LIMIT: i32 = 16; static STACK_LIMIT: u8 = 16;
static VARIABLE_REGISTER_COUNT: i32 = 16; static VARIABLE_REGISTER_COUNT: u8 = 16;
static TIMER_TICK_RATE: u32 = 60; static TIMER_TICK_RATE: f32 = 60.0;
static DESIRED_FPS: u32 = 60; static DESIRED_FPS: f32 = 60.0;
static CYCLES_PER_FRAME: u32 = 10; static CYCLES_PER_FRAME: f32 = 10.0;
static INSTRUCTION_BYTE_SIZE: usize = 2;
#[derive(Clone)] #[derive(Debug, Clone)]
struct Chip8State { struct Chip8State {
// Flags // Flags
eti_600_flag: bool, eti_600_flag: bool,
@@ -39,6 +39,8 @@ struct Chip8State {
input: [bool; 16], input: [bool; 16],
} }
#[derive(Debug, Clone, Copy)]
#[allow(clippy::struct_excessive_bools)]
pub struct Chip8Quirks { pub struct Chip8Quirks {
pub vf_reset: bool, pub vf_reset: bool,
pub memory: bool, pub memory: bool,
@@ -48,7 +50,7 @@ pub struct Chip8Quirks {
pub jumping: bool, pub jumping: bool,
} }
pub fn run<S: AsRef<str>>(chip8_executable_filepath: S, quirks: &Chip8Quirks, debug_mode: bool) { pub fn run<S: AsRef<str>>(chip8_executable_filepath: S, quirks: Chip8Quirks) {
let mut state = Chip8State { let mut state = Chip8State {
eti_600_flag: false, eti_600_flag: false,
vblank_waiting: false, vblank_waiting: false,
@@ -64,42 +66,44 @@ pub fn run<S: AsRef<str>>(chip8_executable_filepath: S, quirks: &Chip8Quirks, de
input: [false; 16], input: [false; 16],
}; };
if !state.eti_600_flag { if state.eti_600_flag {
state.r_pc = 0x200;
} else {
state.r_pc = 0x600; state.r_pc = 0x600;
} else {
state.r_pc = 0x200;
} }
// Load Program // Load Program
let _ = memory::load_file_to_memory(&mut state, chip8_executable_filepath.as_ref()); let _ = memory::load_file_to_memory(&mut state, chip8_executable_filepath.as_ref());
// Run Program // Run Program
start(&mut state, &quirks, debug_mode); start(state, quirks);
} }
fn start(state: &mut Chip8State, quirks: &Chip8Quirks, debug_mode: bool) { fn start(mut state: Chip8State, quirks: Chip8Quirks) {
// TODO rip out as much RL stuff from here and put into renderer // TODO rip out as much RL stuff from here and put into renderer
// Init Rendering Pipeline // Init Rendering Pipeline
let (mut rl, thread) = raylib::init() let (mut rl, thread) = raylib::init()
.size(renderer::DISPLAY_WIDTH, renderer::DISPLAY_HEIGHT) .size(
i32::from(renderer::DISPLAY_WIDTH),
i32::from(renderer::DISPLAY_HEIGHT),
)
.title("Chip8 Emu") .title("Chip8 Emu")
.build(); .build();
rl.set_target_fps(DESIRED_FPS); // Should see if i can get the users hz #[allow(clippy::cast_sign_loss)]
if !debug_mode { rl.set_target_fps(DESIRED_FPS as u32); // Should see if i can get the users hz
rl.set_trace_log(raylib::ffi::TraceLogLevel::LOG_NONE); rl.set_trace_log(raylib::ffi::TraceLogLevel::LOG_NONE);
}
// initialize timer // initialize timer
let mut timer_accumulator: f32 = 0.0f32; let mut timer_accumulator: f32 = 0.0f32;
let timer_increment: f32 = TIMER_TICK_RATE as f32 / DESIRED_FPS as f32; let timer_increment: f32 = TIMER_TICK_RATE / DESIRED_FPS;
// initialize builtin sprites // initialize builtin sprites
gpu::load_builtin_sprites(state); gpu::load_builtin_sprites(&mut state);
// initialize sound system look into struct and impl for functions // initialize sound system look into struct and impl for functions
let stream_handle = let stream_handle =
rodio::OutputStreamBuilder::open_default_stream().expect("open default audio stream"); rodio::OutputStreamBuilder::open_default_stream().expect("open default audio stream");
let sink = rodio::Sink::connect_new(&stream_handle.mixer()); let sink = rodio::Sink::connect_new(stream_handle.mixer());
let source = SineWave::new(440.0) let source = SineWave::new(440.0)
.amplify(0.2) // Volume (0.0 to 1.0) .amplify(0.2) // Volume (0.0 to 1.0)
@@ -122,21 +126,21 @@ fn start(state: &mut Chip8State, quirks: &Chip8Quirks, debug_mode: bool) {
sink.set_volume(0.0f32); sink.set_volume(0.0f32);
} }
input::handle_input(state, &mut rl); input::handle_input(&mut state, &mut rl);
state.vblank_waiting = false; state.vblank_waiting = false;
for _ in 0..CYCLES_PER_FRAME { for _ in 0..CYCLES_PER_FRAME as i32 {
let instruction_bytes = let instruction_bytes = memory::read_n_bytes(
memory::read_n_bytes(&state.mem, state.mem.len(), state.r_pc as usize, 2); &state.mem,
state.mem.len(),
state.r_pc as usize,
INSTRUCTION_BYTE_SIZE,
);
let instruction: u16 = let instruction: u16 =
((instruction_bytes[0] as u16) << 8) | instruction_bytes[1] as u16; (u16::from(instruction_bytes[0]) << 8) | u16::from(instruction_bytes[1]);
state.r_pc += 2; state.r_pc += 2;
if debug_mode { cpu::execute_instruction(&mut state, instruction, quirks);
debug::print_debug(state, instruction);
}
cpu::execute_instruction(state, instruction, &quirks);
if state.vblank_waiting { if state.vblank_waiting {
break; break;
@@ -150,7 +154,7 @@ fn start(state: &mut Chip8State, quirks: &Chip8Quirks, debug_mode: bool) {
while timer_accumulator >= 1.0f32 { while timer_accumulator >= 1.0f32 {
if state.r_dt > 0 { if state.r_dt > 0 {
state.r_dt -= 1; state.r_dt -= 1;
}; }
if state.r_st > 0 { if state.r_st > 0 {
state.r_st -= 1; state.r_st -= 1;
} }

View File

@@ -1,21 +1,22 @@
#![allow(clippy::many_single_char_names)]
use crate::chip8::{Chip8Quirks, gpu}; use crate::chip8::{Chip8Quirks, gpu};
use super::Chip8State; use super::Chip8State;
use super::memory::read_n_bytes; use super::memory::read_n_bytes;
// Need to come back and add good comments for each of the match patterns // Need to come back and add good comments for each of the match patterns
pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Chip8Quirks) { pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: Chip8Quirks) {
let c = ((instruction & 0xF000) >> 12) as u8; let c = ((instruction & 0xF000) >> 12) as u8;
let x = ((instruction & 0x0F00) >> 8) as u8; let x = ((instruction & 0x0F00) >> 8) as u8;
let y = ((instruction & 0x00F0) >> 4) as u8; let y = ((instruction & 0x00F0) >> 4) as u8;
let d = (instruction & 0x000F) as u8; let d = (instruction & 0x000F) as u8;
let n = d; let n = d;
let kk = (instruction & 0x00FF) as u8; let kk = (instruction & 0x00FF) as u8;
let nnn = (instruction & 0x0FFF) as u16; let nnn = instruction & 0x0FFF;
match (c, x, y, d) { match (c, x, y, d) {
(0x0, _, 0xE, 0x0) => { (0x0, _, 0xE, 0x0) => {
for row in state.display.iter_mut() { for row in &mut state.display {
for col in row { for col in row {
*col = false; *col = false;
} }
@@ -68,9 +69,9 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
} }
} }
(0x8, _, _, 0x4) => { (0x8, _, _, 0x4) => {
let val: u16 = state.r_v[x as usize] as u16 + state.r_v[y as usize] as u16; let val: u16 = u16::from(state.r_v[x as usize]) + u16::from(state.r_v[y as usize]);
if val > u8::MAX as u16 { if val > u16::from(u8::MAX) {
state.r_v[x as usize] = (val & 0xFFFF) as u8; state.r_v[x as usize] = val as u8;
state.r_v[0xF] = 1; state.r_v[0xF] = 1;
} else { } else {
state.r_v[x as usize] = val as u8; state.r_v[x as usize] = val as u8;
@@ -80,43 +81,44 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
(0x8, _, _, 0x5) => { (0x8, _, _, 0x5) => {
let flag = state.r_v[x as usize] >= state.r_v[y as usize]; 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[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 }; state.r_v[0xF] = u8::from(flag);
} }
(0x8, _, _, 0x6) => { (0x8, _, _, 0x6) => {
if !quirks.shifting { if !quirks.shifting {
state.r_v[x as usize] = state.r_v[y as usize]; state.r_v[x as usize] = state.r_v[y as usize];
} }
let flag = (state.r_v[x as usize] & 0b00000001) == 1; let flag = (state.r_v[x as usize] & 0b0000_0001) == 1;
state.r_v[x as usize] = state.r_v[x as usize] / 2; state.r_v[x as usize] /= 2;
state.r_v[0xF] = if flag { 1 } else { 0 }; state.r_v[0xF] = u8::from(flag);
} }
(0x8, _, _, 0x7) => { (0x8, _, _, 0x7) => {
let flag = state.r_v[x as usize] <= state.r_v[y as usize]; 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[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 }; state.r_v[0xF] = u8::from(flag);
} }
(0x8, _, _, 0xE) => { (0x8, _, _, 0xE) => {
if !quirks.shifting { if !quirks.shifting {
state.r_v[x as usize] = state.r_v[y as usize]; state.r_v[x as usize] = state.r_v[y as usize];
} }
let flag = ((state.r_v[x as usize] & 0b10000000) >> 7) == 1; let flag = ((state.r_v[x as usize] & 0b1000_0000) >> 7) == 1;
state.r_v[x as usize] = state.r_v[x as usize].wrapping_mul(2); state.r_v[x as usize] = state.r_v[x as usize].wrapping_mul(2);
state.r_v[0xF] = if flag { 1 } else { 0 }; state.r_v[0xF] = u8::from(flag);
} }
(0x9, _, _, _) => { (0x9, _, _, _) => {
if state.r_v[x as usize] != state.r_v[y as usize] { if state.r_v[x as usize] != state.r_v[y as usize] {
state.r_pc += 2 state.r_pc += 2;
} }
} }
(0xA, _, _, _) => state.r_i = nnn, (0xA, _, _, _) => state.r_i = nnn,
(0xB, _, _, _) => { (0xB, _, _, _) => {
if quirks.jumping { if quirks.jumping {
state.r_pc = nnn + state.r_v[x as usize] as u16; state.r_pc = nnn + u16::from(state.r_v[x as usize]);
} else { } else {
state.r_pc = nnn + state.r_v[0] as u16; state.r_pc = nnn + u16::from(state.r_v[0]);
} }
} }
(0xC, _, _, _) => { (0xC, _, _, _) => {
#[allow(clippy::cast_sign_loss)]
let rng = rand::random_range(0..256) as u8; let rng = rand::random_range(0..256) as u8;
let result = rng & kk; let result = rng & kk;
state.r_v[x as usize] = result; state.r_v[x as usize] = result;
@@ -161,15 +163,15 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
} }
(0xF, _, 0x1, 0x5) => state.r_dt = state.r_v[x as usize], (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, 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, _, 0x1, 0xE) => state.r_i += u16::from(state.r_v[x as usize]),
(0xF, _, 0x2, 0x9) => state.r_i = gpu::get_builtin_sprite_addr(x) as u16, (0xF, _, 0x2, 0x9) => state.r_i = u16::from(gpu::get_builtin_sprite_addr(x)),
(0xF, _, 0x3, 0x3) => { (0xF, _, 0x3, 0x3) => {
let mut decimal = state.r_v[x as usize]; let mut decimal = state.r_v[x as usize];
let mut i = 3; let mut i = 3;
loop { loop {
i = i - 1; i -= 1;
state.mem[(state.r_i + i) as usize] = decimal % 10; state.mem[(state.r_i + i) as usize] = decimal % 10;
decimal = decimal / 10; decimal /= 10;
if i == 0 { if i == 0 {
break; break;
@@ -180,15 +182,15 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
let mut i = 0; let mut i = 0;
while i <= x { while i <= x {
match y { match y {
0x5 => state.mem[(state.r_i + i as u16) as usize] = state.r_v[i as usize], 0x5 => state.mem[(state.r_i + u16::from(i)) 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], 0x6 => state.r_v[i as usize] = state.mem[(state.r_i + u16::from(i)) as usize],
_ => panic!("Unmatched OPCODE 0xFx{}5", y), _ => panic!("Unmatched OPCODE 0xFx{y}5"),
} }
i = i + 1; i += 1;
} }
if quirks.memory { if quirks.memory {
state.r_i += i as u16; state.r_i += u16::from(i);
} }
} }
_ => {} _ => {}

View File

@@ -1,27 +0,0 @@
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();
}

View File

@@ -2,25 +2,25 @@ use crate::chip8::memory;
use super::Chip8State; use super::Chip8State;
pub static CHIP8_DISPLAY_WIDTH: i32 = 64; pub static CHIP8_DISPLAY_WIDTH: u8 = 64;
pub static CHIP8_DISPLAY_HEIGHT: i32 = 32; pub static CHIP8_DISPLAY_HEIGHT: u8 = 32;
pub static SPRITE_WIDTH: u8 = 8; pub static SPRITE_WIDTH: u8 = 8;
// I probably don't need state here // I probably don't need state here
// refactor to just receive a mutatable buffer // 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) { pub fn draw(state: &mut Chip8State, vx: u8, vy: u8, bytes_to_draw: &[u8], bytes_to_draw_len: u8) {
state.r_v[0xF] = 0; state.r_v[0xF] = 0;
let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH as u8; let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH;
let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT as u8; let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT;
let mut bytes_idx = 0; let mut bytes_idx = 0;
while bytes_idx < bytes_to_draw_len { while bytes_idx < bytes_to_draw_len {
// TODO: this should be a u8 for safety // TODO: this should be a u8 for safety
let y: u8 = start_y.wrapping_add(bytes_idx) % CHIP8_DISPLAY_HEIGHT as u8; let y: u8 = start_y.wrapping_add(bytes_idx) % CHIP8_DISPLAY_HEIGHT;
let mut bit_idx = 0; let mut bit_idx = 0;
while bit_idx < SPRITE_WIDTH { while bit_idx < SPRITE_WIDTH {
let x: u8 = start_x.wrapping_add(bit_idx) % CHIP8_DISPLAY_WIDTH as u8; let x: u8 = start_x.wrapping_add(bit_idx) % CHIP8_DISPLAY_WIDTH;
let sprite_pixel = ((bytes_to_draw[bytes_idx as usize] >> (7 - bit_idx)) & 1) == 1; let sprite_pixel = ((bytes_to_draw[bytes_idx as usize] >> (7 - bit_idx)) & 1) == 1;
if sprite_pixel { if sprite_pixel {
@@ -48,21 +48,21 @@ pub fn clipping_draw(
bytes_to_draw_len: u8, bytes_to_draw_len: u8,
) { ) {
state.r_v[0xF] = 0; state.r_v[0xF] = 0;
let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH as u8; let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH;
let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT as u8; let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT;
let mut bytes_idx = 0; let mut bytes_idx = 0;
while bytes_idx < bytes_to_draw_len { while bytes_idx < bytes_to_draw_len {
let y: u16 = (start_y + bytes_idx) as u16; let y: u16 = u16::from(start_y + bytes_idx);
// TODO: general reminder to cleanup type casts to be consistent // TODO: general reminder to cleanup type casts to be consistent
if y >= CHIP8_DISPLAY_HEIGHT as u16 { if y >= u16::from(CHIP8_DISPLAY_HEIGHT) {
break; break;
} }
let mut bit_idx = 0; let mut bit_idx = 0;
while bit_idx < SPRITE_WIDTH { while bit_idx < SPRITE_WIDTH {
let x = (start_x + bit_idx) as u16; let x = u16::from(start_x + bit_idx);
if x >= CHIP8_DISPLAY_WIDTH as u16 { if x >= u16::from(CHIP8_DISPLAY_WIDTH) {
break; break;
} }
@@ -94,7 +94,7 @@ pub fn load_builtin_sprites(state: &mut Chip8State) {
} }
pub fn get_builtin_sprite_addr(sprite_index: u8) -> u8 { pub fn get_builtin_sprite_addr(sprite_index: u8) -> u8 {
return BUILTIN_SPRITES_ADDR + (sprite_index * BUILTIN_SPRITES_SIZE); BUILTIN_SPRITES_ADDR + (sprite_index * BUILTIN_SPRITES_SIZE)
} }
static BUILTIN_SPRITES_ADDR: u8 = 0; static BUILTIN_SPRITES_ADDR: u8 = 0;

View File

@@ -23,56 +23,31 @@ static RL_KEY_LAYOUT: [KeyboardKey; 16] = [
pub fn handle_input(state: &mut Chip8State, rl_hdl: &mut RaylibHandle) { pub fn handle_input(state: &mut Chip8State, rl_hdl: &mut RaylibHandle) {
for key in RL_KEY_LAYOUT { for key in RL_KEY_LAYOUT {
let mapped_key = qwerty_to_chip8(key); let Ok(mapped_key) = qwerty_to_chip8(key) else {
// let chip8_input_index = chip8_to_index(mapped_key); continue;
if rl_hdl.is_key_down(key) { };
state.input[mapped_key as usize] = true; state.input[mapped_key as usize] = rl_hdl.is_key_down(key);
} else {
state.input[mapped_key as usize] = false;
}
} }
} }
fn qwerty_to_chip8(keycode: KeyboardKey) -> u8 { fn qwerty_to_chip8(keycode: KeyboardKey) -> Result<u8, String> {
match keycode { match keycode {
KeyboardKey::KEY_ONE => return 0x1, KeyboardKey::KEY_ONE => Ok(0x1),
KeyboardKey::KEY_TWO => return 0x2, KeyboardKey::KEY_TWO => Ok(0x2),
KeyboardKey::KEY_THREE => return 0x3, KeyboardKey::KEY_THREE => Ok(0x3),
KeyboardKey::KEY_FOUR => return 0xC, KeyboardKey::KEY_FOUR => Ok(0xC),
KeyboardKey::KEY_Q => return 0x4, KeyboardKey::KEY_Q => Ok(0x4),
KeyboardKey::KEY_W => return 0x5, KeyboardKey::KEY_W => Ok(0x5),
KeyboardKey::KEY_E => return 0x6, KeyboardKey::KEY_E => Ok(0x6),
KeyboardKey::KEY_R => return 0xD, KeyboardKey::KEY_R => Ok(0xD),
KeyboardKey::KEY_A => return 0x7, KeyboardKey::KEY_A => Ok(0x7),
KeyboardKey::KEY_S => return 0x8, KeyboardKey::KEY_S => Ok(0x8),
KeyboardKey::KEY_D => return 0x9, KeyboardKey::KEY_D => Ok(0x9),
KeyboardKey::KEY_F => return 0xE, KeyboardKey::KEY_F => Ok(0xE),
KeyboardKey::KEY_Z => return 0xA, KeyboardKey::KEY_Z => Ok(0xA),
KeyboardKey::KEY_X => return 0x0, KeyboardKey::KEY_X => Ok(0x0),
KeyboardKey::KEY_C => return 0xB, KeyboardKey::KEY_C => Ok(0xB),
KeyboardKey::KEY_V => return 0xF, KeyboardKey::KEY_V => Ok(0xF),
_ => return 0, _ => Err(format!("Un-mapped keycode {}", keycode as u8)),
} }
} }
// 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),
// }
// }

View File

@@ -10,34 +10,30 @@ pub fn read_n_bytes(
let mut addr = start_addr; let mut addr = start_addr;
let mut bytes = Vec::new(); let mut bytes = Vec::new();
while addr != start_addr + n_bytes { while addr != start_addr + n_bytes {
if addr >= buffer_len { assert!(
panic!( addr < buffer_len,
"Couldn't read from Address {} exceeds buffer length {}", "Couldn't read from Address {addr} exceeds buffer length {buffer_len}"
addr, buffer_len );
) // nice error handling
}
bytes.push(buffer[addr]); bytes.push(buffer[addr]);
addr += 1; addr += 1;
} }
return bytes as Vec<u8>; bytes as Vec<u8>
} }
pub fn load_bytes(state: &mut Chip8State, data: &[u8], data_len: usize, start_addr: usize) { 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..(data_len + start_addr)].copy_from_slice(&data[..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<()> { pub fn load_file_to_memory<P: AsRef<Path>>(state: &mut Chip8State, filepath: P) -> io::Result<()> {
let fp = filepath.as_ref(); let fp = filepath.as_ref();
// read file to Vec(u8) // read file to Vec(u8)
let program = std::fs::read(fp)?; let bytes = std::fs::read(fp)?;
for i in 0..program.len() { for (i, byte) in bytes.into_iter().enumerate() {
state.mem[state.r_pc as usize + i] = program[i]; state.mem[state.r_pc as usize + i] = byte;
} }
// Should return Ok or Err // Should return Ok or Err

View File

@@ -2,27 +2,32 @@ use super::Chip8State;
use super::gpu::{CHIP8_DISPLAY_HEIGHT, CHIP8_DISPLAY_WIDTH}; use super::gpu::{CHIP8_DISPLAY_HEIGHT, CHIP8_DISPLAY_WIDTH};
use raylib::{RaylibHandle, RaylibThread, color::Color, prelude::RaylibDraw}; use raylib::{RaylibHandle, RaylibThread, color::Color, prelude::RaylibDraw};
pub static DISPLAY_WIDTH: i32 = 640; pub static DISPLAY_WIDTH: u16 = 640;
pub static DISPLAY_HEIGHT: i32 = 480; pub static DISPLAY_HEIGHT: u16 = 480;
pub fn render(state: &Chip8State, rl_handle: &mut RaylibHandle, rl_thread: &RaylibThread) { pub fn render(state: &Chip8State, rl_handle: &mut RaylibHandle, rl_thread: &RaylibThread) {
let mut d = rl_handle.begin_drawing(&rl_thread); let mut d = rl_handle.begin_drawing(rl_thread);
// d.clear_background(Color::BLACK); // d.clear_background(Color::BLACK);
let scale_x = DISPLAY_WIDTH / CHIP8_DISPLAY_WIDTH; let scale_x = DISPLAY_WIDTH / u16::from(CHIP8_DISPLAY_WIDTH);
let scale_y = DISPLAY_HEIGHT / CHIP8_DISPLAY_HEIGHT; let scale_y = DISPLAY_HEIGHT / u16::from(CHIP8_DISPLAY_HEIGHT);
for y in 0..CHIP8_DISPLAY_HEIGHT { for y in 0..CHIP8_DISPLAY_HEIGHT {
for x in 0..CHIP8_DISPLAY_WIDTH { for x in 0..CHIP8_DISPLAY_WIDTH {
// fix to render color based on exact bit for pixels // fix to render color based on exact bit for pixels
let color: Color; let color: Color = if state.display[y as usize][x as usize] {
if state.display[y as usize][x as usize] { Color::WHITE
color = Color::WHITE;
} else { } else {
color = Color::BLACK; Color::BLACK
} };
d.draw_rectangle(x * scale_x, y * scale_y, scale_x, scale_y, color); d.draw_rectangle(
i32::from(u16::from(x) * scale_x),
i32::from(u16::from(y) * scale_y),
i32::from(scale_x),
i32::from(scale_y),
color,
);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![allow(clippy::cast_possible_truncation)]
use clap::Parser; use clap::Parser;
@@ -7,9 +8,6 @@ mod chip8;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about = "Chip 8 Emulator")] #[command(version, about = "Chip 8 Emulator")]
struct Args { struct Args {
/// Turn on debug mode
#[arg(short, long, action = clap::ArgAction::SetTrue)]
debug: bool,
/// Set path for chip8 binary file to run /// Set path for chip8 binary file to run
#[arg(short, long)] #[arg(short, long)]
file: Option<String>, file: Option<String>,
@@ -33,10 +31,6 @@ fn main() {
}; };
if let Some(filepath) = args.file { if let Some(filepath) = args.file {
if args.debug { chip8::run(filepath, quirks);
chip8::run(filepath, &quirks, true);
} else {
chip8::run(filepath, &quirks, false);
}
} }
} }