Compare commits
31 Commits
daf87fa5d2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 0695c23a78 | |||
| c3df8672c8 | |||
| 47cce19940 | |||
| d8b3bcae19 | |||
| 7ede5aeafa | |||
| 08a2ec4459 | |||
| 58901521e5 | |||
| fed6762288 | |||
| 342eb4557d | |||
| 63fe937481 | |||
| 73422b4072 | |||
| efba92b8c7 | |||
| 25c6d9beaa | |||
| 38ec89813d | |||
| 596e60abeb | |||
| ad50485d37 | |||
| ce5ff7ee24 | |||
| 124493fd11 | |||
| 0ff0b6c596 | |||
| 253095bc6d | |||
| 063ca15b37 | |||
| 266c685d1b | |||
| 82323846ec | |||
| b3eed758cc | |||
| d6aeda49ca | |||
| 877ee5de9a | |||
| 49e4914c00 | |||
| a3b1dcf32d | |||
| 6d0b4f8f2a | |||
| c500c507af | |||
| 060415a7fa |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
/programs
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -177,7 +177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chip-8-em"
|
||||
name = "chip8-em"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "chip-8-em"
|
||||
name = "chip8-em"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright 2026 Cole Landers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
213
README.md
Normal file
213
README.md
Normal file
@@ -0,0 +1,213 @@
|
||||
<a id="readme-top"></a>
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||
*** See the bottom of this document for the declaration of the reference variables
|
||||
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
||||
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||
-->
|
||||
[![MIT][license-shield]][license-url]
|
||||
<!-- [![LinkedIn][linkedin-shield]][linkedin-url] -->
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<!-- <a href="https://git.colelanders.dev/jlanders/chip8-em">
|
||||
<img src="images/logo.png" alt="Logo" width="80" height="80">
|
||||
</a> -->
|
||||
|
||||
<h3 align="center">Chip8 Emulator</h3>
|
||||
|
||||
<p align="center">
|
||||
Chip8 Emulator built in Rust.
|
||||
<br />
|
||||
<!-- <a href="https://git.colelanders.dev/jlanders/chip8-em"><strong>Explore the docs »</strong></a>
|
||||
<br /> -->
|
||||
<br />
|
||||
<a href="https://git.colelanders.dev/jlanders/chip8-em">View Demo</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#about-the-project">About The Project</a>
|
||||
<ul>
|
||||
<li><a href="#built-with">Built With</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#prerequisites">Prerequisites</a></li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
<li><a href="#running">Running</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#usage">Usage</a></li>
|
||||
<li><a href="#roadmap">Roadmap</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
|
||||
![Chip8 Emulator Screen Shot][product-screenshot]
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
### Built With
|
||||
|
||||
* [![Rust][Rust]][Rust-url]
|
||||
* [![Raylib][Raylib]][Raylib-url]
|
||||
* [![Clap][Clap]][Clap-url]
|
||||
* [![Rodio][Rodio]][Rodio-url]
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
You can find pre-built binaries for Windows/MacOS/Linux -> [releases](https://git.colelanders.dev/jlanders/chip8-em/releases)
|
||||
|
||||
To run the code get a local copy up and running follow these simple example steps.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
This is an example of how to list things you need to use the software and how to install them.
|
||||
* rust - Follow the official install guide [here](https://rust-lang.org/tools/install/)
|
||||
<!-- ```sh
|
||||
npm install npm@latest -g
|
||||
``` -->
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repo
|
||||
```sh
|
||||
git clone https://git.colelanders.dev/jlanders/chip8-em.git
|
||||
```
|
||||
2. Navigate to the repo directory
|
||||
```sh
|
||||
cd chip8-em
|
||||
```
|
||||
3. Build the project
|
||||
```sh
|
||||
cargo build
|
||||
```
|
||||
|
||||
### 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>
|
||||
|
||||
<!-- USAGE EXAMPLES -->
|
||||
## Usage
|
||||
|
||||
**WIP**
|
||||
|
||||
<!-- 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>
|
||||
|
||||
|
||||
|
||||
<!-- ROADMAP -->
|
||||
## Roadmap
|
||||
|
||||
- [ ] Super-Chip8 extension
|
||||
- [ ] XO-CHIP extension
|
||||
- [ ] More Configuration
|
||||
- [ ] Window Resizing
|
||||
- [ ] Allow rebinding keys via GUI
|
||||
- [ ] Replacing default sound via GUI
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
|
||||
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!
|
||||
|
||||
<!-- LICENSE -->
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See `LICENSE` for more information.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- CONTACT -->
|
||||
## Contact
|
||||
|
||||
Cole Landers - jclanders@pm.me
|
||||
|
||||
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>
|
||||
|
||||
|
||||
|
||||
<!-- 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>
|
||||
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- 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-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-url]: https://linkedin.com/in/linkedin_username-->
|
||||
[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 -->
|
||||
[Rust]: https://img.shields.io/badge/rust-%23E32F26.svg?style=for-the-badge&logo=rust&logoColor=white
|
||||
[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
|
||||
BIN
images/screenshot.png
Normal file
BIN
images/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
68
src/chip8.rs
68
src/chip8.rs
@@ -1,5 +1,4 @@
|
||||
mod cpu;
|
||||
mod debug;
|
||||
mod gpu;
|
||||
mod input;
|
||||
mod memory;
|
||||
@@ -7,14 +6,15 @@ 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 = 60;
|
||||
static CYCLES_PER_FRAME: u32 = 10;
|
||||
static MEMORY_LIMIT: u16 = 4096;
|
||||
static STACK_LIMIT: u8 = 16;
|
||||
static VARIABLE_REGISTER_COUNT: u8 = 16;
|
||||
static TIMER_TICK_RATE: f32 = 60.0;
|
||||
static DESIRED_FPS: f32 = 60.0;
|
||||
static CYCLES_PER_FRAME: f32 = 10.0;
|
||||
static INSTRUCTION_BYTE_SIZE: usize = 2;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct Chip8State {
|
||||
// Flags
|
||||
eti_600_flag: bool,
|
||||
@@ -39,6 +39,8 @@ struct Chip8State {
|
||||
input: [bool; 16],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Chip8Quirks {
|
||||
pub vf_reset: bool,
|
||||
pub memory: bool,
|
||||
@@ -48,7 +50,7 @@ pub struct Chip8Quirks {
|
||||
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 {
|
||||
eti_600_flag: false,
|
||||
vblank_waiting: false,
|
||||
@@ -64,42 +66,44 @@ pub fn run<S: AsRef<str>>(chip8_executable_filepath: S, quirks: &Chip8Quirks, de
|
||||
input: [false; 16],
|
||||
};
|
||||
|
||||
if !state.eti_600_flag {
|
||||
state.r_pc = 0x200;
|
||||
} else {
|
||||
if state.eti_600_flag {
|
||||
state.r_pc = 0x600;
|
||||
} else {
|
||||
state.r_pc = 0x200;
|
||||
}
|
||||
|
||||
// Load Program
|
||||
let _ = memory::load_file_to_memory(&mut state, chip8_executable_filepath.as_ref());
|
||||
|
||||
// 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
|
||||
// Init Rendering Pipeline
|
||||
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")
|
||||
.build();
|
||||
rl.set_target_fps(DESIRED_FPS); // Should see if i can get the users hz
|
||||
if !debug_mode {
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
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);
|
||||
}
|
||||
|
||||
// initialize timer
|
||||
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
|
||||
gpu::load_builtin_sprites(state);
|
||||
gpu::load_builtin_sprites(&mut 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 sink = rodio::Sink::connect_new(stream_handle.mixer());
|
||||
|
||||
let source = SineWave::new(440.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);
|
||||
}
|
||||
|
||||
input::handle_input(state, &mut rl);
|
||||
input::handle_input(&mut state, &mut rl);
|
||||
|
||||
state.vblank_waiting = false;
|
||||
for _ in 0..CYCLES_PER_FRAME {
|
||||
let instruction_bytes =
|
||||
memory::read_n_bytes(&state.mem, state.mem.len(), state.r_pc as usize, 2);
|
||||
for _ in 0..CYCLES_PER_FRAME as i32 {
|
||||
let instruction_bytes = memory::read_n_bytes(
|
||||
&state.mem,
|
||||
state.mem.len(),
|
||||
state.r_pc as usize,
|
||||
INSTRUCTION_BYTE_SIZE,
|
||||
);
|
||||
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;
|
||||
|
||||
if debug_mode {
|
||||
debug::print_debug(state, instruction);
|
||||
}
|
||||
|
||||
cpu::execute_instruction(state, instruction, &quirks);
|
||||
cpu::execute_instruction(&mut state, instruction, quirks);
|
||||
|
||||
if state.vblank_waiting {
|
||||
break;
|
||||
@@ -150,7 +154,7 @@ fn start(state: &mut Chip8State, quirks: &Chip8Quirks, debug_mode: bool) {
|
||||
while timer_accumulator >= 1.0f32 {
|
||||
if state.r_dt > 0 {
|
||||
state.r_dt -= 1;
|
||||
};
|
||||
}
|
||||
if state.r_st > 0 {
|
||||
state.r_st -= 1;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
use crate::chip8::{Chip8Quirks, 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, quirks: &Chip8Quirks) {
|
||||
pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: Chip8Quirks) {
|
||||
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;
|
||||
let nnn = instruction & 0x0FFF;
|
||||
|
||||
match (c, x, y, d) {
|
||||
(0x0, _, 0xE, 0x0) => {
|
||||
for row in state.display.iter_mut() {
|
||||
for row in &mut state.display {
|
||||
for col in row {
|
||||
*col = false;
|
||||
}
|
||||
@@ -68,9 +69,9 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
|
||||
}
|
||||
}
|
||||
(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;
|
||||
let val: u16 = u16::from(state.r_v[x as usize]) + u16::from(state.r_v[y as usize]);
|
||||
if val > u16::from(u8::MAX) {
|
||||
state.r_v[x as usize] = val as u8;
|
||||
state.r_v[0xF] = 1;
|
||||
} else {
|
||||
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) => {
|
||||
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 };
|
||||
state.r_v[0xF] = u8::from(flag);
|
||||
}
|
||||
(0x8, _, _, 0x6) => {
|
||||
if !quirks.shifting {
|
||||
state.r_v[x as usize] = state.r_v[y as usize];
|
||||
}
|
||||
let flag = (state.r_v[x as usize] & 0b00000001) == 1;
|
||||
state.r_v[x as usize] = state.r_v[x as usize] / 2;
|
||||
state.r_v[0xF] = if flag { 1 } else { 0 };
|
||||
let flag = (state.r_v[x as usize] & 0b0000_0001) == 1;
|
||||
state.r_v[x as usize] /= 2;
|
||||
state.r_v[0xF] = u8::from(flag);
|
||||
}
|
||||
(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 };
|
||||
state.r_v[0xF] = u8::from(flag);
|
||||
}
|
||||
(0x8, _, _, 0xE) => {
|
||||
if !quirks.shifting {
|
||||
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[0xF] = if flag { 1 } else { 0 };
|
||||
state.r_v[0xF] = u8::from(flag);
|
||||
}
|
||||
(0x9, _, _, _) => {
|
||||
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,
|
||||
(0xB, _, _, _) => {
|
||||
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 {
|
||||
state.r_pc = nnn + state.r_v[0] as u16;
|
||||
state.r_pc = nnn + u16::from(state.r_v[0]);
|
||||
}
|
||||
}
|
||||
(0xC, _, _, _) => {
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
let rng = rand::random_range(0..256) as u8;
|
||||
let result = rng & kk;
|
||||
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, 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, _, 0x1, 0xE) => state.r_i += u16::from(state.r_v[x as usize]),
|
||||
(0xF, _, 0x2, 0x9) => state.r_i = u16::from(gpu::get_builtin_sprite_addr(x)),
|
||||
(0xF, _, 0x3, 0x3) => {
|
||||
let mut decimal = state.r_v[x as usize];
|
||||
let mut i = 3;
|
||||
loop {
|
||||
i = i - 1;
|
||||
i -= 1;
|
||||
state.mem[(state.r_i + i) as usize] = decimal % 10;
|
||||
decimal = decimal / 10;
|
||||
decimal /= 10;
|
||||
|
||||
if i == 0 {
|
||||
break;
|
||||
@@ -180,15 +182,15 @@ pub fn execute_instruction(state: &mut Chip8State, instruction: u16, quirks: &Ch
|
||||
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),
|
||||
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 + u16::from(i)) as usize],
|
||||
_ => panic!("Unmatched OPCODE 0xFx{y}5"),
|
||||
}
|
||||
|
||||
i = i + 1;
|
||||
i += 1;
|
||||
}
|
||||
if quirks.memory {
|
||||
state.r_i += i as u16;
|
||||
state.r_i += u16::from(i);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -2,25 +2,25 @@ use crate::chip8::memory;
|
||||
|
||||
use super::Chip8State;
|
||||
|
||||
pub static CHIP8_DISPLAY_WIDTH: i32 = 64;
|
||||
pub static CHIP8_DISPLAY_HEIGHT: i32 = 32;
|
||||
pub static CHIP8_DISPLAY_WIDTH: u8 = 64;
|
||||
pub static CHIP8_DISPLAY_HEIGHT: u8 = 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) {
|
||||
state.r_v[0xF] = 0;
|
||||
let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH as u8;
|
||||
let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT 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;
|
||||
|
||||
let mut bytes_idx = 0;
|
||||
while bytes_idx < bytes_to_draw_len {
|
||||
// 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;
|
||||
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;
|
||||
if sprite_pixel {
|
||||
@@ -48,21 +48,21 @@ pub fn clipping_draw(
|
||||
bytes_to_draw_len: u8,
|
||||
) {
|
||||
state.r_v[0xF] = 0;
|
||||
let start_x = state.r_v[vx as usize] % CHIP8_DISPLAY_WIDTH as u8;
|
||||
let start_y = state.r_v[vy as usize] % CHIP8_DISPLAY_HEIGHT 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;
|
||||
|
||||
let mut bytes_idx = 0;
|
||||
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
|
||||
if y >= CHIP8_DISPLAY_HEIGHT as u16 {
|
||||
if y >= u16::from(CHIP8_DISPLAY_HEIGHT) {
|
||||
break;
|
||||
}
|
||||
|
||||
let mut bit_idx = 0;
|
||||
while bit_idx < SPRITE_WIDTH {
|
||||
let x = (start_x + bit_idx) as u16;
|
||||
if x >= CHIP8_DISPLAY_WIDTH as u16 {
|
||||
let x = u16::from(start_x + bit_idx);
|
||||
if x >= u16::from(CHIP8_DISPLAY_WIDTH) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ pub fn load_builtin_sprites(state: &mut Chip8State) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -23,56 +23,31 @@ static RL_KEY_LAYOUT: [KeyboardKey; 16] = [
|
||||
|
||||
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;
|
||||
}
|
||||
let Ok(mapped_key) = qwerty_to_chip8(key) else {
|
||||
continue;
|
||||
};
|
||||
state.input[mapped_key as usize] = rl_hdl.is_key_down(key);
|
||||
}
|
||||
}
|
||||
|
||||
fn qwerty_to_chip8(keycode: KeyboardKey) -> u8 {
|
||||
fn qwerty_to_chip8(keycode: KeyboardKey) -> Result<u8, String> {
|
||||
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,
|
||||
KeyboardKey::KEY_ONE => Ok(0x1),
|
||||
KeyboardKey::KEY_TWO => Ok(0x2),
|
||||
KeyboardKey::KEY_THREE => Ok(0x3),
|
||||
KeyboardKey::KEY_FOUR => Ok(0xC),
|
||||
KeyboardKey::KEY_Q => Ok(0x4),
|
||||
KeyboardKey::KEY_W => Ok(0x5),
|
||||
KeyboardKey::KEY_E => Ok(0x6),
|
||||
KeyboardKey::KEY_R => Ok(0xD),
|
||||
KeyboardKey::KEY_A => Ok(0x7),
|
||||
KeyboardKey::KEY_S => Ok(0x8),
|
||||
KeyboardKey::KEY_D => Ok(0x9),
|
||||
KeyboardKey::KEY_F => Ok(0xE),
|
||||
KeyboardKey::KEY_Z => Ok(0xA),
|
||||
KeyboardKey::KEY_X => Ok(0x0),
|
||||
KeyboardKey::KEY_C => Ok(0xB),
|
||||
KeyboardKey::KEY_V => Ok(0xF),
|
||||
_ => 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),
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -10,34 +10,30 @@ pub fn read_n_bytes(
|
||||
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
|
||||
}
|
||||
assert!(
|
||||
addr < buffer_len,
|
||||
"Couldn't read from Address {addr} exceeds buffer length {buffer_len}"
|
||||
);
|
||||
bytes.push(buffer[addr]);
|
||||
|
||||
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) {
|
||||
for i in 0..data_len {
|
||||
state.mem[start_addr + i] = data[i];
|
||||
}
|
||||
state.mem[start_addr..(data_len + start_addr)].copy_from_slice(&data[..data_len]);
|
||||
}
|
||||
|
||||
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)?;
|
||||
let bytes = std::fs::read(fp)?;
|
||||
|
||||
for i in 0..program.len() {
|
||||
state.mem[state.r_pc as usize + i] = program[i];
|
||||
for (i, byte) in bytes.into_iter().enumerate() {
|
||||
state.mem[state.r_pc as usize + i] = byte;
|
||||
}
|
||||
|
||||
// Should return Ok or Err
|
||||
|
||||
@@ -2,27 +2,32 @@ 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 static DISPLAY_WIDTH: u16 = 640;
|
||||
pub static DISPLAY_HEIGHT: u16 = 480;
|
||||
|
||||
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);
|
||||
|
||||
let scale_x = DISPLAY_WIDTH / CHIP8_DISPLAY_WIDTH;
|
||||
let scale_y = DISPLAY_HEIGHT / CHIP8_DISPLAY_HEIGHT;
|
||||
let scale_x = DISPLAY_WIDTH / u16::from(CHIP8_DISPLAY_WIDTH);
|
||||
let scale_y = DISPLAY_HEIGHT / u16::from(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;
|
||||
let color: Color = if state.display[y as usize][x as usize] {
|
||||
Color::WHITE
|
||||
} else {
|
||||
color = Color::GREEN;
|
||||
}
|
||||
d.draw_rectangle(x * scale_x, y * scale_y, scale_x, scale_y, color);
|
||||
Color::BLACK
|
||||
};
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -1,3 +1,6 @@
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
mod chip8;
|
||||
@@ -5,9 +8,6 @@ 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>,
|
||||
@@ -31,10 +31,6 @@ fn main() {
|
||||
};
|
||||
|
||||
if let Some(filepath) = args.file {
|
||||
if args.debug {
|
||||
chip8::run(filepath, &quirks, true);
|
||||
} else {
|
||||
chip8::run(filepath, &quirks, false);
|
||||
}
|
||||
chip8::run(filepath, quirks);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user