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::{Cell, RefCell, UnsafeCell}; 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: UnsafeCell, events: UnsafeCell, frame_buffer: UnsafeCell<[u8; FB_SIZE]>, texture: UnsafeCell, timer: Cell, duration_per_frame: Duration, p1_button_states: UnsafeCell<[bool; 8]>, p1_ctl: &'a Joystick, p1_keymap: UnsafeCell<[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 res = SDLWindow { canvas: UnsafeCell::new(canvas), events: UnsafeCell::new(sdl_context.event_pump().unwrap()), frame_buffer: UnsafeCell::new([0; FB_SIZE]), texture: UnsafeCell::new(texture_creator.create_texture_streaming( PixelFormatEnum::RGB24, WIN_WIDTH, WIN_HEIGHT).unwrap()), timer: Cell::new(Instant::now()), duration_per_frame: Duration::from_millis(1000 / 60), p1_button_states: UnsafeCell::new([false; 8]), p1_ctl, p1_keymap: UnsafeCell::new([Button::Null; 256]) }; let keymap = unsafe{&mut *res.p1_keymap.get()}; 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(&self) -> bool { use Keycode::*; let p1_button_states = unsafe {&mut *self.p1_button_states.get()}; let p1_keymap = unsafe {&mut *self.p1_keymap.get()}; let events = unsafe {&mut *self.events.get()}; for event in 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(&self, x: u8, y: u8, color: u8) { unsafe { 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.get())[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(&self) { let canvas = unsafe{&mut *self.canvas.get()}; let fb = unsafe{&*self.frame_buffer.get()}; let texture = unsafe{&mut *self.texture.get()}; texture.update(None, fb, FB_PITCH).unwrap(); canvas.clear(); canvas.copy(&texture, None, Some(Rect::new(0, 0, WIN_WIDTH, WIN_HEIGHT))).unwrap(); canvas.present(); if self.poll() {std::process::exit(0);} let e = self.timer.get().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.set(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 cart = cartridge::Cartridge::new(chr_rom, prg_rom, sram, mirror); let p1ctl = Joystick::new(); let win = SDLWindow::new(&p1ctl); let mapper = match mapper_id { 0 | 2 => mapper::Mapper2::new(&cart), _ => panic!("unsupported mapper {}", mapper_id) }; let mut ppu = ppu::PPU::new(memory::PPUMemory::new(&mapper, &cart), &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; } } }