Newer
Older
//! Main GameBoy emulation entrypoint functions and structures.
use std::{
cell::RefCell,
collections::VecDeque,
fmt::{self, Display, Formatter},
rc::Rc,
};
cheats::{
genie::{GameGenie, GameGenieCode},
shark::{GameShark, GameSharkCode},
},
data::{BootRom, CGB_BOOT, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT},
devices::{printer::PrinterDevice, stdout::StdoutDevice},
Ppu, PpuMode, Tile, DISPLAY_HEIGHT, DISPLAY_WIDTH, FRAME_BUFFER_RGB1555_SIZE,
FRAME_BUFFER_RGB565_SIZE, FRAME_BUFFER_SIZE, FRAME_BUFFER_XRGB8888_SIZE,
serial::{NullDevice, Serial, SerialDevice},
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;
#[cfg(feature = "wasm")]
use crate::{
gen::dependencies_map,
ppu::{Palette, Pixel},
};
#[cfg(feature = "wasm")]
use std::{
convert::TryInto,
panic::{set_hook, take_hook, PanicInfo},
};
/// Enumeration that describes the multiple running
// modes of the Game Boy emulator.
// DMG = Original Game Boy
// CGB = Game Boy Color
// SGB = Super Game Boy
#[cfg_attr(feature = "wasm", wasm_bindgen)]
#[derive(Clone, Copy, PartialEq, Eq)]
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)",
}
}
match value {
1 => GameBoyMode::Dmg,
2 => GameBoyMode::Cgb,
3 => GameBoyMode::Sgb,
_ => panic!("Invalid mode value: {}", value),
}
}
pub fn from_string(value: &str) -> Self {
"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())
}
}
#[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",
}
}
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())
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct GameBoyConfig {
/// The current running mode of the emulator, this
/// may affect many aspects of the emulation, like
/// CPU frequency, PPU frequency, Boot rome size, etc.
/// If the PPU is enabled, it will be clocked.
/// If the APU is enabled, it will be clocked.
/// if the DMA is enabled, it will be clocked.
dma_enabled: bool,
/// If the timer is enabled, it will be clocked.
/// If the serial is enabled, it will be clocked.
serial_enabled: bool,
/// The current frequency at which the Game Boy
/// emulator is being handled. This is a "hint" that
/// may help components to adjust their internal
/// logic to match the current frequency. For example
/// the APU will adjust its internal clock to match
/// this hint.
clock_freq: u32,
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl GameBoyConfig {
pub fn is_dmg(&self) -> bool {
}
pub fn is_cgb(&self) -> bool {
}
pub fn is_sgb(&self) -> bool {
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,
timer_enabled: true,
serial_enabled: true,
clock_freq: GameBoy::CPU_FREQ,
}
}
}
/// Aggregation structure allowing the bundling of
/// all the components of a GameBoy into a single a
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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) -> u8;
fn audio_buffer(&self) -> &VecDeque<u8>;
fn clear_audio_buffer(&mut self);
}
/// Top level structure that abstracts the usage of the
/// Game Boy system under the Boytacean emulator.
/// Should serve as the main entry-point API.
#[cfg_attr(feature = "wasm", wasm_bindgen)]
pub struct GameBoy {
/// The current running mode of the emulator, this
/// may affect many aspects of the emulation, like
/// CPU frequency, PPU frequency, Boot rome size, etc.
/// This is a clone of the configuration value
/// kept for performance reasons.
mode: GameBoyMode,
/// If the PPU is enabled, it will be clocked.
/// This is a clone of the configuration value
/// kept for performance reasons.
ppu_enabled: bool,
/// If the APU is enabled, it will be clocked.
/// This is a clone of the configuration value
/// kept for performance reasons.
/// If the DMA is enabled, it will be clocked.
/// This is a clone of the configuration value
/// kept for performance reasons.
dma_enabled: bool,
/// If the timer is enabled, it will be clocked.
/// This is a clone of the configuration value
/// kept for performance reasons.
timer_enabled: bool,
/// If the serial is enabled, it will be clocked.
/// This is a clone of the configuration value
/// kept for performance reasons.
serial_enabled: bool,
/// The current frequency at which the Game Boy
/// emulator is being handled. This is a "hint" that
/// may help components to adjust their internal
/// logic to match the current frequency. For example
/// the APU will adjust its internal clock to match
/// this hint.
/// This is a clone of the configuration value
/// kept for performance reasons.
/// Reference to the Game Boy CPU component to be
/// used as the main element of the system, when
/// clocked, the amount of ticks from it will be
/// used as reference or the rest of the components.
cpu: Cpu,
/// The reference counted and mutable reference to
/// Game Boy configuration structure that can be
/// used by the GB components to access global
/// configuration values on the current emulator.
/// If performance is required (may value access)
/// the values should be cloned and stored locally.
gbc: Rc<RefCell<GameBoyConfig>>,
}
#[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
pub fn new(mode: Option<GameBoyMode>) -> Self {
let mode = mode.unwrap_or(GameBoyMode::Dmg);
let gbc = Rc::new(RefCell::new(GameBoyConfig {
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,
ppu_enabled: true,
apu_enabled: true,
timer_enabled: true,
serial_enabled: true,
clock_freq: GameBoy::CPU_FREQ,
cpu,
gbc,
}
pub fn reset(&mut self) {
self.ppu().reset();
self.timer().reset();
self.serial().reset();
self.mmu().reset();
self.cpu.reset();
pub fn reload(&mut self) {
let rom = self.rom().clone();
self.reset();
self.load(true);
self.load_cartridge(rom);
}
pub fn clock(&mut self) -> u16 {
let cycles = self.cpu_clock() as u16;
let cycles_n = cycles / self.multiplier() as 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);
}
cycles
}
/// Risky function that will clock the CPU multiple times
/// allowing an undefined number of cycles to be executed
/// in the other Game Boy components.
/// This can cause unwanted behaviour in components like
/// the PPU where only one mode switch operation is expected
/// per each clock call.
pub fn clock_m(&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;
if self.dma_enabled {
self.dma_clock(cycles);
}
self.timer_clock(cycles);
}
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);
}
pub fn ppu_clock(&mut self, cycles: u16) {
pub fn apu_clock(&mut self, cycles: u16) {
pub fn dma_clock(&mut self, cycles: u16) {
self.mmu().clock_dma(cycles);
pub fn timer_clock(&mut self, cycles: u16) {
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.cpu.boot();
pub fn load(&mut self, boot: bool) {
GameBoyMode::Dmg => self.load_dmg(boot),
GameBoyMode::Cgb => self.load_cgb(boot),
GameBoyMode::Sgb => unimplemented!(),
}
}
pub fn load_dmg(&mut self, boot: bool) {
self.mmu().allocate_dmg();
if boot {
self.load_boot_dmg();
}
}
pub fn load_cgb(&mut self, boot: bool) {
self.mmu().allocate_cgb();
if boot {
self.load_boot_cgb();
}
}
pub fn load_boot(&mut self, data: &[u8]) {
self.cpu.mmu().write_boot(0x0000, data);
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),
self.load_boot_dmg();
}
pub fn load_boot_dmg(&mut self) {
self.load_boot_static(BootRom::DmgBootix);
pub fn load_boot_cgb(&mut self) {
self.load_boot_static(BootRom::Cgb);
}
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 audio_buffer_eager(&mut self, clear: bool) -> Vec<u8> {
let buffer = Vec::from(self.audio_buffer().clone());
if clear {
self.clear_audio_buffer();
}
buffer
pub fn audio_output(&self) -> u8 {
self.apu_i().output()
}
pub fn audio_all_output(&self) -> Vec<u8> {
vec![
self.audio_output(),
self.audio_ch1_output(),
self.audio_ch2_output(),
self.audio_ch3_output(),
self.audio_ch4_output(),
]
}
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().ch2_out_enabled()
}
pub fn set_audio_ch1_enabled(&mut self, enabled: bool) {
pub fn audio_ch2_enabled(&self) -> bool {
self.apu_i().ch2_out_enabled()
}
pub fn set_audio_ch2_enabled(&mut self, enabled: bool) {
pub fn audio_ch3_enabled(&self) -> bool {
self.apu_i().ch3_out_enabled()
}
pub fn set_audio_ch3_enabled(&mut self, enabled: bool) {
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 {
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,
scy: ppu_registers.scy,
scx: ppu_registers.scx,
wy: ppu_registers.wy,
wx: ppu_registers.wx,
ly: ppu_registers.ly,
lyc: ppu_registers.lyc,
/// Obtains the tile structure for the tile at the
/// given index, no conversion in the pixel buffer
/// is done so that the color reference is the GB one.
pub fn get_tile(&mut self, index: usize) -> Tile {
self.ppu().tiles()[index]
}
/// Obtains the pixel buffer for the tile at the
/// provided index, converting the color buffer
/// using the currently loaded (background) palette.
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 {
pub fn multiplier(&self) -> u8 {
self.mmu_i().speed().multiplier()
pub fn set_mode(&mut self, value: GameBoyMode) {
(*self.gbc).borrow_mut().set_mode(value);
}
pub fn set_ppu_enabled(&mut self, value: bool) {
(*self.gbc).borrow_mut().set_ppu_enabled(value);
}
pub fn set_apu_enabled(&mut self, value: bool) {
(*self.gbc).borrow_mut().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).borrow_mut().set_dma_enabled(value);
}
}
pub fn set_timer_enabled(&mut self, value: bool) {
(*self.gbc).borrow_mut().set_timer_enabled(value);
pub fn serial_enabled(&self) -> bool {
}
pub fn set_serial_enabled(&mut self, value: bool) {
(*self.gbc).borrow_mut().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 set_clock_freq(&mut self, value: u32) {
(*self.gbc).borrow_mut().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 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 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);
"{} {}\n{} {}\n{} {}\n{} {}\n{} {}\n{} {}",
self.serial_i().device().description(),
/// Gameboy implementations that are meant with performance
/// in mind and that do not support WASM interface of copy.
/// The logic frequency of the Game Boy
/// CPU in hz.
pub const CPU_FREQ: u32 = 4194304;
/// The visual frequency (refresh rate)
/// of the Game Boy, close to 60 hz.
pub const VISUAL_FREQ: f32 = 59.7275;
/// The cycles taken to run a complete frame
/// loop in the Game Boy's PPU (in CPU cycles).
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_i(&self) -> &Mmu {
self.cpu.mmu_i()
}
pub fn ppu(&mut self) -> &mut Ppu {
self.cpu.ppu()
pub fn ppu_i(&self) -> &Ppu {
self.cpu.ppu_i()
}
pub fn apu(&mut self) -> &mut Apu {
self.cpu.apu()
}
pub fn apu_i(&self) -> &Apu {
self.cpu.apu_i()
}
pub fn dma(&mut self) -> &mut Dma {
self.cpu.dma()
}
pub fn dma_i(&self) -> &Dma {
self.cpu.dma_i()
}
pub fn pad(&mut self) -> &mut Pad {
self.cpu.pad()
}
pub fn pad_i(&self) -> &Pad {
self.cpu.pad_i()
}
pub fn timer(&mut self) -> &mut Timer {
self.cpu.timer()
}
pub fn timer_i(&self) -> &Timer {
self.cpu.timer_i()
}
pub fn serial(&mut self) -> &mut Serial {
self.cpu.serial()
}
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 audio_buffer(&mut self) -> &VecDeque<u8> {
pub fn cartridge(&mut self) -> &mut Cartridge {
self.mmu().rom()
}
pub fn cartridge_i(&self) -> &Cartridge {