//! A very inaccurate gameboy emulator #![feature(decl_macro)] #![allow(clippy::unit_arg)] /// A gameboy has: /// - A 16-bit memory bus /// - A CPU /// - A picture processing unit pub mod error { use crate::cpu::disasm::Insn; use std::fmt::Display; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Error { kind: ErrorKind, // any other information about an error } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ErrorKind { InvalidInstruction(u8), InvalidAddress(u16), UnimplementedInsn(Insn), Unimplemented(u8), HitBreak(u16, u8), Todo, } impl From for Error { fn from(value: ErrorKind) -> Self { Self { kind: value } } } impl From for Result { fn from(value: Error) -> Self { Err(value) } } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.kind.fmt(f) } } impl Display for ErrorKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ErrorKind::InvalidInstruction(i) => write!(f, "Invalid instruction: {i}"), ErrorKind::InvalidAddress(addr) => write!(f, "Invalid address: {addr:04x}"), ErrorKind::UnimplementedInsn(i) => write!(f, "Unimplemented instruction: {i}"), ErrorKind::Unimplemented(i) => write!(f, "Unimplemented instruction: {i}"), ErrorKind::HitBreak(addr, m) => { write!(f, "Hit breakpoint {addr:x} (Took {m} cycles)") } ErrorKind::Todo => write!(f, "Not yet implemented"), } } } } pub mod memory; pub mod graphics; pub mod cpu; pub mod audio; #[cfg(test)] mod tests { use crate::memory::{io::*, Cart}; const TEST_ROMS: &[&str] = &[ // "roms/Legend of Zelda, The - Link's Awakening DX (USA, Europe) (Rev B) (SGB Enhanced).gbc", // "roms/Pokemon - Crystal Version (USA, Europe) (Rev A).gbc", // "roms/Pokemon Pinball (USA) (Rumble Version) (SGB Enhanced).gbc", // "roms/Pokemon - Red Version (USA, Europe) (SGB Enhanced).gb", "roms/Super Mario Land 2 - 6 Golden Coins (USA, Europe) (Rev B).gb", "roms/Super Mario Land (World) (Rev A).gb", "roms/Tetris (World) (Rev A).gb", ]; #[test] /// Checksums the cart headers of every cart defined in [TEST_ROMS], /// and compares them against the checksum stored in the header fn cart_header_checksum() { for rom in TEST_ROMS { let rom = std::fs::read(rom).unwrap(); let cart = Cart::from(rom); let mut cksum = 0u8; for index in 0x134..0x14d { cksum = cksum .wrapping_sub(cart.read(index).unwrap()) .wrapping_sub(1) } assert_eq!(cksum, cart.headercksum().unwrap()) } } }