chirp: Break frontends into separate projects
This commit is contained in:
18
chirp-minifb/src/error.rs
Normal file
18
chirp-minifb/src/error.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! Error type for chirp-minifb
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
/// Error originated in [`chirp`]
|
||||
#[error(transparent)]
|
||||
Chirp(#[from] chirp::error::Error),
|
||||
/// Error originated in [`std::io`]
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
/// Error originated in [`minifb`]
|
||||
#[error(transparent)]
|
||||
Minifb(#[from] minifb::Error),
|
||||
}
|
||||
228
chirp-minifb/src/main.rs
Normal file
228
chirp-minifb/src/main.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
// (c) 2023 John A. Breaux
|
||||
// This code is licensed under MIT license (see LICENSE for details)
|
||||
|
||||
//! Chirp: A chip-8 interpreter in Rust
|
||||
//! Hello, world!
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod error;
|
||||
mod ui;
|
||||
|
||||
use chirp::error::Error::BreakpointHit;
|
||||
use chirp::*;
|
||||
use error::Result;
|
||||
use gumdrop::*;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use ui::*;
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
let options = Arguments::parse_args_default_or_exit();
|
||||
let state = State::new(options)?;
|
||||
for result in state {
|
||||
if let Err(e) = result {
|
||||
eprintln!("{}", e.bold().red());
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parses a hexadecimal string into a u16
|
||||
fn parse_hex(value: &str) -> std::result::Result<u16, std::num::ParseIntError> {
|
||||
u16::from_str_radix(value, 16)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)]
|
||||
struct Arguments {
|
||||
#[options(help = "Load a ROM to run on Chirp.", required, free)]
|
||||
pub file: PathBuf,
|
||||
#[options(help = "Print this help message.")]
|
||||
help: bool,
|
||||
#[options(help = "Enable debug mode at startup.")]
|
||||
pub debug: bool,
|
||||
#[options(help = "Enable pause mode at startup.")]
|
||||
pub pause: bool,
|
||||
|
||||
#[options(help = "Set the instructions-per-delay rate. If unspecified, use realtime.")]
|
||||
pub speed: Option<usize>,
|
||||
#[options(help = "Set the instructions-per-frame rate.")]
|
||||
pub step: Option<usize>,
|
||||
#[options(help = "Enable performance benchmarking on stderr (requires -S)")]
|
||||
pub perf: bool,
|
||||
|
||||
#[options(
|
||||
help = "Run in (Chip8, SChip, XOChip) mode.",
|
||||
//parse(from_str = "parse_mode")
|
||||
)]
|
||||
pub mode: Option<Mode>,
|
||||
|
||||
#[options(
|
||||
short = "z",
|
||||
help = "Disable setting vF to 0 after a bitwise operation."
|
||||
)]
|
||||
pub vfreset: bool,
|
||||
#[options(
|
||||
short = "x",
|
||||
help = "Disable waiting for vblank after issuing a draw call."
|
||||
)]
|
||||
pub drawsync: bool,
|
||||
|
||||
#[options(
|
||||
short = "c",
|
||||
help = "Use CHIP-48 style DMA instructions, which don't touch I."
|
||||
)]
|
||||
pub memory: bool,
|
||||
|
||||
#[options(
|
||||
short = "v",
|
||||
help = "Use CHIP-48 style bit-shifts, which don't touch vY."
|
||||
)]
|
||||
pub shift: bool,
|
||||
|
||||
#[options(
|
||||
short = "b",
|
||||
help = "Use SUPER-CHIP style indexed jump, which is indexed relative to v[adr]."
|
||||
)]
|
||||
pub jumping: bool,
|
||||
|
||||
#[options(
|
||||
long = "break",
|
||||
help = "Set breakpoints for the emulator to stop at.",
|
||||
parse(try_from_str = "parse_hex"),
|
||||
meta = "BP"
|
||||
)]
|
||||
pub breakpoints: Vec<u16>,
|
||||
|
||||
#[options(
|
||||
help = "Load additional word at address 0x1fe",
|
||||
parse(try_from_str = "parse_hex"),
|
||||
meta = "WORD"
|
||||
)]
|
||||
pub data: u16,
|
||||
|
||||
#[options(help = "Set the target framerate.", default = "60", meta = "FR")]
|
||||
pub frame_rate: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Chip8 {
|
||||
pub cpu: CPU,
|
||||
pub screen: Screen,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
pub speed: usize,
|
||||
pub step: Option<usize>,
|
||||
pub rate: u64,
|
||||
pub perf: bool,
|
||||
pub ch8: Chip8,
|
||||
pub ui: UI,
|
||||
pub ft: Instant,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(options: Arguments) -> Result<Self> {
|
||||
let mut state = State {
|
||||
speed: options.speed.unwrap_or(8),
|
||||
step: options.step,
|
||||
rate: options.frame_rate,
|
||||
perf: options.perf,
|
||||
ch8: Chip8 {
|
||||
cpu: CPU::new(
|
||||
Some(&options.file),
|
||||
0x50,
|
||||
0x200,
|
||||
Dis::default(),
|
||||
options.breakpoints,
|
||||
Flags {
|
||||
quirks: options.mode.unwrap_or_default().into(),
|
||||
debug: options.debug,
|
||||
pause: options.pause,
|
||||
..Default::default()
|
||||
},
|
||||
)?,
|
||||
screen: Screen::default(),
|
||||
},
|
||||
ui: UIBuilder::new(128, 64, &options.file).build()?,
|
||||
ft: Instant::now(),
|
||||
};
|
||||
// Flip the state of the quirks
|
||||
state.ch8.cpu.flags.quirks.bin_ops ^= options.vfreset;
|
||||
state.ch8.cpu.flags.quirks.dma_inc ^= options.memory;
|
||||
state.ch8.cpu.flags.quirks.draw_wait ^= options.drawsync;
|
||||
state.ch8.cpu.flags.quirks.shift ^= options.shift;
|
||||
state.ch8.cpu.flags.quirks.stupid_jumps ^= options.jumping;
|
||||
state.ch8.screen.write(0x1feu16, options.data);
|
||||
Ok(state)
|
||||
}
|
||||
fn keys(&mut self) -> Result<bool> {
|
||||
self.ui.keys(&mut self.ch8)
|
||||
}
|
||||
fn frame(&mut self) -> Result<bool> {
|
||||
self.ui.frame(&self.ch8)
|
||||
}
|
||||
fn tick_cpu(&mut self) -> Result<()> {
|
||||
if !self.ch8.cpu.flags.pause {
|
||||
let rate = self.speed;
|
||||
match self.step {
|
||||
Some(ticks) => {
|
||||
let time = Instant::now();
|
||||
self.ch8.cpu.multistep(&mut self.ch8.screen, ticks)?;
|
||||
if self.perf {
|
||||
let time = time.elapsed();
|
||||
let nspt = time.as_secs_f64() / ticks as f64;
|
||||
eprintln!(
|
||||
"{ticks},\t{time:.05?},\t{:.4} nspt,\t{} ipf,\t{} mips",
|
||||
nspt * 1_000_000_000.0,
|
||||
((1.0 / 60.0f64) / nspt).trunc(),
|
||||
(1.0 / nspt).trunc() / 1_000_000.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.ch8.cpu.multistep(&mut self.ch8.screen, rate)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn wait_for_next_frame(&mut self) {
|
||||
let rate = Duration::from_nanos(1_000_000_000 / self.rate + 1);
|
||||
std::thread::sleep(rate.saturating_sub(self.ft.elapsed()));
|
||||
self.ft += rate;
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for State {
|
||||
type Item = Result<()>;
|
||||
|
||||
/// Pretty heavily abusing iterators here, in an annoying way
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.wait_for_next_frame();
|
||||
match self.keys() {
|
||||
Ok(opt) if !opt => return None,
|
||||
Err(e) => return Some(Err(e)), // summary lol
|
||||
_ => (),
|
||||
}
|
||||
// Allow breakpoint hit messages
|
||||
match self.tick_cpu() {
|
||||
Err(error::Error::Chirp(BreakpointHit { addr, next })) => {
|
||||
eprintln!("Breakpoint hit: {:3x} ({:4x})", addr, next);
|
||||
}
|
||||
Err(e) => return Some(Err(e)),
|
||||
_ => (),
|
||||
}
|
||||
match self.frame() {
|
||||
Ok(opt) if !opt => return None,
|
||||
Err(e) => return Some(Err(e)),
|
||||
_ => (),
|
||||
}
|
||||
Some(Ok(()))
|
||||
}
|
||||
}
|
||||
149
chirp-minifb/src/tests.rs
Normal file
149
chirp-minifb/src/tests.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
//! Tests for chirp-minifb
|
||||
#![allow(clippy::redundant_clone)]
|
||||
|
||||
use super::ui::*;
|
||||
use super::Chip8;
|
||||
use crate::error::Result;
|
||||
use chirp::*;
|
||||
use std::{collections::hash_map::DefaultHasher, hash::Hash};
|
||||
|
||||
mod ui_builder {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn ui_builder() -> Result<()> {
|
||||
let builder = UIBuilder::new(32, 64, "dummy.ch8").build()?;
|
||||
println!("{builder:?}");
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn default() {
|
||||
let ui_builder = UIBuilder::default();
|
||||
println!("{ui_builder:?}");
|
||||
}
|
||||
#[test]
|
||||
#[allow(clippy::redundant_clone)]
|
||||
fn clone_debug() {
|
||||
let ui_builder_clone = UIBuilder::default().clone();
|
||||
println!("{ui_builder_clone:?}");
|
||||
}
|
||||
}
|
||||
mod ui {
|
||||
use super::*;
|
||||
fn new_chip8() -> Chip8 {
|
||||
Chip8 {
|
||||
cpu: CPU::default(),
|
||||
screen: Screen::default(),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn frame() -> Result<()> {
|
||||
let mut ui = UIBuilder::new(32, 64, "dummy.ch8").build()?;
|
||||
let ch8 = new_chip8();
|
||||
ui.frame(&ch8).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn keys() -> Result<()> {
|
||||
let mut ui = UIBuilder::new(32, 64, "dummy.ch8").build()?;
|
||||
let mut ch8 = new_chip8();
|
||||
let ch8 = &mut ch8;
|
||||
ui.frame(ch8).unwrap();
|
||||
ui.keys(ch8).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
#[test]
|
||||
fn debug() -> Result<()> {
|
||||
println!("{:?}", UIBuilder::new(32, 64, "dummy.ch8").build()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
mod framebuffer_format {
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn default() {
|
||||
let _fbf = FrameBufferFormat::default();
|
||||
}
|
||||
#[test]
|
||||
fn clone() {
|
||||
let fbf = FrameBufferFormat {
|
||||
fg: 0x12345678,
|
||||
bg: 0x90abcdef,
|
||||
};
|
||||
let fbf2 = fbf.clone();
|
||||
assert_eq!(fbf, fbf2);
|
||||
}
|
||||
#[test]
|
||||
fn debug() {
|
||||
println!("{:?}", FrameBufferFormat::default());
|
||||
}
|
||||
#[test]
|
||||
fn eq() {
|
||||
assert_eq!(FrameBufferFormat::default(), FrameBufferFormat::default());
|
||||
assert_ne!(
|
||||
FrameBufferFormat {
|
||||
fg: 0xff00ff,
|
||||
bg: 0x00ff00
|
||||
},
|
||||
FrameBufferFormat {
|
||||
fg: 0x00ff00,
|
||||
bg: 0xff00ff
|
||||
},
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn ord() {
|
||||
assert!(
|
||||
FrameBufferFormat::default()
|
||||
== FrameBufferFormat {
|
||||
fg: 0xffffff,
|
||||
bg: 0xffffff,
|
||||
}
|
||||
.min(FrameBufferFormat::default())
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn hash() {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
FrameBufferFormat::default().hash(&mut hasher);
|
||||
println!("{hasher:?}");
|
||||
}
|
||||
}
|
||||
|
||||
mod framebuffer {
|
||||
use super::*;
|
||||
// [derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[test]
|
||||
fn new() {
|
||||
assert_eq!(FrameBuffer::new(64, 32), FrameBuffer::default());
|
||||
}
|
||||
#[test]
|
||||
fn clone() {
|
||||
let fb1 = FrameBuffer::default();
|
||||
let fb2 = fb1.clone();
|
||||
assert_eq!(fb1, fb2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
println!("{:?}", FrameBuffer::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eq() {
|
||||
assert_eq!(FrameBuffer::new(64, 32), FrameBuffer::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ord() {
|
||||
assert!(FrameBuffer::new(21, 12) == FrameBuffer::new(21, 12).min(FrameBuffer::new(34, 46)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash() {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
FrameBuffer::default().hash(&mut hasher);
|
||||
println!("{hasher:?}");
|
||||
}
|
||||
}
|
||||
279
chirp-minifb/src/ui.rs
Normal file
279
chirp-minifb/src/ui.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
// (c) 2023 John A. Breaux
|
||||
// This code is licensed under MIT license (see LICENSE for details)
|
||||
#![allow(missing_docs)]
|
||||
//! Platform-specific IO/UI code, and some debug functionality.
|
||||
//! TODO: Destroy this all.
|
||||
|
||||
use super::Chip8;
|
||||
use crate::error::Result;
|
||||
use chirp::screen::Screen;
|
||||
use minifb::*;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UIBuilder {
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub name: Option<&'static str>,
|
||||
pub rom: Option<PathBuf>,
|
||||
pub window_options: WindowOptions,
|
||||
}
|
||||
|
||||
impl UIBuilder {
|
||||
#[allow(dead_code)] // this code is used in tests thank you
|
||||
pub fn new(width: usize, height: usize, rom: impl AsRef<Path>) -> Self {
|
||||
UIBuilder {
|
||||
width,
|
||||
height,
|
||||
rom: Some(rom.as_ref().to_owned()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn build(&self) -> Result<UI> {
|
||||
let ui = UI {
|
||||
window: Window::new(
|
||||
self.name.unwrap_or_default(),
|
||||
self.width,
|
||||
self.height,
|
||||
self.window_options,
|
||||
)?,
|
||||
keyboard: Default::default(),
|
||||
fb: Default::default(),
|
||||
rom: self.rom.to_owned().unwrap_or_default(),
|
||||
time: Instant::now(),
|
||||
};
|
||||
Ok(ui)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UIBuilder {
|
||||
fn default() -> Self {
|
||||
UIBuilder {
|
||||
width: 128,
|
||||
height: 64,
|
||||
name: Some("Chip-8 Interpreter"),
|
||||
rom: None,
|
||||
window_options: WindowOptions {
|
||||
title: true,
|
||||
resize: false,
|
||||
scale: Scale::X8,
|
||||
scale_mode: ScaleMode::AspectRatioStretch,
|
||||
none: true,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FrameBufferFormat {
|
||||
pub fg: u32,
|
||||
pub bg: u32,
|
||||
}
|
||||
|
||||
impl Default for FrameBufferFormat {
|
||||
fn default() -> Self {
|
||||
FrameBufferFormat {
|
||||
// fg: 0x0011a434,
|
||||
// bg: 0x001E2431,
|
||||
fg: 0x00FFFF00,
|
||||
bg: 0x00623701,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FrameBuffer {
|
||||
buffer: Vec<u32>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
format: FrameBufferFormat,
|
||||
}
|
||||
|
||||
impl FrameBuffer {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
FrameBuffer {
|
||||
buffer: vec![0x00be4d; width * height],
|
||||
width,
|
||||
height,
|
||||
format: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn render(&mut self, window: &mut Window, screen: &Screen) -> Result<()> {
|
||||
// Resizing the buffer does not unmap memory.
|
||||
// After the first use of high-res mode, this is pretty cheap
|
||||
(self.width, self.height) = match screen.len() {
|
||||
256 => (64, 32),
|
||||
1024 => (128, 64),
|
||||
_ => {
|
||||
unimplemented!("Screen must be 64*32 or 128*64");
|
||||
}
|
||||
};
|
||||
self.buffer.resize(self.width * self.height, 0);
|
||||
for (idx, byte) in screen.as_slice().iter().enumerate() {
|
||||
for bit in 0..8 {
|
||||
self.buffer[8 * idx + bit] = if byte & (1 << (7 - bit)) as u8 != 0 {
|
||||
self.format.fg
|
||||
} else {
|
||||
self.format.bg
|
||||
// .wrapping_add(0x001104 * (idx / self.width) as u32)
|
||||
// .wrapping_add(0x141000 * (idx & 3) as u32)
|
||||
}
|
||||
}
|
||||
}
|
||||
window.update_with_buffer(&self.buffer, self.width, self.height)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrameBuffer {
|
||||
fn default() -> Self {
|
||||
Self::new(64, 32)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UI {
|
||||
window: Window,
|
||||
keyboard: Vec<Key>,
|
||||
fb: FrameBuffer,
|
||||
rom: PathBuf,
|
||||
time: Instant,
|
||||
}
|
||||
|
||||
impl UI {
|
||||
pub fn frame(&mut self, ch8: &Chip8) -> Result<bool> {
|
||||
if ch8.cpu.flags.pause {
|
||||
self.window.set_title("Chirp ⏸")
|
||||
} else {
|
||||
self.window.set_title(&format!(
|
||||
"Chirp ▶ {:02.02}",
|
||||
(1.0 / self.time.elapsed().as_secs_f64())
|
||||
));
|
||||
}
|
||||
if !self.window.is_open() {
|
||||
return Ok(false);
|
||||
}
|
||||
self.time = Instant::now();
|
||||
// update framebuffer
|
||||
self.fb.render(&mut self.window, &ch8.screen)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn keys(&mut self, ch8: &mut Chip8) -> Result<bool> {
|
||||
// TODO: Remove this hacky workaround for minifb's broken get_keys_* functions.
|
||||
let get_keys_pressed = || {
|
||||
self.window
|
||||
.get_keys()
|
||||
.into_iter()
|
||||
.filter(|key| !self.keyboard.contains(key))
|
||||
};
|
||||
let get_keys_released = || {
|
||||
self.keyboard
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|key| !self.window.get_keys().contains(key))
|
||||
};
|
||||
for key in get_keys_released() {
|
||||
if let Some(key) = identify_key(key) {
|
||||
ch8.cpu.release(key)?;
|
||||
}
|
||||
}
|
||||
// handle keybinds for the UI
|
||||
for key in get_keys_pressed() {
|
||||
use Key::*;
|
||||
match key {
|
||||
F1 | Comma => ch8.cpu.dump(),
|
||||
F2 | Period => ch8.screen.print_screen(),
|
||||
F3 => {
|
||||
debug_dump_screen(ch8, &self.rom).expect("Unable to write debug screen dump");
|
||||
}
|
||||
F4 | Slash => {
|
||||
eprintln!("Debug {}.", {
|
||||
ch8.cpu.flags.debug();
|
||||
if ch8.cpu.flags.debug {
|
||||
"enabled"
|
||||
} else {
|
||||
"disabled"
|
||||
}
|
||||
})
|
||||
}
|
||||
F5 | Backslash => eprintln!("{}.", {
|
||||
ch8.cpu.flags.pause();
|
||||
if ch8.cpu.flags.pause {
|
||||
"Paused"
|
||||
} else {
|
||||
"Unpaused"
|
||||
}
|
||||
}),
|
||||
F6 | Enter => {
|
||||
eprintln!("Step");
|
||||
ch8.cpu.singlestep(&mut ch8.screen)?;
|
||||
}
|
||||
F7 => {
|
||||
eprintln!("Set breakpoint {:03x}.", ch8.cpu.pc());
|
||||
ch8.cpu.set_break(ch8.cpu.pc());
|
||||
}
|
||||
F8 => {
|
||||
eprintln!("Unset breakpoint {:03x}.", ch8.cpu.pc());
|
||||
ch8.cpu.unset_break(ch8.cpu.pc());
|
||||
}
|
||||
F9 | Delete => {
|
||||
eprintln!("Soft reset state.cpu {:03x}", ch8.cpu.pc());
|
||||
ch8.cpu.soft_reset();
|
||||
ch8.screen.clear();
|
||||
}
|
||||
Escape => return Ok(false),
|
||||
key => {
|
||||
if let Some(key) = identify_key(key) {
|
||||
ch8.cpu.press(key)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.keyboard = self.window.get_keys();
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identify_key(key: Key) -> Option<usize> {
|
||||
match key {
|
||||
Key::Key1 => Some(0x1),
|
||||
Key::Key2 => Some(0x2),
|
||||
Key::Key3 => Some(0x3),
|
||||
Key::Key4 => Some(0xc),
|
||||
Key::Q => Some(0x4),
|
||||
Key::W => Some(0x5),
|
||||
Key::E => Some(0x6),
|
||||
Key::R => Some(0xD),
|
||||
Key::A => Some(0x7),
|
||||
Key::S => Some(0x8),
|
||||
Key::D => Some(0x9),
|
||||
Key::F => Some(0xE),
|
||||
Key::Z => Some(0xA),
|
||||
Key::X => Some(0x0),
|
||||
Key::C => Some(0xB),
|
||||
Key::V => Some(0xF),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> {
|
||||
let mut path = PathBuf::new().join(format!(
|
||||
"{}_{}.bin",
|
||||
rom.file_stem().unwrap_or_default().to_string_lossy(),
|
||||
ch8.cpu.cycle()
|
||||
));
|
||||
path.set_extension("bin");
|
||||
if std::fs::write(&path, ch8.screen.as_slice()).is_ok() {
|
||||
eprintln!("Saved to {}", &path.display());
|
||||
} else if std::fs::write("screen_dump.bin", ch8.screen.as_slice()).is_ok() {
|
||||
eprintln!("Saved to screen_dump.bin");
|
||||
} else {
|
||||
eprintln!("Failed to dump screen to file.")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user