use std::{
collections::VecDeque,
fmt::{self, Display, Formatter},
io::Read,
sync::{Arc, Mutex},
};
#[cfg(feature = "wasm")]
use std::{
convert::TryInto,
panic::{set_hook, take_hook, PanicInfo},
};
use boytacean_common::{
error::Error,
util::{read_file, SharedThread},
};
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
use crate::{
apu::{Apu, HighPassFilter},
cheats::{
genie::{GameGenie, GameGenieCode},
shark::{GameShark, GameSharkCode},
},
cpu::Cpu,
data::{BootRom, CGB_BOOT, CGB_BOYTACEAN, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT},
devices::{printer::PrinterDevice, stdout::StdoutDevice},
dma::Dma,
info::Info,
mmu::Mmu,
pad::{Pad, PadKey},
ppu::{
Ppu, PpuMode, Tile, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB1555_SIZE,
FRAME_BUFFER_RGB565_SIZE, FRAME_BUFFER_RGBA_SIZE, FRAME_BUFFER_SIZE,
FRAME_BUFFER_XRGB8888_SIZE,
},
rom::{Cartridge, RamSize},
serial::{NullDevice, Serial, SerialDevice},
timer::Timer,
};
#[cfg(feature = "wasm")]
use crate::{color::Pixel, ppu::Palette};
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum GameBoyMode {
Dmg = 1,
Cgb = 2,
Sgb = 3,
}
impl GameBoyMode {
pub fn description(&self) -> &'static str {
match self {
GameBoyMode::Dmg => "Game Boy (DMG)",
GameBoyMode::Cgb => "Game Boy Color (CGB)",
GameBoyMode::Sgb => "Super Game Boy (SGB)",
}
}
pub fn from_u8(value: u8) -> Self {
match value {
1 => GameBoyMode::Dmg,
2 => GameBoyMode::Cgb,
3 => GameBoyMode::Sgb,
_ => panic!("Invalid mode value: {value}"),
}
}
pub fn from_string(value: &str) -> Self {
match value {
"dmg" | "DMG" => GameBoyMode::Dmg,
"cgb" | "CGB" => GameBoyMode::Cgb,
"sgb" | "SGB" => GameBoyMode::Sgb,
_ => panic!("Invalid mode value: {value}"),
}
}
pub fn to_string(&self, uppercase: Option<bool>) -> String {
let uppercase = uppercase.unwrap_or(false);
match self {
GameBoyMode::Dmg => (if uppercase { "DMG" } else { "dmg" }).to_string(),
GameBoyMode::Cgb => (if uppercase { "CGB" } else { "cgb" }).to_string(),
GameBoyMode::Sgb => (if uppercase { "SGB" } else { "sgb" }).to_string(),
}
}
pub fn is_dmg(&self) -> bool {
*self == GameBoyMode::Dmg
}
pub fn is_cgb(&self) -> bool {
*self == GameBoyMode::Cgb
}
pub fn is_sgb(&self) -> bool {
*self == GameBoyMode::Sgb
}
}
impl Display for GameBoyMode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl From<u8> for GameBoyMode {
fn from(value: u8) -> Self {
Self::from_u8(value)
}
}
impl From<&str> for GameBoyMode {
fn from(value: &str) -> Self {
Self::from_string(value)
}
}
impl From<GameBoyMode> for String {
fn from(value: GameBoyMode) -> Self {
value.to_string(Some(true))
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum GameBoySpeed {
Normal = 0,
Double = 1,
}
impl GameBoySpeed {
pub fn description(&self) -> &'static str {
match self {
GameBoySpeed::Normal => "Normal Speed",
GameBoySpeed::Double => "Double Speed",
}
}
pub fn switch(&self) -> Self {
match self {
GameBoySpeed::Normal => GameBoySpeed::Double,
GameBoySpeed::Double => GameBoySpeed::Normal,
}
}
pub fn multiplier(&self) -> u8 {
match self {
GameBoySpeed::Normal => 1,
GameBoySpeed::Double => 2,
}
}
pub fn from_u8(value: u8) -> Self {
match value {
0 => GameBoySpeed::Normal,
1 => GameBoySpeed::Double,
_ => panic!("Invalid speed value: {value}"),
}
}
}
impl Display for GameBoySpeed {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl From<u8> for GameBoySpeed {
fn from(value: u8) -> Self {
Self::from_u8(value)
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum GameBoyDevice {
Cpu = 0,
Mmu = 1,
Ppu = 3,
Apu = 4,
Dma = 5,
Pad = 6,
Timer = 7,
Serial = 8,
Unknown = 100,
}
impl GameBoyDevice {
pub fn description(&self) -> &'static str {
match self {
GameBoyDevice::Cpu => "CPU",
GameBoyDevice::Mmu => "MMU",
GameBoyDevice::Ppu => "PPU",
GameBoyDevice::Apu => "APU",
GameBoyDevice::Dma => "DMA",
GameBoyDevice::Pad => "GamePad",
GameBoyDevice::Timer => "Timer",
GameBoyDevice::Serial => "Serial",
GameBoyDevice::Unknown => "Unknown",
}
}
pub fn from_u8(value: u8) -> Self {
match value {
0 => GameBoyDevice::Cpu,
1 => GameBoyDevice::Mmu,
3 => GameBoyDevice::Ppu,
4 => GameBoyDevice::Apu,
5 => GameBoyDevice::Dma,
6 => GameBoyDevice::Pad,
7 => GameBoyDevice::Timer,
8 => GameBoyDevice::Serial,
_ => GameBoyDevice::Unknown,
}
}
pub fn into_u8(&self) -> u8 {
match self {
GameBoyDevice::Cpu => 0,
GameBoyDevice::Mmu => 1,
GameBoyDevice::Ppu => 3,
GameBoyDevice::Apu => 4,
GameBoyDevice::Dma => 5,
GameBoyDevice::Pad => 6,
GameBoyDevice::Timer => 7,
GameBoyDevice::Serial => 8,
GameBoyDevice::Unknown => 100,
}
}
}
impl Display for GameBoyDevice {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description())
}
}
impl From<u8> for GameBoyDevice {
fn from(value: u8) -> Self {
Self::from_u8(value)
}
}
impl From<GameBoyDevice> for u8 {
fn from(value: GameBoyDevice) -> Self {
value.into_u8()
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct GameBoyConfig {
mode: GameBoyMode,
ppu_enabled: bool,
apu_enabled: bool,
dma_enabled: bool,
timer_enabled: bool,
serial_enabled: bool,
clock_freq: u32,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl GameBoyConfig {
pub fn is_dmg(&self) -> bool {
self.mode == GameBoyMode::Dmg
}
pub fn is_cgb(&self) -> bool {
self.mode == GameBoyMode::Cgb
}
pub fn is_sgb(&self) -> bool {
self.mode == GameBoyMode::Sgb
}
pub fn mode(&self) -> GameBoyMode {
self.mode
}
pub fn set_mode(&mut self, value: GameBoyMode) {
self.mode = value;
}
pub fn ppu_enabled(&self) -> bool {
self.ppu_enabled
}
pub fn set_ppu_enabled(&mut self, value: bool) {
self.ppu_enabled = value;
}
pub fn apu_enabled(&self) -> bool {
self.apu_enabled
}
pub fn set_apu_enabled(&mut self, value: bool) {
self.apu_enabled = value;
}
pub fn dma_enabled(&self) -> bool {
self.dma_enabled
}
pub fn set_dma_enabled(&mut self, value: bool) {
self.dma_enabled = value;
}
pub fn timer_enabled(&self) -> bool {
self.timer_enabled
}
pub fn set_timer_enabled(&mut self, value: bool) {
self.timer_enabled = value;
}
pub fn serial_enabled(&self) -> bool {
self.serial_enabled
}
pub fn set_serial_enabled(&mut self, value: bool) {
self.serial_enabled = value;
}
pub fn clock_freq(&self) -> u32 {
self.clock_freq
}
pub fn set_clock_freq(&mut self, value: u32) {
self.clock_freq = value;
}
}
impl Default for GameBoyConfig {
fn default() -> Self {
Self {
mode: GameBoyMode::Dmg,
ppu_enabled: true,
apu_enabled: true,
dma_enabled: true,
timer_enabled: true,
serial_enabled: true,
clock_freq: GameBoy::CPU_FREQ,
}
}
}
pub struct Components {
pub ppu: Ppu,
pub apu: Apu,
pub dma: Dma,
pub pad: Pad,
pub timer: Timer,
pub serial: Serial,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct Registers {
pub pc: u16,
pub sp: u16,
pub a: u8,
pub b: u8,
pub c: u8,
pub d: u8,
pub e: u8,
pub h: u8,
pub l: u8,
pub scy: u8,
pub scx: u8,
pub wy: u8,
pub wx: u8,
pub ly: u8,
pub lyc: u8,
}
pub trait AudioProvider {
fn audio_output(&self) -> u16;
fn audio_buffer(&self) -> &VecDeque<i16>;
fn clear_audio_buffer(&mut self);
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct ClockFrame {
pub cycles: u64,
pub frames: u16,
frame_buffer: Option<Vec<u8>>,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl ClockFrame {
pub fn frame_buffer_eager(&mut self) -> Option<Vec<u8>> {
self.frame_buffer.take()
}
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct GameBoy {
mode: GameBoyMode,
ppu_enabled: bool,
apu_enabled: bool,
dma_enabled: bool,
timer_enabled: bool,
serial_enabled: bool,
clock_freq: u32,
boot_rom: BootRom,
cpu: Cpu,
gbc: SharedThread<GameBoyConfig>,
}
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl GameBoy {
#[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
pub fn new(mode: Option<GameBoyMode>) -> Self {
let mode = mode.unwrap_or(GameBoyMode::Dmg);
let gbc = Arc::new(Mutex::new(GameBoyConfig {
mode,
ppu_enabled: true,
apu_enabled: true,
dma_enabled: true,
timer_enabled: true,
serial_enabled: true,
clock_freq: GameBoy::CPU_FREQ,
}));
let components = Components {
ppu: Ppu::new(mode, gbc.clone()),
apu: Apu::default(),
dma: Dma::default(),
pad: Pad::default(),
timer: Timer::default(),
serial: Serial::default(),
};
let mmu = Mmu::new(components, mode, gbc.clone());
let cpu = Cpu::new(mmu, gbc.clone());
Self {
mode,
boot_rom: BootRom::None,
ppu_enabled: true,
apu_enabled: true,
dma_enabled: true,
timer_enabled: true,
serial_enabled: true,
clock_freq: GameBoy::CPU_FREQ,
cpu,
gbc,
}
}
pub fn verify_rom(data: &[u8]) -> bool {
Cartridge::from_data(data).is_ok()
}
pub fn reset(&mut self) {
self.ppu().reset();
self.apu().reset();
self.timer().reset();
self.serial().reset();
self.mmu().reset();
self.cpu.reset();
self.reset_cheats();
}
pub fn reset_cheats(&mut self) {
self.reset_game_genie();
self.reset_game_shark();
}
#[inline(always)]
pub fn clock(&mut self) -> u16 {
let cycles = self.cpu_clock() as u16;
let cycles_n = cycles / self.multiplier() as u16;
self.clock_devices(cycles, cycles_n);
cycles
}
pub fn clock_many(&mut self, count: usize) -> u16 {
let mut cycles = 0u16;
for _ in 0..count {
cycles += self.cpu_clock() as u16;
}
let cycles_n = cycles / self.multiplier() as u16;
self.clock_devices(cycles, cycles_n);
cycles
}
pub fn clock_step(&mut self, addr: u16) -> u16 {
let cycles = self.cpu_clock() as u16;
if self.cpu_i().pc() == addr {
return cycles;
}
let cycles_n = cycles / self.multiplier() as u16;
self.clock_devices(cycles, cycles_n);
cycles
}
pub fn clocks(&mut self, count: usize) -> u64 {
let mut cycles = 0_u64;
for _ in 0..count {
cycles += self.clock() as u64;
}
cycles
}
pub fn clocks_cycles(&mut self, limit: usize) -> u64 {
let mut cycles = 0_u64;
while cycles < limit as u64 {
cycles += self.clock() as u64;
}
cycles
}
pub fn clocks_frame_buffer(&mut self, limit: usize) -> ClockFrame {
let mut cycles = 0_u64;
let mut frames = 0_u16;
let mut frame_buffer: Option<Vec<u8>> = None;
let mut last_frame = self.ppu_frame();
while cycles < limit as u64 {
cycles += self.clock() as u64;
if self.ppu_frame() != last_frame {
frame_buffer = Some(self.frame_buffer().to_vec());
last_frame = self.ppu_frame();
frames += 1;
}
}
ClockFrame {
cycles,
frames,
frame_buffer,
}
}
pub fn next_frame(&mut self) -> u32 {
let mut cycles = 0u32;
let current_frame = self.ppu_frame();
while self.ppu_frame() == current_frame {
cycles += self.clock() as u32;
}
cycles
}
pub fn step_to(&mut self, addr: u16) -> u32 {
let mut cycles = 0u32;
while self.cpu_i().pc() != addr {
cycles += self.clock_step(addr) as u32;
}
cycles
}
#[inline(always)]
fn clock_devices(&mut self, cycles: u16, cycles_n: u16) {
if self.ppu_enabled {
self.ppu_clock(cycles_n);
}
if self.apu_enabled {
self.apu_clock(cycles_n);
}
if self.dma_enabled {
self.dma_clock(cycles);
}
if self.timer_enabled {
self.timer_clock(cycles);
}
if self.serial_enabled {
self.serial_clock(cycles);
}
}
pub fn key_press(&mut self, key: PadKey) {
self.pad().key_press(key);
}
pub fn key_lift(&mut self, key: PadKey) {
self.pad().key_lift(key);
}
#[inline(always)]
pub fn cpu_clock(&mut self) -> u8 {
self.cpu.clock()
}
#[inline(always)]
pub fn ppu_clock(&mut self, cycles: u16) {
self.ppu().clock(cycles)
}
#[inline(always)]
pub fn apu_clock(&mut self, cycles: u16) {
self.apu().clock(cycles)
}
#[inline(always)]
pub fn dma_clock(&mut self, cycles: u16) {
self.mmu().clock_dma(cycles);
}
#[inline(always)]
pub fn timer_clock(&mut self, cycles: u16) {
self.timer().clock(cycles)
}
#[inline(always)]
pub fn serial_clock(&mut self, cycles: u16) {
self.serial().clock(cycles)
}
pub fn ppu_ly(&mut self) -> u8 {
self.ppu().ly()
}
pub fn ppu_mode(&mut self) -> PpuMode {
self.ppu().mode()
}
pub fn ppu_frame(&mut self) -> u16 {
self.ppu().frame_index()
}
pub fn boot(&mut self) {
self.load_boot_state();
}
pub fn load_unsafe(&mut self, boot: bool) {
self.load(boot).unwrap();
}
pub fn load_boot_state(&mut self) {
self.cpu.boot();
}
pub fn vram_eager(&mut self) -> Vec<u8> {
self.ppu().vram().to_vec()
}
pub fn hram_eager(&mut self) -> Vec<u8> {
self.ppu().vram().to_vec()
}
pub fn frame_buffer_eager(&mut self) -> Vec<u8> {
self.frame_buffer().to_vec()
}
pub fn frame_buffer_raw_eager(&mut self) -> Vec<u8> {
self.frame_buffer_raw().to_vec()
}
pub fn audio_buffer_eager(&mut self, clear: bool) -> Vec<i16> {
let buffer = Vec::from(self.audio_buffer().clone());
if clear {
self.clear_audio_buffer();
}
buffer
}
pub fn audio_output(&self) -> u16 {
self.apu_i().output()
}
pub fn audio_all_output(&self) -> Vec<u16> {
vec![
self.audio_output(),
self.audio_ch1_output() as u16,
self.audio_ch2_output() as u16,
self.audio_ch3_output() as u16,
self.audio_ch4_output() as u16,
]
}
pub fn audio_ch1_output(&self) -> u8 {
self.apu_i().ch1_output()
}
pub fn audio_ch2_output(&self) -> u8 {
self.apu_i().ch2_output()
}
pub fn audio_ch3_output(&self) -> u8 {
self.apu_i().ch3_output()
}
pub fn audio_ch4_output(&self) -> u8 {
self.apu_i().ch4_output()
}
pub fn audio_ch1_enabled(&self) -> bool {
self.apu_i().ch1_out_enabled()
}
pub fn set_audio_ch1_enabled(&mut self, enabled: bool) {
self.apu().set_ch1_out_enabled(enabled)
}
pub fn audio_ch2_enabled(&self) -> bool {
self.apu_i().ch2_out_enabled()
}
pub fn set_audio_ch2_enabled(&mut self, enabled: bool) {
self.apu().set_ch2_out_enabled(enabled)
}
pub fn audio_ch3_enabled(&self) -> bool {
self.apu_i().ch3_out_enabled()
}
pub fn set_audio_ch3_enabled(&mut self, enabled: bool) {
self.apu().set_ch3_out_enabled(enabled)
}
pub fn audio_ch4_enabled(&self) -> bool {
self.apu_i().ch4_out_enabled()
}
pub fn set_audio_ch4_enabled(&mut self, enabled: bool) {
self.apu().set_ch4_out_enabled(enabled)
}
pub fn audio_sampling_rate(&self) -> u16 {
self.apu_i().sampling_rate()
}
pub fn audio_channels(&self) -> u8 {
self.apu_i().channels()
}
pub fn cartridge_eager(&mut self) -> Cartridge {
self.mmu().rom().clone()
}
pub fn ram_data_eager(&mut self) -> Vec<u8> {
self.mmu().rom().ram_data_eager()
}
pub fn set_ram_data(&mut self, ram_data: Vec<u8>) {
self.mmu().rom().set_ram_data(&ram_data)
}
pub fn registers(&mut self) -> Registers {
let ppu_registers = self.ppu().registers();
Registers {
pc: self.cpu.pc,
sp: self.cpu.sp,
a: self.cpu.a,
b: self.cpu.b,
c: self.cpu.c,
d: self.cpu.d,
e: self.cpu.e,
h: self.cpu.h,
l: self.cpu.l,
scy: ppu_registers.scy,
scx: ppu_registers.scx,
wy: ppu_registers.wy,
wx: ppu_registers.wx,
ly: ppu_registers.ly,
lyc: ppu_registers.lyc,
}
}
pub fn get_tile(&mut self, index: usize) -> Tile {
self.ppu().tiles()[index]
}
pub fn get_tile_buffer(&mut self, index: usize) -> Vec<u8> {
let tile = self.get_tile(index);
tile.palette_buffer(self.ppu().palette_bg())
}
pub fn is_dmg(&self) -> bool {
self.mode == GameBoyMode::Dmg
}
pub fn is_cgb(&self) -> bool {
self.mode == GameBoyMode::Cgb
}
pub fn is_sgb(&self) -> bool {
self.mode == GameBoyMode::Sgb
}
pub fn speed(&self) -> GameBoySpeed {
self.mmu_i().speed()
}
pub fn multiplier(&self) -> u8 {
self.mmu_i().speed().multiplier()
}
pub fn mode(&self) -> GameBoyMode {
self.mode
}
pub fn set_mode(&mut self, value: GameBoyMode) {
self.mode = value;
(*self.gbc).lock().unwrap().set_mode(value);
self.mmu().set_mode(value);
self.ppu().set_gb_mode(value);
}
pub fn ppu_enabled(&self) -> bool {
self.ppu_enabled
}
pub fn set_ppu_enabled(&mut self, value: bool) {
self.ppu_enabled = value;
(*self.gbc).lock().unwrap().set_ppu_enabled(value);
}
pub fn apu_enabled(&self) -> bool {
self.apu_enabled
}
pub fn set_apu_enabled(&mut self, value: bool) {
self.apu_enabled = value;
(*self.gbc).lock().unwrap().set_apu_enabled(value);
}
pub fn dma_enabled(&self) -> bool {
self.dma_enabled
}
pub fn set_dma_enabled(&mut self, value: bool) {
self.dma_enabled = value;
(*self.gbc).lock().unwrap().set_dma_enabled(value);
}
pub fn timer_enabled(&self) -> bool {
self.timer_enabled
}
pub fn set_timer_enabled(&mut self, value: bool) {
self.timer_enabled = value;
(*self.gbc).lock().unwrap().set_timer_enabled(value);
}
pub fn serial_enabled(&self) -> bool {
self.serial_enabled
}
pub fn set_serial_enabled(&mut self, value: bool) {
self.serial_enabled = value;
(*self.gbc).lock().unwrap().set_serial_enabled(value);
}
pub fn set_all_enabled(&mut self, value: bool) {
self.set_ppu_enabled(value);
self.set_apu_enabled(value);
self.set_dma_enabled(value);
self.set_timer_enabled(value);
self.set_serial_enabled(value);
}
pub fn clock_freq(&self) -> u32 {
self.clock_freq
}
pub fn set_clock_freq(&mut self, value: u32) {
self.clock_freq = value;
(*self.gbc).lock().unwrap().set_clock_freq(value);
self.apu().set_clock_freq(value);
}
pub fn clock_freq_s(&self) -> String {
format!("{:.02} Mhz", self.clock_freq() as f32 / 1000.0 / 1000.0)
}
pub fn boot_rom(&self) -> BootRom {
self.boot_rom
}
pub fn set_boot_rom(&mut self, value: BootRom) {
self.boot_rom = value;
}
pub fn boot_rom_s(&self) -> String {
String::from(self.boot_rom().description())
}
pub fn attach_null_serial(&mut self) {
self.attach_serial(Box::<NullDevice>::default());
}
pub fn attach_stdout_serial(&mut self) {
self.attach_serial(Box::<StdoutDevice>::default());
}
pub fn attach_printer_serial(&mut self) {
self.attach_serial(Box::<PrinterDevice>::default());
}
pub fn display_width(&self) -> usize {
DISPLAY_WIDTH
}
pub fn display_height(&self) -> usize {
DISPLAY_HEIGHT
}
pub fn ram_size(&self) -> RamSize {
match self.mode {
GameBoyMode::Dmg => RamSize::Size8K,
GameBoyMode::Cgb => RamSize::Size32K,
GameBoyMode::Sgb => RamSize::Size8K,
}
}
pub fn vram_size(&self) -> RamSize {
match self.mode {
GameBoyMode::Dmg => RamSize::Size8K,
GameBoyMode::Cgb => RamSize::Size16K,
GameBoyMode::Sgb => RamSize::Size8K,
}
}
pub fn description(&self, column_length: usize) -> String {
let version_l = format!("{:width$}", "Version", width = column_length);
let mode_l = format!("{:width$}", "Mode", width = column_length);
let boot_rom_l = format!("{:width$}", "Boot ROM", width = column_length);
let clock_l = format!("{:width$}", "Clock", width = column_length);
let ram_size_l = format!("{:width$}", "RAM Size", width = column_length);
let vram_size_l = format!("{:width$}", "VRAM Size", width = column_length);
let serial_l = format!("{:width$}", "Serial", width = column_length);
format!(
"{} {}\n{} {}\n{} {}\n{} {}\n{} {}\n{} {}\n{} {}",
version_l,
Info::version(),
mode_l,
self.mode(),
boot_rom_l,
self.boot_rom(),
clock_l,
self.clock_freq_s(),
ram_size_l,
self.ram_size(),
vram_size_l,
self.vram_size(),
serial_l,
self.serial_i().device().description(),
)
}
pub fn description_debug(&self) -> String {
format!(
"{}\nCPU:\n{}\nDMA:\n{}\nAPU:\n{}",
self.description(12),
self.cpu_i().description_default(),
self.dma_i().description(),
self.apu_i().description()
)
}
}
impl GameBoy {
pub const CPU_FREQ: u32 = 4194304;
pub const VISUAL_FREQ: f32 = 59.7275;
pub const LCD_CYCLES: u32 = 70224;
pub fn cpu(&mut self) -> &mut Cpu {
&mut self.cpu
}
pub fn cpu_i(&self) -> &Cpu {
&self.cpu
}
pub fn mmu(&mut self) -> &mut Mmu {
self.cpu.mmu()
}
pub fn mmu_i(&self) -> &Mmu {
self.cpu.mmu_i()
}
#[inline(always)]
pub fn ppu(&mut self) -> &mut Ppu {
self.cpu.ppu()
}
#[inline(always)]
pub fn ppu_i(&self) -> &Ppu {
self.cpu.ppu_i()
}
#[inline(always)]
pub fn apu(&mut self) -> &mut Apu {
self.cpu.apu()
}
#[inline(always)]
pub fn apu_i(&self) -> &Apu {
self.cpu.apu_i()
}
#[inline(always)]
pub fn dma(&mut self) -> &mut Dma {
self.cpu.dma()
}
#[inline(always)]
pub fn dma_i(&self) -> &Dma {
self.cpu.dma_i()
}
#[inline(always)]
pub fn pad(&mut self) -> &mut Pad {
self.cpu.pad()
}
#[inline(always)]
pub fn pad_i(&self) -> &Pad {
self.cpu.pad_i()
}
#[inline(always)]
pub fn timer(&mut self) -> &mut Timer {
self.cpu.timer()
}
#[inline(always)]
pub fn timer_i(&self) -> &Timer {
self.cpu.timer_i()
}
#[inline(always)]
pub fn serial(&mut self) -> &mut Serial {
self.cpu.serial()
}
#[inline(always)]
pub fn serial_i(&self) -> &Serial {
self.cpu.serial_i()
}
pub fn rom(&mut self) -> &mut Cartridge {
self.mmu().rom()
}
pub fn rom_i(&self) -> &Cartridge {
self.mmu_i().rom_i()
}
pub fn frame_buffer(&mut self) -> &[u8; FRAME_BUFFER_SIZE] {
self.ppu().frame_buffer()
}
pub fn frame_buffer_xrgb8888(&mut self) -> [u8; FRAME_BUFFER_XRGB8888_SIZE] {
self.ppu().frame_buffer_xrgb8888()
}
pub fn frame_buffer_xrgb8888_u32(&mut self) -> [u32; FRAME_BUFFER_SIZE] {
self.ppu().frame_buffer_xrgb8888_u32()
}
pub fn frame_buffer_rgb1555(&mut self) -> [u8; FRAME_BUFFER_RGB1555_SIZE] {
self.ppu().frame_buffer_rgb1555()
}
pub fn frame_buffer_rgb1555_u16(&mut self) -> [u16; FRAME_BUFFER_SIZE] {
self.ppu().frame_buffer_rgb1555_u16()
}
pub fn frame_buffer_rgb565(&mut self) -> [u8; FRAME_BUFFER_RGB565_SIZE] {
self.ppu().frame_buffer_rgb565()
}
pub fn frame_buffer_rgb565_u16(&mut self) -> [u16; FRAME_BUFFER_SIZE] {
self.ppu().frame_buffer_rgb565_u16()
}
pub fn frame_buffer_rgba(&mut self) -> [u8; FRAME_BUFFER_RGBA_SIZE] {
self.ppu().frame_buffer_rgba()
}
pub fn frame_buffer_raw(&mut self) -> [u8; FRAME_BUFFER_SIZE] {
self.ppu().frame_buffer_raw()
}
pub fn audio_buffer(&mut self) -> &VecDeque<i16> {
self.apu().audio_buffer()
}
pub fn cartridge(&mut self) -> &mut Cartridge {
self.mmu().rom()
}
pub fn cartridge_i(&self) -> &Cartridge {
self.mmu_i().rom_i()
}
#[inline(always)]
pub fn reload(&mut self) -> Result<(), Error> {
let rom = self.rom().clone();
self.reset();
self.load(true)?;
self.load_cartridge(rom)?;
Ok(())
}
pub fn load(&mut self, boot: bool) -> Result<(), Error> {
let boot_rom = self.boot_rom().reusable(self.mode());
match self.mode() {
GameBoyMode::Dmg => self.load_dmg(boot, boot_rom)?,
GameBoyMode::Cgb => self.load_cgb(boot, boot_rom)?,
GameBoyMode::Sgb => unimplemented!("SGB is not supported"),
}
Ok(())
}
pub fn load_dmg(&mut self, boot: bool, boot_rom: Option<BootRom>) -> Result<(), Error> {
self.mmu().allocate_dmg();
if boot {
self.load_boot_dmg(boot_rom)?;
}
Ok(())
}
pub fn load_cgb(&mut self, boot: bool, boot_rom: Option<BootRom>) -> Result<(), Error> {
self.mmu().allocate_cgb();
if boot {
self.load_boot_cgb(boot_rom)?;
}
Ok(())
}
pub fn load_boot(&mut self, data: &[u8]) {
self.cpu.mmu().write_boot(0x0000, data);
}
pub fn load_boot_path(&mut self, path: &str) -> Result<(), Error> {
let data = read_file(path)?;
self.load_boot(&data);
self.boot_rom = BootRom::Other;
Ok(())
}
pub fn load_boot_static(&mut self, boot_rom: BootRom) {
match boot_rom {
BootRom::Dmg => self.load_boot(&DMG_BOOT),
BootRom::Sgb => self.load_boot(&SGB_BOOT),
BootRom::DmgBootix => self.load_boot(&DMG_BOOTIX),
BootRom::MgbBootix => self.load_boot(&MGB_BOOTIX),
BootRom::Cgb => self.load_boot(&CGB_BOOT),
BootRom::CgbBoytacean => self.load_boot(&CGB_BOYTACEAN),
BootRom::Other | BootRom::None => (),
}
self.boot_rom = boot_rom;
}
pub fn load_boot_file(&mut self, boot_rom: BootRom) -> Result<(), Error> {
match boot_rom {
BootRom::Dmg => self.load_boot_path("./res/boot/dmg_boot.bin")?,
BootRom::Sgb => self.load_boot_path("./res/boot/sgb_boot.bin")?,
BootRom::DmgBootix => self.load_boot_path("./res/boot/dmg_bootix.bin")?,
BootRom::MgbBootix => self.load_boot_path("./res/boot/mgb_bootix.bin")?,
BootRom::Cgb => self.load_boot_path("./res/boot/cgb_boot.bin")?,
BootRom::CgbBoytacean => self.load_boot_path("./res/boot/cgb_boytacean.bin")?,
BootRom::Other | BootRom::None => (),
}
self.boot_rom = boot_rom;
Ok(())
}
pub fn load_boot_default(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
self.load_boot_dmg(boot_rom)
}
pub fn load_boot_smart(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
match self.mode() {
GameBoyMode::Dmg => self.load_boot_dmg(boot_rom)?,
GameBoyMode::Cgb => self.load_boot_cgb(boot_rom)?,
GameBoyMode::Sgb => unimplemented!("SGB is not supported"),
}
Ok(())
}
pub fn load_boot_dmg(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
let boot_rom = boot_rom.unwrap_or(BootRom::DmgBootix);
if !boot_rom.is_dmg_compat() {
return Err(Error::IncompatibleBootRom);
}
self.load_boot_static(boot_rom);
Ok(())
}
pub fn load_boot_cgb(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
let boot_rom = boot_rom.unwrap_or(BootRom::CgbBoytacean);
if !boot_rom.is_cgb_compat() {
return Err(Error::IncompatibleBootRom);
}
self.load_boot_static(boot_rom);
Ok(())
}
pub fn load_boot_default_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
self.load_boot_dmg_f(boot_rom)?;
Ok(())
}
pub fn load_boot_smart_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
match self.mode() {
GameBoyMode::Dmg => self.load_boot_dmg_f(boot_rom)?,
GameBoyMode::Cgb => self.load_boot_cgb_f(boot_rom)?,
GameBoyMode::Sgb => unimplemented!("SGB is not supported"),
}
Ok(())
}
pub fn load_boot_dmg_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
let boot_rom = boot_rom.unwrap_or(BootRom::DmgBootix);
if !boot_rom.is_dmg_compat() {
return Err(Error::IncompatibleBootRom);
}
self.load_boot_file(boot_rom)?;
Ok(())
}
pub fn load_boot_cgb_f(&mut self, boot_rom: Option<BootRom>) -> Result<(), Error> {
let boot_rom = boot_rom.unwrap_or(BootRom::Cgb);
if !boot_rom.is_cgb_compat() {
return Err(Error::IncompatibleBootRom);
}
self.load_boot_file(boot_rom)?;
Ok(())
}
pub fn load_cartridge(&mut self, rom: Cartridge) -> Result<&mut Cartridge, Error> {
self.mmu().set_rom(rom);
Ok(self.mmu().rom())
}
pub fn load_rom(
&mut self,
data: &[u8],
ram_data: Option<&[u8]>,
) -> Result<&mut Cartridge, Error> {
let mut rom = Cartridge::from_data(data)?;
if let Some(ram_data) = ram_data {
rom.set_ram_data(ram_data)
}
self.load_cartridge(rom)
}
pub fn load_rom_file(
&mut self,
path: &str,
ram_path: Option<&str>,
) -> Result<&mut Cartridge, Error> {
let data = read_file(path)?;
match ram_path {
Some(ram_path) => {
let ram_data = read_file(ram_path)?;
self.load_rom(&data, Some(&ram_data))
}
None => self.load_rom(&data, None),
}
}
pub fn load_rom_reader<R: Read>(
&mut self,
reader: &mut R,
ram_reader: Option<&mut R>,
) -> Result<&mut Cartridge, Error> {
let mut data = vec![];
reader.read_to_end(&mut data)?;
match ram_reader {
Some(ram_reader) => {
let mut ram_data = vec![];
ram_reader.read_to_end(&mut ram_data)?;
self.load_rom(&data, Some(&ram_data))
}
None => self.load_rom(&data, None),
}
}
pub fn load_rom_empty(&mut self) -> Result<&mut Cartridge, Error> {
let data = [0u8; 32 * 1024];
self.load_rom(&data, None)
}
pub fn attach_serial(&mut self, device: Box<dyn SerialDevice>) {
self.serial().set_device(device);
}
pub fn read_memory(&mut self, addr: u16) -> u8 {
self.mmu().read(addr)
}
pub fn write_memory(&mut self, addr: u16, value: u8) {
self.mmu().write(addr, value);
}
pub fn set_speed_callback(&mut self, callback: fn(speed: GameBoySpeed)) {
self.mmu().set_speed_callback(callback);
}
pub fn audio_filter_mode(&self) -> HighPassFilter {
self.apu_i().filter_mode()
}
pub fn set_audio_filter_mode(&mut self, mode: HighPassFilter) {
self.apu().set_filter_mode(mode);
}
pub fn add_cheat_code(&mut self, code: &str) -> Result<bool, Error> {
if GameGenie::is_code(code) {
return self.add_game_genie_code(code).map(|_| true);
}
if GameShark::is_code(code) {
return self.add_game_shark_code(code).map(|_| true);
}
Err(Error::CustomError(String::from("Not a valid cheat code")))
}
pub fn add_game_genie_code(&mut self, code: &str) -> Result<&GameGenieCode, Error> {
let rom = self.mmu().rom();
if rom.game_genie().is_none() {
let game_genie = GameGenie::default();
rom.attach_genie(game_genie)?;
}
let game_genie = rom.game_genie_mut().as_mut().unwrap();
game_genie.add_code(code)
}
pub fn add_game_shark_code(&mut self, code: &str) -> Result<&GameSharkCode, Error> {
let rom = self.rom();
if rom.game_shark().is_none() {
let game_shark = GameShark::default();
rom.attach_shark(game_shark)?;
}
let game_shark = rom.game_shark_mut().as_mut().unwrap();
game_shark.add_code(code)
}
pub fn reset_game_genie(&mut self) {
let rom = self.rom();
if rom.game_genie().is_some() {
rom.game_genie_mut().as_mut().unwrap().reset();
}
}
pub fn reset_game_shark(&mut self) {
let rom = self.mmu().rom();
if rom.game_shark().is_some() {
rom.game_shark_mut().as_mut().unwrap().reset();
}
}
}
#[cfg(feature = "wasm")]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl GameBoy {
pub fn set_panic_hook_wa() {
let prev = take_hook();
set_hook(Box::new(move |info| {
hook_impl(info);
prev(info);
}));
}
pub fn reload_wa(&mut self) -> Result<(), String> {
self.reload()?;
Ok(())
}
pub fn load_rom_wa(&mut self, data: &[u8]) -> Result<Cartridge, String> {
let rom = self.load_rom(data, None)?;
rom.set_rumble_cb(|active| {
rumble_callback(active);
});
Ok(rom.clone())
}
pub fn load_callbacks_wa(&mut self) {
self.set_speed_callback(|speed| {
speed_callback(speed);
});
}
pub fn load_null_wa(&mut self) {
let null = Box::<NullDevice>::default();
self.attach_serial(null);
}
pub fn load_logger_wa(&mut self) {
let mut logger = Box::<StdoutDevice>::default();
logger.set_callback(|data| {
logger_callback(data.to_vec());
});
self.attach_serial(logger);
}
pub fn load_printer_wa(&mut self) {
let mut printer = Box::<PrinterDevice>::default();
printer.set_callback(|image_buffer| {
printer_callback(image_buffer.to_vec());
});
self.attach_serial(printer);
}
pub fn add_cheat_code_wa(&mut self, code: &str) -> Result<bool, String> {
Ok(self.add_cheat_code(code)?)
}
pub fn infer_mode_wa(&mut self, data: &[u8]) -> Result<(), String> {
let mode = Cartridge::from_data(data)?.gb_mode();
self.set_mode(mode);
Ok(())
}
pub fn set_palette_colors_wa(&mut self, value: Vec<JsValue>) {
let palette: Palette = value
.into_iter()
.map(|v| Self::js_to_pixel(&v))
.collect::<Vec<Pixel>>()
.try_into()
.unwrap();
self.ppu().set_palette_colors(&palette);
}
pub fn audio_filter_mode_wa(&self) -> u8 {
self.apu_i().filter_mode().into()
}
pub fn set_audio_filter_mode_wa(&mut self, mode: u8) {
self.apu().set_filter_mode(mode.into());
}
fn js_to_pixel(value: &JsValue) -> Pixel {
value
.as_string()
.unwrap()
.chars()
.collect::<Vec<char>>()
.chunks(2)
.map(|s| s.iter().collect::<String>())
.map(|s| u8::from_str_radix(&s, 16).unwrap())
.collect::<Vec<u8>>()
.try_into()
.unwrap()
}
}
#[cfg(feature = "wasm")]
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = window)]
fn panic(message: &str);
#[wasm_bindgen(js_namespace = window, js_name = speedCallback)]
fn speed_callback(speed: GameBoySpeed);
#[wasm_bindgen(js_namespace = window, js_name = loggerCallback)]
fn logger_callback(data: Vec<u8>);
#[wasm_bindgen(js_namespace = window, js_name = printerCallback)]
fn printer_callback(image_buffer: Vec<u8>);
#[wasm_bindgen(js_namespace = window, js_name = rumbleCallback)]
fn rumble_callback(active: bool);
}
#[cfg(feature = "wasm")]
pub fn hook_impl(info: &PanicInfo) {
let message = info.to_string();
panic(message.as_str());
}
impl AudioProvider for GameBoy {
fn audio_output(&self) -> u16 {
self.apu_i().output()
}
fn audio_buffer(&self) -> &VecDeque<i16> {
self.apu_i().audio_buffer()
}
fn clear_audio_buffer(&mut self) {
self.apu().clear_audio_buffer()
}
}
impl Default for GameBoy {
fn default() -> Self {
Self::new(None)
}
}
impl Display for GameBoy {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description(9))
}
}