Compare commits
No commits in common. "a0e3c7c6023f27df0d56ee02e0685803df086d2e" and "dda02576556813f053454427172fd6124a60ba2f" have entirely different histories.
a0e3c7c602
...
dda0257655
16
src/apu.rs
16
src/apu.rs
|
@ -211,8 +211,8 @@ impl Apu {
|
||||||
self.ch4.poly = Default::default();
|
self.ch4.poly = Default::default();
|
||||||
self.ch4.freq = Default::default();
|
self.ch4.freq = Default::default();
|
||||||
|
|
||||||
self.ctrl.channel = ChannelControl(0);
|
self.ctrl.channel = Default::default();
|
||||||
self.ctrl.out = SoundOutput(0);
|
self.ctrl.out = Default::default();
|
||||||
|
|
||||||
// Disable the Channels
|
// Disable the Channels
|
||||||
self.ch1.enabled = Default::default();
|
self.ch1.enabled = Default::default();
|
||||||
|
@ -344,7 +344,7 @@ impl Apu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct SoundControl {
|
pub(crate) struct SoundControl {
|
||||||
/// 0xFF24 | NR50 - Channel Control
|
/// 0xFF24 | NR50 - Channel Control
|
||||||
channel: ChannelControl,
|
channel: ChannelControl,
|
||||||
|
@ -354,16 +354,6 @@ pub(crate) struct SoundControl {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SoundControl {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
channel: ChannelControl(0),
|
|
||||||
out: SoundOutput(0),
|
|
||||||
enabled: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundControl {
|
impl SoundControl {
|
||||||
/// 0xFF24 | NR50 - Channel Control
|
/// 0xFF24 | NR50 - Channel Control
|
||||||
pub(crate) fn channel(&self) -> u8 {
|
pub(crate) fn channel(&self) -> u8 {
|
||||||
|
|
|
@ -416,6 +416,12 @@ pub(super) mod common {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for WavePattern {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::OneEighth // Rationale: OneEighth is 0x00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<WavePattern> for u8 {
|
impl From<WavePattern> for u8 {
|
||||||
fn from(pattern: WavePattern) -> Self {
|
fn from(pattern: WavePattern) -> Self {
|
||||||
pattern as Self
|
pattern as Self
|
||||||
|
@ -473,6 +479,12 @@ impl Clone for SoundOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SoundOutput {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for SoundOutput {
|
impl From<u8> for SoundOutput {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -511,6 +523,12 @@ impl Clone for ChannelControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ChannelControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ChannelControl {
|
impl From<u8> for ChannelControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
|
364
src/cartridge.rs
364
src/cartridge.rs
|
@ -6,66 +6,66 @@ use crate::Cycle;
|
||||||
|
|
||||||
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
||||||
const ROM_SIZE_ADDRESS: usize = 0x0148;
|
const ROM_SIZE_ADDRESS: usize = 0x0148;
|
||||||
const MBC_KIND_ADDRESS: usize = 0x0147;
|
const MBC_TYPE_ADDRESS: usize = 0x0147;
|
||||||
const ROM_TITLE_START: usize = 0x134;
|
const ROM_TITLE_START: usize = 0x134;
|
||||||
const ROM_TITLE_MAX_SIZE: usize = 16;
|
const ROM_TITLE_MAX_SIZE: usize = 16;
|
||||||
const ROM_MANUFACTURER_START: usize = 0x13F;
|
const ROM_MANUFACTURER_START: usize = 0x13F;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Cartridge {
|
pub(crate) struct Cartridge {
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
pub(crate) title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
mbc: Box<dyn MBCIo>,
|
mbc: Box<dyn MBCIo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub(crate) fn new(mem: Vec<u8>) -> Self {
|
pub(crate) fn new(memory: Vec<u8>) -> Self {
|
||||||
let title_mem: &[u8; 16] = mem[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)]
|
let title_mem: &[u8; 16] = memory[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)]
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("coerce slice containing cartridge title from ROM to [u8; 16]");
|
.expect("coerce slice containing cartridge title from ROM to [u8; 16]");
|
||||||
|
|
||||||
let title = Self::detect_title(title_mem);
|
let title = Self::detect_title(title_mem);
|
||||||
let mbc = Self::detect_mbc(&mem);
|
|
||||||
tracing::info!("Title: {:?}", title);
|
tracing::info!("Title: {:?}", title);
|
||||||
|
|
||||||
Self { mem, title, mbc }
|
Self {
|
||||||
|
mbc: Self::detect_mbc(&memory),
|
||||||
|
title,
|
||||||
|
memory,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ext_ram(&self) -> Option<&[u8]> {
|
pub(crate) fn ext_ram(&self) -> Option<&[u8]> {
|
||||||
self.mbc.ext_ram()
|
self.mbc.ext_ram()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
pub(crate) fn write_ext_ram(&mut self, memory: Vec<u8>) {
|
||||||
self.mbc.ext_ram_mut()
|
self.mbc.write_ext_ram(memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn tick(&mut self) {
|
pub(crate) fn tick(&mut self) {
|
||||||
self.mbc.tick()
|
self.mbc.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_mbc(mem: &[u8]) -> Box<dyn MBCIo> {
|
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
|
||||||
let ram_size: RamSize = mem[RAM_SIZE_ADDRESS].into();
|
let ram_size = Self::detect_ram_info(memory);
|
||||||
let rom_size: RomSize = mem[ROM_SIZE_ADDRESS].into();
|
let rom_size = Self::detect_rom_info(memory);
|
||||||
let mbc_kind = Self::detect_mbc_kind(mem[MBC_KIND_ADDRESS]);
|
let mbc_kind = Self::find_mbc(memory);
|
||||||
let ram_cap = ram_size.capacity();
|
let ram_cap = ram_size.capacity();
|
||||||
let rom_cap = rom_size.capacity();
|
let rom_cap = rom_size.capacity();
|
||||||
|
|
||||||
tracing::info!("RAM size: {} bytes", ram_cap);
|
tracing::info!("RAM size: {} bytes", ram_cap);
|
||||||
tracing::info!("ROM size: {} bytes", rom_cap);
|
tracing::info!("ROM size: {} bytes", rom_size.capacity());
|
||||||
tracing::info!("MBC kind: {:?}", mbc_kind);
|
tracing::info!("MBC kind: {:?}", mbc_kind);
|
||||||
|
|
||||||
match mbc_kind {
|
match mbc_kind {
|
||||||
MBCKind::None => Box::new(NoMBC),
|
MBCKind::None => Box::new(NoMBC),
|
||||||
MBCKind::MBC1(hw) => Box::new(MBC1::new(hw, ram_size, rom_size)),
|
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
|
||||||
MBCKind::MBC2(hw) => Box::new(MBC2::new(hw, rom_cap)),
|
MBCKind::MBC1WithBattery => Box::new(MBC1::with_battery(ram_size, rom_size)),
|
||||||
MBCKind::MBC3(hw @ MBC3Hardware::RTC) => Box::new(MBC3::new(hw, ram_cap)),
|
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
|
||||||
MBCKind::MBC3(hw @ MBC3Hardware::RTCAndBatteryRAM) => Box::new(MBC3::new(hw, ram_cap)),
|
MBCKind::MBC2WithBattery => Box::new(MBC2::with_battery(rom_cap)),
|
||||||
MBCKind::MBC5(hw @ MBC5Hardware::None) => Box::new(MBC5::new(hw, ram_cap, rom_cap)),
|
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
|
||||||
MBCKind::MBC5(hw @ MBC5Hardware::BatteryRAM) => {
|
MBCKind::MBC3WithBattery => Box::new(MBC3::with_battery(ram_cap)),
|
||||||
Box::new(MBC5::new(hw, ram_cap, rom_cap))
|
MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
|
||||||
}
|
MBCKind::MBC5WithBattery => Box::new(MBC5::with_battery(ram_cap, rom_cap)),
|
||||||
kind => todo!("ROMS with {:?} are currently unsupported", kind),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,30 +88,30 @@ impl Cartridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_mbc_kind(id: u8) -> MBCKind {
|
fn detect_ram_info(memory: &[u8]) -> RamSize {
|
||||||
|
let id = memory[RAM_SIZE_ADDRESS];
|
||||||
|
id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_rom_info(memory: &[u8]) -> RomSize {
|
||||||
|
let id = memory[ROM_SIZE_ADDRESS];
|
||||||
|
id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_mbc(memory: &[u8]) -> MBCKind {
|
||||||
use MBCKind::*;
|
use MBCKind::*;
|
||||||
|
|
||||||
match id {
|
match memory[MBC_TYPE_ADDRESS] {
|
||||||
0x00 => None,
|
0x00 => None,
|
||||||
0x01 => MBC1(MBC1Hardware::None),
|
0x01 | 0x02 => MBC1,
|
||||||
0x02 => MBC1(MBC1Hardware::RAM),
|
0x03 => MBC1WithBattery,
|
||||||
0x03 => MBC1(MBC1Hardware::BatteryRAM),
|
0x05 => MBC2,
|
||||||
0x05 => MBC2(MBC2Hardware::None),
|
0x06 => MBC2WithBattery,
|
||||||
0x06 => MBC2(MBC2Hardware::BatteryRAM),
|
0x19 | 0x1A => MBC5,
|
||||||
0x08 | 0x09 => unimplemented!("NoMBC + RAM and NoMBC + Battery unsupported"),
|
0x1B => MBC5WithBattery,
|
||||||
0x0B | 0x0C | 0x0D => unimplemented!("MM01 unsupported"),
|
0x13 => MBC3WithBattery,
|
||||||
0x0F => MBC3(MBC3Hardware::RTC),
|
0x11 | 0x12 => MBC3,
|
||||||
0x10 => MBC3(MBC3Hardware::RTCAndBatteryRAM),
|
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
|
||||||
0x11 => MBC3(MBC3Hardware::None),
|
|
||||||
0x12 => MBC3(MBC3Hardware::RAM),
|
|
||||||
0x13 => MBC3(MBC3Hardware::BatteryRAM),
|
|
||||||
0x19 => MBC5(MBC5Hardware::None),
|
|
||||||
0x1A => MBC5(MBC5Hardware::RAM),
|
|
||||||
0x1B => MBC5(MBC5Hardware::BatteryRAM),
|
|
||||||
0x1C => MBC5(MBC5Hardware::Rumble),
|
|
||||||
0x1D => MBC5(MBC5Hardware::RumbleRAM),
|
|
||||||
0x1E => MBC5(MBC5Hardware::RumbleBatteryRAM),
|
|
||||||
id => unimplemented!("MBC with code {:#04X} is unsupported", id),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,8 @@ impl BusIo for Cartridge {
|
||||||
use MBCResult::*;
|
use MBCResult::*;
|
||||||
|
|
||||||
match self.mbc.handle_read(addr) {
|
match self.mbc.handle_read(addr) {
|
||||||
Addr(addr) => self.mem[addr],
|
Address(addr) => self.memory[addr],
|
||||||
Byte(byte) => byte,
|
Value(byte) => byte,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,24 +139,37 @@ struct MBC1 {
|
||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
mode: bool,
|
mode: bool,
|
||||||
ram_size: RamSize,
|
ram_size: RamSize,
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
rom_size: RomSize,
|
rom_size: RomSize,
|
||||||
mem_enabled: bool,
|
mem_enabled: bool,
|
||||||
|
|
||||||
hw: MBC1Hardware,
|
has_battery: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MBC1 {
|
impl MBC1 {
|
||||||
fn new(hw: MBC1Hardware, ram_size: RamSize, rom_size: RomSize) -> Self {
|
fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: vec![0; ram_size.capacity() as usize],
|
memory: vec![0; ram_size.capacity() as usize],
|
||||||
ram_size,
|
ram_size,
|
||||||
rom_size,
|
rom_size,
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,16 +240,15 @@ impl MBC1 {
|
||||||
|
|
||||||
impl Savable for MBC1 {
|
impl Savable for MBC1 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn ext_ram(&self) -> Option<&[u8]> {
|
||||||
match self.hw {
|
match self.has_battery {
|
||||||
MBC1Hardware::BatteryRAM => Some(&self.mem),
|
true => Some(&self.memory),
|
||||||
_ => None,
|
false => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn write_ext_ram(&mut self, memory: Vec<u8>) {
|
||||||
match self.hw {
|
if self.has_battery {
|
||||||
MBC1Hardware::BatteryRAM => Some(&mut self.mem),
|
self.memory.copy_from_slice(&memory);
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,12 +259,16 @@ impl MBCIo for MBC1 {
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF if self.mode => {
|
0x0000..=0x3FFF if self.mode => {
|
||||||
Addr(0x4000 * self.zero_bank() as usize + addr as usize)
|
Address(0x4000 * self.zero_bank() as usize + addr as usize)
|
||||||
}
|
}
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)),
|
0x4000..=0x7FFF => {
|
||||||
0xA000..=0xBFFF if self.mem_enabled => Byte(self.mem[self.ram_addr(addr)]),
|
Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
|
||||||
0xA000..=0xBFFF => Byte(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),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,9 +283,9 @@ impl MBCIo for MBC1 {
|
||||||
}
|
}
|
||||||
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
|
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
|
||||||
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
|
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
|
||||||
let ram_addr = self.ram_addr(addr);
|
let ram_addr = self.ram_addr(addr);
|
||||||
self.mem[ram_addr] = byte;
|
self.memory[ram_addr] = byte;
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
|
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
|
||||||
|
@ -297,7 +313,7 @@ struct RtClock {
|
||||||
|
|
||||||
impl RtClock {
|
impl RtClock {
|
||||||
fn inc_day(&mut self) {
|
fn inc_day(&mut self) {
|
||||||
// TODO: Figure out order of operations, the brackets are a bit too defensive here
|
// TODO: Figure out order of operations, the brackets are a bit too defenseive here
|
||||||
let days: u16 = (((self.day_high.ninth() as u16) << 8) | self.day_low as u16) + 1;
|
let days: u16 = (((self.day_high.ninth() as u16) << 8) | self.day_low as u16) + 1;
|
||||||
|
|
||||||
if days > 0x1FF {
|
if days > 0x1FF {
|
||||||
|
@ -402,20 +418,34 @@ struct MBC3 {
|
||||||
|
|
||||||
devs_enabled: bool,
|
devs_enabled: bool,
|
||||||
mapped: Option<MBC3Device>,
|
mapped: Option<MBC3Device>,
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
|
|
||||||
// RTC Data Latch Previous Write
|
// RTC Data Latch Previous Write
|
||||||
prev_latch_write: Option<u8>,
|
prev_latch_write: Option<u8>,
|
||||||
|
|
||||||
hw: MBC3Hardware,
|
has_battery: bool,
|
||||||
rtc: RtClock,
|
rtc: RtClock,
|
||||||
rtc_latch: Option<RtClock>,
|
rtc_latch: Option<RtClock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MBC3 {
|
impl MBC3 {
|
||||||
fn new(hw: MBC3Hardware, ram_cap: usize) -> Self {
|
fn new(ram_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mem: vec![0; ram_cap],
|
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: Default::default(),
|
||||||
|
rtc: Default::default(),
|
||||||
|
rtc_latch: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_battery(ram_cap: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
memory: vec![0; ram_cap],
|
||||||
rom_bank: Default::default(),
|
rom_bank: Default::default(),
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
devs_enabled: Default::default(),
|
devs_enabled: Default::default(),
|
||||||
|
@ -423,23 +453,22 @@ impl MBC3 {
|
||||||
prev_latch_write: Default::default(),
|
prev_latch_write: Default::default(),
|
||||||
rtc: Default::default(),
|
rtc: Default::default(),
|
||||||
rtc_latch: Default::default(),
|
rtc_latch: Default::default(),
|
||||||
hw,
|
has_battery: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Savable for MBC3 {
|
impl Savable for MBC3 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn ext_ram(&self) -> Option<&[u8]> {
|
||||||
match self.hw {
|
match self.has_battery {
|
||||||
MBC3Hardware::BatteryRAM | MBC3Hardware::RTCAndBatteryRAM => Some(&self.mem),
|
true => Some(&self.memory),
|
||||||
_ => None,
|
false => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn write_ext_ram(&mut self, memory: Vec<u8>) {
|
||||||
match self.hw {
|
if self.has_battery {
|
||||||
MBC3Hardware::BatteryRAM | MBC3Hardware::RTCAndBatteryRAM => Some(&mut self.mem),
|
self.memory.copy_from_slice(&memory);
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,13 +479,13 @@ impl MBCIo for MBC3 {
|
||||||
use RtcRegister::*;
|
use RtcRegister::*;
|
||||||
|
|
||||||
let res = match addr {
|
let res = match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
|
0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
|
||||||
0xA000..=0xBFFF => match self.mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
||||||
}
|
}
|
||||||
Some(MBC3Device::Clock(reg)) if self.devs_enabled => Byte(
|
Some(MBC3Device::Clock(reg)) if self.devs_enabled => Value(
|
||||||
self.rtc_latch
|
self.rtc_latch
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|rtc| match reg {
|
.map(|rtc| match reg {
|
||||||
|
@ -468,7 +497,7 @@ impl MBCIo for MBC3 {
|
||||||
})
|
})
|
||||||
.unwrap_or(0xFF),
|
.unwrap_or(0xFF),
|
||||||
),
|
),
|
||||||
_ => Byte(0xFF),
|
_ => Value(0xFF),
|
||||||
},
|
},
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr),
|
||||||
};
|
};
|
||||||
|
@ -510,7 +539,7 @@ impl MBCIo for MBC3 {
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => match self.mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
|
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
|
||||||
}
|
}
|
||||||
Some(MBC3Device::Clock(rtc_reg)) if self.devs_enabled => match rtc_reg {
|
Some(MBC3Device::Clock(rtc_reg)) if self.devs_enabled => match rtc_reg {
|
||||||
Second => {
|
Second => {
|
||||||
|
@ -532,9 +561,7 @@ impl MBCIo for MBC3 {
|
||||||
|
|
||||||
impl RtClockTick for MBC3 {
|
impl RtClockTick for MBC3 {
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self) {
|
||||||
if let MBC3Hardware::RTCAndBatteryRAM | MBC3Hardware::RTC = self.hw {
|
self.rtc.tick();
|
||||||
self.rtc.tick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -546,21 +573,32 @@ struct MBC5 {
|
||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
|
|
||||||
rom_cap: usize,
|
rom_cap: usize,
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
mem_enabled: bool,
|
mem_enabled: bool,
|
||||||
|
|
||||||
hw: MBC5Hardware,
|
has_battery: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MBC5 {
|
impl MBC5 {
|
||||||
fn new(hw: MBC5Hardware, ram_cap: usize, rom_cap: usize) -> Self {
|
fn new(ram_cap: usize, rom_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: vec![0; ram_cap],
|
memory: vec![0; ram_cap],
|
||||||
rom_cap,
|
rom_cap,
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,16 +609,15 @@ impl MBC5 {
|
||||||
|
|
||||||
impl Savable for MBC5 {
|
impl Savable for MBC5 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn ext_ram(&self) -> Option<&[u8]> {
|
||||||
match self.hw {
|
match self.has_battery {
|
||||||
MBC5Hardware::RumbleBatteryRAM | MBC5Hardware::BatteryRAM => Some(&self.mem),
|
true => Some(&self.memory),
|
||||||
_ => None,
|
false => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn write_ext_ram(&mut self, memory: Vec<u8>) {
|
||||||
match self.hw {
|
if self.has_battery {
|
||||||
MBC5Hardware::RumbleBatteryRAM | MBC5Hardware::BatteryRAM => Some(&mut self.mem),
|
self.memory.copy_from_slice(&memory);
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,12 +627,12 @@ impl MBCIo for MBC5 {
|
||||||
use MBCResult::*;
|
use MBCResult::*;
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(self.bank_addr(addr)),
|
0x4000..=0x7FFF => Address(self.bank_addr(addr)),
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => Byte(0xFF),
|
0xA000..=0xBFFF => Value(0xFF),
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -607,7 +644,7 @@ impl MBCIo for MBC5 {
|
||||||
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
|
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
|
||||||
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
|
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
|
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {}
|
0xA000..=0xBFFF => {}
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
|
||||||
|
@ -623,23 +660,33 @@ impl RtClockTick for MBC5 {
|
||||||
struct MBC2 {
|
struct MBC2 {
|
||||||
/// 4-bit number
|
/// 4-bit number
|
||||||
rom_bank: u8,
|
rom_bank: u8,
|
||||||
mem: Box<[u8; Self::RAM_SIZE]>,
|
memory: Box<[u8; Self::RAM_SIZE]>,
|
||||||
|
|
||||||
mem_enabled: bool,
|
mem_enabled: bool,
|
||||||
|
|
||||||
rom_cap: usize,
|
rom_cap: usize,
|
||||||
hw: MBC2Hardware,
|
has_battery: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MBC2 {
|
impl MBC2 {
|
||||||
const RAM_SIZE: usize = 0x0200;
|
const RAM_SIZE: usize = 0x0200;
|
||||||
|
|
||||||
fn new(hw: MBC2Hardware, rom_cap: usize) -> Self {
|
fn new(rom_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: Box::new([0; Self::RAM_SIZE]),
|
memory: Box::new([0; Self::RAM_SIZE]),
|
||||||
rom_cap,
|
rom_cap,
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,16 +697,15 @@ impl MBC2 {
|
||||||
|
|
||||||
impl Savable for MBC2 {
|
impl Savable for MBC2 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn ext_ram(&self) -> Option<&[u8]> {
|
||||||
match self.hw {
|
match self.has_battery {
|
||||||
MBC2Hardware::BatteryRAM => Some(self.mem.as_ref()),
|
true => Some(self.memory.as_ref()),
|
||||||
MBC2Hardware::None => None,
|
false => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn write_ext_ram(&mut self, memory: Vec<u8>) {
|
||||||
match self.hw {
|
if self.has_battery {
|
||||||
MBC2Hardware::BatteryRAM => Some(self.mem.as_mut()),
|
self.memory.copy_from_slice(&memory);
|
||||||
MBC2Hardware::None => None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -669,13 +715,13 @@ impl MBCIo for MBC2 {
|
||||||
use MBCResult::*;
|
use MBCResult::*;
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(self.rom_addr(addr)),
|
0x4000..=0x7FFF => Address(self.rom_addr(addr)),
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
||||||
Byte(self.mem[mbc2_addr] | 0xF0)
|
Value(self.memory[mbc2_addr] | 0xF0)
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => Byte(0xFF),
|
0xA000..=0xBFFF => Value(0xFF),
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -690,7 +736,7 @@ impl MBCIo for MBC2 {
|
||||||
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
|
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
||||||
self.mem[mbc2_addr] = nybble;
|
self.memory[mbc2_addr] = nybble;
|
||||||
}
|
}
|
||||||
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
|
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
|
||||||
|
@ -710,14 +756,14 @@ impl Savable for NoMBC {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn write_ext_ram(&mut self, _memory: Vec<u8>) {
|
||||||
None
|
// 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::Addr(addr as usize)
|
MBCResult::Address(addr as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_write(&mut self, _: u16, byte: u8) {
|
fn handle_write(&mut self, _: u16, byte: u8) {
|
||||||
|
@ -736,49 +782,27 @@ trait MBCIo: Savable + RtClockTick {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum MBCResult {
|
enum MBCResult {
|
||||||
Addr(usize),
|
Address(usize),
|
||||||
Byte(u8),
|
Value(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum MBCKind {
|
enum MBCKind {
|
||||||
None,
|
None,
|
||||||
MBC1(MBC1Hardware),
|
MBC1,
|
||||||
MBC2(MBC2Hardware),
|
MBC1WithBattery,
|
||||||
MBC3(MBC3Hardware),
|
MBC2,
|
||||||
MBC5(MBC5Hardware),
|
MBC2WithBattery,
|
||||||
|
MBC3,
|
||||||
|
MBC3WithBattery,
|
||||||
|
MBC5,
|
||||||
|
MBC5WithBattery,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
impl Default for MBCKind {
|
||||||
enum MBC1Hardware {
|
fn default() -> Self {
|
||||||
None,
|
Self::None
|
||||||
RAM,
|
}
|
||||||
BatteryRAM,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum MBC2Hardware {
|
|
||||||
None,
|
|
||||||
BatteryRAM,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum MBC3Hardware {
|
|
||||||
RTC,
|
|
||||||
RTCAndBatteryRAM,
|
|
||||||
None,
|
|
||||||
RAM,
|
|
||||||
BatteryRAM,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum MBC5Hardware {
|
|
||||||
None,
|
|
||||||
RAM,
|
|
||||||
BatteryRAM,
|
|
||||||
Rumble,
|
|
||||||
RumbleRAM,
|
|
||||||
RumbleBatteryRAM,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
@ -806,6 +830,12 @@ impl RamSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RamSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for RamSize {
|
impl From<u8> for RamSize {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
use RamSize::*;
|
use RamSize::*;
|
||||||
|
@ -880,9 +910,15 @@ impl std::fmt::Debug for Box<dyn MBCIo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Box<dyn MBCIo> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(NoMBC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait Savable {
|
trait Savable {
|
||||||
fn ext_ram(&self) -> Option<&[u8]>;
|
fn ext_ram(&self) -> Option<&[u8]>;
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]>;
|
fn write_ext_ram(&mut self, memory: Vec<u8>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
24
src/cpu.rs
24
src/cpu.rs
|
@ -19,9 +19,9 @@ impl Cpu {
|
||||||
Self {
|
Self {
|
||||||
bus: Bus::with_boot(rom),
|
bus: Bus::with_boot(rom),
|
||||||
reg: Default::default(),
|
reg: Default::default(),
|
||||||
flags: Flags(0),
|
flags: Default::default(),
|
||||||
ime: ImeState::Disabled,
|
ime: Default::default(),
|
||||||
state: State::Execute,
|
state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +238,12 @@ enum State {
|
||||||
// Stop,
|
// Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Execute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub(crate) fn set_register(&mut self, register: Register, value: u8) {
|
pub(crate) fn set_register(&mut self, register: Register, value: u8) {
|
||||||
use Register::*;
|
use Register::*;
|
||||||
|
@ -431,6 +437,12 @@ impl Clone for Flags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Flags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Flags {
|
impl Display for Flags {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
if self.z() {
|
if self.z() {
|
||||||
|
@ -485,3 +497,9 @@ pub(crate) enum ImeState {
|
||||||
Pending,
|
Pending,
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ImeState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
19
src/emu.rs
19
src/emu.rs
|
@ -70,7 +70,6 @@ pub fn write_save(cpu: &Cpu) {
|
||||||
Some(cart) => match write_save_to_file(cart) {
|
Some(cart) => match write_save_to_file(cart) {
|
||||||
Ok(path) => tracing::info!("Wrote to save at {:?}", path),
|
Ok(path) => tracing::info!("Wrote to save at {:?}", path),
|
||||||
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err),
|
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err),
|
||||||
Err(SaveError::DiffSize) => unreachable!(),
|
|
||||||
Err(SaveError::Io(err)) => tracing::error!("{:?}", err),
|
Err(SaveError::Io(err)) => tracing::error!("{:?}", err),
|
||||||
},
|
},
|
||||||
None => tracing::error!("No cartridge is currently present"),
|
None => tracing::error!("No cartridge is currently present"),
|
||||||
|
@ -81,8 +80,7 @@ pub fn load_save(cpu: &mut Cpu) {
|
||||||
match cpu.bus.cart.as_mut() {
|
match cpu.bus.cart.as_mut() {
|
||||||
Some(cart) => match read_save_from_file(cart) {
|
Some(cart) => match read_save_from_file(cart) {
|
||||||
Ok(path) => tracing::info!("Loaded save from {:?}", path),
|
Ok(path) => tracing::info!("Loaded save from {:?}", path),
|
||||||
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {}", err),
|
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {:?}", err),
|
||||||
Err(err @ SaveError::DiffSize) => tracing::error!("Unable to load save: {}", err),
|
|
||||||
Err(SaveError::Io(err)) => match err.kind() {
|
Err(SaveError::Io(err)) => match err.kind() {
|
||||||
std::io::ErrorKind::NotFound => tracing::warn!("Save not found"),
|
std::io::ErrorKind::NotFound => tracing::warn!("Save not found"),
|
||||||
_ => tracing::error!("{:?}", err),
|
_ => tracing::error!("{:?}", err),
|
||||||
|
@ -108,8 +106,8 @@ fn write_save_to_file(cart: &Cartridge) -> Result<PathBuf, SaveError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
|
fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
|
||||||
match cart.title.clone().zip(cart.ext_ram_mut()) {
|
match cart.title.as_deref() {
|
||||||
Some((title, ext_ram)) => {
|
Some(title) => {
|
||||||
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
||||||
save_path.push(title);
|
save_path.push(title);
|
||||||
save_path.set_extension("sav");
|
save_path.set_extension("sav");
|
||||||
|
@ -118,11 +116,10 @@ fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
|
||||||
let mut memory = Vec::new();
|
let mut memory = Vec::new();
|
||||||
file.read_to_end(&mut memory)?;
|
file.read_to_end(&mut memory)?;
|
||||||
|
|
||||||
if ext_ram.len() != memory.len() {
|
// FIXME: We call this whether we can write to Ext RAM or not.
|
||||||
return Err(SaveError::DiffSize);
|
// We should add a check that ensures that by this point we know whether
|
||||||
}
|
// the cartridge has external RAM or not.
|
||||||
|
cart.write_ext_ram(memory);
|
||||||
ext_ram.copy_from_slice(&memory);
|
|
||||||
Ok(save_path)
|
Ok(save_path)
|
||||||
}
|
}
|
||||||
None => Err(SaveError::NotApplicable),
|
None => Err(SaveError::NotApplicable),
|
||||||
|
@ -154,6 +151,4 @@ pub enum SaveError {
|
||||||
NotApplicable,
|
NotApplicable,
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("save file size differs from external ram")]
|
|
||||||
DiffSize,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Interrupt {
|
pub(crate) struct Interrupt {
|
||||||
pub(crate) flag: InterruptFlag,
|
pub(crate) flag: InterruptFlag,
|
||||||
pub(crate) enable: InterruptEnable,
|
pub(crate) enable: InterruptEnable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Interrupt {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
flag: InterruptFlag(0),
|
|
||||||
enable: InterruptEnable(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct InterruptEnable(u8);
|
pub struct InterruptEnable(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -32,6 +23,12 @@ impl Clone for InterruptEnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for InterruptEnable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for InterruptEnable {
|
impl From<u8> for InterruptEnable {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -61,6 +58,12 @@ impl Clone for InterruptFlag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for InterruptFlag {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for InterruptFlag {
|
impl From<u8> for InterruptFlag {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
|
20
src/ppu.rs
20
src/ppu.rs
|
@ -470,10 +470,10 @@ impl Default for Ppu {
|
||||||
dot: Default::default(),
|
dot: Default::default(),
|
||||||
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
|
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
|
||||||
int: Default::default(),
|
int: Default::default(),
|
||||||
ctrl: LCDControl(0),
|
ctrl: Default::default(),
|
||||||
monochrome: Default::default(),
|
monochrome: Default::default(),
|
||||||
pos: Default::default(),
|
pos: Default::default(),
|
||||||
stat: LCDStatus(0x80), // bit 7 is always 1
|
stat: Default::default(),
|
||||||
oam: Default::default(),
|
oam: Default::default(),
|
||||||
scan_dot: Default::default(),
|
scan_dot: Default::default(),
|
||||||
fetch: Default::default(),
|
fetch: Default::default(),
|
||||||
|
@ -528,7 +528,7 @@ pub(crate) struct ScreenPosition {
|
||||||
pub(crate) window_x: u8,
|
pub(crate) window_x: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Monochrome {
|
pub(crate) struct Monochrome {
|
||||||
/// 0xFF47 | BGP - Background Palette Data
|
/// 0xFF47 | BGP - Background Palette Data
|
||||||
pub(crate) bg_palette: BackgroundPalette,
|
pub(crate) bg_palette: BackgroundPalette,
|
||||||
|
@ -538,16 +538,6 @@ pub(crate) struct Monochrome {
|
||||||
pub(crate) obj_palette_1: ObjectPalette,
|
pub(crate) obj_palette_1: ObjectPalette,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Monochrome {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
bg_palette: BackgroundPalette(0),
|
|
||||||
obj_palette_0: ObjectPalette(0),
|
|
||||||
obj_palette_1: ObjectPalette(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ObjectAttrTable {
|
pub(crate) struct ObjectAttrTable {
|
||||||
buf: Box<[u8; OAM_SIZE]>,
|
buf: Box<[u8; OAM_SIZE]>,
|
||||||
|
@ -585,7 +575,7 @@ impl Default for ObjectAttrTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
struct ObjectAttr {
|
struct ObjectAttr {
|
||||||
y: u8,
|
y: u8,
|
||||||
x: u8,
|
x: u8,
|
||||||
|
@ -846,7 +836,7 @@ struct BgPixelProperty {
|
||||||
shade_id: u8,
|
shade_id: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
struct ObjPixelProperty {
|
struct ObjPixelProperty {
|
||||||
shade_id: u8,
|
shade_id: u8,
|
||||||
palette_kind: ObjectPaletteKind,
|
palette_kind: ObjectPaletteKind,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::Cycle;
|
use crate::Cycle;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct DirectMemoryAccess {
|
pub(crate) struct DirectMemoryAccess {
|
||||||
pub(crate) state: DmaState,
|
pub(crate) state: DmaState,
|
||||||
cycle: Cycle,
|
cycle: Cycle,
|
||||||
|
@ -8,16 +8,6 @@ pub(crate) struct DirectMemoryAccess {
|
||||||
pub(crate) start: DmaAddress,
|
pub(crate) start: DmaAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DirectMemoryAccess {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
state: DmaState::Disabled,
|
|
||||||
cycle: Default::default(),
|
|
||||||
start: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectMemoryAccess {
|
impl DirectMemoryAccess {
|
||||||
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
|
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
|
||||||
match self.state {
|
match self.state {
|
||||||
|
@ -79,6 +69,12 @@ pub(crate) enum DmaState {
|
||||||
Transferring,
|
Transferring,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for DmaState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub(crate) struct DmaAddress(Option<u16>);
|
pub(crate) struct DmaAddress(Option<u16>);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ impl Clone for LCDStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LCDStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0x80) // bit 7 is always 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LCDStatus> for u8 {
|
impl From<LCDStatus> for u8 {
|
||||||
fn from(status: LCDStatus) -> Self {
|
fn from(status: LCDStatus) -> Self {
|
||||||
status.0
|
status.0
|
||||||
|
@ -61,6 +67,12 @@ impl From<PpuMode> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PpuMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::HBlank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct LCDControl(u8);
|
pub struct LCDControl(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -81,6 +93,12 @@ impl Clone for LCDControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LCDControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for LCDControl {
|
impl From<u8> for LCDControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -124,6 +142,12 @@ impl From<TileMapAddress> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TileMapAddress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::X9800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum TileDataAddress {
|
pub enum TileDataAddress {
|
||||||
X8800 = 0,
|
X8800 = 0,
|
||||||
|
@ -146,6 +170,12 @@ impl From<TileDataAddress> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TileDataAddress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::X8800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ObjectSize {
|
pub enum ObjectSize {
|
||||||
Eight = 0,
|
Eight = 0,
|
||||||
|
@ -179,6 +209,12 @@ impl From<ObjectSize> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Eight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct BackgroundPalette(u8);
|
pub struct BackgroundPalette(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -207,6 +243,12 @@ impl Clone for BackgroundPalette {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for BackgroundPalette {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for BackgroundPalette {
|
impl From<u8> for BackgroundPalette {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -246,6 +288,12 @@ impl Clone for ObjectPalette {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectPalette {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ObjectPalette {
|
impl From<u8> for ObjectPalette {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -313,12 +361,24 @@ impl From<ObjectFlags> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectFlags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ObjectPaletteKind {
|
pub enum ObjectPaletteKind {
|
||||||
Zero = 0,
|
Zero = 0,
|
||||||
One = 1,
|
One = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectPaletteKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ObjectPaletteKind {
|
impl From<u8> for ObjectPaletteKind {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
@ -357,6 +417,12 @@ impl From<RenderPriority> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RenderPriority {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum GrayShade {
|
pub enum GrayShade {
|
||||||
White = 0,
|
White = 0,
|
||||||
|
@ -376,6 +442,12 @@ impl GrayShade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GrayShade {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for GrayShade {
|
impl From<u8> for GrayShade {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b11 {
|
match byte & 0b11 {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Serial {
|
pub(crate) struct Serial {
|
||||||
/// 0xFF01 | SB - Serial Transfer Data
|
/// 0xFF01 | SB - Serial Transfer Data
|
||||||
pub(crate) next: u8,
|
pub(crate) next: u8,
|
||||||
|
@ -8,15 +8,6 @@ pub(crate) struct Serial {
|
||||||
pub(crate) ctrl: SerialControl,
|
pub(crate) ctrl: SerialControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Serial {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
next: Default::default(),
|
|
||||||
ctrl: SerialControl(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct SerialControl(u8);
|
pub struct SerialControl(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -32,6 +23,12 @@ impl Clone for SerialControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SerialControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for SerialControl {
|
impl From<u8> for SerialControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -50,6 +47,12 @@ enum ShiftClock {
|
||||||
Internal = 1,
|
Internal = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ShiftClock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::External
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ShiftClock {
|
impl From<u8> for ShiftClock {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
@ -66,6 +69,12 @@ enum ClockSpeed {
|
||||||
Fast = 1,
|
Fast = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ClockSpeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ClockSpeed {
|
impl From<u8> for ClockSpeed {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
|
|
@ -106,7 +106,7 @@ impl Timer {
|
||||||
impl Default for Timer {
|
impl Default for Timer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ctrl: TimerControl(0),
|
ctrl: Default::default(),
|
||||||
counter: 0,
|
counter: 0,
|
||||||
modulo: 0,
|
modulo: 0,
|
||||||
divider: 0,
|
divider: 0,
|
||||||
|
@ -157,6 +157,12 @@ impl Clone for TimerControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TimerControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for TimerControl {
|
impl From<u8> for TimerControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
|
Loading…
Reference in New Issue