Compare commits
	
		
			13 Commits
		
	
	
		
			01064bab69
			...
			refactor
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9baa15050e | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | 
							
								
								
									
										569
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										569
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -3,6 +3,7 @@ name = "gb" | ||||
| version = "0.1.0" | ||||
| authors = ["Rekai Musuka <rekai@musuka.dev>"] | ||||
| edition = "2018" | ||||
| resolver = "2" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| @@ -11,7 +12,7 @@ anyhow = "^1.0" | ||||
| bitfield = "^0.13" | ||||
| clap = "^2.33" | ||||
| gilrs = "^0.8" | ||||
| pixels = "^0.5" | ||||
| pixels = "^0.6" | ||||
| winit = "^0.25" | ||||
| winit_input_helper = "^0.10" | ||||
| rodio = "^0.14" | ||||
|   | ||||
							
								
								
									
										35
									
								
								src/apu.rs
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								src/apu.rs
									
									
									
									
									
								
							| @@ -468,22 +468,8 @@ impl Channel1 { | ||||
|  | ||||
|         // If this bit is set, a trigger event occurs | ||||
|         if self.freq_hi.initial() { | ||||
|             // Envelope Behaviour during trigger event | ||||
|             self.period_timer = self.envelope.period(); | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Sweep behaviour during trigger event | ||||
|             let sweep_period = self.sweep.period(); | ||||
|             let sweep_shift = self.sweep.shift_count(); | ||||
|             self.shadow_freq = self.frequency(); | ||||
|             self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period }; | ||||
|  | ||||
|             if sweep_period != 0 || sweep_shift != 0 { | ||||
|                 self.sweep_enabled = true; | ||||
|             } | ||||
|  | ||||
|             if sweep_shift != 0 { | ||||
|                 let _ = self.calc_sweep_freq(); | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
| @@ -491,8 +477,21 @@ impl Channel1 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             // Envelope Behaviour during trigger event | ||||
|             self.period_timer = self.envelope.period(); | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Sweep behaviour during trigger event | ||||
|             let sweep_period = self.sweep.period(); | ||||
|             let sweep_shift = self.sweep.shift_count(); | ||||
|  | ||||
|             self.shadow_freq = self.frequency(); | ||||
|             self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period }; | ||||
|  | ||||
|             self.sweep_enabled = sweep_period != 0 || sweep_shift != 0; | ||||
|  | ||||
|             if sweep_shift != 0 { | ||||
|                 let _ = self.calc_sweep_freq(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode}; | ||||
| use crate::serial::Serial; | ||||
| use crate::timer::Timer; | ||||
| use crate::work_ram::{VariableWorkRam, WorkRam}; | ||||
| use std::{fs::File, io::Read}; | ||||
|  | ||||
| const BOOT_ROM_SIZE: usize = 0x100; | ||||
| pub(crate) const BOOT_SIZE: usize = 0x100; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Bus { | ||||
|     boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long | ||||
|     cartridge: Option<Cartridge>, | ||||
|     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||
|     cart: Option<Cartridge>, | ||||
|     pub(crate) ppu: Ppu, | ||||
|     work_ram: WorkRam, | ||||
|     var_ram: VariableWorkRam, | ||||
| @@ -30,7 +29,7 @@ impl Default for Bus { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             boot: None, | ||||
|             cartridge: None, | ||||
|             cart: None, | ||||
|             ppu: Default::default(), | ||||
|             work_ram: Default::default(), | ||||
|             var_ram: Default::default(), | ||||
| @@ -45,25 +44,19 @@ impl Default for Bus { | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> { | ||||
|         let mut file = File::open(path)?; | ||||
|         let mut boot_rom = [0u8; 256]; | ||||
|  | ||||
|         file.read_exact(&mut boot_rom)?; | ||||
|  | ||||
|         Ok(Self { | ||||
|             boot: Some(boot_rom), | ||||
|     pub(crate) fn with_boot(rom: [u8; 256]) -> Self { | ||||
|         Self { | ||||
|             boot: Some(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.cartridge = Some(Cartridge::new(path)?); | ||||
|         Ok(()) | ||||
|     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cart = Some(Cartridge::new(rom)); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { | ||||
|         self.cartridge.as_ref()?.title() | ||||
|     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||
|         self.cart.as_ref()?.title() | ||||
|     } | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
| @@ -104,13 +97,13 @@ impl Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -157,7 +150,7 @@ impl BusIo for Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
| @@ -169,7 +162,7 @@ impl BusIo for Bus { | ||||
|                     _ => self.ppu.read_byte(addr), | ||||
|                 } | ||||
|             } | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -262,7 +255,7 @@ impl BusIo for Bus { | ||||
|             0x0000..=0x7FFF => { | ||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
| @@ -276,7 +269,7 @@ impl BusIo for Bus { | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 // 8KB External RAM | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
|   | ||||
							
								
								
									
										139
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								src/cartridge.rs
									
									
									
									
									
								
							| @@ -1,7 +1,3 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{self, Read}; | ||||
| use std::path::Path; | ||||
|  | ||||
| use crate::bus::BusIo; | ||||
|  | ||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||
| @@ -17,19 +13,15 @@ pub(crate) struct Cartridge { | ||||
| } | ||||
|  | ||||
| impl Cartridge { | ||||
|     pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> { | ||||
|         let mut memory = vec![]; | ||||
|         let mut rom = File::open(path)?; | ||||
|         rom.read_to_end(&mut memory)?; | ||||
|  | ||||
|     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||
|         let title = Self::find_title(&memory); | ||||
|         eprintln!("Cartridge Title: {:?}", title); | ||||
|  | ||||
|         Ok(Self { | ||||
|         Self { | ||||
|             mbc: Self::detect_mbc(&memory), | ||||
|             title, | ||||
|             memory, | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||
| @@ -47,6 +39,8 @@ impl Cartridge { | ||||
|             MBCKind::None => Box::new(NoMBC), | ||||
|             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), | ||||
|             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving | ||||
|             MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), | ||||
|             MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), | ||||
|             MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), | ||||
| @@ -80,14 +74,18 @@ impl Cartridge { | ||||
|     } | ||||
|  | ||||
|     fn find_mbc(memory: &[u8]) -> MBCKind { | ||||
|         use MBCKind::*; | ||||
|  | ||||
|         match memory[MBC_TYPE_ADDRESS] { | ||||
|             0x00 => MBCKind::None, | ||||
|             0x01 | 0x02 => MBCKind::MBC1, | ||||
|             0x03 => MBCKind::MBC1WithBattery, | ||||
|             0x19 | 0x1A => MBCKind::MBC5, | ||||
|             0x1B => MBCKind::MBC5WithBattery, | ||||
|             0x13 => MBCKind::MBC3WithBattery, | ||||
|             0x11 | 0x12 => MBCKind::MBC3, | ||||
|             0x00 => None, | ||||
|             0x01 | 0x02 => MBC1, | ||||
|             0x03 => MBC1WithBattery, | ||||
|             0x05 => MBC2, | ||||
|             0x06 => MBC2WithBattery, | ||||
|             0x19 | 0x1A => MBC5, | ||||
|             0x1B => MBC5WithBattery, | ||||
|             0x13 => MBC3WithBattery, | ||||
|             0x11 | 0x12 => MBC3, | ||||
|             id => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||
|         } | ||||
|     } | ||||
| @@ -182,16 +180,16 @@ impl MBC1 { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ram_addr(&self, addr: u16) -> u16 { | ||||
|     fn ram_addr(&self, addr: u16) -> usize { | ||||
|         use RamSize::*; | ||||
|  | ||||
|         match self.ram_size { | ||||
|             Unused | One => (addr - 0xA000) % self.ram_size.capacity() as u16, | ||||
|             Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(), | ||||
|             Four => { | ||||
|                 if self.mode { | ||||
|                     0x2000 * self.ram_bank as u16 + (addr - 0xA000) | ||||
|                     0x2000 * self.ram_bank as usize + (addr as usize - 0xA000) | ||||
|                 } else { | ||||
|                     addr - 0xA000 | ||||
|                     addr as usize - 0xA000 | ||||
|                 } | ||||
|             } | ||||
|             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), | ||||
| @@ -204,23 +202,17 @@ impl MBCIo for MBC1 { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => { | ||||
|                 if self.mode { | ||||
|                     Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||
|                 } else { | ||||
|                     Address(addr as usize) | ||||
|                 } | ||||
|             0x0000..=0x3FFF if self.mode => { | ||||
|                 Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||
|             } | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => { | ||||
|                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 if self.mem_enabled { | ||||
|                     Value(self.memory[self.ram_addr(addr) as usize]) | ||||
|                 } else { | ||||
|                     Value(0xFF) | ||||
|                 } | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|                 Value(self.memory[self.ram_addr(addr)]) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), | ||||
|         } | ||||
|     } | ||||
| @@ -229,18 +221,14 @@ impl MBCIo for MBC1 { | ||||
|         match addr { | ||||
|             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, | ||||
|             0x2000..=0x3FFF => { | ||||
|                 self.rom_bank = if byte == 0x00 { | ||||
|                     0x01 | ||||
|                 } else { | ||||
|                     byte & self.rom_size_mask() | ||||
|                 }; | ||||
|  | ||||
|                 self.rom_bank &= 0x1F; | ||||
|                 let value = byte & 0x1F; | ||||
|                 let masked_value = byte & self.rom_size_mask(); | ||||
|                 self.rom_bank = if value == 0 { 0x01 } else { masked_value }; | ||||
|             } | ||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, | ||||
|             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let ram_addr = self.ram_addr(addr) as usize; | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|                 let ram_addr = self.ram_addr(addr); | ||||
|                 self.memory[ram_addr] = byte; | ||||
|             } | ||||
|             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write | ||||
| @@ -405,6 +393,67 @@ impl MBCIo for MBC5 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC2 { | ||||
|     /// 4-bit number | ||||
|     rom_bank: u8, | ||||
|     memory: Box<[u8; Self::RAM_SIZE]>, | ||||
|     mem_enabled: bool, | ||||
|  | ||||
|     rom_cap: usize, | ||||
| } | ||||
|  | ||||
| impl MBC2 { | ||||
|     const RAM_SIZE: usize = 0x0200; | ||||
|  | ||||
|     fn new(rom_cap: usize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: Box::new([0; Self::RAM_SIZE]), | ||||
|             mem_enabled: Default::default(), | ||||
|             rom_cap, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn rom_addr(&self, addr: u16) -> usize { | ||||
|         (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC2 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(self.rom_addr(addr)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 Value(self.memory[mbc2_addr] | 0xF0) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         let nybble = byte & 0x0F; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => { | ||||
|                 self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble }; | ||||
|             } | ||||
|             0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 self.memory[mbc2_addr] = nybble; | ||||
|             } | ||||
|             0x4000..=0x7FFF | 0xA000..=0xBFFF => {} | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct NoMBC; | ||||
|  | ||||
| @@ -434,6 +483,8 @@ enum MBCKind { | ||||
|     None, | ||||
|     MBC1, | ||||
|     MBC1WithBattery, | ||||
|     MBC2, | ||||
|     MBC2WithBattery, | ||||
|     MBC3, | ||||
|     MBC3WithBattery, | ||||
|     MBC5, | ||||
| @@ -446,7 +497,7 @@ impl Default for MBCKind { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| enum RamSize { | ||||
|     None = 0x00, | ||||
|     Unused = 0x01, | ||||
|   | ||||
							
								
								
									
										49
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,10 +1,7 @@ | ||||
| use crate::apu::Apu; | ||||
| use crate::bus::{Bus, BusIo}; | ||||
| use crate::instruction::cycle::Cycle; | ||||
| use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||
| use crate::instruction::Instruction; | ||||
| use crate::interrupt::{InterruptEnable, InterruptFlag}; | ||||
| use crate::joypad::Joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use crate::Cycle; | ||||
| use bitfield::bitfield; | ||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||
|  | ||||
| @@ -18,7 +15,7 @@ pub struct Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn new() -> Self { | ||||
|     pub(crate) fn without_boot() -> Self { | ||||
|         Self { | ||||
|             reg: Registers { | ||||
|                 a: 0x01, | ||||
| @@ -36,11 +33,11 @@ impl Cpu { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { | ||||
|         Ok(Self { | ||||
|             bus: Bus::with_boot(path)?, | ||||
|     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||
|         Self { | ||||
|             bus: Bus::with_boot(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn ime(&self) -> ImeState { | ||||
| @@ -72,14 +69,6 @@ impl Cpu { | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.bus.load_cartridge(path) | ||||
|     } | ||||
|  | ||||
|     pub fn rom_title(&self) -> Option<&str> { | ||||
|         self.bus.rom_title() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
| @@ -133,7 +122,7 @@ impl Cpu { | ||||
|             self.bus.clock(); | ||||
|  | ||||
|             let elapsed = match kind { | ||||
|                 ImeEnabled | NonePending => Cycle::new(4), | ||||
|                 ImeEnabled | NonePending => 4, | ||||
|                 SomePending => todo!("Implement HALT bug"), | ||||
|             }; | ||||
|  | ||||
| @@ -146,11 +135,11 @@ impl Cpu { | ||||
|         self.handle_ei(); | ||||
|  | ||||
|         // For use in Blargg's Test ROMs | ||||
|         // if self.read_byte(0xFF02) == 0x81 { | ||||
|         //     let c = self.read_byte(0xFF01) as char; | ||||
|         //     self.write_byte(0xFF02, 0x00); | ||||
|         //     eprint!("{}", c); | ||||
|         // } | ||||
|         if self.read_byte(0xFF02) == 0x81 { | ||||
|             let c = self.read_byte(0xFF01) as char; | ||||
|             self.write_byte(0xFF02, 0x00); | ||||
|             eprint!("{}", c); | ||||
|         } | ||||
|  | ||||
|         elapsed | ||||
|     } | ||||
| @@ -167,16 +156,12 @@ impl BusIo for Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn ppu(&mut self) -> &Ppu { | ||||
|         &self.bus.ppu | ||||
|     pub(crate) fn bus(&self) -> &Bus { | ||||
|         &self.bus | ||||
|     } | ||||
|  | ||||
|     pub fn apu_mut(&mut self) -> &mut Apu { | ||||
|         &mut self.bus.apu | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.bus.joypad | ||||
|     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||
|         &mut self.bus | ||||
|     } | ||||
|  | ||||
|     fn handle_ei(&mut self) { | ||||
|   | ||||
							
								
								
									
										158
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										158
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,73 +1,121 @@ | ||||
| use crate::cpu::Cpu as SM83; | ||||
| use crate::instruction::cycle::Cycle; | ||||
| use crate::joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use anyhow::Result; | ||||
| use crate::apu::gen::SampleProducer; | ||||
| use crate::cpu::Cpu; | ||||
| use crate::joypad::{self, Joypad}; | ||||
| use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use std::time::Duration; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
|  | ||||
| pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | ||||
| pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines | ||||
| pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines | ||||
| pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | ||||
| const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | ||||
| const GAMEPAD_ENABLED: bool = true; | ||||
|  | ||||
| pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | ||||
|     let mut cpu = match boot_path { | ||||
|         Some(path) => SM83::boot_new(path)?, | ||||
|         None => SM83::new(), | ||||
|     }; | ||||
| pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||
|     let mut elapsed = 0; | ||||
|  | ||||
|     eprintln!("Initialized GB Emulator"); | ||||
|  | ||||
|     cpu.load_cartridge(rom_path)?; | ||||
|     Ok(cpu) | ||||
| } | ||||
|  | ||||
| pub fn rom_title(game_boy: &SM83) -> &str { | ||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) | ||||
| } | ||||
|  | ||||
| pub fn run( | ||||
|     game_boy: &mut SM83, | ||||
|     gamepad: &mut Gilrs, | ||||
|     input: &WinitInputHelper, | ||||
|     target: Cycle, | ||||
| ) -> Cycle { | ||||
|     let mut elapsed = Cycle::new(0); | ||||
|  | ||||
|     if GAMEPAD_ENABLED { | ||||
|         if let Some(event) = gamepad.next_event() { | ||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); | ||||
|         } | ||||
|     if let Some(event) = gamepad.next_event() { | ||||
|         joypad::handle_gamepad_input(emu.joyp_mut(), event); | ||||
|     } | ||||
|  | ||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | ||||
|     while elapsed < target { | ||||
|         elapsed += game_boy.step(); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { | ||||
|     let mut elapsed = Cycle::new(0); | ||||
|  | ||||
|     if GAMEPAD_ENABLED { | ||||
|         if let Some(event) = gamepad.next_event() { | ||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | ||||
|     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||
|     while elapsed < CYCLES_IN_FRAME { | ||||
|         elapsed += game_boy.step(); | ||||
|         elapsed += emu.step(); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { | ||||
|     ppu.copy_to_gui(frame); | ||||
| pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||
|     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||
| } | ||||
|  | ||||
| pub struct Emulator { | ||||
|     cpu: Cpu, | ||||
|     timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Emulator { | ||||
|     fn new(cpu: Cpu) -> Self { | ||||
|         Self { | ||||
|             cpu, | ||||
|             timestamp: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn step(&mut self) -> Cycle { | ||||
|         self.cpu.step() | ||||
|     } | ||||
|  | ||||
|     fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cpu.bus_mut().load_cart(rom) | ||||
|     } | ||||
|  | ||||
|     fn joyp_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.cpu.bus_mut().joypad | ||||
|     } | ||||
|  | ||||
|     pub fn set_prod(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.cpu.bus_mut().apu.attach_producer(prod) | ||||
|     } | ||||
|  | ||||
|     pub fn title(&self) -> &str { | ||||
|         self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub mod build { | ||||
|     use std::fs::File; | ||||
|     use std::io::{Read, Result}; | ||||
|     use std::path::Path; | ||||
|  | ||||
|     use crate::bus::BOOT_SIZE; | ||||
|     use crate::cpu::Cpu; | ||||
|  | ||||
|     use super::Emulator; | ||||
|  | ||||
|     #[derive(Debug, Default)] | ||||
|     pub struct EmulatorBuilder { | ||||
|         boot: Option<[u8; BOOT_SIZE]>, | ||||
|         cart: Option<Vec<u8>>, | ||||
|     } | ||||
|  | ||||
|     impl EmulatorBuilder { | ||||
|         pub fn new() -> Self { | ||||
|             Default::default() | ||||
|         } | ||||
|  | ||||
|         pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = [0x00; BOOT_SIZE]; | ||||
|             file.read_exact(&mut buf)?; | ||||
|  | ||||
|             self.boot = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = Vec::new(); | ||||
|             file.read_to_end(&mut buf)?; | ||||
|  | ||||
|             self.cart = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn finish(mut self) -> Emulator { | ||||
|             let mut emu = Emulator::new(match self.boot { | ||||
|                 Some(rom) => Cpu::with_boot(rom), | ||||
|                 None => Cpu::without_boot(), | ||||
|             }); | ||||
|  | ||||
|             if let Some(rom) = self.cart.take() { | ||||
|                 emu.load_cart(rom) | ||||
|             } | ||||
|  | ||||
|             emu | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,5 +1,5 @@ | ||||
| pub use apu::gen::AudioSPSC; | ||||
| pub use instruction::cycle::Cycle; | ||||
| pub type Cycle = u64; | ||||
|  | ||||
| pub const GB_WIDTH: usize = 160; | ||||
| pub const GB_HEIGHT: usize = 144; | ||||
|   | ||||
							
								
								
									
										88
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,18 +1,20 @@ | ||||
| use std::convert::TryInto; | ||||
|  | ||||
| use anyhow::{anyhow, Result}; | ||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||
| use gb::emu::build::EmulatorBuilder; | ||||
| use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use pixels::{PixelsBuilder, SurfaceTexture}; | ||||
| use rodio::{OutputStream, Sink}; | ||||
| use std::time::{Duration, Instant}; | ||||
| use winit::dpi::LogicalSize; | ||||
| use winit::dpi::{LogicalSize, PhysicalSize}; | ||||
| use winit::event::{Event, VirtualKeyCode}; | ||||
| use winit::event_loop::{ControlFlow, EventLoop}; | ||||
| use winit::window::{Window, WindowBuilder}; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
|  | ||||
| const WINDOW_SCALE: f64 = 2.0; | ||||
| const AUDIO_ENABLED: bool = true; | ||||
| const WINDOW_SCALE: usize = 3; | ||||
| const AUDIO_ENABLED: bool = false; | ||||
|  | ||||
| fn main() -> Result<()> { | ||||
|     let app = App::new(crate_name!()) | ||||
| @@ -39,13 +41,15 @@ fn main() -> Result<()> { | ||||
|         ) | ||||
|         .get_matches(); | ||||
|  | ||||
|     let rom_path = m | ||||
|         .value_of("rom") | ||||
|         .expect("Required value 'rom' was provided"); | ||||
|     let mut emu_build = | ||||
|         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||
|  | ||||
|     let mut game_boy = | ||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator"); | ||||
|     let rom_title = gb::emu::rom_title(&game_boy); | ||||
|     if let Some(path) = m.value_of("boot") { | ||||
|         emu_build = emu_build.with_boot(path)?; | ||||
|     } | ||||
|  | ||||
|     let mut emu = emu_build.finish(); | ||||
|     let rom_title = emu.title(); | ||||
|  | ||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||
|  | ||||
| @@ -69,17 +73,20 @@ fn main() -> Result<()> { | ||||
|     if AUDIO_ENABLED { | ||||
|         let spsc: AudioSPSC<f32> = Default::default(); | ||||
|         let (prod, cons) = spsc.init(); | ||||
|         let sink = Sink::try_new(&stream_handle)?; | ||||
|         sink.append(cons); | ||||
|         game_boy.apu_mut().attach_producer(prod); | ||||
|         let sink = { | ||||
|             let s = Sink::try_new(&stream_handle)?; | ||||
|             s.append(cons); | ||||
|             s.set_volume(0.1); | ||||
|             s | ||||
|         }; | ||||
|  | ||||
|         emu.set_prod(prod); | ||||
|  | ||||
|         std::thread::spawn(move || { | ||||
|             sink.sleep_until_end(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let mut start = Instant::now(); | ||||
|     let frame_time = Duration::from_secs_f64(1.0 / 59.73); // 59.73 Hz on Host | ||||
|     let mut cycle_count: Cycle = Default::default(); | ||||
|  | ||||
|     event_loop.run(move |event, _, control_flow| { | ||||
| @@ -104,57 +111,34 @@ fn main() -> Result<()> { | ||||
|                 pixels.resize_surface(size.width, size.height); | ||||
|             } | ||||
|  | ||||
|             let mut diff = Instant::now() - start; | ||||
|             while diff.subsec_nanos() < frame_time.subsec_nanos() { | ||||
|                 if cycle_count < gb::emu::CYCLES_IN_FRAME { | ||||
|                     cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input); | ||||
|                 } | ||||
|  | ||||
|                 diff = Instant::now() - start; | ||||
|             } | ||||
|             start = Instant::now(); | ||||
|             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||
|  | ||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||
|                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||
|  | ||||
|                 gb::emu::draw(game_boy.ppu(), pixels.get_frame()); | ||||
|                 let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels | ||||
|                     .get_frame() | ||||
|                     .try_into() | ||||
|                     .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); | ||||
|  | ||||
|                 gb::emu::draw_frame(&emu, buf); | ||||
|                 window.request_redraw(); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| #[cfg(not(windows))] | ||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | ||||
|     let size = LogicalSize::new( | ||||
|         (GB_WIDTH as f64) * WINDOW_SCALE, | ||||
|         (GB_HEIGHT as f64) * WINDOW_SCALE, | ||||
|     let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64); | ||||
|     let physical = PhysicalSize::new( | ||||
|         (GB_WIDTH * WINDOW_SCALE) as f32, | ||||
|         (GB_HEIGHT * WINDOW_SCALE) as f32, | ||||
|     ); | ||||
|  | ||||
|     Ok(WindowBuilder::new() | ||||
|         .with_title(title) | ||||
|         .with_inner_size(size) | ||||
|         .with_min_inner_size(size) | ||||
|         .with_min_inner_size(logical) | ||||
|         .with_inner_size(physical) | ||||
|         .with_resizable(true) | ||||
|         .with_decorations(true) | ||||
|         .with_transparent(false) | ||||
|         .build(event_loop)?) | ||||
| } | ||||
|  | ||||
| #[cfg(windows)] | ||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | ||||
|     use winit::platform::windows::WindowBuilderExtWindows; | ||||
|  | ||||
|     let size = LogicalSize::new( | ||||
|         (GB_WIDTH as f64) * WINDOW_SCALE, | ||||
|         (GB_HEIGHT as f64) * WINDOW_SCALE, | ||||
|     ); | ||||
|     Ok(WindowBuilder::new() | ||||
|         .with_title(title) | ||||
|         .with_inner_size(size) | ||||
|         .with_min_inner_size(size) | ||||
|         .with_resizable(true) | ||||
|         .with_decorations(true) | ||||
|         .with_transparent(false) | ||||
|         .with_drag_and_drop(false) | ||||
|         .build(event_loop)?) | ||||
| } | ||||
|   | ||||
							
								
								
									
										18
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| use crate::bus::BusIo; | ||||
| use crate::instruction::cycle::Cycle; | ||||
| use crate::Cycle; | ||||
| use crate::GB_HEIGHT; | ||||
| use crate::GB_WIDTH; | ||||
| use dma::DirectMemoryAccess; | ||||
| @@ -79,7 +79,7 @@ impl Ppu { | ||||
|  | ||||
|         match self.stat.mode() { | ||||
|             PpuMode::OamScan => { | ||||
|                 if self.cycle >= 80.into() { | ||||
|                 if self.cycle >= 80 { | ||||
|                     self.stat.set_mode(PpuMode::Drawing); | ||||
|                 } | ||||
|  | ||||
| @@ -88,7 +88,7 @@ impl Ppu { | ||||
|             PpuMode::Drawing => { | ||||
|                 if self.ctrl.lcd_enabled() { | ||||
|                     // Only Draw when the LCD Is Enabled | ||||
|                     self.draw(self.cycle.into()); | ||||
|                     self.draw(self.cycle); | ||||
|                 } else { | ||||
|                     self.reset(); | ||||
|                 } | ||||
| @@ -125,7 +125,7 @@ impl Ppu { | ||||
|             PpuMode::HBlank => { | ||||
|                 // This mode will always end at 456 cycles | ||||
|  | ||||
|                 if self.cycle >= 456.into() { | ||||
|                 if self.cycle >= 456 { | ||||
|                     self.cycle %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
| @@ -167,7 +167,7 @@ impl Ppu { | ||||
|                 } | ||||
|             } | ||||
|             PpuMode::VBlank => { | ||||
|                 if self.cycle > 456.into() { | ||||
|                 if self.cycle > 456 { | ||||
|                     self.cycle %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
| @@ -228,7 +228,7 @@ impl Ppu { | ||||
|         self.scan_state.next(); | ||||
|     } | ||||
|  | ||||
|     fn draw(&mut self, _cycle: u32) { | ||||
|     fn draw(&mut self, _cycle: Cycle) { | ||||
|         use FetcherState::*; | ||||
|  | ||||
|         let mut iter = self.obj_buffer.iter_mut(); | ||||
| @@ -418,8 +418,8 @@ impl Ppu { | ||||
|         self.frame_buf.swap_with_slice(&mut blank); | ||||
|     } | ||||
|  | ||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { | ||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); | ||||
|     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||
|         &self.frame_buf | ||||
|     } | ||||
|  | ||||
|     fn clock_fifo(&mut self) -> Option<GrayShade> { | ||||
| @@ -470,7 +470,7 @@ impl Default for Ppu { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             vram: Box::new([0u8; VRAM_SIZE]), | ||||
|             cycle: Cycle::new(0), | ||||
|             cycle: Default::default(), | ||||
|             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), | ||||
|             int: Default::default(), | ||||
|             ctrl: Default::default(), | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| use crate::instruction::cycle::Cycle; | ||||
| use crate::Cycle; | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct DirectMemoryAccess { | ||||
| @@ -56,7 +56,7 @@ impl DirectMemoryAccess { | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.cycle = Cycle::new(0); | ||||
|         self.cycle = 0; | ||||
|         self.state = DmaState::Disabled; | ||||
|         self.start.0 = None; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| use crate::Cycle; | ||||
| use std::collections::BinaryHeap; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Scheduler { | ||||
|     timestamp: Cycle, | ||||
|     queue: BinaryHeap<Event>, | ||||
| } | ||||
|  | ||||
| impl Scheduler { | ||||
|     pub(crate) fn init() -> Self { | ||||
|         let mut scheduler = Self { | ||||
|             timestamp: Default::default(), | ||||
|             queue: Default::default(), | ||||
|         }; | ||||
|  | ||||
|         scheduler.push(Event { | ||||
|             kind: EventKind::TimestampOverflow, | ||||
|             timestamp: Cycle::MAX, | ||||
|             cb: |_delay| panic!("Reached Cycle::MAX"), | ||||
|         }); | ||||
|  | ||||
|         scheduler | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn push(&mut self, event: Event) { | ||||
|         self.queue.push(event); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn step(&mut self, cycles: Cycle) { | ||||
|         self.timestamp += cycles; | ||||
|  | ||||
|         loop { | ||||
|             let should_pop = match self.queue.peek() { | ||||
|                 Some(event) => self.timestamp >= event.timestamp, | ||||
|                 None => false, | ||||
|             }; | ||||
|  | ||||
|             if !should_pop { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             let event = self.queue.pop().expect("Pop Event from Scheduler Queue"); | ||||
|  | ||||
|             (event.cb)(self.timestamp - event.timestamp); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Event { | ||||
|     kind: EventKind, | ||||
|     cb: fn(Cycle), | ||||
|     pub(crate) timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Eq for Event {} | ||||
| impl PartialEq for Event { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.kind == other.kind && self.timestamp == other.timestamp | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialOrd for Event { | ||||
|     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||||
|         Some(self.cmp(other)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Ord for Event { | ||||
|     fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||||
|         self.timestamp.cmp(&other.timestamp) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub(crate) enum EventKind { | ||||
|     TimestampOverflow, | ||||
| } | ||||
		Reference in New Issue
	
	Block a user