save files

This commit is contained in:
Alex Janka 2023-03-02 11:29:54 +11:00
parent f40268666e
commit e3330a4df7
11 changed files with 220 additions and 56 deletions

29
Cargo.lock generated
View file

@ -252,6 +252,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "ctrlc"
version = "3.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639"
dependencies = [
"nix 0.26.2",
"windows-sys 0.45.0",
]
[[package]]
name = "cty"
version = "0.2.2"
@ -417,6 +427,7 @@ dependencies = [
"async-ringbuf",
"clap",
"cpal",
"ctrlc",
"futures",
"gilrs",
"itertools",
@ -795,6 +806,18 @@ dependencies = [
"libc",
]
[[package]]
name = "nix"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"static_assertions",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1237,6 +1260,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.10.0"

View file

@ -18,3 +18,4 @@ async-ringbuf = "0.1.2"
futures = "0.3.26"
once_cell = "1.17.1"
itertools = "0.10.5"
ctrlc = "3.2.5"

View file

@ -1,5 +1,9 @@
pub use crate::processor::memory::mmio::joypad::JoypadState;
pub enum EmulatorMessage {
Stop,
}
pub trait Renderer {
fn prepare(&mut self, width: usize, height: usize);

View file

@ -10,12 +10,16 @@ use crate::{
processor::memory::Memory,
util::{pause, print_cycles},
};
use connect::Renderer;
use connect::{EmulatorMessage, Renderer};
use once_cell::sync::OnceCell;
use processor::{memory::Rom, Cpu};
use std::{
fs,
fs::{self},
io::{stdout, Write},
path::PathBuf,
process::exit,
str::FromStr,
sync::mpsc::Receiver,
};
use util::pause_then_step;
@ -42,14 +46,19 @@ pub const WIDTH: usize = 160;
pub const HEIGHT: usize = 144;
pub fn init(
receiver: Receiver<EmulatorMessage>,
options: Options,
mut window: Box<dyn Renderer>,
tile_window: Option<Box<dyn Renderer>>,
) {
VERBOSE.set(options.verbose).unwrap();
let maybe_save = PathBuf::from_str(&options.rom_path)
.unwrap()
.with_extension("sav");
let rom: Rom = match fs::read(options.rom_path) {
Ok(data) => Rom::load(data),
Ok(data) => Rom::load(data, maybe_save),
Err(e) => {
println!("Error reading ROM: {e}");
return;
@ -94,6 +103,14 @@ pub fn init(
pause();
},
None => loop {
while let Ok(msg) = receiver.try_recv() {
match msg {
EmulatorMessage::Stop => {
cpu.memory.flush_rom();
exit(0);
}
}
}
cycle_num += 1;
if options.cycle_count {
print_cycles(&cycle_num);

View file

@ -1,8 +1,10 @@
#![feature(let_chains)]
use std::sync::mpsc::channel;
use clap::{ArgGroup, Parser};
use gb_emu::{
connect::{JoypadState, Renderer},
connect::{EmulatorMessage, JoypadState, Renderer},
util::scale_buffer,
};
use gilrs::{
@ -73,7 +75,12 @@ fn main() {
None
};
let (sender, receiver) = channel::<EmulatorMessage>();
ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap();
gb_emu::init(
receiver,
options,
Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))),
tile_window,

View file

@ -220,6 +220,10 @@ impl Memory {
self.set(i, if rand::random() { 0xFF } else { 0x00 });
}
}
pub fn flush_rom(&mut self) {
self.rom.flush();
}
}
impl Cpu {

View file

@ -1,17 +1,89 @@
use crate::processor::memory::Address;
use std::str::from_utf8_unchecked;
use std::{
fs::{File, OpenOptions},
io::{Read, Write},
path::PathBuf,
str::from_utf8_unchecked,
};
use self::mbcs::{Mbc, Mbc1, Mbc3, Mbc5, None};
mod mbcs;
struct MaybeBufferedSram {
buf: Vec<u8>,
inner: Option<File>,
unbuffered_writes: usize,
}
const NUM_WRITES_TO_FLUSH: usize = 256;
impl MaybeBufferedSram {
fn new(path: Option<PathBuf>, length: usize) -> Self {
let mut buf = vec![0; length];
let inner = if let Some(path) = path {
if path.exists() {
let mut writer = OpenOptions::new()
.write(true)
.read(true)
.open(path)
.unwrap();
writer.read_exact(&mut buf).unwrap();
Some(writer)
} else {
let writer = OpenOptions::new()
.write(true)
.create_new(true)
.open(path)
.unwrap();
Some(writer)
}
} else {
None
};
Self {
buf,
inner,
unbuffered_writes: 0,
}
}
fn len(&self) -> usize {
self.buf.len()
}
fn get(&self, addr: usize) -> u8 {
self.buf[addr]
}
fn set(&mut self, addr: usize, data: u8) {
self.unbuffered_writes += 1;
self.buf[addr] = data;
if self.unbuffered_writes >= NUM_WRITES_TO_FLUSH {
self.flush();
}
}
fn flush(&mut self) {
if let Some(ref mut writer) = self.inner {
writer.write_all(&self.buf).unwrap();
}
}
}
impl Drop for MaybeBufferedSram {
fn drop(&mut self) {
self.flush();
}
}
pub struct Rom {
title: String,
mbc: Box<dyn Mbc>,
}
impl Rom {
pub fn load(data: Vec<u8>) -> Self {
pub fn load(data: Vec<u8>, save_path: PathBuf) -> Self {
let mut title_length = 0x143;
for (i, val) in data.iter().enumerate().take(0x143).skip(0x134) {
title_length = i;
@ -28,36 +100,18 @@ impl Rom {
0x00 => Box::new(None::init(data)),
0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)),
0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, None)),
0x03 => {
println!("MBC1 w/battery - battery not implemented!");
Box::new(Mbc1::init(data, rom_size, ram_size, None))
}
0x0F => {
println!("MBC3 w/timer + battery - battery not implemented!");
Box::new(Mbc3::init(data, rom_size, 0, true))
}
0x10 => {
println!("MBC3 w/timer + battery - battery not implemented!");
Box::new(Mbc3::init(data, rom_size, ram_size, true))
}
0x11 => Box::new(Mbc3::init(data, rom_size, 0, false)),
0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false)),
0x13 => {
println!("MBC3 w/battery - battery not implemented!");
Box::new(Mbc3::init(data, rom_size, 0, false))
}
0x19 => Box::new(Mbc5::init(data, rom_size, 0, false)),
0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false)),
0x1B => {
println!("MBC5 w/battery - battery not implemented!");
Box::new(Mbc5::init(data, rom_size, ram_size, false))
}
0x1C => Box::new(Mbc5::init(data, rom_size, 0, true)),
0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true)),
0x1E => {
println!("MBC5 w/rumble + battery - battery not implemented!");
Box::new(Mbc5::init(data, rom_size, ram_size, true))
}
0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, Some(save_path))),
0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, Some(save_path))),
0x10 => Box::new(Mbc3::init(data, rom_size, ram_size, true, Some(save_path))),
0x11 => Box::new(Mbc3::init(data, rom_size, 0, false, None)),
0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false, None)),
0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, Some(save_path))),
0x19 => Box::new(Mbc5::init(data, rom_size, 0, false, None)),
0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false, None)),
0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, Some(save_path))),
0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)),
0x1D => Box::new(Mbc5::init(data, rom_size, ram_size, true, None)),
0x1E => Box::new(Mbc5::init(data, rom_size, ram_size, true, Some(save_path))),
_ => panic!("unimplemented mbc: {:#X}", data[0x147]),
};
Self { title, mbc }
@ -94,4 +148,8 @@ impl Rom {
pub fn mbc_type(&self) -> String {
self.mbc.mbc_type()
}
pub(super) fn flush(&mut self) {
self.mbc.flush();
}
}

View file

@ -27,6 +27,7 @@ pub(super) trait Mbc {
fn can_rumble(&self) -> bool {
false
}
fn flush(&mut self) {}
}
fn rom_banks(rom_size: u8) -> usize {

View file

@ -1,5 +1,7 @@
use std::path::PathBuf;
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
use crate::processor::memory::Address;
use crate::processor::memory::{rom::MaybeBufferedSram, Address};
#[derive(Clone, Copy)]
enum BankingMode {
@ -12,17 +14,17 @@ pub struct Mbc1 {
rom_len: usize,
rom_bank: u8,
ram_enabled: bool,
ram: Option<Vec<u8>>,
ram: Option<MaybeBufferedSram>,
ram_bank: u8,
upper_banks: u8,
bank_mode: BankingMode,
}
impl Mbc1 {
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, _save_file: Option<Vec<u8>>) -> Self {
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, save_file: Option<PathBuf>) -> Self {
let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
// in kb
let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]);
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
Self {
data,
rom_len,
@ -43,8 +45,8 @@ impl Mbc for Mbc1 {
fn get_ram(&self, address: Address) -> u8 {
if self.ram_enabled && let Some(ram) = &self.ram {
let addr = self.get_ram_addr(address)%ram.len();
return ram[addr];
let addr = self.get_ram_addr(address) % ram.len();
return ram.get(addr);
}
0xFF
}
@ -53,7 +55,7 @@ impl Mbc for Mbc1 {
let mut addr = self.get_ram_addr(address);
if self.ram_enabled && let Some(ram) = &mut self.ram {
addr %= ram.len();
ram[addr] = data;
ram.set(addr, data);
}
}
@ -94,6 +96,12 @@ impl Mbc for Mbc1 {
format!("{}KB MBC1", self.rom_len / KB)
}
}
fn flush(&mut self) {
if let Some(ref mut ram) = self.ram {
ram.flush();
}
}
}
impl Mbc1 {

View file

@ -1,6 +1,12 @@
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
use crate::{processor::memory::Address, util::set_or_clear_bit};
use std::time::{Duration, Instant};
use crate::{
processor::memory::{rom::MaybeBufferedSram, Address},
util::set_or_clear_bit,
};
use std::{
path::PathBuf,
time::{Duration, Instant},
};
#[derive(Debug)]
enum RtcRegister {
@ -106,7 +112,7 @@ pub struct Mbc3 {
data: Vec<u8>,
rom_bank: u8,
rom_size: usize,
ram: Option<Vec<u8>>,
ram: Option<MaybeBufferedSram>,
ram_bank: RamBank,
ram_size: usize,
ram_enabled: bool,
@ -114,8 +120,14 @@ pub struct Mbc3 {
}
impl Mbc3 {
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, rtc: bool) -> Self {
let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]);
pub fn init(
data: Vec<u8>,
rom_size: u8,
ram_size: u8,
rtc: bool,
save_file: Option<PathBuf>,
) -> Self {
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
Self {
data,
rom_bank: 1,
@ -154,7 +166,7 @@ impl Mbc for Mbc3 {
match &self.ram_bank {
RamBank::Ram(ram_bank) => {
if let Some(ram) = &self.ram {
return ram[self.get_ram_addr(address, *ram_bank as usize)];
return ram.get(self.get_ram_addr(address, *ram_bank as usize));
}
}
RamBank::Rtc(rtc_register) => {
@ -216,7 +228,7 @@ impl Mbc for Mbc3 {
RamBank::Ram(ram_bank) => {
let real_addr = self.get_ram_addr(address, *ram_bank as usize);
if let Some(ram) = &mut self.ram {
ram[real_addr] = data;
ram.set(real_addr, data);
}
}
RamBank::Rtc(rtc_register) => {
@ -239,4 +251,10 @@ impl Mbc for Mbc3 {
format!("{}KB MBC3", self.rom_size / KB)
}
}
fn flush(&mut self) {
if let Some(ref mut ram) = self.ram {
ram.flush();
}
}
}

View file

@ -1,4 +1,9 @@
use crate::{processor::memory::Address, util::get_bit};
use std::path::PathBuf;
use crate::{
processor::memory::{rom::MaybeBufferedSram, Address},
util::get_bit,
};
use super::{ram_size_kb, rom_banks, Mbc, KB, RAM_BANK_SIZE, ROM_BANK_SIZE};
@ -6,7 +11,7 @@ pub struct Mbc5 {
data: Vec<u8>,
rom_bank: u16,
rom_size: usize,
ram: Option<Vec<u8>>,
ram: Option<MaybeBufferedSram>,
ram_bank: u8,
ram_size: usize,
ram_enabled: bool,
@ -15,8 +20,14 @@ pub struct Mbc5 {
}
impl Mbc5 {
pub fn init(data: Vec<u8>, rom_size: u8, ram_size: u8, rumble: bool) -> Self {
let ram = ram_size_kb(ram_size).map(|s| vec![0; s * KB]);
pub fn init(
data: Vec<u8>,
rom_size: u8,
ram_size: u8,
rumble: bool,
save_file: Option<PathBuf>,
) -> Self {
let ram = ram_size_kb(ram_size).map(|s| MaybeBufferedSram::new(save_file, s * KB));
Self {
data,
rom_bank: 1,
@ -53,7 +64,7 @@ impl Mbc for Mbc5 {
fn get_ram(&self, address: Address) -> u8 {
if self.ram_enabled && let Some(ram) = &self.ram {
ram[self.get_ram_addr(address)]
ram.get(self.get_ram_addr(address))
} else {
0xFF
}
@ -86,7 +97,7 @@ impl Mbc for Mbc5 {
fn set_ram(&mut self, address: Address, data: u8) {
let real_addr = self.get_ram_addr(address);
if self.ram_enabled && let Some(ram) = &mut self.ram {
ram[real_addr] = data;
ram.set(real_addr, data);
}
}
@ -110,4 +121,10 @@ impl Mbc for Mbc5 {
fn can_rumble(&self) -> bool {
self.rumble
}
fn flush(&mut self) {
if let Some(ref mut ram) = self.ram {
ram.flush();
}
}
}