From 704d1c2e7feb0501097e70032f951893d29e358a Mon Sep 17 00:00:00 2001 From: Determinant Date: Mon, 8 Jan 2018 12:54:07 +0800 Subject: finish load/save features --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.rst | 19 ++++--- src/bin.rs | 168 +++++++++++++++++++++++++++++++++++++++++-------------- src/cartridge.rs | 2 + src/mapper.rs | 8 +++ src/utils.rs | 88 +++++++++++++++++++++++++++++ 7 files changed, 235 insertions(+), 54 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 0adf88f..64afd87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -134,7 +134,7 @@ dependencies = [ [[package]] name = "runes" -version = "0.1.5" +version = "0.1.6" dependencies = [ "clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 42bd0e2..8a39686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runes" -version = "0.1.5" +version = "0.1.6" authors = ["Determinant "] description = "No-std NES emulator library and minimal emulator written purely in Rust." repository = "https://github.com/Determinant/runes" diff --git a/README.rst b/README.rst index 7e41122..4e19c9a 100644 --- a/README.rst +++ b/README.rst @@ -8,22 +8,23 @@ RuNES As we know, there have been a ton of NES emulator implementations in various kinds of languages (mostly C). All of these emulators come with different -accuracy and portability. RuNES is an attempt to build a reasonably -accurate (cycle-level accurate), light-weight and efficient emulation -core library using Rust. Unlike sprocketnes_ or pinky_, RuNES strives to -provide with a clean and minimal core library without standard library (i.e., -without Box, Rc, Vectors, etc.) that could be compiled and easily ported to -embedded environments. Of course, a minimal SDL-based GUI is also provided as a -demonstration of use. +accuracy and portability. RuNES is an attempt to build a reasonably accurate +(cycle-level accurate), light-weight and efficient emulation core library using +Rust. Unlike sprocketnes_ or pinky_, RuNES strives to provide with a clean and +minimal core library without standard library (i.e., without Box, Rc, Vectors, +etc.) that could be compiled and easily ported to embedded environments. Of +course, a minimal but usable SDL-based GUI is also provided as a demonstration +of use. Feature ======= - Core library with minimal use of the Rust core crate, and zero use of std. +- Lightweight and clean code base. - Support standard 6502 instruction set (unofficial instruction will be considered in the future). - -- Cycle-level accuracy. +- Load/save the machine state. +- Cycle-level accuracy (in-progress). Guidelines ========== diff --git a/src/bin.rs b/src/bin.rs index d306fb4..07f8487 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -66,6 +66,22 @@ impl SimpleCart { mirror_type: MirrorType) -> Self { SimpleCart{chr_rom, prg_rom, sram, mirror_type} } + + fn load_vec(vec: &mut Vec, reader: &mut utils::Read) -> bool { + let len = vec.len(); + match reader.read(vec) { + Some(x) => x == len, + None => false + } + } + + fn save_vec(vec: &Vec, writer: &mut utils::Write) -> bool { + let len = vec.len(); + match writer.write(vec) { + Some(x) => x == len, + None => false + } + } } impl Cartridge for SimpleCart { @@ -100,22 +116,24 @@ impl Cartridge for SimpleCart { fn set_mirror_type(&mut self, mt: MirrorType) {self.mirror_type = mt} fn load(&mut self, reader: &mut utils::Read) -> bool { - let len = self.sram.len(); - (match reader.read(&mut self.sram) { - Some(x) => x == len, - None => false - }) && + self.load_sram(reader) && + SimpleCart::load_vec(&mut self.chr_rom, reader) && utils::load_prefix(&mut self.mirror_type, 0, reader) } fn save(&self, writer: &mut utils::Write) -> bool { - let len = self.sram.len(); - (match writer.write(&self.sram) { - Some(x) => x == len, - None => false - }) && + self.save_sram(writer) && + SimpleCart::save_vec(&self.chr_rom, writer) && utils::save_prefix(&self.mirror_type, 0, writer) } + + fn load_sram(&mut self, reader: &mut utils::Read) -> bool { + SimpleCart::load_vec(&mut self.sram, reader) + } + + fn save_sram(&self, writer: &mut utils::Write) -> bool { + SimpleCart::save_vec(&self.sram, writer) + } } struct FileIO(File); @@ -387,39 +405,74 @@ fn print_cpu_trace(cpu: &CPU) { } fn main() { - let matches = App::new("RuNES") - .version("0.1.5") - .author("Ted Yin ") - .about("A Rust NES emulator") - .arg(Arg::with_name("scale") - .short("x") - .long("scale") - .required(false) - .takes_value(true)) - .arg(Arg::with_name("full") - .short("f") - .long("full") - .required(false) - .takes_value(false)) - .arg(Arg::with_name("INPUT") - .help("the iNES ROM file") - .required(true) - .index(1)) - .arg(Arg::with_name("load") - .short("l") - .long("load") - .required(false) - .takes_value(true)) - .get_matches(); + let matches = + App::new("RuNES") + .version("0.1") + .author("Ted Yin ") + .about("A Rust NES emulator") + .arg(Arg::with_name("scale") + .short("x") + .long("scale") + .help("Set pixel scaling factor (3 by default)") + .required(false) + .takes_value(true)) + .arg(Arg::with_name("full") + .help("Enable the entire PPU rendering area") + .short("f") + .long("full") + .required(false) + .takes_value(false)) + .arg(Arg::with_name("INPUT") + .help("iNES ROM file") + .required(true) + .index(1)) + .arg(Arg::with_name("load") + .help("Load from specified machine state file") + .short("l") + .long("load") + .required(false) + .takes_value(true)) + .arg(Arg::with_name("save") + .help("Save to specified machine state file when exit") + .short("s") + .long("save") + .required(false) + .takes_value(true)) + .arg(Arg::with_name("load-sram") + .help("Load from specified sram file") + .short("L") + .long("load-sram") + .required(false) + .takes_value(true)) + .arg(Arg::with_name("save-sram") + .help("Save to specified sram file when exit") + .short("S") + .long("save-sram") + .required(false) + .takes_value(true)) + .arg(Arg::with_name("no-state") + .help("Power up the emulator with initial state") + .short("n") + .long("no-state") + .required(false) + .takes_value(false)) + .get_matches(); let scale = std::cmp::min(8, std::cmp::max(1, value_t!(matches, "scale", u32).unwrap_or(3))); let full = matches.is_present("full"); - /* load and parse iNES file */ let fname = matches.value_of("INPUT").unwrap(); - let lname = matches.value_of("load"); + let load_state_name = matches.value_of("load"); + let save_state_name = matches.value_of("save"); + let save_sram_name = matches.value_of("save-sram"); + let load_sram_name = matches.value_of("load-sram"); + let default_state_name = fname.to_string() + ".runes"; + let default_sram_name = fname.to_string() + ".runes_sram"; + let no_state = matches.is_present("no-state"); + + /* load and parse iNES file */ let mut file = File::open(fname).unwrap(); let mut rheader = [0; 16]; file.read(&mut rheader[..]).unwrap(); @@ -493,19 +546,38 @@ fn main() { let mut apu = APU::new(&mut spkr); let cpu_ptr = &mut cpu as *mut CPU; cpu.mem.bus.attach(cpu_ptr, &mut ppu, &mut apu); - match lname { - Some(s) => { - let mut file = FileIO(File::open(s).unwrap()); + let load_state = !no_state && match match load_state_name { + Some(s) => Some(File::open(s).unwrap()), + None => match File::open(&default_state_name) { + Ok(file) => Some(file), + Err(_) => None + } + } { + Some(f) => { + let mut file = FileIO(f); cpu.load(&mut file); ppu.load(&mut file); apu.load(&mut file); mapper.get_mut().load(&mut file); - debug_assert!(cpu.cycle == 0); + true }, - None => { - cpu.powerup() + None => false + }; + + if !load_state { + if let Some(f) = match load_sram_name { + Some(s) => Some(File::open(s).unwrap()), + None => match File::open(&default_sram_name) { + Ok(file) => Some(file), + Err(_) => None + } + } { + let mut file = FileIO(f); + mapper.get_mut().get_cart_mut().load_sram(&mut file); } + cpu.powerup() } + device.resume(); loop { /* consume the leftover cycles from the last instruction */ @@ -514,12 +586,22 @@ fn main() { } if exit_flag.get() { { - let mut file = FileIO(File::create("t.dat").unwrap()); + let mut file = FileIO(File::create(match save_state_name { + Some(s) => s.to_string(), + None => default_state_name + }).unwrap()); cpu.save(&mut file); ppu.save(&mut file); apu.save(&mut file); mapper.save(&mut file); } + { + let mut file = FileIO(File::create(match save_sram_name { + Some(s) => s.to_string(), + None => default_sram_name + }).unwrap()); + mapper.get_cart().save_sram(&mut file); + } exit(0); } //print_cpu_trace(&cpu); diff --git a/src/cartridge.rs b/src/cartridge.rs index ca9c31e..29cfbd7 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -24,4 +24,6 @@ pub trait Cartridge { #[inline(always)] fn set_mirror_type(&mut self, mt: MirrorType); fn load(&mut self, reader: &mut Read) -> bool; fn save(&self, writer: &mut Write) -> bool; + fn load_sram(&mut self, reader: &mut Read) -> bool; + fn save_sram(&self, writer: &mut Write) -> bool; } diff --git a/src/mapper.rs b/src/mapper.rs index 3d72189..8a6c261 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -7,6 +7,7 @@ use utils::{Read, Write, load_prefix, save_prefix}; pub trait Mapper : VMem { fn get_cart(&self) -> &Cartridge; + fn get_cart_mut(&mut self) -> &mut Cartridge; fn tick(&mut self, _bus: &CPUBus) {} fn load(&mut self, reader: &mut Read) -> bool; fn save(&self, writer: &mut Write) -> bool; @@ -35,6 +36,7 @@ impl<'a> core::ops::Deref for RefMapper<'a> { } } +#[repr(C)] pub struct Mapper1<'a, C> where C: Cartridge { cart: C, prg_banks: [&'a [u8]; 2], @@ -177,6 +179,7 @@ impl<'a, C> Mapper1<'a, C> where C: Cartridge { impl<'a, C> Mapper for Mapper1<'a, C> where C: Cartridge { fn get_cart(&self) -> &Cartridge {&self.cart} + fn get_cart_mut(&mut self) -> &mut Cartridge {&mut self.cart} fn load(&mut self, reader: &mut Read) -> bool { for v in self.prg_banks.iter_mut() { @@ -215,6 +218,7 @@ impl<'a, C> Mapper for Mapper1<'a, C> where C: Cartridge { } } +#[repr(C)] pub struct Mapper2<'a, C> where C: Cartridge { cart: C, prg_banks: [&'a [u8]; 2], @@ -282,6 +286,7 @@ impl<'a, C> Mapper2<'a, C> where C: Cartridge { impl<'a, C> Mapper for Mapper2<'a, C> where C: Cartridge { fn get_cart(&self) -> &Cartridge {&self.cart} + fn get_cart_mut(&mut self) -> &mut Cartridge {&mut self.cart} fn load(&mut self, reader: &mut Read) -> bool { for v in self.prg_banks.iter_mut() { @@ -312,6 +317,7 @@ impl<'a, C> Mapper for Mapper2<'a, C> where C: Cartridge { } } +#[repr(C)] pub struct Mapper4<'a, C> where C: Cartridge { cart: C, prg_banks: [&'a [u8]; 4], @@ -500,6 +506,8 @@ impl<'a, C> Mapper4<'a, C> where C: Cartridge { impl<'a, C> Mapper for Mapper4<'a, C> where C: Cartridge { fn get_cart(&self) -> &Cartridge {&self.cart} + fn get_cart_mut(&mut self) -> &mut Cartridge {&mut self.cart} + fn tick(&mut self, bus: &CPUBus) { let ppu = bus.get_ppu(); if ppu.cycle != 260 { diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..ea20ac9 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,88 @@ +use core::intrinsics::transmute; +use core::mem::size_of; +use core::slice::{from_raw_parts_mut, from_raw_parts}; + +pub struct Sampler { + freq2: u32, + q0: u32, + r0: u32, + ddl: (u32, u32), + cnt: u32, + sec_cnt: u32 +} + +impl Sampler { + pub fn new(freq1: u32, freq2: u32) -> Self { + let q0 = freq1 / freq2; + let r0 = freq1 - q0 * freq2; + Sampler { + freq2, + q0, + r0, + ddl: (q0, r0), + cnt: 0, + sec_cnt: 0 + } + } + + pub fn load(&mut self, reader: &mut Read) -> bool { + load_prefix(self, 0, reader) + } + + pub fn save(&self, writer: &mut Write) -> bool { + save_prefix(self, 0, writer) + } + + pub fn tick(&mut self) -> bool { + let (q, r) = self.ddl; + if self.cnt == q { + let nr = r + self.r0; + self.ddl = if nr > self.freq2 { + (self.q0, nr - self.freq2) + } else { + (self.q0 - 1, nr) + }; + self.cnt = 0; + self.sec_cnt += 1; + if self.sec_cnt == self.freq2 { + self.sec_cnt = 0 + } + true + } else { + self.cnt += 1; + false + } + } +} + +pub trait Read { + fn read(&mut self, buf: &mut [u8]) -> Option; +} + +pub trait Write { + fn write(&mut self, buf: &[u8]) -> Option; +} + +pub fn load_prefix(obj: &mut T, ignored: usize, reader: &mut Read) -> bool { + let len = size_of::() - ignored; + match reader.read(unsafe { + from_raw_parts_mut( + transmute::<*mut T, *mut u8>(obj as *mut T), + len + )}) { + Some(x) => x == len, + None => false + } +} + +pub fn save_prefix(obj: &T, ignored: usize, writer: &mut Write) -> bool { + let len = size_of::() - ignored; + match writer.write(unsafe { + from_raw_parts( + transmute::<*const T, *const u8>(obj as *const T), + len + )}) { + Some(x) => x == len, + None => false + } +} -- cgit v1.2.3