Compare commits
5 Commits
refactor
...
71ce3f43e0
| Author | SHA1 | Date | |
|---|---|---|---|
| 71ce3f43e0 | |||
| ce121864d2 | |||
| e1fe00ab64 | |||
| 5882678bc5 | |||
| bcd67cb317 |
49
Cargo.lock
generated
49
Cargo.lock
generated
@@ -592,6 +592,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "discard"
|
||||
version = "1.0.4"
|
||||
@@ -671,6 +692,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bitfield",
|
||||
"clap",
|
||||
"directories-next",
|
||||
"gilrs",
|
||||
"pixels",
|
||||
"rodio",
|
||||
@@ -679,6 +701,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "gilrs"
|
||||
version = "0.8.1"
|
||||
@@ -1493,6 +1526,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
@@ -1916,6 +1959,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.77"
|
||||
|
||||
@@ -17,6 +17,7 @@ winit = "^0.25"
|
||||
winit_input_helper = "^0.10"
|
||||
rodio = "^0.14"
|
||||
rtrb = "^0.1.4"
|
||||
directories-next = "2.0.0"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
60
src/bus.rs
60
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)]
|
||||
@@ -71,10 +64,12 @@ impl Bus {
|
||||
self.boot.is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn clock(&mut self) {
|
||||
self.tick(4);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn tick(&mut self, limit: u8) {
|
||||
for _ in 0..limit {
|
||||
self.timer.tick();
|
||||
@@ -90,6 +85,21 @@ impl Bus {
|
||||
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 {
|
||||
@@ -104,13 +114,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 +167,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 +179,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 +272,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 +286,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"),
|
||||
}
|
||||
|
||||
167
src/cartridge.rs
167
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,23 @@ 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -46,13 +46,13 @@ impl Cartridge {
|
||||
match mbc_kind {
|
||||
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::MBC1WithBattery => Box::new(MBC1::with_battery(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::MBC2WithBattery => Box::new(MBC2::with_battery(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::MBC3WithBattery => Box::new(MBC3::with_battery(ram_cap)), // TODO: Implement Saving
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ struct MBC1 {
|
||||
memory: Vec<u8>,
|
||||
rom_size: RomSize,
|
||||
mem_enabled: bool,
|
||||
|
||||
has_battery: bool,
|
||||
}
|
||||
|
||||
impl MBC1 {
|
||||
@@ -137,6 +139,20 @@ impl MBC1 {
|
||||
ram_bank: Default::default(),
|
||||
mode: 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,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 {
|
||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||
use MBCResult::*;
|
||||
@@ -264,6 +295,8 @@ struct MBC3 {
|
||||
|
||||
// RTC Data Latch Previous Write
|
||||
prev_latch_write: Option<u8>,
|
||||
|
||||
has_battery: bool,
|
||||
}
|
||||
|
||||
impl MBC3 {
|
||||
@@ -275,6 +308,34 @@ impl MBC3 {
|
||||
devs_enabled: Default::default(),
|
||||
mapped: 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,9 +411,10 @@ struct MBC5 {
|
||||
ram_bank: u8,
|
||||
|
||||
rom_cap: usize,
|
||||
|
||||
memory: Vec<u8>,
|
||||
mem_enabled: bool,
|
||||
|
||||
has_battery: bool,
|
||||
}
|
||||
|
||||
impl MBC5 {
|
||||
@@ -363,6 +425,18 @@ impl MBC5 {
|
||||
rom_cap,
|
||||
ram_bank: 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,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 {
|
||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||
use MBCResult::*;
|
||||
@@ -409,6 +498,7 @@ struct MBC2 {
|
||||
mem_enabled: bool,
|
||||
|
||||
rom_cap: usize,
|
||||
has_battery: bool,
|
||||
}
|
||||
|
||||
impl MBC2 {
|
||||
@@ -418,8 +508,19 @@ impl MBC2 {
|
||||
Self {
|
||||
rom_bank: 0x01,
|
||||
memory: Box::new([0; Self::RAM_SIZE]),
|
||||
mem_enabled: Default::default(),
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,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 {
|
||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||
use MBCResult::*;
|
||||
@@ -465,6 +581,16 @@ impl MBCIo for MBC2 {
|
||||
#[derive(Debug)]
|
||||
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 {
|
||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||
MBCResult::Address(addr as usize)
|
||||
@@ -475,7 +601,7 @@ impl MBCIo for NoMBC {
|
||||
}
|
||||
}
|
||||
|
||||
trait MBCIo {
|
||||
trait MBCIo: Savable {
|
||||
fn handle_read(&self, addr: u16) -> MBCResult;
|
||||
fn handle_write(&mut self, addr: u16, byte: u8);
|
||||
}
|
||||
@@ -615,3 +741,8 @@ impl Default for Box<dyn MBCIo> {
|
||||
Box::new(NoMBC)
|
||||
}
|
||||
}
|
||||
|
||||
trait Savable {
|
||||
fn ext_ram(&self) -> Option<&[u8]>;
|
||||
fn write_ext_ram(&mut self, memory: Vec<u8>);
|
||||
}
|
||||
|
||||
42
src/cpu.rs
42
src/cpu.rs
@@ -1,9 +1,6 @@
|
||||
use crate::apu::Apu;
|
||||
use crate::bus::{Bus, BusIo};
|
||||
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 {
|
||||
@@ -60,10 +57,7 @@ impl Cpu {
|
||||
}
|
||||
|
||||
pub(crate) fn is_halted(&self) -> bool {
|
||||
match self.state {
|
||||
State::Halt(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.state, State::Halt(_))
|
||||
}
|
||||
|
||||
pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
|
||||
@@ -72,14 +66,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 {
|
||||
@@ -167,16 +153,14 @@ impl BusIo for Cpu {
|
||||
}
|
||||
|
||||
impl Cpu {
|
||||
pub fn ppu(&mut self) -> &Ppu {
|
||||
&self.bus.ppu
|
||||
#[inline]
|
||||
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
|
||||
#[inline]
|
||||
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
|
||||
&mut self.bus
|
||||
}
|
||||
|
||||
fn handle_ei(&mut self) {
|
||||
|
||||
198
src/emu.rs
198
src/emu.rs
@@ -1,9 +1,12 @@
|
||||
use crate::cpu::Cpu as SM83;
|
||||
use crate::joypad;
|
||||
use crate::ppu::Ppu;
|
||||
use crate::Cycle;
|
||||
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 clap::crate_name;
|
||||
use gilrs::Gilrs;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
|
||||
@@ -12,56 +15,159 @@ 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";
|
||||
|
||||
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(),
|
||||
};
|
||||
|
||||
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 {
|
||||
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
|
||||
let mut elapsed = 0;
|
||||
|
||||
if let Some(event) = gamepad.next_event() {
|
||||
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
|
||||
joypad::handle_gamepad_input(emu.joyp_mut(), event);
|
||||
}
|
||||
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
|
||||
joypad::handle_keyboard_input(emu.joyp_mut(), key);
|
||||
|
||||
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 = 0;
|
||||
|
||||
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);
|
||||
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 {
|
||||
let cycles = self.cpu.step();
|
||||
self.timestamp += cycles;
|
||||
cycles
|
||||
}
|
||||
|
||||
fn load_cart(&mut self, rom: Vec<u8>) {
|
||||
self.cpu.bus_mut().load_cart(rom);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn joyp_mut(&mut self) -> &mut Joypad {
|
||||
self.cpu.bus_mut().joyp_mut()
|
||||
}
|
||||
|
||||
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 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 {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ impl ButtonEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
|
||||
use winit::event::VirtualKeyCode;
|
||||
|
||||
@@ -146,17 +147,17 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
|
||||
state.dpad_right.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::T) {
|
||||
if input.key_pressed(VirtualKeyCode::Return) {
|
||||
state.start.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::T) {
|
||||
if input.key_released(VirtualKeyCode::Return) {
|
||||
state.start.update(false, irq);
|
||||
}
|
||||
|
||||
if input.key_pressed(VirtualKeyCode::Y) {
|
||||
if input.key_pressed(VirtualKeyCode::RShift) {
|
||||
state.select.update(true, irq);
|
||||
}
|
||||
if input.key_released(VirtualKeyCode::Y) {
|
||||
if input.key_released(VirtualKeyCode::RShift) {
|
||||
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) {
|
||||
use Button::*;
|
||||
use GamepadEventType::*;
|
||||
|
||||
47
src/main.rs
47
src/main.rs
@@ -1,5 +1,8 @@
|
||||
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};
|
||||
@@ -11,7 +14,7 @@ use winit::window::{Window, WindowBuilder};
|
||||
use winit_input_helper::WinitInputHelper;
|
||||
|
||||
const WINDOW_SCALE: usize = 3;
|
||||
const AUDIO_ENABLED: bool = false;
|
||||
const AUDIO_ENABLED: bool = true;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let app = App::new(crate_name!())
|
||||
@@ -38,13 +41,18 @@ 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();
|
||||
|
||||
// Load Save file if it exists
|
||||
emu.try_load_sav().expect("Load save if exists");
|
||||
let rom_title = emu.title();
|
||||
|
||||
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
||||
|
||||
@@ -68,10 +76,14 @@ 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);
|
||||
sink.set_volume(0.1); // TODO: Is this the right way to go about this?
|
||||
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();
|
||||
@@ -87,6 +99,8 @@ fn main() -> Result<()> {
|
||||
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
|
||||
.is_err()
|
||||
{
|
||||
emu.try_write_sav().expect("Write game save if need be");
|
||||
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
@@ -94,6 +108,8 @@ fn main() -> Result<()> {
|
||||
|
||||
if input.update(&event) {
|
||||
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||
emu.try_write_sav().expect("Write game save if need be");
|
||||
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
@@ -102,12 +118,17 @@ fn main() -> Result<()> {
|
||||
pixels.resize_surface(size.width, size.height);
|
||||
}
|
||||
|
||||
cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ impl Ppu {
|
||||
TileLow => {
|
||||
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);
|
||||
self.fetch.obj.tile.with_low_byte(byte);
|
||||
@@ -272,7 +272,7 @@ impl Ppu {
|
||||
TileHigh => {
|
||||
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);
|
||||
self.fetch.obj.tile.with_high_byte(byte);
|
||||
@@ -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> {
|
||||
@@ -629,6 +629,7 @@ impl ObjectBuffer {
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> {
|
||||
self.inner.iter_mut()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user