From 2f4262c1273108abfcce00e897c520f5a7f4be41 Mon Sep 17 00:00:00 2001 From: Determinant Date: Fri, 17 Nov 2017 15:00:24 -0500 Subject: rearrange the proj --- .gitignore | 1 - Cargo.lock | 119 +++++++++++++++++++++ Cargo.toml | 11 +- README.rst | 5 +- src/bin.rs | 305 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/cartridge.rs | 32 +----- src/controller.rs | 34 +++--- src/disasm.rs | 97 +++++++++++++++++ src/lib.rs | 2 +- src/main.rs | 282 -------------------------------------------------- src/mapper.rs | 10 +- src/memory.rs | 11 -- src/mos6502.rs | 100 +----------------- 13 files changed, 559 insertions(+), 450 deletions(-) create mode 100644 Cargo.lock create mode 100644 src/bin.rs create mode 100644 src/disasm.rs delete mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 143b1ca..0196246 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target/ **/*.rs.bk -Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..30e2288 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,119 @@ +[root] +name = "runes" +version = "0.1.0" +dependencies = [ + "sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fuchsia-zircon" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sdl2-sys" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" +"checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" +"checksum lazy_static 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "236eb37a62591d4a41a89b7763d7de3e06ca02d5ab2815446a8bae5d2f8c2d57" +"checksum libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "5ba3df4dcb460b9dfbd070d41c94c19209620c191b0340b929ce748a2bcd42d2" +"checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" +"checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" +"checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" +"checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" +"checksum rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6475140dfd8655aeb72e1fd4b7a1cc1c202be65d71669476e392fe62532b9edd" +"checksum sdl2 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74c2a98a354b20713b90cce70aef9e927e46110d1bc4ef728fd74e0d53eba60" +"checksum sdl2-sys 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c543ce8a6e33a30cb909612eeeb22e693848211a84558d5a00bb11e791b7ab7" diff --git a/Cargo.toml b/Cargo.toml index 8954ae9..566905d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,16 @@ name = "runes" version = "0.1.0" authors = ["Determinant "] +description = "A no-std NES emulator library written purely in Rust." -[dependencies.sdl2] +[dev-dependencies.sdl2] version = "0.31" features = ["unsafe_textures"] + +[lib] +name = "runes" +path = "src/lib.rs" + +[[example]] +name = "runes" +path = "src/bin.rs" diff --git a/README.rst b/README.rst index 6712025..fc94bbf 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ RuNES ===== -As we know, there have been a ton of NES emulators implementation in various +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 (instruction level accurate), light-weight and efficient emulation @@ -38,4 +38,5 @@ Build :: - cargo build --release + cargo build --release # build the library only (no std dep) + cargo build --examples --release # or build the example emulator (requires std) diff --git a/src/bin.rs b/src/bin.rs new file mode 100644 index 0000000..a66b430 --- /dev/null +++ b/src/bin.rs @@ -0,0 +1,305 @@ +extern crate core; +mod memory; +mod mos6502; +mod ppu; +mod cartridge; +mod mapper; +mod controller; + +use std::fs::File; +use std::io::Read; +use core::cell::RefCell; +use core::intrinsics::transmute; +use cartridge::*; +use controller::stdctl; +use std::time::{Instant, Duration}; +use std::thread::sleep; + +extern crate sdl2; + +use sdl2::pixels::Color; +use sdl2::rect::Rect; +use sdl2::pixels::PixelFormatEnum; +use sdl2::event::Event; +use sdl2::keyboard::Keycode; + +const PIXEL_SIZE: u32 = 2; +const RGB_COLORS: [u32; 64] = [ + 0x666666, 0x002a88, 0x1412a7, 0x3b00a4, 0x5c007e, 0x6e0040, 0x6c0600, 0x561d00, + 0x333500, 0x0b4800, 0x005200, 0x004f08, 0x00404d, 0x000000, 0x000000, 0x000000, + 0xadadad, 0x155fd9, 0x4240ff, 0x7527fe, 0xa01acc, 0xb71e7b, 0xb53120, 0x994e00, + 0x6b6d00, 0x388700, 0x0c9300, 0x008f32, 0x007c8d, 0x000000, 0x000000, 0x000000, + 0xfffeff, 0x64b0ff, 0x9290ff, 0xc676ff, 0xf36aff, 0xfe6ecc, 0xfe8170, 0xea9e22, + 0xbcbe00, 0x88d800, 0x5ce430, 0x45e082, 0x48cdde, 0x4f4f4f, 0x000000, 0x000000, + 0xfffeff, 0xc0dfff, 0xd3d2ff, 0xe8c8ff, 0xfbc2ff, 0xfec4ea, 0xfeccc5, 0xf7d8a5, + 0xe4e594, 0xcfef96, 0xbdf4ab, 0xb3f3cc, 0xb5ebf2, 0xb8b8b8, 0x000000, 0x000000, +]; + +const PIX_WIDTH: usize = 256; +const PIX_HEIGHT: usize = 240; +const FB_PITCH: usize = PIX_WIDTH * 3 * (PIXEL_SIZE as usize); +const FB_SIZE: usize = PIX_HEIGHT * FB_PITCH * (PIXEL_SIZE as usize); +const WIN_WIDTH: u32 = PIX_WIDTH as u32 * PIXEL_SIZE; +const WIN_HEIGHT: u32 = PIX_HEIGHT as u32 * PIXEL_SIZE; + +pub struct SimpleCart { + chr_rom: Vec, + prg_rom: Vec, + sram: Vec, + pub mirror_type: MirrorType +} + +impl SimpleCart { + pub fn new(chr_rom: Vec, + prg_rom: Vec, + sram: Vec, + mirror_type: MirrorType) -> Self { + SimpleCart{chr_rom, prg_rom, sram, mirror_type} + } +} + +impl Cartridge for SimpleCart { + fn get_size(&self, kind: BankType) -> usize { + match kind { + BankType::PrgRom => self.prg_rom.len(), + BankType::ChrRom => self.chr_rom.len(), + BankType::Sram => self.sram.len() + } + } + fn get_bank(&mut self, base: usize, size: usize, kind: BankType) -> *mut [u8] { + &mut (match kind { + BankType::PrgRom => &mut self.prg_rom, + BankType::ChrRom => &mut self.chr_rom, + BankType::Sram => &mut self.sram, + })[base..base + size] + } + fn get_mirror_type(&self) -> MirrorType {self.mirror_type} +} + +struct SDLWindow<'a> { + canvas: sdl2::render::WindowCanvas, + events: sdl2::EventPump, + frame_buffer: [u8; FB_SIZE], + texture: sdl2::render::Texture, + timer: Instant, + duration_per_frame: Duration, + p1_button_state: u8, + p1_ctl: &'a stdctl::Joystick, + p1_keymap: [u8; 256], +} + +macro_rules! gen_keymap { + ($tab: ident, [$($x: expr, $y: expr), *]) => { + { + $( + $tab[($x as usize) & 0xff] = $y; + )* + } + }; +} + +impl<'a> SDLWindow<'a> { + fn new(p1_ctl: &'a stdctl::Joystick) -> Self { + use Keycode::*; + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem.window("RuNES", WIN_WIDTH, WIN_HEIGHT) + .position_centered() + .opengl() + .build() + .unwrap(); + let mut canvas = window.into_canvas() + .accelerated() + .build().unwrap(); + let texture_creator = canvas.texture_creator(); + canvas.set_draw_color(Color::RGB(255, 255, 255)); + canvas.clear(); + canvas.present(); + let mut res = SDLWindow { + canvas, + events: sdl_context.event_pump().unwrap(), + frame_buffer: [0; FB_SIZE], + texture: texture_creator.create_texture_streaming( + PixelFormatEnum::RGB24, WIN_WIDTH, WIN_HEIGHT).unwrap(), + timer: Instant::now(), + duration_per_frame: Duration::from_millis(1000 / 60), + p1_button_state: 0, + p1_ctl, p1_keymap: [stdctl::NULL; 256] + }; + { + let keymap = &mut res.p1_keymap; + gen_keymap!(keymap, [I, stdctl::UP, + K, stdctl::DOWN, + J, stdctl::LEFT, + L, stdctl::RIGHT, + Z, stdctl::A, + X, stdctl::B, + Return, stdctl::START, + S, stdctl::SELECT, + Up, stdctl::UP, + Down, stdctl::DOWN, + Left, stdctl::LEFT, + Right, stdctl::RIGHT + ]); + } + res + } + + #[inline] + fn poll(&mut self) -> bool { + use Keycode::*; + let p1_keymap = &self.p1_keymap; + for event in self.events.poll_iter() { + match event { + Event::Quit {..} | Event::KeyDown { keycode: Some(Escape), .. } => { + return true; + }, + Event::KeyDown { keycode: Some(c), .. } => { + self.p1_button_state |= p1_keymap[(c as usize) & 0xff]; + self.p1_ctl.set(self.p1_button_state) + }, + Event::KeyUp { keycode: Some(c), .. } => { + self.p1_button_state &= !p1_keymap[(c as usize) & 0xff]; + self.p1_ctl.set(self.p1_button_state) + }, + _ => () + } + } + false + } +} + +#[inline] +fn get_rgb(color: u8) -> (u8, u8, u8) { + let c = RGB_COLORS[color as usize]; + ((c >> 16) as u8, ((c >> 8) & 0xff) as u8, (c & 0xff) as u8) +} + +impl<'a> ppu::Screen for SDLWindow<'a> { + fn put(&mut self, x: u8, y: u8, color: u8) { + let (r, g, b) = get_rgb(color); + let mut base = ((y as u32 * PIXEL_SIZE) as usize * FB_PITCH) + + (x as u32 * 3 * PIXEL_SIZE) as usize; + for _ in 0..PIXEL_SIZE { + let slice = &mut self.frame_buffer[base..base + 3 * PIXEL_SIZE as usize]; + let mut j = 0; + for _ in 0..PIXEL_SIZE { + slice[j] = r; + slice[j + 1] = g; + slice[j + 2] = b; + j += 3; + } + base += FB_PITCH; + } + } + + fn render(&mut self) { + self.texture.update(None, &self.frame_buffer, FB_PITCH).unwrap(); + self.canvas.clear(); + self.canvas.copy(&self.texture, None, Some(Rect::new(0, 0, WIN_WIDTH, WIN_HEIGHT))).unwrap(); + self.canvas.present(); + if self.poll() {std::process::exit(0);} + let e = self.timer.elapsed(); + if self.duration_per_frame > e { + let diff = self.duration_per_frame - e; + sleep(diff); + //println!("{} faster", diff.subsec_nanos() as f64 / 1e6); + } else { + //println!("{} slower", (e - duration_per_frame).subsec_nanos() as f64 / 1e6); + } + self.timer = Instant::now(); + //canvas.set_draw_color(Color::RGB(128, 128, 128)); + } +} + +#[repr(C, packed)] +struct INesHeader { + magic: [u8; 4], + prg_rom_nbanks: u8, + chr_rom_nbanks: u8, + flags6: u8, + flags7: u8, + prg_ram_nbanks: u8, + flags9: u8, + flags10: u8, + padding: [u8; 5] +} + +fn main() { + let fname = std::env::args().nth(1).unwrap(); + let mut file = File::open(fname).unwrap(); + let mut rheader = [0; 16]; + println!("read {}", file.read(&mut rheader[..]).unwrap()); + let header = unsafe{transmute::<[u8; 16], INesHeader>(rheader)}; + let mirror = match ((header.flags6 >> 2) & 2) | (header.flags6 & 1) { + 0 => MirrorType::Horizontal, + 1 => MirrorType::Vertical, + 2 => MirrorType::Single0, + 3 => MirrorType::Single1, + _ => MirrorType::Four, + }; + let mapper_id = (header.flags7 & 0xf0) | (header.flags6 >> 4); + println!("maigc:{} prg:{} chr:{} mirror:{} mapper:{}", + std::str::from_utf8(&header.magic).unwrap(), + header.prg_rom_nbanks, + header.chr_rom_nbanks, + mirror as u8, + mapper_id); + if header.flags6 & 0x04 == 0x04 { + let mut trainer: [u8; 512] = unsafe{std::mem::uninitialized()}; + file.read(&mut trainer[..]).unwrap(); + println!("skipping trainer"); + } + + let prg_len = header.prg_rom_nbanks as usize * 0x4000; + let mut chr_len = header.chr_rom_nbanks as usize * 0x2000; + if chr_len == 0 { + chr_len = 0x2000; + } + let mut prg_rom = Vec::::with_capacity(prg_len); + let mut chr_rom = Vec::::with_capacity(chr_len); + unsafe { + prg_rom.set_len(prg_len); + chr_rom.set_len(chr_len); + } + let sram = vec![0; 0x4000]; + println!("read prg {}", file.read(&mut prg_rom[..]).unwrap()); + /* + for (i, v) in prg_rom.iter().enumerate() { + print!("{:02x} ", v); + if i & 15 == 15 { + println!(" {:04x}", i); + } + } + */ + println!("read chr {}", file.read(&mut chr_rom[..]).unwrap()); + /* + for (i, v) in chr_rom.iter().enumerate() { + print!("{:02x} ", v); + if i & 15 == 15 { + println!(""); + } + } + */ + let p1ctl = stdctl::Joystick::new(); + let mut win = SDLWindow::new(&p1ctl); + let mut m = match mapper_id { + 0 | 2 => mapper::Mapper2::new(SimpleCart::new(chr_rom, prg_rom, sram, mirror)), + _ => panic!("unsupported mapper {}", mapper_id) + }; + let mt = m.get_cart().get_mirror_type(); + let mapper = RefCell::new(&mut m as &mut memory::VMem); + let mut ppu = ppu::PPU::new(memory::PPUMemory::new(&mapper, mt), &mut win); + let mut cpu = mos6502::CPU::new(memory::CPUMemory::new(&mut ppu, &mapper, Some(&p1ctl), None)); + let ptr = &mut cpu as *mut mos6502::CPU; + cpu.mem.init(ptr); + loop { + cpu.step(); + while cpu.cycle > 0 { + if ppu.tick() || ppu.tick() || ppu.tick() { + cpu.trigger_delayed_nmi() + } + cpu.cycle -= 1; + } + } +} diff --git a/src/cartridge.rs b/src/cartridge.rs index 0d21a42..524ef12 100644 --- a/src/cartridge.rs +++ b/src/cartridge.rs @@ -15,32 +15,8 @@ pub enum BankType { Sram, /* save ram */ } -pub struct Cartridge { - chr_rom: Vec, - prg_rom: Vec, - sram: Vec, - pub mirror_type: MirrorType -} - -impl Cartridge { - pub fn get_size(&self, kind: BankType) -> usize { - match kind { - BankType::PrgRom => self.prg_rom.len(), - BankType::ChrRom => self.chr_rom.len(), - BankType::Sram => self.sram.len() - } - } - pub fn get_bank(&mut self, base: usize, size: usize, kind: BankType) -> *mut [u8] { - &mut (match kind { - BankType::PrgRom => &mut self.prg_rom, - BankType::ChrRom => &mut self.chr_rom, - BankType::Sram => &mut self.sram, - })[base..base + size] - } - pub fn new(chr_rom: Vec, - prg_rom: Vec, - sram: Vec, - mirror_type: MirrorType) -> Self { - Cartridge{chr_rom, prg_rom, sram, mirror_type} - } +pub trait Cartridge { + fn get_size(&self, kind: BankType) -> usize; + fn get_bank(&mut self, base: usize, size: usize, kind: BankType) -> *mut [u8]; + fn get_mirror_type(&self) -> MirrorType; } diff --git a/src/controller.rs b/src/controller.rs index 2685cd1..f2ed4f7 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -8,18 +8,16 @@ pub trait Controller { pub mod stdctl { use core::cell::Cell; use controller::Controller; - #[derive(Copy, Clone)] - pub enum Button { - A = 0, - B = 1, - Select = 2, - Start = 3, - Up = 4, - Down = 5, - Left = 6, - Right = 7, - Null = 8, - } + pub const A: u8 = 1 << 0; + pub const B: u8 = 1 << 1; + pub const SELECT: u8 = 1 << 2; + pub const START: u8 = 1 << 3; + pub const UP: u8 = 1 << 4; + pub const DOWN: u8 = 1 << 5; + pub const LEFT: u8 = 1 << 6; + pub const RIGHT: u8 = 1 << 7; + pub const NULL: u8 = 0; + pub struct Joystick { strobe: Cell, reg: Cell, @@ -31,15 +29,9 @@ pub mod stdctl { Joystick{reg: Cell::new(0), strobe: Cell::new(false), back_reg: Cell::new(0)} } - pub fn set(&self, buttons: &[bool]) { - let mut reg = 0; - for (i, v) in buttons.iter().enumerate() { - if *v { - reg |= 1 << i; - } - } - self.reg.set(reg); - self.back_reg.set(reg); + pub fn set(&self, buttons: u8) { + self.reg.set(buttons); + self.back_reg.set(buttons); } } diff --git a/src/disasm.rs b/src/disasm.rs new file mode 100644 index 0000000..c9b3696 --- /dev/null +++ b/src/disasm.rs @@ -0,0 +1,97 @@ +use mos6502::{make_optable, make_addrtable}; + +mod disops { + make_optable!(OPS, &str); + ids2strs!(adc, and, asl, bcc, bcs, beq, bit, bmi, + bne, bpl, brk, bvc, bvs, clc, cld, cli, + clv, cmp, cpx, cpy, dec, dex, dey, eor, + inc, inx, iny, jmp, jsr, lda, ldx, ldy, + lsr, nop, ora, pha, php, pla, plp, rol, + ror, rti, rts, sbc, sec, sed, sei, sta, + stx, sty, tax, tay, tsx, txa, txs, tya, nil); +} + +mod disaddr { + pub type T<'a, 'b> = &'a mut Iterator; + make_addrtable!(ADDR_MODES, fn (T) -> String); + fn acc(_code: T) -> String { + "a".to_string() + } + fn imm(code: T) -> String { + format!("#${:02x}", code.next().unwrap()) + } + fn zpg(code: T) -> String { + format!("${:02x}", code.next().unwrap()) + } + fn zpx(code: T) -> String { + format!("${:02x}, x", code.next().unwrap()) + } + fn zpy(code: T) -> String { + format!("${:02x}, y", code.next().unwrap()) + } + fn rel(code: T) -> String { + let b = *code.next().unwrap() as i8 as i16; + if b >= 0 { + format!("+${:02x}, x", b) + } else { + format!("-${:02x}, x", -b) + } + } + fn abs(code: T) -> String { + let low = *code.next().unwrap() as u16; + let high = *code.next().unwrap() as u16; + format!("${:04x}", (high << 8) | low) + } + fn abx(code: T) -> String { + let low = *code.next().unwrap() as u16; + let high = *code.next().unwrap() as u16; + format!("${:04x}, x", (high << 8) | low) + } + fn aby(code: T) -> String { + let low = *code.next().unwrap() as u16; + let high = *code.next().unwrap() as u16; + format!("${:04x}, y", (high << 8) | low) + } + fn ind(code: T) -> String { + let low = *code.next().unwrap() as u16; + let high = *code.next().unwrap() as u16; + format!("(${:04x})", (high << 8) | low) + } + fn xin(code: T) -> String { + format!("(${:02x}, x)", code.next().unwrap()) + } + fn iny(code: T) -> String { + format!("(${:02x}), y", code.next().unwrap()) + } + fn nil(_code: T) -> String { + "".to_string() + } +} + +pub struct Disassembler<'a, T> where T: Iterator { + raw_code: T +} + +impl<'a, T> Disassembler<'a, T> where T: Iterator { + pub fn new(raw_code: T) -> Self { + Disassembler{raw_code} + } + fn parse(opcode: u8, code: &mut T) -> String { + format!("{} {}", disops::OPS[opcode as usize], + disaddr::ADDR_MODES[opcode as usize](code)) + } +} + +impl<'a, T> Iterator for Disassembler<'a, T> where T: Iterator { + type Item = String; + fn next(&mut self) -> Option { + match self.raw_code.next() { + Some(opcode) => Some(Disassembler::parse(*opcode, &mut self.raw_code)), + None => None + } + } +} + +pub fn parse(opcode: u8, code: &[u8]) -> String { + Disassembler::parse(opcode, &mut code.iter()) +} diff --git a/src/lib.rs b/src/lib.rs index 0c35e3d..18ec0a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -extern crate core; +#![no_std] mod memory; mod mos6502; mod ppu; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 6a350ff..0000000 --- a/src/main.rs +++ /dev/null @@ -1,282 +0,0 @@ -extern crate core; -mod memory; -mod mos6502; -mod ppu; -mod cartridge; -mod mapper; -mod controller; - -use std::fs::File; -use std::io::Read; -use core::cell::RefCell; -use core::intrinsics::transmute; -use cartridge::*; -use controller::stdctl::{Joystick, Button}; -use std::time::{Instant, Duration}; -use std::thread::sleep; - -extern crate sdl2; - -use sdl2::pixels::Color; -use sdl2::rect::Rect; -use sdl2::pixels::PixelFormatEnum; -use sdl2::event::Event; -use sdl2::keyboard::Keycode; - -const PIXEL_SIZE: u32 = 2; -const RGB_COLORS: [u32; 64] = [ - 0x666666, 0x002a88, 0x1412a7, 0x3b00a4, 0x5c007e, 0x6e0040, 0x6c0600, 0x561d00, - 0x333500, 0x0b4800, 0x005200, 0x004f08, 0x00404d, 0x000000, 0x000000, 0x000000, - 0xadadad, 0x155fd9, 0x4240ff, 0x7527fe, 0xa01acc, 0xb71e7b, 0xb53120, 0x994e00, - 0x6b6d00, 0x388700, 0x0c9300, 0x008f32, 0x007c8d, 0x000000, 0x000000, 0x000000, - 0xfffeff, 0x64b0ff, 0x9290ff, 0xc676ff, 0xf36aff, 0xfe6ecc, 0xfe8170, 0xea9e22, - 0xbcbe00, 0x88d800, 0x5ce430, 0x45e082, 0x48cdde, 0x4f4f4f, 0x000000, 0x000000, - 0xfffeff, 0xc0dfff, 0xd3d2ff, 0xe8c8ff, 0xfbc2ff, 0xfec4ea, 0xfeccc5, 0xf7d8a5, - 0xe4e594, 0xcfef96, 0xbdf4ab, 0xb3f3cc, 0xb5ebf2, 0xb8b8b8, 0x000000, 0x000000, -]; - -const PIX_WIDTH: usize = 256; -const PIX_HEIGHT: usize = 240; -const FB_PITCH: usize = PIX_WIDTH * 3 * (PIXEL_SIZE as usize); -const FB_SIZE: usize = PIX_HEIGHT * FB_PITCH * (PIXEL_SIZE as usize); -const WIN_WIDTH: u32 = PIX_WIDTH as u32 * PIXEL_SIZE; -const WIN_HEIGHT: u32 = PIX_HEIGHT as u32 * PIXEL_SIZE; - -struct SDLWindow<'a> { - canvas: sdl2::render::WindowCanvas, - events: sdl2::EventPump, - frame_buffer: [u8; FB_SIZE], - texture: sdl2::render::Texture, - timer: Instant, - duration_per_frame: Duration, - p1_button_states: [bool; 8], - p1_ctl: &'a Joystick, - p1_keymap: [Button; 256], -} - -macro_rules! gen_keymap { - ($tab: ident, [$($x: expr, $y: expr), *]) => { - { - $( - $tab[($x as usize) & 0xff] = $y; - )* - } - }; -} - -impl<'a> SDLWindow<'a> { - fn new(p1_ctl: &'a Joystick) -> Self { - use Keycode::*; - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); - let window = video_subsystem.window("RuNES", WIN_WIDTH, WIN_HEIGHT) - .position_centered() - .opengl() - .build() - .unwrap(); - let mut canvas = window.into_canvas() - .accelerated() - .build().unwrap(); - let texture_creator = canvas.texture_creator(); - canvas.set_draw_color(Color::RGB(255, 255, 255)); - canvas.clear(); - canvas.present(); - let mut res = SDLWindow { - canvas, - events: sdl_context.event_pump().unwrap(), - frame_buffer: [0; FB_SIZE], - texture: texture_creator.create_texture_streaming( - PixelFormatEnum::RGB24, WIN_WIDTH, WIN_HEIGHT).unwrap(), - timer: Instant::now(), - duration_per_frame: Duration::from_millis(1000 / 60), - p1_button_states: [false; 8], - p1_ctl, p1_keymap: [Button::Null; 256] - }; - { - let keymap = &mut res.p1_keymap; - gen_keymap!(keymap, [I, Button::Up, - K, Button::Down, - J, Button::Left, - L, Button::Right, - Z, Button::A, - X, Button::B, - Return, Button::Start, - S, Button::Select, - Up, Button::Up, - Down, Button::Down, - Left, Button::Left, - Right, Button::Right - ]); - } - res - } - - #[inline] - fn poll(&mut self) -> bool { - use Keycode::*; - let p1_button_states = &mut self.p1_button_states; - let p1_keymap = &self.p1_keymap; - for event in self.events.poll_iter() { - match event { - Event::Quit {..} | Event::KeyDown { keycode: Some(Escape), .. } => { - return true; - }, - Event::KeyDown { keycode: Some(c), .. } => { - match p1_keymap[(c as usize) & 0xff] { - Button::Null => (), - i => { - p1_button_states[i as usize] = true; - self.p1_ctl.set(p1_button_states); - } - } - }, - Event::KeyUp { keycode: Some(c), .. } => { - match p1_keymap[(c as usize) & 0xff] { - Button::Null => (), - i => { - p1_button_states[i as usize] = false; - self.p1_ctl.set(p1_button_states); - } - } - }, - _ => () - } - } - false - } -} - -#[inline] -fn get_rgb(color: u8) -> (u8, u8, u8) { - let c = RGB_COLORS[color as usize]; - ((c >> 16) as u8, ((c >> 8) & 0xff) as u8, (c & 0xff) as u8) -} - -impl<'a> ppu::Screen for SDLWindow<'a> { - fn put(&mut self, x: u8, y: u8, color: u8) { - let (r, g, b) = get_rgb(color); - let mut base = ((y as u32 * PIXEL_SIZE) as usize * FB_PITCH) + - (x as u32 * 3 * PIXEL_SIZE) as usize; - for _ in 0..PIXEL_SIZE { - let slice = &mut self.frame_buffer[base..base + 3 * PIXEL_SIZE as usize]; - let mut j = 0; - for _ in 0..PIXEL_SIZE { - slice[j] = r; - slice[j + 1] = g; - slice[j + 2] = b; - j += 3; - } - base += FB_PITCH; - } - } - - fn render(&mut self) { - self.texture.update(None, &self.frame_buffer, FB_PITCH).unwrap(); - self.canvas.clear(); - self.canvas.copy(&self.texture, None, Some(Rect::new(0, 0, WIN_WIDTH, WIN_HEIGHT))).unwrap(); - self.canvas.present(); - if self.poll() {std::process::exit(0);} - let e = self.timer.elapsed(); - if self.duration_per_frame > e { - let diff = self.duration_per_frame - e; - sleep(diff); - //println!("{} faster", diff.subsec_nanos() as f64 / 1e6); - } else { - //println!("{} slower", (e - duration_per_frame).subsec_nanos() as f64 / 1e6); - } - self.timer = Instant::now(); - //canvas.set_draw_color(Color::RGB(128, 128, 128)); - } -} - -#[repr(C, packed)] -struct INesHeader { - magic: [u8; 4], - prg_rom_nbanks: u8, - chr_rom_nbanks: u8, - flags6: u8, - flags7: u8, - prg_ram_nbanks: u8, - flags9: u8, - flags10: u8, - padding: [u8; 5] -} - -fn main() { - let fname = std::env::args().nth(1).unwrap(); - let mut file = File::open(fname).unwrap(); - let mut rheader = [0; 16]; - println!("read {}", file.read(&mut rheader[..]).unwrap()); - let header = unsafe{transmute::<[u8; 16], INesHeader>(rheader)}; - let mirror = match ((header.flags6 >> 2) & 2) | (header.flags6 & 1) { - 0 => MirrorType::Horizontal, - 1 => MirrorType::Vertical, - 2 => MirrorType::Single0, - 3 => MirrorType::Single1, - _ => MirrorType::Four, - }; - let mapper_id = (header.flags7 & 0xf0) | (header.flags6 >> 4); - println!("maigc:{} prg:{} chr:{} mirror:{} mapper:{}", - std::str::from_utf8(&header.magic).unwrap(), - header.prg_rom_nbanks, - header.chr_rom_nbanks, - mirror as u8, - mapper_id); - if header.flags6 & 0x04 == 0x04 { - let mut trainer: [u8; 512] = unsafe{std::mem::uninitialized()}; - file.read(&mut trainer[..]).unwrap(); - println!("skipping trainer"); - } - - let prg_len = header.prg_rom_nbanks as usize * 0x4000; - let mut chr_len = header.chr_rom_nbanks as usize * 0x2000; - if chr_len == 0 { - chr_len = 0x2000; - } - let mut prg_rom = Vec::::with_capacity(prg_len); - let mut chr_rom = Vec::::with_capacity(chr_len); - unsafe { - prg_rom.set_len(prg_len); - chr_rom.set_len(chr_len); - } - let sram = vec![0; 0x4000]; - println!("read prg {}", file.read(&mut prg_rom[..]).unwrap()); - /* - for (i, v) in prg_rom.iter().enumerate() { - print!("{:02x} ", v); - if i & 15 == 15 { - println!(" {:04x}", i); - } - } - */ - println!("read chr {}", file.read(&mut chr_rom[..]).unwrap()); - /* - for (i, v) in chr_rom.iter().enumerate() { - print!("{:02x} ", v); - if i & 15 == 15 { - println!(""); - } - } - */ - let p1ctl = Joystick::new(); - let mut win = SDLWindow::new(&p1ctl); - let mut m = match mapper_id { - 0 | 2 => mapper::Mapper2::new(cartridge::Cartridge::new(chr_rom, prg_rom, sram, mirror)), - _ => panic!("unsupported mapper {}", mapper_id) - }; - let mt = m.get_cart().mirror_type; - let mapper = RefCell::new(&mut m as &mut memory::VMem); - let mut ppu = ppu::PPU::new(memory::PPUMemory::new(&mapper, mt), &mut win); - let mut cpu = mos6502::CPU::new(memory::CPUMemory::new(&mut ppu, &mapper, Some(&p1ctl), None)); - let ptr = &mut cpu as *mut mos6502::CPU; - cpu.mem.init(ptr); - loop { - cpu.step(); - while cpu.cycle > 0 { - if ppu.tick() || ppu.tick() || ppu.tick() { - cpu.trigger_delayed_nmi() - } - cpu.cycle -= 1; - } - } -} diff --git a/src/mapper.rs b/src/mapper.rs index fc02ed2..583c452 100644 --- a/src/mapper.rs +++ b/src/mapper.rs @@ -3,8 +3,8 @@ extern crate core; use memory::VMem; use cartridge::{Cartridge, BankType}; -pub struct Mapper2<'a> { - cart: Cartridge, +pub struct Mapper2<'a, C> where C: Cartridge { + cart: C, prg_bank1: &'a [u8], prg_bank2: &'a [u8], chr_bank: &'a mut [u8], @@ -12,7 +12,7 @@ pub struct Mapper2<'a> { prg_nbank: usize } -impl<'a> VMem for Mapper2<'a> { +impl<'a, C> VMem for Mapper2<'a, C> where C: Cartridge { fn read(&self, addr: u16) -> u8 { let addr = addr as usize; if addr < 0x2000 { /* 0x2000 size bank */ @@ -46,8 +46,8 @@ impl<'a> VMem for Mapper2<'a> { } } -impl<'a> Mapper2<'a> { - pub fn new(cart: Cartridge) -> Self { +impl<'a, C> Mapper2<'a, C> where C: Cartridge { + pub fn new(cart: C) -> Self { unsafe { let nbank = cart.get_size(BankType::PrgRom) >> 14; let null = core::mem::uninitialized(); diff --git a/src/memory.rs b/src/memory.rs index 6a62678..fb07ad5 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -115,17 +115,6 @@ impl<'a> PPUMemory<'a> { mirror_type, mapper} } - - pub fn dump(&self) { - for (i, v) in self.palette.iter().enumerate() { - print!("{:02x} ", *v); - if (i & 0x7) == 0x7 {println!("@{:02x}", i)} - } - for (i, v) in self.nametable.iter().enumerate() { - print!("{:02x} ", *v); - if (i & 0x1f) == 0x1f {println!("@{:02x}", i)} - } - } } const MIRROR_IDX: [[u8; 4]; 5] = [ diff --git a/src/mos6502.rs b/src/mos6502.rs index 9dd3d85..f279a2a 100644 --- a/src/mos6502.rs +++ b/src/mos6502.rs @@ -1,4 +1,6 @@ #![allow(dead_code)] +#![allow(unused_macros)] + use memory::{CPUMemory, VMem}; pub const CPU_FREQ: u32 = 1789773; macro_rules! make_optable { @@ -121,104 +123,6 @@ macro_rules! ids2strs { }; } -pub mod disasm { - mod disops { - make_optable!(OPS, &str); - ids2strs!(adc, and, asl, bcc, bcs, beq, bit, bmi, - bne, bpl, brk, bvc, bvs, clc, cld, cli, - clv, cmp, cpx, cpy, dec, dex, dey, eor, - inc, inx, iny, jmp, jsr, lda, ldx, ldy, - lsr, nop, ora, pha, php, pla, plp, rol, - ror, rti, rts, sbc, sec, sed, sei, sta, - stx, sty, tax, tay, tsx, txa, txs, tya, nil); - } - - mod disaddr { - pub type T<'a, 'b> = &'a mut Iterator; - make_addrtable!(ADDR_MODES, fn (T) -> String); - fn acc(_code: T) -> String { - "a".to_string() - } - fn imm(code: T) -> String { - format!("#${:02x}", code.next().unwrap()) - } - fn zpg(code: T) -> String { - format!("${:02x}", code.next().unwrap()) - } - fn zpx(code: T) -> String { - format!("${:02x}, x", code.next().unwrap()) - } - fn zpy(code: T) -> String { - format!("${:02x}, y", code.next().unwrap()) - } - fn rel(code: T) -> String { - let b = *code.next().unwrap() as i8 as i16; - if b >= 0 { - format!("+${:02x}, x", b) - } else { - format!("-${:02x}, x", -b) - } - } - fn abs(code: T) -> String { - let low = *code.next().unwrap() as u16; - let high = *code.next().unwrap() as u16; - format!("${:04x}", (high << 8) | low) - } - fn abx(code: T) -> String { - let low = *code.next().unwrap() as u16; - let high = *code.next().unwrap() as u16; - format!("${:04x}, x", (high << 8) | low) - } - fn aby(code: T) -> String { - let low = *code.next().unwrap() as u16; - let high = *code.next().unwrap() as u16; - format!("${:04x}, y", (high << 8) | low) - } - fn ind(code: T) -> String { - let low = *code.next().unwrap() as u16; - let high = *code.next().unwrap() as u16; - format!("(${:04x})", (high << 8) | low) - } - fn xin(code: T) -> String { - format!("(${:02x}, x)", code.next().unwrap()) - } - fn iny(code: T) -> String { - format!("(${:02x}), y", code.next().unwrap()) - } - fn nil(_code: T) -> String { - "".to_string() - } - } - - pub struct Disassembler<'a, T> where T: Iterator { - raw_code: T - } - - impl<'a, T> Disassembler<'a, T> where T: Iterator { - pub fn new(raw_code: T) -> Self { - Disassembler{raw_code} - } - fn parse(opcode: u8, code: &mut T) -> String { - format!("{} {}", disops::OPS[opcode as usize], - disaddr::ADDR_MODES[opcode as usize](code)) - } - } - - impl<'a, T> Iterator for Disassembler<'a, T> where T: Iterator { - type Item = String; - fn next(&mut self) -> Option { - match self.raw_code.next() { - Some(opcode) => Some(Disassembler::parse(*opcode, &mut self.raw_code)), - None => None - } - } - } - - pub fn parse(opcode: u8, code: &[u8]) -> String { - Disassembler::parse(opcode, &mut code.iter()) - } -} - macro_rules! stack_addr { ($sp: ident, $disp: expr) => (($sp.wrapping_sub($disp) as u16) | 0x0100); } -- cgit v1.2.3