Compare commits

..

12 Commits

Author SHA1 Message Date
64230973f1 chore: suggest inline for hot code 2021-09-24 16:16:14 -03:00
dbbf87af52 chore: add a creative-commons boot rom for compat 2021-09-24 16:15:55 -03:00
1440cd1fc7 chore: update README.md 2021-09-24 12:34:48 -03:00
9964b49ce1 fix(ppu): improve accuracy of timer 2021-09-21 12:50:31 -03:00
142231d355 chore(ppu): remoe unnecessary brackets 2021-09-21 12:13:55 -03:00
227928e8ca Revert "fix(ppu): explicity choose to use sign extension"
This reverts commit 1001b0b124.
2021-09-21 12:09:35 -03:00
1001b0b124 fix(ppu): explicity choose to use sign extension 2021-09-21 09:52:12 -03:00
71ce3f43e0 chore: satisfy clippy 2021-09-20 04:15:05 -03:00
ce121864d2 feat: implement cartridge saving
Implemented for MBC1, MBC2, MBC3 and MBC5
2021-09-20 04:13:25 -03:00
e1fe00ab64 fix: have start and select keybinds match other emulators 2021-09-20 03:26:06 -03:00
5882678bc5 chore: inline some functions
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-09-20 01:34:41 -03:00
bcd67cb317 chore: begin refactor of public api 2021-09-20 01:34:21 -03:00
12 changed files with 340 additions and 48 deletions

49
Cargo.lock generated
View File

@@ -592,6 +592,27 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "directories-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc"
dependencies = [
"cfg-if 1.0.0",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "discard" name = "discard"
version = "1.0.4" version = "1.0.4"
@@ -671,6 +692,7 @@ dependencies = [
"anyhow", "anyhow",
"bitfield", "bitfield",
"clap", "clap",
"directories-next",
"gilrs", "gilrs",
"pixels", "pixels",
"rodio", "rodio",
@@ -679,6 +701,17 @@ dependencies = [
"winit_input_helper", "winit_input_helper",
] ]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]] [[package]]
name = "gilrs" name = "gilrs"
version = "0.8.1" version = "0.8.1"
@@ -1493,6 +1526,16 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.5.4" version = "1.5.4"
@@ -1916,6 +1959,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.77" version = "0.2.77"

View File

@@ -17,6 +17,7 @@ winit = "^0.25"
winit_input_helper = "^0.10" winit_input_helper = "^0.10"
rodio = "^0.14" rodio = "^0.14"
rtrb = "^0.1.4" rtrb = "^0.1.4"
directories-next = "2.0.0"
[profile.release] [profile.release]
debug = true debug = true

View File

@@ -2,13 +2,37 @@
[![Build Status](https://ci.paoda.moe/api/badges/paoda/gb/status.svg)](https://ci.paoda.moe/paoda/gb) [![Build Status](https://ci.paoda.moe/api/badges/paoda/gb/status.svg)](https://ci.paoda.moe/paoda/gb)
### Status ### Status
* Passes Blargg's cpu_instrs Test * From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
* Renders Background & Window Tiles * [x] cpu_instrs
* Implements a PPU FIFO * [x] instr_timing
* [x] mem_timing
* [x] mem_timing-2
* [ ] Partially dmg_sound
* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
* Cartridges:
* [x] MBC1
* [ ] MBC1M
* [x] MBC2
* [x] MBC5
* Implements a cycle-accurate PPU FIFO
* Doesn't \*exactly\* work right just yet
Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
### Notes
* [gameboy-logs](https://github.com/wheremyfoodat/Gameboy-logs) suggests that there are still some underlying problems with the cpu implementation ### Controls
* The Sprite FIFO does not work as expected yet Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/src/joypad.rs#L114)
* Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic.
* Code cleanup is pending completion of some minimum viable product of the emulator Key | Button
--- | ---
<kbd>X</kbd> | B
<kbd>Z</kbd> | A
<kbd>Enter</kbd> | START
<kbd>Shift</kbd> | SELECT
Then use the Arrow keys for the D-Pad
### Credits
The Boot ROM found in the `bin/` directory was made by [Optix](https://github.com/Hacktix) over [here](https://github.com/Hacktix/Bootix)

BIN
bin/bootix_dmg.bin Normal file

Binary file not shown.

View File

@@ -64,10 +64,12 @@ impl Bus {
self.boot.is_some() self.boot.is_some()
} }
#[inline]
pub(crate) fn clock(&mut self) { pub(crate) fn clock(&mut self) {
self.tick(4); self.tick(4);
} }
#[inline]
fn tick(&mut self, limit: u8) { fn tick(&mut self, limit: u8) {
for _ in 0..limit { for _ in 0..limit {
self.timer.tick(); self.timer.tick();
@@ -83,6 +85,21 @@ impl Bus {
self.oam_write_byte(dest_addr, byte); self.oam_write_byte(dest_addr, byte);
} }
} }
#[inline]
pub(crate) fn joyp_mut(&mut self) -> &mut Joypad {
&mut self.joypad
}
#[inline]
pub(crate) fn cart(&self) -> Option<&Cartridge> {
self.cart.as_ref()
}
#[inline]
pub(crate) fn cart_mut(&mut self) -> Option<&mut Cartridge> {
self.cart.as_mut()
}
} }
impl Bus { impl Bus {

View File

@@ -24,6 +24,14 @@ impl Cartridge {
} }
} }
pub(crate) fn ext_ram(&self) -> Option<&[u8]> {
self.mbc.ext_ram()
}
pub(crate) fn write_ext_ram(&mut self, memory: Vec<u8>) {
self.mbc.write_ext_ram(memory)
}
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
let ram_size = Self::detect_ram_info(memory); let ram_size = Self::detect_ram_info(memory);
let rom_size = Self::detect_rom_info(memory); let rom_size = Self::detect_rom_info(memory);
@@ -38,13 +46,13 @@ impl Cartridge {
match mbc_kind { match mbc_kind {
MBCKind::None => Box::new(NoMBC), MBCKind::None => Box::new(NoMBC),
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving MBCKind::MBC1WithBattery => Box::new(MBC1::with_battery(ram_size, rom_size)), // TODO: Implement Saving
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving MBCKind::MBC2WithBattery => Box::new(MBC2::with_battery(rom_cap)), // TODO: Implement Saving
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving MBCKind::MBC3WithBattery => Box::new(MBC3::with_battery(ram_cap)), // TODO: Implement Saving
MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving MBCKind::MBC5WithBattery => Box::new(MBC5::with_battery(ram_cap, rom_cap)), // TDO: Implement Saving
} }
} }
@@ -117,6 +125,8 @@ struct MBC1 {
memory: Vec<u8>, memory: Vec<u8>,
rom_size: RomSize, rom_size: RomSize,
mem_enabled: bool, mem_enabled: bool,
has_battery: bool,
} }
impl MBC1 { impl MBC1 {
@@ -129,6 +139,20 @@ impl MBC1 {
ram_bank: Default::default(), ram_bank: Default::default(),
mode: Default::default(), mode: Default::default(),
mem_enabled: Default::default(), mem_enabled: Default::default(),
has_battery: Default::default(),
}
}
fn with_battery(ram_size: RamSize, rom_size: RomSize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_size.capacity() as usize],
ram_size,
rom_size,
ram_bank: Default::default(),
mode: Default::default(),
mem_enabled: Default::default(),
has_battery: true,
} }
} }
@@ -197,6 +221,21 @@ impl MBC1 {
} }
} }
impl Savable for MBC1 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.has_battery {
true => Some(&self.memory),
false => None,
}
}
fn write_ext_ram(&mut self, memory: Vec<u8>) {
if self.has_battery {
self.memory.copy_from_slice(&memory);
}
}
}
impl MBCIo for MBC1 { impl MBCIo for MBC1 {
fn handle_read(&self, addr: u16) -> MBCResult { fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*; use MBCResult::*;
@@ -256,6 +295,8 @@ struct MBC3 {
// RTC Data Latch Previous Write // RTC Data Latch Previous Write
prev_latch_write: Option<u8>, prev_latch_write: Option<u8>,
has_battery: bool,
} }
impl MBC3 { impl MBC3 {
@@ -267,6 +308,34 @@ impl MBC3 {
devs_enabled: Default::default(), devs_enabled: Default::default(),
mapped: Default::default(), mapped: Default::default(),
prev_latch_write: Default::default(), prev_latch_write: Default::default(),
has_battery: Default::default(),
}
}
fn with_battery(ram_cap: usize) -> Self {
Self {
memory: vec![0; ram_cap],
rom_bank: Default::default(),
ram_bank: Default::default(),
devs_enabled: Default::default(),
mapped: Default::default(),
prev_latch_write: Default::default(),
has_battery: true,
}
}
}
impl Savable for MBC3 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.has_battery {
true => Some(&self.memory),
false => None,
}
}
fn write_ext_ram(&mut self, memory: Vec<u8>) {
if self.has_battery {
self.memory.copy_from_slice(&memory);
} }
} }
} }
@@ -342,9 +411,10 @@ struct MBC5 {
ram_bank: u8, ram_bank: u8,
rom_cap: usize, rom_cap: usize,
memory: Vec<u8>, memory: Vec<u8>,
mem_enabled: bool, mem_enabled: bool,
has_battery: bool,
} }
impl MBC5 { impl MBC5 {
@@ -355,6 +425,18 @@ impl MBC5 {
rom_cap, rom_cap,
ram_bank: Default::default(), ram_bank: Default::default(),
mem_enabled: Default::default(), mem_enabled: Default::default(),
has_battery: Default::default(),
}
}
fn with_battery(ram_cap: usize, rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_cap],
rom_cap,
ram_bank: Default::default(),
mem_enabled: Default::default(),
has_battery: true,
} }
} }
@@ -363,6 +445,21 @@ impl MBC5 {
} }
} }
impl Savable for MBC5 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.has_battery {
true => Some(&self.memory),
false => None,
}
}
fn write_ext_ram(&mut self, memory: Vec<u8>) {
if self.has_battery {
self.memory.copy_from_slice(&memory);
}
}
}
impl MBCIo for MBC5 { impl MBCIo for MBC5 {
fn handle_read(&self, addr: u16) -> MBCResult { fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*; use MBCResult::*;
@@ -401,6 +498,7 @@ struct MBC2 {
mem_enabled: bool, mem_enabled: bool,
rom_cap: usize, rom_cap: usize,
has_battery: bool,
} }
impl MBC2 { impl MBC2 {
@@ -410,8 +508,19 @@ impl MBC2 {
Self { Self {
rom_bank: 0x01, rom_bank: 0x01,
memory: Box::new([0; Self::RAM_SIZE]), memory: Box::new([0; Self::RAM_SIZE]),
mem_enabled: Default::default(),
rom_cap, rom_cap,
mem_enabled: Default::default(),
has_battery: Default::default(),
}
}
fn with_battery(rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: Box::new([0; Self::RAM_SIZE]),
rom_cap,
mem_enabled: Default::default(),
has_battery: true,
} }
} }
@@ -420,6 +529,21 @@ impl MBC2 {
} }
} }
impl Savable for MBC2 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.has_battery {
true => Some(self.memory.as_ref()),
false => None,
}
}
fn write_ext_ram(&mut self, memory: Vec<u8>) {
if self.has_battery {
self.memory.copy_from_slice(&memory);
}
}
}
impl MBCIo for MBC2 { impl MBCIo for MBC2 {
fn handle_read(&self, addr: u16) -> MBCResult { fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*; use MBCResult::*;
@@ -457,6 +581,16 @@ impl MBCIo for MBC2 {
#[derive(Debug)] #[derive(Debug)]
struct NoMBC; struct NoMBC;
impl Savable for NoMBC {
fn ext_ram(&self) -> Option<&[u8]> {
None
}
fn write_ext_ram(&mut self, _memory: Vec<u8>) {
// Nothing Happens Here
}
}
impl MBCIo for NoMBC { impl MBCIo for NoMBC {
fn handle_read(&self, addr: u16) -> MBCResult { fn handle_read(&self, addr: u16) -> MBCResult {
MBCResult::Address(addr as usize) MBCResult::Address(addr as usize)
@@ -467,7 +601,7 @@ impl MBCIo for NoMBC {
} }
} }
trait MBCIo { trait MBCIo: Savable {
fn handle_read(&self, addr: u16) -> MBCResult; fn handle_read(&self, addr: u16) -> MBCResult;
fn handle_write(&mut self, addr: u16, byte: u8); fn handle_write(&mut self, addr: u16, byte: u8);
} }
@@ -607,3 +741,8 @@ impl Default for Box<dyn MBCIo> {
Box::new(NoMBC) Box::new(NoMBC)
} }
} }
trait Savable {
fn ext_ram(&self) -> Option<&[u8]>;
fn write_ext_ram(&mut self, memory: Vec<u8>);
}

View File

@@ -15,6 +15,7 @@ pub struct Cpu {
} }
impl Cpu { impl Cpu {
#[allow(dead_code)]
pub(crate) fn without_boot() -> Self { pub(crate) fn without_boot() -> Self {
Self { Self {
reg: Registers { reg: Registers {
@@ -57,10 +58,7 @@ impl Cpu {
} }
pub(crate) fn is_halted(&self) -> bool { pub(crate) fn is_halted(&self) -> bool {
match self.state { matches!(self.state, State::Halt(_))
State::Halt(_) => true,
_ => false,
}
} }
pub(crate) fn halt_kind(&self) -> Option<HaltKind> { pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
@@ -156,10 +154,12 @@ impl BusIo for Cpu {
} }
impl Cpu { impl Cpu {
#[inline]
pub(crate) fn bus(&self) -> &Bus { pub(crate) fn bus(&self) -> &Bus {
&self.bus &self.bus
} }
#[inline]
pub(crate) fn bus_mut(&mut self) -> &mut Bus { pub(crate) fn bus_mut(&mut self) -> &mut Bus {
&mut self.bus &mut self.bus
} }

View File

@@ -2,7 +2,11 @@ use crate::apu::gen::SampleProducer;
use crate::cpu::Cpu; use crate::cpu::Cpu;
use crate::joypad::{self, Joypad}; use crate::joypad::{self, Joypad};
use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
use clap::crate_name;
use gilrs::Gilrs; use gilrs::Gilrs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::time::Duration; use std::time::Duration;
use winit_input_helper::WinitInputHelper; use winit_input_helper::WinitInputHelper;
@@ -17,8 +21,8 @@ pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper
if let Some(event) = gamepad.next_event() { if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(emu.joyp_mut(), event); joypad::handle_gamepad_input(emu.joyp_mut(), event);
} }
joypad::handle_keyboard_input(emu.joyp_mut(), key); joypad::handle_keyboard_input(emu.joyp_mut(), key);
while elapsed < CYCLES_IN_FRAME { while elapsed < CYCLES_IN_FRAME {
elapsed += emu.step(); elapsed += emu.step();
} }
@@ -44,15 +48,18 @@ impl Emulator {
} }
fn step(&mut self) -> Cycle { fn step(&mut self) -> Cycle {
self.cpu.step() let cycles = self.cpu.step();
self.timestamp += cycles;
cycles
} }
fn load_cart(&mut self, rom: Vec<u8>) { fn load_cart(&mut self, rom: Vec<u8>) {
self.cpu.bus_mut().load_cart(rom) self.cpu.bus_mut().load_cart(rom);
} }
#[inline]
fn joyp_mut(&mut self) -> &mut Joypad { fn joyp_mut(&mut self) -> &mut Joypad {
&mut self.cpu.bus_mut().joypad self.cpu.bus_mut().joyp_mut()
} }
pub fn set_prod(&mut self, prod: SampleProducer<f32>) { pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
@@ -62,6 +69,51 @@ impl Emulator {
pub fn title(&self) -> &str { pub fn title(&self) -> &str {
self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
} }
pub fn try_write_sav(&self) -> std::io::Result<()> {
if let Some(ext_ram) = self.cpu.bus().cart().map(|c| c.ext_ram()).flatten() {
if let Some(title) = self.cpu.bus().cart_title() {
let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from("."));
save_path.push(title);
save_path.set_extension("sav");
let mut file = File::create(save_path)?;
file.write_all(ext_ram)?;
}
}
Ok(())
}
pub fn try_load_sav(&mut self) -> std::io::Result<()> {
if let Some(cart) = self.cpu.bus_mut().cart_mut() {
if let Some(title) = cart.title() {
let mut save_path = Self::data_path().unwrap_or_else(|| PathBuf::from("."));
save_path.push(title);
save_path.set_extension("sav");
if let Ok(mut file) = File::open(save_path) {
let mut memory = Vec::new();
file.read_to_end(&mut memory)?;
cart.write_ext_ram(memory);
}
}
}
Ok(())
}
fn data_path() -> Option<PathBuf> {
match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
Some(dirs) => {
let data_local = dirs.data_local_dir();
std::fs::create_dir_all(data_local).ok()?;
Some(data_local.to_path_buf())
}
None => None,
}
}
} }
pub mod build { pub mod build {
@@ -108,7 +160,7 @@ pub mod build {
pub fn finish(mut self) -> Emulator { pub fn finish(mut self) -> Emulator {
let mut emu = Emulator::new(match self.boot { let mut emu = Emulator::new(match self.boot {
Some(rom) => Cpu::with_boot(rom), Some(rom) => Cpu::with_boot(rom),
None => Cpu::without_boot(), None => Cpu::with_boot(*include_bytes!("../bin/bootix_dmg.bin")),
}); });
if let Some(rom) = self.cart.take() { if let Some(rom) = self.cart.take() {

View File

@@ -110,6 +110,7 @@ impl ButtonEvent {
} }
} }
#[inline]
pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
use winit::event::VirtualKeyCode; use winit::event::VirtualKeyCode;
@@ -146,17 +147,17 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
state.dpad_right.update(false, irq); state.dpad_right.update(false, irq);
} }
if input.key_pressed(VirtualKeyCode::T) { if input.key_pressed(VirtualKeyCode::Return) {
state.start.update(true, irq); state.start.update(true, irq);
} }
if input.key_released(VirtualKeyCode::T) { if input.key_released(VirtualKeyCode::Return) {
state.start.update(false, irq); state.start.update(false, irq);
} }
if input.key_pressed(VirtualKeyCode::Y) { if input.key_pressed(VirtualKeyCode::RShift) {
state.select.update(true, irq); state.select.update(true, irq);
} }
if input.key_released(VirtualKeyCode::Y) { if input.key_released(VirtualKeyCode::RShift) {
state.select.update(false, irq); state.select.update(false, irq);
} }
@@ -175,6 +176,7 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
} }
} }
#[inline]
pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) { pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
use Button::*; use Button::*;
use GamepadEventType::*; use GamepadEventType::*;

View File

@@ -14,7 +14,7 @@ use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper; use winit_input_helper::WinitInputHelper;
const WINDOW_SCALE: usize = 3; const WINDOW_SCALE: usize = 3;
const AUDIO_ENABLED: bool = false; const AUDIO_ENABLED: bool = true;
fn main() -> Result<()> { fn main() -> Result<()> {
let app = App::new(crate_name!()) let app = App::new(crate_name!())
@@ -49,6 +49,9 @@ fn main() -> Result<()> {
} }
let mut emu = emu_build.finish(); let mut emu = emu_build.finish();
// Load Save file if it exists
emu.try_load_sav().expect("Load save if exists");
let rom_title = emu.title(); let rom_title = emu.title();
let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
@@ -96,6 +99,8 @@ fn main() -> Result<()> {
.map_err(|e| anyhow!("pixels.render() failed: {}", e)) .map_err(|e| anyhow!("pixels.render() failed: {}", e))
.is_err() .is_err()
{ {
emu.try_write_sav().expect("Write game save if need be");
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }
@@ -103,6 +108,8 @@ fn main() -> Result<()> {
if input.update(&event) { if input.update(&event) {
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() { if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
emu.try_write_sav().expect("Write game save if need be");
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
return; return;
} }

View File

@@ -261,7 +261,7 @@ impl Ppu {
TileLow => { TileLow => {
let obj_size = self.ctrl.obj_size(); let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); let addr = PixelFetcher::get_obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr); let byte = self.read_byte(addr);
self.fetch.obj.tile.with_low_byte(byte); self.fetch.obj.tile.with_low_byte(byte);
@@ -272,7 +272,7 @@ impl Ppu {
TileHigh => { TileHigh => {
let obj_size = self.ctrl.obj_size(); let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); let addr = PixelFetcher::get_obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr + 1); let byte = self.read_byte(addr + 1);
self.fetch.obj.tile.with_high_byte(byte); self.fetch.obj.tile.with_high_byte(byte);
@@ -418,6 +418,7 @@ impl Ppu {
self.frame_buf.swap_with_slice(&mut blank); self.frame_buf.swap_with_slice(&mut blank);
} }
#[inline]
pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
&self.frame_buf &self.frame_buf
} }
@@ -629,6 +630,7 @@ impl ObjectBuffer {
self.len += 1; self.len += 1;
} }
#[inline]
fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> { fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> {
self.inner.iter_mut() self.inner.iter_mut()
} }
@@ -699,7 +701,7 @@ impl PixelFetcher {
let id = self.back.tile.id.expect("Tile Number is present"); let id = self.back.tile.id.expect("Tile Number is present");
let tile_data_addr = match control.tile_data_addr() { let tile_data_addr = match control.tile_data_addr() {
TileDataAddress::X8800 => 0x9000u16.wrapping_add(((id as i8) as i16 * 16) as u16), TileDataAddress::X8800 => 0x9000u16.wrapping_add((id as i8 as i16 * 16) as u16),
TileDataAddress::X8000 => 0x8000 + (id as u16 * 16), TileDataAddress::X8000 => 0x8000 + (id as u16 * 16),
}; };

View File

@@ -21,17 +21,6 @@ impl Timer {
use State::*; use State::*;
use TimerSpeed::*; use TimerSpeed::*;
match self.state {
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
LoadTMA => {
self.counter = self.modulo;
self.interrupt = true;
self.next();
}
Normal => {}
}
self.divider = self.divider.wrapping_add(1); self.divider = self.divider.wrapping_add(1);
// Get Bit Position // Get Bit Position
@@ -53,6 +42,17 @@ impl Timer {
} }
self.and_result = Some(new_result); self.and_result = Some(new_result);
match self.state {
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
LoadTIMA => {
self.counter = self.modulo;
self.interrupt = true;
self.next();
}
Normal => {}
}
} }
/// 0xFF05 | TIMA - Timer Counter /// 0xFF05 | TIMA - Timer Counter
@@ -70,7 +70,7 @@ impl Timer {
self.counter = byte; self.counter = byte;
self.state = AbortedTIMAOverflow(step); self.state = AbortedTIMAOverflow(step);
} }
LoadTMA => {} LoadTIMA => { /* Ignored */ }
} }
} }
@@ -95,9 +95,8 @@ impl Timer {
use State::*; use State::*;
self.state = match self.state { self.state = match self.state {
Normal | LoadTMA => Normal, Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal,
AbortedTIMAOverflow(4) => Normal, TIMAOverflow(3) => LoadTIMA,
TIMAOverflow(4) => LoadTMA,
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1), AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
TIMAOverflow(step) => TIMAOverflow(step + 1), TIMAOverflow(step) => TIMAOverflow(step + 1),
} }
@@ -181,5 +180,5 @@ enum State {
TIMAOverflow(u8), TIMAOverflow(u8),
AbortedTIMAOverflow(u8), AbortedTIMAOverflow(u8),
Normal, Normal,
LoadTMA, LoadTIMA,
} }