aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.rst19
-rw-r--r--src/bin.rs168
-rw-r--r--src/cartridge.rs2
-rw-r--r--src/mapper.rs8
-rw-r--r--src/utils.rs88
7 files changed, 235 insertions, 54 deletions
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 <[email protected]>"]
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<u8>, reader: &mut utils::Read) -> bool {
+ let len = vec.len();
+ match reader.read(vec) {
+ Some(x) => x == len,
+ None => false
+ }
+ }
+
+ fn save_vec(vec: &Vec<u8>, 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 <[email protected]>")
- .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 <[email protected]>")
+ .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<usize>;
+}
+
+pub trait Write {
+ fn write(&mut self, buf: &[u8]) -> Option<usize>;
+}
+
+pub fn load_prefix<T>(obj: &mut T, ignored: usize, reader: &mut Read) -> bool {
+ let len = size_of::<T>() - 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<T>(obj: &T, ignored: usize, writer: &mut Write) -> bool {
+ let len = size_of::<T>() - ignored;
+ match writer.write(unsafe {
+ from_raw_parts(
+ transmute::<*const T, *const u8>(obj as *const T),
+ len
+ )}) {
+ Some(x) => x == len,
+ None => false
+ }
+}