save files
This commit is contained in:
parent
f40268666e
commit
e3330a4df7
29
Cargo.lock
generated
29
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
23
src/lib.rs
23
src/lib.rs
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue