extern crate core; use std::fs::File; use std::sync::Mutex; use std::io::Read; use std::cell::RefCell; use std::intrinsics::transmute; 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; use sdl2::audio::{AudioCallback, AudioSpecDesired}; mod memory; #[macro_use] mod mos6502; mod ppu; mod apu; mod cartridge; mod mapper; mod controller; mod disasm; use mos6502::CPU; use ppu::PPU; use apu::APU; use memory::{CPUMemory, PPUMemory}; use cartridge::{BankType, MirrorType, Cartridge}; use controller::stdctl; const PIXEL_SIZE: u32 = 3; 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} fn set_mirror_type(&mut self, mt: MirrorType) {self.mirror_type = mt} } struct SDLWindow<'a> { canvas: sdl2::render::WindowCanvas, events: sdl2::EventPump, frame_buffer: [u8; FB_SIZE], texture: sdl2::render::Texture, 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(sdl_context: &'a sdl2::Sdl, p1_ctl: &'a stdctl::Joystick) -> Self { use Keycode::*; 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(), 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(always)] 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 pattern = [0; 3 * PIXEL_SIZE as usize]; let mut base = ((y as u32 * PIXEL_SIZE) as usize * FB_PITCH) + (x as u32 * 3 * PIXEL_SIZE) as usize; { let mut i = 0; for _ in 0..PIXEL_SIZE { pattern[i] = r; pattern[i + 1] = g; pattern[i + 2] = b; i += 3; } } for _ in 0..PIXEL_SIZE { self.frame_buffer[base..base + 3 * PIXEL_SIZE as usize] .copy_from_slice(&pattern[..]); base += FB_PITCH; } } fn render(&mut self) { self.texture.update(None, &self.frame_buffer, FB_PITCH).unwrap(); //canvas.set_draw_color(Color::RGB(128, 128, 128)); } fn frame(&mut self) { 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);} } } struct CircularBuffer { buffer: [i16; 65536], head: usize, tail: usize } impl CircularBuffer { fn new() -> Self { CircularBuffer { buffer: [0; 65536], head: 0, tail: 1 } } fn enque(&mut self, sample: i16) { self.buffer[self.tail] = sample; self.tail += 1; if self.tail == self.buffer.len() { self.tail = 0 } } fn deque(&mut self) -> i16 { let res = self.buffer[self.head]; { let mut h = self.head + 1; if h == self.buffer.len() { h = 0 } if h != self.tail { self.head = h } } res } } struct SDLAudio<'a> { timer: Instant, duration_per_frame: Duration, lagged: Duration, cnt: u16, buffer: &'a Mutex<&'a mut CircularBuffer>, } impl<'a> SDLAudio<'a> { fn new(lbuff: &'a Mutex<&'a mut CircularBuffer>) -> Self { SDLAudio { timer: Instant::now(), duration_per_frame: Duration::from_millis(10), lagged: Duration::new(0, 0), cnt: 0, buffer: lbuff, } } } struct SDLAudioPlayback<'a>(&'a Mutex<&'a mut CircularBuffer>); impl<'a> AudioCallback for SDLAudioPlayback<'a> { type Channel = i16; fn callback(&mut self, out: &mut[i16]) { let mut b = self.0.lock().unwrap(); for x in out.iter_mut() { *x = b.deque() } } } impl<'a> apu::Speaker for SDLAudio<'a> { fn queue(&mut self, sample: u16) { let mut b = self.buffer.lock().unwrap(); b.enque(sample.wrapping_sub(32768) as i16); self.cnt += 1; if self.cnt == apu::AUDIO_SAMPLE_FREQ as u16 / 100 { let e = self.timer.elapsed(); if self.duration_per_frame > e { let mut diff = self.duration_per_frame - e; let delta = std::cmp::min(diff, self.lagged); diff -= delta; self.lagged -= delta; sleep(diff); } else { self.lagged += e - self.duration_per_frame } self.cnt = 0; self.timer = Instant::now(); } } } #[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 print_cpu_trace(cpu: &CPU) { use disasm; let pc = cpu.get_pc(); let mem = cpu.get_mem(); let opcode = mem.read_without_tick(pc) as usize; let len = mos6502::INST_LENGTH[opcode]; let mut code = vec![0; len as usize]; for i in 0..len as u16 { code[i as usize] = mem.read_without_tick(pc + i); } println!("0x{:04x} {} a:{:02x} x:{:02x} y:{:02x} s: {:02x} sp: {:02x}", pc, disasm::parse(opcode as u8, &code[1..]), cpu.get_a(), cpu.get_x(), cpu.get_y(), cpu.get_status(), cpu.get_sp()); } 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!(""); } } */ /* audio */ let sdl_context = sdl2::init().unwrap(); let audio_subsystem = sdl_context.audio().unwrap(); let mut buff = CircularBuffer::new(); let lbuff = Mutex::new(&mut buff); let mut spkr = SDLAudio::new(&lbuff); let desired_spec = AudioSpecDesired { freq: Some(apu::AUDIO_SAMPLE_FREQ as i32), channels: Some(1), samples: Some(4096) }; let device = audio_subsystem.open_playback(None, &desired_spec, |_| { SDLAudioPlayback(&lbuff) }).unwrap(); device.resume(); let p1ctl = stdctl::Joystick::new(); let cart = SimpleCart::new(chr_rom, prg_rom, sram, mirror); let mut win = SDLWindow::new(&sdl_context, &p1ctl); let mut m: Box = match mapper_id { 0 | 2 => Box::new(mapper::Mapper2::new(cart)), 1 => Box::new(mapper::Mapper1::new(cart)), _ => panic!("unsupported mapper {}", mapper_id) }; let mapper = RefCell::new(&mut (*m) as &mut mapper::Mapper); let mut cpu = CPU::new(CPUMemory::new(&mapper, Some(&p1ctl), None)/*, &mut f*/); let mut ppu = PPU::new(PPUMemory::new(&mapper), &mut win); 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); cpu.powerup(); loop { /* consume the leftover cycles from the last instruction */ while cpu.cycle > 0 { cpu.mem.bus.tick() } //print_cpu_trace(&cpu); cpu.step(); } }