diff --git a/src/bus.rs b/src/bus.rs index cce1ff5..3ff3ae6 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,3 +1,4 @@ +use std::env; use std::ops::RangeInclusive; use crate::utils::join_bytes; use crate::rom::{ROM, load_rom}; @@ -29,6 +30,21 @@ pub const IO_REGISTERS: RangeInclusive = 0xFF00..=0xFF7F; pub const HIGH_RAM: RangeInclusive = 0xFF80..=0xFFFE; pub const PREPARE_SPEED_SWITCH_ADDRESS: u16 = 0xFF4D; +enum MemoryMap { + BankZero, + BankSwitchable, + VideoRam, + ExternalRam, + WorkRam1, + WorkRam2, + EchoRam, + SpriteAttributeTable, + NotUsable, + IoRegisters, + HighRam, + InterruptEnable, +} + pub struct Bus { data: [u8; 0x10000], pub rom: Box, @@ -59,7 +75,8 @@ impl Bus { }, }; let info = rom.info().clone(); - let cgb_mode = info.cgb_features() || info.cgb_only(); + let force_dmg_mode = !env::var("FORCE_DMG").is_err(); + let cgb_mode = (info.cgb_features() || info.cgb_only()) && !force_dmg_mode; let mut bus = Self { data: [0x00; 0x10000], rom, @@ -103,34 +120,53 @@ impl Bus { bus } - pub fn read(&self, address: u16) -> u8 { - if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS { - let byte = self.data[address as usize]; - let current_speed = (self.double_speed_mode as u8) << 7; - let prepare_speed_switch = self.prepare_double_speed_mode as u8; - return (byte & 0b0111_1110) | current_speed | prepare_speed_switch; - } else if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { - return self.rom.read(address); - } else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS { - return self.ram.read(address); - } else if ECHO_RAM.contains(&address) { - return self.ram.read(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF)); - } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { - return self.interrupts.read(address); - } else if VIDEO_RAM.contains(&address) { - return self.ppu.read_vram_external(address); - } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { - return self.ppu.read_oam(address); - } else if PPU::is_io_register(address) { - return self.ppu.get_register(address); - } else if Sound::is_io_register(address) { - return self.sound.get_register(address); - } else if address == JOYPAD_ADDRESS { - return self.joypad.read(self.data[address as usize]); - } else if Timer::is_io_register(address) { - return self.timer.get_register(address); + fn map_address(address: u16) -> MemoryMap { + match address { + 0x0000..=0x3FFF => MemoryMap::BankZero, + 0x4000..=0x7FFF => MemoryMap::BankSwitchable, + 0x8000..=0x9FFF => MemoryMap::VideoRam, + 0xA000..=0xBFFF => MemoryMap::ExternalRam, + 0xC000..=0xCFFF => MemoryMap::WorkRam1, + 0xD000..=0xDFFF => MemoryMap::WorkRam2, + 0xE000..=0xFDFF => MemoryMap::EchoRam, + 0xFE00..=0xFE9F => MemoryMap::SpriteAttributeTable, + 0xFEA0..=0xFEFF => MemoryMap::NotUsable, + 0xFF00..=0xFF7F => MemoryMap::IoRegisters, + 0xFF80..=0xFFFE => MemoryMap::HighRam, + INTERRUPT_ENABLE_ADDRESS => MemoryMap::InterruptEnable, + } + } + + pub fn read(&self, address: u16) -> u8 { + match Bus::map_address(address) { + MemoryMap::BankZero | MemoryMap::BankSwitchable | MemoryMap::ExternalRam => self.rom.read(address), + MemoryMap::WorkRam1 | MemoryMap::WorkRam2 | MemoryMap::EchoRam => self.ram.read(address), + MemoryMap::VideoRam => self.ppu.read_vram_external(address), + MemoryMap::SpriteAttributeTable => self.ppu.read_oam(address), + MemoryMap::IoRegisters => { + if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS { + let byte = self.data[address as usize]; + let current_speed = (self.double_speed_mode as u8) << 7; + let prepare_speed_switch = self.prepare_double_speed_mode as u8; + return (byte & 0b0111_1110) | current_speed | prepare_speed_switch; + } else if address == WRAM_BANK_SELECT_ADDRESS { + return self.ram.read(address); + } else if address == INTERRUPT_FLAG_ADDRESS { + return self.interrupts.read(address); + } else if PPU::is_io_register(address) { + return self.ppu.get_register(address); + } else if Sound::is_io_register(address) { + return self.sound.get_register(address); + } else if Timer::is_io_register(address) { + return self.timer.get_register(address); + } else if address == JOYPAD_ADDRESS { + return self.joypad.read(self.data[address as usize]); + } + return self.data[address as usize]; + }, + MemoryMap::InterruptEnable => self.interrupts.read(address), + _ => self.data[address as usize], } - self.data[address as usize] } pub fn read_16bit(&self, address: u16) -> u16 { @@ -138,47 +174,48 @@ impl Bus { } pub fn write(&mut self, address: u16, data: u8) { - if address == 0xFF01 { - // print!("{}", data as char); - } - - if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS { - let current_byte = self.data[address as usize]; - self.prepare_double_speed_mode = (data & 1) == 1; - // bit 7 is read only on cgb mode - self.data[address as usize] = (current_byte & 0b1000_0000) | (data & 0b0111_1111); - } else if BANK_ZERO.contains(&address) || BANK_SWITCHABLE.contains(&address) || EXTERNAL_RAM.contains(&address) { - self.rom.write(address, data); - } else if address == INTERRUPT_ENABLE_ADDRESS || address == INTERRUPT_FLAG_ADDRESS { - self.interrupts.write(address, data); - } else if WORK_RAM_1.contains(&address) || WORK_RAM_2.contains(&address) || address == WRAM_BANK_SELECT_ADDRESS { - self.ram.write(address, data); - } else if EXTERNAL_RAM.contains(&address) { - self.rom.write(address, data); - } else if ECHO_RAM.contains(&address) { - self.ram.write(WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF), data); - } else if Timer::is_io_register(address) { - self.timer.set_register(address, data); - } else if Sound::is_io_register(address) { - self.sound.set_register(address, data); - } else if address == JOYPAD_ADDRESS { - let byte = self.data[address as usize]; - self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111); - } else if VIDEO_RAM.contains(&address) { - return self.ppu.write_vram_external(address, data); - } else if SPRITE_ATTRIBUTE_TABLE.contains(&address) { - return self.ppu.write_oam(address, data); - } else if address == DMA_ADDRESS { - self.ppu.set_register(address, data); - self.dma_transfer(data); - } else if address == HDMA5_ADDRESS { - self.ppu.set_register(address, data); - self.hdma_transfer(data); - } else if PPU::is_io_register(address) { - self.ppu.set_register(address, data); - } else { - self.data[address as usize] = data; - } + match Bus::map_address(address) { + MemoryMap::BankZero | MemoryMap::BankSwitchable | MemoryMap::ExternalRam => self.rom.write(address, data), + MemoryMap::WorkRam1 | MemoryMap::WorkRam2 | MemoryMap::EchoRam => self.ram.write(address, data), + MemoryMap::VideoRam => self.ppu.write_vram_external(address, data), + MemoryMap::SpriteAttributeTable => self.ppu.write_oam(address, data), + MemoryMap::IoRegisters => { + if self.cgb_mode && address == PREPARE_SPEED_SWITCH_ADDRESS { + let current_byte = self.data[address as usize]; + self.prepare_double_speed_mode = (data & 1) == 1; + // bit 7 is read only on cgb mode + self.data[address as usize] = (current_byte & 0b1000_0000) | (data & 0b0111_1111); + } else if address == WRAM_BANK_SELECT_ADDRESS { + self.ram.write(address, data); + } else if address == INTERRUPT_FLAG_ADDRESS { + self.interrupts.write(address, data); + } else if PPU::is_io_register(address) { + self.ppu.set_register(address, data); + match address { + DMA_ADDRESS => { + self.ppu.set_register(address, data); + self.dma_transfer(data); + }, + HDMA5_ADDRESS => { + self.ppu.set_register(address, data); + self.hdma_transfer(data); + }, + _ => {} + } + } else if Sound::is_io_register(address) { + self.sound.set_register(address, data); + } else if Timer::is_io_register(address) { + self.timer.set_register(address, data); + } else if address == JOYPAD_ADDRESS { + let byte = self.data[address as usize]; + self.data[address as usize] = (data & 0b11110000) | (byte & 0b00001111); + } else { + self.data[address as usize] = data; + } + }, + MemoryMap::InterruptEnable => self.interrupts.write(address, data), + _ => self.data[address as usize] = data, + }; } pub fn write_16bit(&mut self, address: u16, data: u16) { diff --git a/src/cpu.rs b/src/cpu.rs index ab10eec..1eebff1 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -920,7 +920,6 @@ impl CPU { } pub fn handle_interrupt(&mut self, bus: &mut Bus, interrupt: Interrupt) { - // println!("Interrupt: {:?}", interrupt); bus.interrupts.set(interrupt, false); self.ime = false; self.registers.decrement(Register::PC, 3); diff --git a/src/ppu.rs b/src/ppu.rs index a939372..a6cad29 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -394,85 +394,83 @@ impl PPU { } pub fn get_register(&self, address: u16) -> u8 { - if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS { - return match address { + match address { + HDMA1_ADDRESS..=HDMA5_ADDRESS => match address { HDMA1_ADDRESS => self.hdma_source.to_be_bytes()[0], HDMA2_ADDRESS => self.hdma_source.to_be_bytes()[1], HDMA3_ADDRESS => self.hdma_destination.to_be_bytes()[0], HDMA4_ADDRESS => self.hdma_destination.to_be_bytes()[1], HDMA5_ADDRESS => self.hdma_start, _ => 0x00, - } - } else if address >= 0xFF68 && address <= 0xFF6B { - return self.cram_registers[(address as usize) - 0xFF68]; - } else if address == VRAM_BANK_SELECT_ADDRESS { - return self.get_vram_bank(); - } else if address == LCD_CONTROL_ADDRESS { - return self.lcd_control; - } else if address == LCD_Y_ADDRESS { - return self.lcd_y; + }, + 0xFF68..=0xFF6B => self.cram_registers[(address as usize) - 0xFF68], + VRAM_BANK_SELECT_ADDRESS => self.get_vram_bank(), + LCD_CONTROL_ADDRESS => self.lcd_control, + LCD_Y_ADDRESS => self.lcd_y, + _ => self.io_registers[(address - 0xFF40) as usize], } - self.io_registers[(address - 0xFF40) as usize] } pub fn set_register(&mut self, address: u16, data: u8) { - if address >= HDMA1_ADDRESS && address <= HDMA5_ADDRESS { - match address { + match address { + HDMA1_ADDRESS..=HDMA5_ADDRESS => match address { HDMA1_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF) | ((data as u16) << 8), HDMA2_ADDRESS => self.hdma_source = (self.hdma_source & 0xFF00) | (data as u16), HDMA3_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF) | ((data as u16) << 8), HDMA4_ADDRESS => self.hdma_destination = (self.hdma_destination & 0xFF00) | (data as u16), HDMA5_ADDRESS => self.hdma_start = data, _ => (), - }; - } else if address >= 0xFF68 && address <= 0xFF6B { - self.cram_registers[(address as usize) - 0xFF68] = data; - - if address == BCPD_BGPD_ADDRESS { - if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { - return; + }, + 0xFF68..=0xFF6B => { + self.cram_registers[(address as usize) - 0xFF68] = data; + match address { + BCPD_BGPD_ADDRESS => { + if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { + return; + } + let byte = self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68]; + let auto_increment = get_bit(byte, BitIndex::I7); + let cram_address = byte & 0b111111; + self.bg_cram[cram_address as usize] = data; + if auto_increment { + self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); + } + }, + OCPD_OBPD_ADDRESS => { + if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { + return; + } + let byte = self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68]; + let auto_increment = get_bit(byte, BitIndex::I7); + let cram_address = byte & 0b111111; + self.obj_cram[cram_address as usize] = data; + if auto_increment { + self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); + } + } + _ => {}, } - let byte = self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68]; - let auto_increment = get_bit(byte, BitIndex::I7); - let cram_address = byte & 0b111111; - self.bg_cram[cram_address as usize] = data; - if auto_increment { - self.cram_registers[(BCPS_BGPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); + }, + VRAM_BANK_SELECT_ADDRESS => self.set_vram_bank(data), + LCD_Y_ADDRESS => {}, + LCD_CONTROL_ADDRESS => { + self.lcd_control = data; + // Check if LCD is being turned on or off + self.lcd_enable = get_bit(data, BitIndex::I7); + if !get_bit(data, BitIndex::I7) || (get_bit(data, BitIndex::I7) && !get_bit(self.lcd_control, BitIndex::I7)) { + self.lcd_y = 0x00; + // Set Hblank + let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40]; + self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100; } - } else if address == OCPD_OBPD_ADDRESS { - if self.get_lcd_status(LCDStatus::ModeFlag(LCDStatusModeFlag::TransferringToLCD)) { - return; - } - let byte = self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68]; - let auto_increment = get_bit(byte, BitIndex::I7); - let cram_address = byte & 0b111111; - self.obj_cram[cram_address as usize] = data; - if auto_increment { - self.cram_registers[(OCPS_OBPI_ADDRESS as usize) - 0xFF68] = ((byte + 1) & 0b111111) | ((auto_increment as u8) << 7); - } - } - } else if address == VRAM_BANK_SELECT_ADDRESS { - return self.set_vram_bank(data); - } else if address == LCD_Y_ADDRESS { - return; - } else if address == LCD_CONTROL_ADDRESS { - self.lcd_control = data; - // Check if LCD is being turned on or off - self.lcd_enable = get_bit(data, BitIndex::I7); - if !get_bit(data, BitIndex::I7) || (get_bit(data, BitIndex::I7) && !get_bit(self.lcd_control, BitIndex::I7)) { - self.lcd_y = 0x00; - // Set Hblank - let byte = self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40]; - self.io_registers[LCD_STATUS_ADDRESS as usize - 0xFF40] = byte & 0b11111100; - } - return; - } else if address == LCD_STATUS_ADDRESS { - let address = address - 0xFF40; - let byte = self.io_registers[address as usize]; - self.io_registers[address as usize] = (data & 0b11111000) | (byte & 0b00000111); - } else { - self.io_registers[address as usize - 0xFF40] = data; - } + }, + LCD_STATUS_ADDRESS => { + let address = address - 0xFF40; + let byte = self.io_registers[address as usize]; + self.io_registers[address as usize] = (data & 0b11111000) | (byte & 0b00000111); + }, + _ => self.io_registers[address as usize - 0xFF40] = data, + }; } pub fn force_set_register(&mut self, address: u16, data: u8) { diff --git a/src/ram.rs b/src/ram.rs index 502154f..983f932 100644 --- a/src/ram.rs +++ b/src/ram.rs @@ -1,3 +1,5 @@ +use crate::bus::{ECHO_RAM, WORK_RAM_1}; + pub const WRAM_BANK_SELECT_ADDRESS: u16 = 0xFF70; pub trait RAM { @@ -17,11 +19,20 @@ impl DMGRAM { } } +fn parse_echo_ram_address(address: u16) -> u16 { + let mut address = address; + if ECHO_RAM.contains(&address) { + address = WORK_RAM_1.min().unwrap() + ((address - ECHO_RAM.min().unwrap()) & 0x1FFF); + } + address +} + impl RAM for DMGRAM { fn read(&self, address: u16) -> u8 { if address == WRAM_BANK_SELECT_ADDRESS { return 0xFF; } + let address = parse_echo_ram_address(address); self.data[(address - 0xC000) as usize] } @@ -29,6 +40,7 @@ impl RAM for DMGRAM { if address == WRAM_BANK_SELECT_ADDRESS { return; } + let address = parse_echo_ram_address(address); self.data[(address - 0xC000) as usize] = value; } } @@ -63,6 +75,7 @@ impl RAM for CGBRAM { if address == WRAM_BANK_SELECT_ADDRESS { return self.bank; } + let address = parse_echo_ram_address(address); if address <= 0xCFFF { return self.data[(address - 0xC000) as usize]; } @@ -72,7 +85,9 @@ impl RAM for CGBRAM { fn write(&mut self, address: u16, value: u8) { if address == WRAM_BANK_SELECT_ADDRESS { return self.switch_bank(value); - } else if address <= 0xCFFF { + } + let address = parse_echo_ram_address(address); + if address <= 0xCFFF { return self.data[(address - 0xC000) as usize] = value; } self.data[((address - 0xD000) as usize) + (4096 * (self.bank as usize))] = value; diff --git a/src/render.rs b/src/render.rs index ba9cba4..019cd1c 100644 --- a/src/render.rs +++ b/src/render.rs @@ -2,6 +2,7 @@ use crate::emulator::Emulator; use crate::frames::Frames; use crate::ppu::{WIDTH, HEIGHT}; +use std::env; use log::error; use pixels::{wgpu, Pixels, PixelsBuilder, SurfaceTexture}; use winit::dpi::LogicalSize; @@ -10,10 +11,13 @@ use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::{Window, WindowBuilder}; use winit_input_helper::WinitInputHelper; +fn is_fps_unlocked() -> bool { + !env::var("UNLOCK_FPS").is_err() +} + pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels { let window_size = window.inner_size(); let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, window); - // Pixels::new(width, height, surface_texture).unwrap() PixelsBuilder::new(width, height, surface_texture) .device_descriptor(wgpu::DeviceDescriptor { limits: wgpu::Limits { @@ -24,7 +28,7 @@ pub fn create_pixels(width: u32, height: u32, window: &Window) -> Pixels { }, ..wgpu::DeviceDescriptor::default() }) - .enable_vsync(false) + .enable_vsync(!is_fps_unlocked()) .build() .unwrap() } @@ -88,7 +92,9 @@ pub fn start_eventloop() { frame_counter.reset_timer(); } window.request_redraw(); - frame_limit.limit(); + if !is_fps_unlocked() { + frame_limit.limit(); + } frame_limit.reset_timer(); }, Event::RedrawRequested(_) => {