Newer
Older
data::{BootRom, CGB_BOOT, DMG_BOOT, DMG_BOOTIX, MGB_BOOTIX, SGB_BOOT},
devices::{printer::PrinterDevice, stdout::StdoutDevice},
gen::{COMPILATION_DATE, COMPILATION_TIME, COMPILER, COMPILER_VERSION, VERSION},
ppu::{Ppu, PpuMode, Tile, FRAME_BUFFER_SIZE},
serial::{NullDevice, Serial, SerialDevice},
use std::{
cell::RefCell,
collections::VecDeque,
fmt::{self, Display, Formatter},
rc::Rc,
};
#[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)",
}
}
pub fn from_u8(value: u8) -> GameBoyMode {
match value {
1 => GameBoyMode::Dmg,
2 => GameBoyMode::Cgb,
3 => GameBoyMode::Sgb,
_ => panic!("Invalid mode value: {}", value),
}
}
pub fn from_str(value: &str) -> GameBoyMode {
match value {
"dmg" => GameBoyMode::Dmg,
"cgb" => GameBoyMode::Cgb,
"sgb" => GameBoyMode::Sgb,
_ => panic!("Invalid mode value: {}", value),
}
}
}
impl Display for GameBoyMode {
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 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) {
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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 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;
}
}
/// 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.
apu_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,
/// Rhe 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)]
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,
fn audio_buffer(&self) -> &VecDeque<u8>;
fn clear_audio_buffer(&mut self);
#[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
let gbc = Rc::new(RefCell::new(GameBoyConfig {
}));
let ppu = Ppu::default();
let apu = Apu::default();
let pad = Pad::default();
let timer = Timer::default();
let serial = Serial::default();
let mmu = Mmu::new(ppu, apu, pad, timer, serial, 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 clock(&mut self) -> u8 {
let cycles = self.cpu_clock();
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: u8) {
self.ppu().clock(cycles)
}
pub fn apu_clock(&mut self, cycles: u8) {
self.apu().clock(cycles)
pub fn timer_clock(&mut self, cycles: u8) {
self.timer().clock(cycles)
}
pub fn serial_clock(&mut self, cycles: u8) {
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 => todo!(),
}
}
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 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 palette.
pub fn get_tile_buffer(&mut self, index: usize) -> Vec<u8> {
let tile = self.get_tile(index);
tile.palette_buffer(self.ppu().palette())
}
/// Obtains the name of the compiler that has been
/// used in the compilation of the base Boytacean
/// library. Can be used for diagnostics.
String::from(COMPILER)
}
pub fn compiler_version(&self) -> String {
String::from(COMPILER_VERSION)
}
pub fn compilation_date(&self) -> String {
String::from(COMPILATION_DATE)
}
pub fn compilation_time(&self) -> String {
String::from(COMPILATION_TIME)
}
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 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_clock_freq(&mut self, value: u32) {
(*self.gbc).borrow_mut().set_clock_freq(value);
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());
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
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 {
format!(
"{} {}\n{} {}\n{} {}\n{} {}",
format!("{:width$}", "Version", width = column_length),
VERSION,
format!("{:width$}", "Mode", width = column_length),
self.mode(),
format!("{:width$}", "RAM Size", width = column_length),
self.ram_size(),
format!("{:width$}", "VRAM Size", width = column_length),
self.vram_size(),
)
}
/// 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 ppu(&mut self) -> &mut Ppu {
self.cpu.ppu()
pub fn apu(&mut self) -> &mut Apu {
self.cpu.apu()
}
pub fn apu_i(&self) -> &Apu {
self.cpu.apu_i()
}
pub fn pad(&mut self) -> &mut Pad {
self.cpu.pad()
}
pub fn timer(&mut self) -> &mut Timer {
self.cpu.timer()
}
pub fn serial(&mut self) -> &mut Serial {
self.cpu.serial()
}
pub fn frame_buffer(&mut self) -> &[u8; FRAME_BUFFER_SIZE] {
&(self.ppu().frame_buffer)
}
pub fn audio_buffer(&mut self) -> &VecDeque<u8> {
pub fn load_boot_path(&mut self, path: &str) {
let data = read_file(path);
self.load_boot(&data);
}
pub fn load_boot_file(&mut self, boot_rom: BootRom) {
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"),
}
}
pub fn load_boot_default_f(&mut self) {
}
pub fn load_boot_dmg_f(&mut self) {
self.load_boot_file(BootRom::DmgBootix);
}
pub fn load_boot_cgb_f(&mut self) {
self.load_boot_file(BootRom::Cgb);
}
pub fn load_rom(&mut self, data: &[u8]) -> &Cartridge {
let rom = Cartridge::from_data(data);
self.mmu().set_rom(rom);
self.mmu().rom()
}
pub fn load_rom_file(&mut self, path: &str) -> &Cartridge {
let data = read_file(path);
self.load_rom(&data)
}
pub fn attach_serial(&mut self, device: Box<dyn SerialDevice>) {
self.serial().set_device(device);
}
#[cfg(feature = "wasm")]
#[cfg_attr(feature = "wasm", wasm_bindgen)]
impl GameBoy {
pub fn set_panic_hook_ws() {
let prev = take_hook();
set_hook(Box::new(move |info| {
hook_impl(info);
prev(info);
}));
}
pub fn load_rom_ws(&mut self, data: &[u8]) -> Cartridge {
self.load_rom(data).clone()
}
pub fn load_null_ws(&mut self) {
let null = Box::<NullDevice>::default();
self.attach_serial(null);
}
pub fn load_logger_ws(&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_ws(&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 set_palette_colors_ws(&mut self, value: Vec<JsValue>) {
let palette: Palette = value
.into_iter()
.try_into()
.unwrap();
self.ppu().set_palette_colors(&palette);
}
pub fn wasm_engine_ws(&self) -> Option<String> {
let dependencies = dependencies_map();
if !dependencies.contains_key("wasm-bindgen") {
Some(String::from(format!(
"wasm-bindgen/{}",
*dependencies.get("wasm-bindgen").unwrap()
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)]
#[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>);
}
#[cfg(feature = "wasm")]
pub fn hook_impl(info: &PanicInfo) {
let message = info.to_string();
panic(message.as_str());
impl AudioProvider for GameBoy {
fn audio_buffer(&self) -> &VecDeque<u8> {
fn clear_audio_buffer(&mut self) {
impl Default for GameBoy {
fn default() -> Self {
impl Display for GameBoy {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.description(9))
}
}