initial commit
This commit is contained in:
commit
7a6e299da5
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "jsgb"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "jsgb"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "~4.4.0", features = ["derive"] }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [ cargo rustc ];
|
||||
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
// use crate::breakpoint_shell::{BreakPointShell, BreakPointShellCommand};
|
||||
|
||||
pub enum Command {
|
||||
Ignore,
|
||||
Step,
|
||||
Cont,
|
||||
Kill,
|
||||
}
|
||||
|
||||
pub enum State {
|
||||
Cont,
|
||||
Step
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Trigger {
|
||||
Address(usize),
|
||||
WriteTo(usize),
|
||||
Step(usize),
|
||||
}
|
||||
|
||||
pub trait BreakPointHandler {
|
||||
fn handler(&mut self, t: Trigger) -> Command;
|
||||
}
|
||||
|
||||
pub struct BreakPointManager {
|
||||
handlers: Vec<Box<dyn BreakPointHandler>>,
|
||||
breakpoints: HashMap<usize, usize>,
|
||||
steppers: Vec<usize>,
|
||||
}
|
||||
|
||||
impl BreakPointManager {
|
||||
pub fn new() -> Self {
|
||||
BreakPointManager {
|
||||
handlers: Vec::new(),
|
||||
breakpoints: HashMap::new(),
|
||||
steppers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME handler cannot use borrowed values adding more specific lifetimes could work but maybe
|
||||
// it is better to not own it at all?
|
||||
pub fn register_breakpoint(&mut self, adr: usize, bp: impl BreakPointHandler + 'static) {
|
||||
self.handlers.insert(adr, Box::new(bp));
|
||||
let i = self.handlers.len();
|
||||
self.breakpoints.insert(adr, i);
|
||||
}
|
||||
|
||||
pub fn inform(&mut self, adr: usize) -> Command {
|
||||
let mut new_steppers = Vec::new();
|
||||
new_steppers.reserve(self.steppers.len());
|
||||
|
||||
for hdl_i in self.steppers.drain(0..) {
|
||||
let cmd = self.handlers[hdl_i].handler(Trigger::Step(adr));
|
||||
match cmd {
|
||||
Command::Step => new_steppers.push(hdl_i),
|
||||
Command::Kill => return Command::Kill,
|
||||
Command::Cont | Command::Ignore => (),
|
||||
}
|
||||
}
|
||||
self.steppers = new_steppers;
|
||||
|
||||
if let Some(hdl_i) = self.breakpoints.get(&adr) {
|
||||
println!("hit breakpoint {adr:#08x}");
|
||||
let cmd = self.handlers[*hdl_i].handler(Trigger::Address(adr));
|
||||
match cmd {
|
||||
Command::Step => self.steppers.push(*hdl_i),
|
||||
Command::Kill => return Command::Kill,
|
||||
Command::Cont | Command::Ignore => (),
|
||||
}
|
||||
} else {
|
||||
return Command::Ignore;
|
||||
}
|
||||
|
||||
Command::Ignore // FIXME
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
use clap::{Parser, Subcommand};
|
||||
|
||||
use crate::breakpoint::{BreakPointHandler, Command, Trigger};
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub struct BreakPointShell {
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(disable_help_flag=true, disable_help_subcommand=false, disable_version_flag=true)]
|
||||
struct BreakPointShellParser {
|
||||
#[command(subcommand)]
|
||||
cmd: ShellCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum ShellCommand {
|
||||
#[command(visible_alias="s")]
|
||||
Step,
|
||||
#[command(visible_alias="q")]
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl BreakPointShell {
|
||||
pub fn new() -> BreakPointShell {
|
||||
BreakPointShell{}
|
||||
}
|
||||
}
|
||||
|
||||
impl BreakPointHandler for BreakPointShell {
|
||||
fn handler(&mut self, _: Trigger) -> Command {
|
||||
println!("welcome to the default debug shell; enter 'help' for a list of commands");
|
||||
|
||||
let stdin = io::stdin();
|
||||
|
||||
loop {
|
||||
print!("$ ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut user_input = String::new();
|
||||
stdin
|
||||
.read_line(&mut user_input)
|
||||
.expect("could not read command from stdin");
|
||||
|
||||
// FIXME needs to be able to handle quotes
|
||||
let args = [""].into_iter().chain(user_input.trim().split(' '));
|
||||
|
||||
let parser = match BreakPointShellParser::try_parse_from(args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match parser.cmd {
|
||||
ShellCommand::Step => break Command::Step,
|
||||
ShellCommand::Quit => break Command::Kill,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
use std::cmp;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use crate::breakpoint::{Command as BpCommand, BreakPointManager};
|
||||
use crate::breakpoint_shell::BreakPointShell;
|
||||
use crate::op::*;
|
||||
|
||||
const RAM_SIZE: usize = 0xFFFF + 1;
|
||||
const BOOT_ROM_SIZE: usize = 256;
|
||||
const REG_SIZE: usize = 0xB;
|
||||
|
||||
pub struct GameBoy {
|
||||
boot_rom: [u8; BOOT_ROM_SIZE],
|
||||
mem: [u8; RAM_SIZE],
|
||||
reg: [u8; REG_SIZE],
|
||||
pc: u16,
|
||||
sp: u16,
|
||||
|
||||
bp_manager: BreakPointManager,
|
||||
}
|
||||
|
||||
impl GameBoy {
|
||||
pub fn new() -> GameBoy {
|
||||
GameBoy {
|
||||
boot_rom: [0; BOOT_ROM_SIZE],
|
||||
mem: [0; RAM_SIZE],
|
||||
reg: [0; REG_SIZE],
|
||||
pc: 0,
|
||||
sp: 0,
|
||||
|
||||
bp_manager: BreakPointManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set(adr: u16, val: u8) {
|
||||
// check for special registers
|
||||
}
|
||||
|
||||
fn set_fast(adr: u16, val: u8) {
|
||||
// no checks for special registers
|
||||
}
|
||||
|
||||
pub fn load<T: io::Read>(&mut self, mut src: T) {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
let _ = src.read_to_end(&mut buf);
|
||||
// FIXME read only allowed amount
|
||||
match (&mut self.mem[..]).write_all(&buf) {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("{e}"),
|
||||
};
|
||||
|
||||
let mut bios = fs::File::open("DMG_ROM.bin").expect("could not open bios");
|
||||
bios.read_exact(&mut self.boot_rom)
|
||||
.expect("fail to read rom");
|
||||
}
|
||||
|
||||
pub fn register_breakpoint(&mut self, adr: &str) {
|
||||
let adr = match adr.strip_prefix("0x") {
|
||||
Some(adr) => adr,
|
||||
None => adr,
|
||||
};
|
||||
let Ok(adr) = usize::from_str_radix(adr, 16) else {
|
||||
panic!("FIXME error handling on given bad breakpoint address");
|
||||
};
|
||||
self.bp_manager.register_breakpoint(adr, BreakPointShell::new());
|
||||
}
|
||||
|
||||
fn validate_header(&self) {
|
||||
let mem = &self.mem;
|
||||
|
||||
let _nintendo_title = &mem[0x104..=0x133];
|
||||
|
||||
let _game_title = &mem[0x134..=0x13e];
|
||||
let _game_code = &mem[0x13f..=0x142];
|
||||
let _cgb_support_code = mem[0x143];
|
||||
let _marker_code = ((mem[0x144] as u16) << 8) + (mem[0x145] as u16);
|
||||
let _sgb_support_code = mem[0x146];
|
||||
let _software_type = mem[0x147];
|
||||
let _rom_size = mem[0x148];
|
||||
let _ext_ram_size = mem[0x149];
|
||||
let _dest_code = mem[0x14A];
|
||||
let _mask_rom_ver = mem[0x14C];
|
||||
|
||||
let reg_data = &mem[0x134..=0x14C];
|
||||
let sum = reg_data.iter().map(|x| *x as usize).sum::<usize>() + 0x19;
|
||||
let comp = (-(sum as isize) as usize) & 0xFF;
|
||||
|
||||
let comp_exp = mem[0x14D] as usize;
|
||||
if comp != comp_exp {
|
||||
panic!("comp check failed: calc: {comp:#010b}\nexp: {comp_exp:#010b}");
|
||||
} else {
|
||||
println!("complement header check ok");
|
||||
}
|
||||
|
||||
// let checksum = ((mem[0x14E] as u16) << 8) + mem[0x15F] as u16;
|
||||
// println!("checksum: {checksum}");
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.validate_header();
|
||||
self.pc = 0x100;
|
||||
self.sp = 0xFFFE;
|
||||
|
||||
loop {
|
||||
self.step();
|
||||
}
|
||||
}
|
||||
|
||||
fn op_ld(&mut self, dst: Loc, src: Loc) {
|
||||
let pc = self.pc as usize;
|
||||
|
||||
let (val, src_adr) = match src {
|
||||
Loc::Reg(i) => (self.reg[i as usize], i as usize),
|
||||
Loc::Val => (self.mem[pc + 1], 0),
|
||||
Loc::Mem(_) => todo!(),
|
||||
Loc::HMem(_) => todo!(),
|
||||
};
|
||||
|
||||
let (dst_ref, dst_adr) = match dst {
|
||||
Loc::Mem(ref src) => match src {
|
||||
AddrLoc::Val => {
|
||||
let adr = self.next16(pc) as usize;
|
||||
(&mut self.mem[adr], adr)
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
Loc::HMem(ref src) => match src {
|
||||
AddrLoc::Val => {
|
||||
let adr = self.mem[pc + 1] as usize + 0xFF00;
|
||||
(&mut self.mem[adr], adr)
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
Loc::Reg(r) => (&mut self.reg[r as usize], r as usize),
|
||||
Loc::Val => panic!("val as destination should not be possible"),
|
||||
};
|
||||
|
||||
let dst_str = dst.repr(dst_adr, *dst_ref);
|
||||
let src_str = src.repr(src_adr, val);
|
||||
println!("ld {dst_str} <- {src_str}");
|
||||
|
||||
*dst_ref = val;
|
||||
}
|
||||
|
||||
fn op_ld16(&mut self, dst: Loc, src: Loc) {
|
||||
let pc = self.pc as usize;
|
||||
|
||||
let (l, h) = match src {
|
||||
Loc::Val => (self.mem[pc + 1], self.mem[pc + 2]),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let dst_ref = match dst {
|
||||
Loc::Reg(reg) => {
|
||||
// FIXME assuming DD is a typo for DE...
|
||||
let reg_loc = reg as usize;
|
||||
&mut self.reg[reg_loc..=reg_loc + 1]
|
||||
}
|
||||
Loc::Val => panic!("value as dst is not allowed!"),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
match dst {
|
||||
Loc::Reg(_) => println!("ld16 {dst:6?} <- 0x{h:02x}{l:02x}"),
|
||||
_ => panic!("print address"),
|
||||
}
|
||||
|
||||
// FIXME does this break when (if) copying from another register?
|
||||
dst_ref[0] = l;
|
||||
dst_ref[1] = h;
|
||||
}
|
||||
|
||||
fn next16(&self, adr: usize) -> u16 {
|
||||
let ladr = self.mem[adr + 1] as u16;
|
||||
let hadr = self.mem[adr + 2] as u16;
|
||||
(hadr << 8) + ladr
|
||||
}
|
||||
|
||||
fn step(&mut self) {
|
||||
use Instr::*;
|
||||
|
||||
let mut pc = self.pc as usize;
|
||||
let instr = self.mem[pc];
|
||||
|
||||
match self.bp_manager.inform(pc) {
|
||||
BpCommand::Ignore | BpCommand::Cont => (),
|
||||
BpCommand::Step => todo!(),
|
||||
BpCommand::Kill => panic!("kill from breakpoint"),
|
||||
};
|
||||
|
||||
let win = &self.mem[pc..cmp::min(RAM_SIZE, pc + 3)];
|
||||
let op = match Operation::try_from(instr) {
|
||||
Ok(op) => op,
|
||||
Err(err) => {
|
||||
panic!("pc:{pc:#08x}:{win:02x?} error decoding operation {instr:#010b}: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
println!("pc:{pc:#08x}:{win:02x?} {op:?}");
|
||||
|
||||
match op.inst {
|
||||
Unimpl(instr) => {
|
||||
let h = (instr & HD_MSK) >> 6;
|
||||
let t1 = (instr & T1_MSK) >> 3;
|
||||
let t2 = instr & T2_MSK;
|
||||
panic!("unimplemented instruction {instr:#04x}: 0b{h:02b} {t1:03b} {t2:03b}")
|
||||
}
|
||||
Nop => (),
|
||||
Jp => {
|
||||
let new_pc = self.next16(pc);
|
||||
self.pc = new_pc;
|
||||
println!("jp {pc:#06x}->{new_pc:#06x}");
|
||||
return;
|
||||
}
|
||||
Jr => {
|
||||
let e = self.mem[pc + 1] as i8 + 2;
|
||||
let old_pc = self.pc;
|
||||
let new_pc = (old_pc as i16 + e as i16) as u16;
|
||||
self.pc = new_pc;
|
||||
println!("jr {new_pc:#06x} <- {old_pc:#06x} + {e}");
|
||||
return;
|
||||
}
|
||||
Call => {
|
||||
let sp = self.sp as usize;
|
||||
self.mem[sp - 1] = (pc >> 8) as u8;
|
||||
self.mem[sp - 2] = (pc & 0xFF) as u8;
|
||||
let new_pc = self.next16(pc);
|
||||
let old_pc = self.pc;
|
||||
self.pc = new_pc;
|
||||
self.sp -= 2;
|
||||
println!("call {new_pc:#06x} <- {old_pc:#06x}");
|
||||
return;
|
||||
}
|
||||
Ret => {
|
||||
let new_pc = self.next16((self.sp as usize) - 1);
|
||||
let old_pc = self.pc;
|
||||
self.pc = new_pc;
|
||||
self.sp += 2;
|
||||
println!("ret {new_pc:#06x} <- {old_pc:#06x}");
|
||||
}
|
||||
Ld(dst, src) => self.op_ld(dst, src),
|
||||
Ld16(dst, src) => self.op_ld16(dst, src),
|
||||
Di => println!("FIXME Di instruction not implemented"),
|
||||
}
|
||||
|
||||
pc += op.offs;
|
||||
self.pc = pc as u16;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
mod breakpoint;
|
||||
mod breakpoint_shell;
|
||||
mod emu;
|
||||
mod op;
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use crate::emu::GameBoy;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(short, long)]
|
||||
breakpoint: Option<String>,
|
||||
|
||||
#[arg(long, default_value = "rom.gb")]
|
||||
rom: PathBuf,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let mut gb = GameBoy::new();
|
||||
|
||||
let rom_path = cli.rom;
|
||||
let rom = fs::File::open(rom_path).expect("could not open rom"); // FIXME better message
|
||||
gb.load(rom);
|
||||
|
||||
if let Some(bp) = cli.breakpoint {
|
||||
gb.register_breakpoint(&bp);
|
||||
}
|
||||
|
||||
gb.run();
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
pub const HD_MSK: u8 = 0b1100_0000;
|
||||
pub const T1_MSK: u8 = 0b0011_1000;
|
||||
pub const T2_MSK: u8 = 0b0000_0111;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Operation {
|
||||
pub inst: Instr,
|
||||
pub cycl: usize,
|
||||
pub offs: usize,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Instr {
|
||||
// Pseudo
|
||||
Unimpl(u8),
|
||||
|
||||
// Misc
|
||||
Nop,
|
||||
|
||||
// Load
|
||||
Ld(Loc, Loc),
|
||||
Ld16(Loc, Loc),
|
||||
|
||||
// CPU ctl
|
||||
Di,
|
||||
|
||||
// Jump
|
||||
Jp,
|
||||
Jr,
|
||||
Call,
|
||||
Ret,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AddrLoc {
|
||||
Reg(Register),
|
||||
Mem,
|
||||
Val,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Loc {
|
||||
Reg(Register), // direct
|
||||
Mem(AddrLoc),
|
||||
HMem(AddrLoc),
|
||||
Val,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Register {
|
||||
A = 0x07,
|
||||
F = 0x08,
|
||||
B = 0x00,
|
||||
C = 0x01,
|
||||
D = 0x02,
|
||||
E = 0x03,
|
||||
H = 0x04,
|
||||
L = 0x05,
|
||||
|
||||
SP = 0x09,
|
||||
}
|
||||
|
||||
impl Loc {
|
||||
pub fn repr(&self, adr: usize, val: u8) -> String {
|
||||
match self {
|
||||
Loc::Reg(_) => format!("{self:6?}"),
|
||||
Loc::Mem(_) | Loc::HMem(_) => format!("({adr:#06x})"),
|
||||
Loc::Val => format!("{val:#04x}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Register {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use Register::*;
|
||||
let v = match value {
|
||||
0b111 => A,
|
||||
0b000 => B,
|
||||
0b001 => C,
|
||||
0b010 => D,
|
||||
0b011 => E,
|
||||
0b100 => H,
|
||||
0b101 => L,
|
||||
val => return Err(format!("invalid register value {val:#b}")),
|
||||
};
|
||||
|
||||
Ok(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for Operation {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
use Instr::*;
|
||||
use Loc::*;
|
||||
|
||||
let (inst, cycl, offs) = match value {
|
||||
0b00_000_000 => (Nop, 1, 1),
|
||||
0b11_000_011 => (Jp, 4, 3),
|
||||
0b00_011_000 => (Jr, 3, 2),
|
||||
0b11_001_101 => (Call, 6, 3),
|
||||
0b11_001_001 => (Ret, 4, 1),
|
||||
0b11_110_011 => (Di, 1, 1),
|
||||
0b11_101_010 => (Ld(Mem(AddrLoc::Val), Reg(Register::A)), 4, 3),
|
||||
0b11_100_000 => (Ld(HMem(AddrLoc::Val), Reg(Register::A)), 3, 2),
|
||||
_ if (value & (HD_MSK | T2_MSK)) == 0b00_000_110 => {
|
||||
let reg = Register::try_from((value & T1_MSK) >> 3)?;
|
||||
(Ld(Reg(reg), Val), 2, 2)
|
||||
}
|
||||
_ if (value & HD_MSK) == 0b01_000_000 => {
|
||||
let dst = Register::try_from((value & T1_MSK) >> 3)?;
|
||||
let src = Register::try_from(value & T2_MSK)?;
|
||||
(Ld(Reg(dst), Reg(src)), 1, 1)
|
||||
}
|
||||
_ if (value & (HD_MSK | 0b1_000 | T2_MSK)) == 0b00_000_001 => {
|
||||
let reg = match (value & 0b110_000) >> 4 {
|
||||
0b00 => Register::B,
|
||||
0b01 => Register::D,
|
||||
0b10 => Register::H,
|
||||
0b11 => Register::SP,
|
||||
val => return Err(format!("invalid register {val}")),
|
||||
};
|
||||
(Ld16(Reg(reg), Val), 3, 3)
|
||||
}
|
||||
_ => (Unimpl(value), 0, 0),
|
||||
};
|
||||
|
||||
Ok(Operation { inst, cycl, offs })
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue