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", "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]] [[package]]
name = "cty" name = "cty"
version = "0.2.2" version = "0.2.2"
@ -417,6 +427,7 @@ dependencies = [
"async-ringbuf", "async-ringbuf",
"clap", "clap",
"cpal", "cpal",
"ctrlc",
"futures", "futures",
"gilrs", "gilrs",
"itertools", "itertools",
@ -795,6 +806,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@ -1237,6 +1260,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,17 +1,89 @@
use crate::processor::memory::Address; 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}; use self::mbcs::{Mbc, Mbc1, Mbc3, Mbc5, None};
mod mbcs; 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 { pub struct Rom {
title: String, title: String,
mbc: Box<dyn Mbc>, mbc: Box<dyn Mbc>,
} }
impl Rom { impl Rom {
pub fn load(data: Vec<u8>) -> Self { pub fn load(data: Vec<u8>, save_path: PathBuf) -> Self {
let mut title_length = 0x143; let mut title_length = 0x143;
for (i, val) in data.iter().enumerate().take(0x143).skip(0x134) { for (i, val) in data.iter().enumerate().take(0x143).skip(0x134) {
title_length = i; title_length = i;
@ -28,36 +100,18 @@ impl Rom {
0x00 => Box::new(None::init(data)), 0x00 => Box::new(None::init(data)),
0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)), 0x01 => Box::new(Mbc1::init(data, rom_size, 0, None)),
0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, None)), 0x02 => Box::new(Mbc1::init(data, rom_size, ram_size, None)),
0x03 => { 0x03 => Box::new(Mbc1::init(data, rom_size, ram_size, Some(save_path))),
println!("MBC1 w/battery - battery not implemented!"); 0x0F => Box::new(Mbc3::init(data, rom_size, 0, true, Some(save_path))),
Box::new(Mbc1::init(data, rom_size, ram_size, None)) 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)),
0x0F => { 0x12 => Box::new(Mbc3::init(data, rom_size, ram_size, false, None)),
println!("MBC3 w/timer + battery - battery not implemented!"); 0x13 => Box::new(Mbc3::init(data, rom_size, ram_size, false, Some(save_path))),
Box::new(Mbc3::init(data, rom_size, 0, true)) 0x19 => Box::new(Mbc5::init(data, rom_size, 0, false, None)),
} 0x1A => Box::new(Mbc5::init(data, rom_size, ram_size, false, None)),
0x10 => { 0x1B => Box::new(Mbc5::init(data, rom_size, ram_size, false, Some(save_path))),
println!("MBC3 w/timer + battery - battery not implemented!"); 0x1C => Box::new(Mbc5::init(data, rom_size, 0, true, None)),
Box::new(Mbc3::init(data, rom_size, ram_size, true)) 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))),
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))
}
_ => panic!("unimplemented mbc: {:#X}", data[0x147]), _ => panic!("unimplemented mbc: {:#X}", data[0x147]),
}; };
Self { title, mbc } Self { title, mbc }
@ -94,4 +148,8 @@ impl Rom {
pub fn mbc_type(&self) -> String { pub fn mbc_type(&self) -> String {
self.mbc.mbc_type() 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 { fn can_rumble(&self) -> bool {
false false
} }
fn flush(&mut self) {}
} }
fn rom_banks(rom_size: u8) -> usize { 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 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)] #[derive(Clone, Copy)]
enum BankingMode { enum BankingMode {
@ -12,17 +14,17 @@ pub struct Mbc1 {
rom_len: usize, rom_len: usize,
rom_bank: u8, rom_bank: u8,
ram_enabled: bool, ram_enabled: bool,
ram: Option<Vec<u8>>, ram: Option<MaybeBufferedSram>,
ram_bank: u8, ram_bank: u8,
upper_banks: u8, upper_banks: u8,
bank_mode: BankingMode, bank_mode: BankingMode,
} }
impl Mbc1 { 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; let rom_len = rom_banks(rom_size) * ROM_BANK_SIZE;
// in kb // 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 { Self {
data, data,
rom_len, rom_len,
@ -44,7 +46,7 @@ impl Mbc for Mbc1 {
fn get_ram(&self, address: Address) -> u8 { fn get_ram(&self, address: Address) -> u8 {
if self.ram_enabled && let Some(ram) = &self.ram { if self.ram_enabled && let Some(ram) = &self.ram {
let addr = self.get_ram_addr(address) % ram.len(); let addr = self.get_ram_addr(address) % ram.len();
return ram[addr]; return ram.get(addr);
} }
0xFF 0xFF
} }
@ -53,7 +55,7 @@ impl Mbc for Mbc1 {
let mut addr = self.get_ram_addr(address); let mut addr = self.get_ram_addr(address);
if self.ram_enabled && let Some(ram) = &mut self.ram { if self.ram_enabled && let Some(ram) = &mut self.ram {
addr %= ram.len(); 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) format!("{}KB MBC1", self.rom_len / KB)
} }
} }
fn flush(&mut self) {
if let Some(ref mut ram) = self.ram {
ram.flush();
}
}
} }
impl Mbc1 { impl Mbc1 {

View file

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