// const RAM_SIZE: usize = 0x2000; const WRAM_SIZE: usize = 0x8000; const HIRAM_SIZE: usize = (0xFFFE - 0xFF80) + 1; pub const INTERRUPT_INPUT: u8 = 1 << 4; pub const INTERRUPT_SERIAL: u8 = 1 << 3; pub const INTERRUPT_TIMER_OVERFLOW: u8 = 1 << 2; pub const INTERRUPT_DISPLAY_STAT: u8 = 1 << 1; pub const INTERRUPT_DISPLAY_VBLANK: u8 = 1 << 0; const KEY_SPECIAL: u8 = 1 << 5; const KEY_REGULAR: u8 = 1 << 4; const KEY_DOWN: u8 = 1 << 3; const KEY_UP: u8 = 1 << 2; const KEY_LEFT: u8 = 1 << 1; const KEY_RIGHT: u8 = 1 << 0; const KEY_START: u8 = 1 << 3; const KEY_SELECT: u8 = 1 << 2; const KEY_B: u8 = 1 << 1; const KEY_A: u8 = 1 << 0; use super::display; use super::sound; use super::timer; use super::serial; use super::cartridge; extern crate sdl2; use self::sdl2::event::Event; use self::sdl2::keyboard::Keycode; #[derive(Debug)] enum Key { UP, LEFT, DOWN, RIGHT, START, SELECT, A, B } pub struct Interconnect { bios: Box<[u8]>, cartridge: cartridge::Cartridge, ram: Box<[u8]>, hiram: Box<[u8]>, wram_bank: u8, sound: sound::Sound, display: display::Display, interrupt: u8, interrupt_request_flags: u8, disable_bootrom: u8, infrared_com_port: u8, serial: serial::Serial, timer: timer::Timer, vram_dma_source_high: u8, vram_dma_source_low: u8, vram_dma_destination_high: u8, vram_dma_destination_low: u8, vram_dma_length: u8, joy_regular_keys: u8, joy_special_keys: u8, joy_switch: u8, // Used for polling SDL events cycles: u16, } impl Interconnect { pub fn new(bios: Box<[u8]>, rom: Box<[u8]>, save_file: Option) -> Interconnect { Interconnect { bios: bios, cartridge: cartridge::Cartridge::new(rom, save_file), ram: vec![0; WRAM_SIZE].into_boxed_slice(), hiram: vec![0; HIRAM_SIZE].into_boxed_slice(), wram_bank: 1, sound: sound::Sound::new(), display: display::Display::new(), // Refactor those interrupt_request_flags: 0, interrupt: 0, infrared_com_port: 0, disable_bootrom: 0, timer: timer::Timer::new(), serial: serial::Serial::new(), vram_dma_source_high: 0, vram_dma_source_low: 0, vram_dma_destination_high: 0, vram_dma_destination_low: 0, vram_dma_length: 0, // No keys pushed by default joy_regular_keys: 0x0F, joy_special_keys: 0x0F, joy_switch: 0x30, cycles: 0, } } fn press_key(&mut self, key: Key) { // println!("Press key {:?}", &key); match key { Key::UP => self.joy_regular_keys &= !KEY_UP, Key::DOWN => self.joy_regular_keys &= !KEY_DOWN, Key::RIGHT => self.joy_regular_keys &= !KEY_RIGHT, Key::LEFT => self.joy_regular_keys &= !KEY_LEFT, Key::START => self.joy_special_keys &= !KEY_START, Key::SELECT => self.joy_special_keys &= !KEY_SELECT, Key::A => self.joy_special_keys &= !KEY_A, Key::B => self.joy_special_keys &= !KEY_B, } self.interrupt_request_flags |= INTERRUPT_INPUT; } fn release_key(&mut self, key: Key) { // println!("Release key {:?}", &key); match key { Key::UP => self.joy_regular_keys |= KEY_UP, Key::DOWN => self.joy_regular_keys |= KEY_DOWN, Key::RIGHT => self.joy_regular_keys |= KEY_RIGHT, Key::LEFT => self.joy_regular_keys |= KEY_LEFT, Key::START => self.joy_special_keys |= KEY_START, Key::SELECT => self.joy_special_keys |= KEY_SELECT, Key::A => self.joy_special_keys |= KEY_A, Key::B => self.joy_special_keys |= KEY_B, } self.interrupt_request_flags |= INTERRUPT_INPUT; } // Somehow we need different timers for this. pub fn tick(&mut self, cycles: u8) { self.display.tick(cycles as u16); self.timer.tick(cycles as u16); if self.display.vblank_interrupt() { self.interrupt_request_flags |= INTERRUPT_DISPLAY_VBLANK; } if self.display.stat_interrupt() { self.interrupt_request_flags |= INTERRUPT_DISPLAY_STAT; } if self.timer.timer_interrupt() { self.interrupt_request_flags |= INTERRUPT_TIMER_OVERFLOW; } if self.serial.serial_interrupt() { self.interrupt_request_flags |= INTERRUPT_SERIAL; } // Make sure the window is responsive: if self.cycles > 500 { loop { if let Some(event) = self.display.event_pump.poll_event(){ match event { Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { self.cartridge.save(); panic!("TODO: Proper shutdown"); }, Event::KeyDown { keycode: Some(Keycode::Left), .. } => self.press_key(Key::LEFT), Event::KeyDown { keycode: Some(Keycode::Down), .. } => self.press_key(Key::DOWN), Event::KeyDown { keycode: Some(Keycode::Up), .. } => self.press_key(Key::UP), Event::KeyDown { keycode: Some(Keycode::Right), .. } => self.press_key(Key::RIGHT), Event::KeyDown { keycode: Some(Keycode::A), .. } => self.press_key(Key::START), Event::KeyDown { keycode: Some(Keycode::S), .. } => self.press_key(Key::SELECT), Event::KeyDown { keycode: Some(Keycode::Z), .. } => self.press_key(Key::A), Event::KeyDown { keycode: Some(Keycode::X), .. } => self.press_key(Key::B), Event::KeyUp { keycode: Some(Keycode::Left), .. } => self.release_key(Key::LEFT), Event::KeyUp { keycode: Some(Keycode::Down), .. } => self.release_key(Key::DOWN), Event::KeyUp { keycode: Some(Keycode::Up), .. } => self.release_key(Key::UP), Event::KeyUp { keycode: Some(Keycode::Right), .. } => self.release_key(Key::RIGHT), Event::KeyUp { keycode: Some(Keycode::A), .. } => self.release_key(Key::START), Event::KeyUp { keycode: Some(Keycode::S), .. } => self.release_key(Key::SELECT), Event::KeyUp { keycode: Some(Keycode::Z), .. } => self.release_key(Key::A), Event::KeyUp { keycode: Some(Keycode::X), .. } => self.release_key(Key::B), _ => {} } } else { break; } } self.cycles = 0; } else { self.cycles += cycles as u16; } } #[inline] pub fn is_boot_rom(&self) -> bool { self.disable_bootrom == 0 } #[inline] pub fn read_byte(&self, addr: u16) -> u8 { // TODO: Make this more beautiful. // TODO: if some flag set, use bios, otherwise only use rom // For now, just use bios match addr { 0x0000 ... 0x100 => { if self.disable_bootrom == 0 { self.bios[addr as usize] } else { self.cartridge.read_byte(addr) } }, 0x100 ... 0x7FFF => self.cartridge.read_byte(addr), 0x8000 ... 0x9FFF => self.display.read_byte(addr), 0xA000 ... 0xBFFF => self.cartridge.read_byte(addr), 0xC000 ... 0xCFFF => { self.ram[(addr - 0xC000) as usize] }, 0xD000 ... 0xDFFF => { self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000] }, 0xE000 ... 0xEFFF => { self.ram[(addr - 0xE000) as usize] } 0xFF00 => { if self.joy_switch & KEY_REGULAR == 0 { self.joy_regular_keys } else if self.joy_switch & KEY_SPECIAL == 0 { self.joy_special_keys } else { println!("Reading none from joystick?"); 0x3F } } 0xFF01 ... 0xFF02 => self.serial.read_byte(addr), 0xFF04 ... 0xFF07 => self.timer.read_byte(addr), 0xFF0F => { // println!("Reading IF: {:02X}", self.interrupt_request_flags); self.interrupt_request_flags }, 0xFF10 ... 0xFF26 => { self.sound.read_byte(addr) }, 0xFF30 ... 0xFF3F => self.sound.read_byte(addr), 0xFF40 ... 0xFF4B => { self.display.read_byte(addr) }, 0xFF50 => { self.disable_bootrom }, 0xFF51 => self.vram_dma_source_high, 0xFF52 => self.vram_dma_source_low, 0xFF53 => self.vram_dma_destination_high, 0xFF54 => self.vram_dma_destination_low, 0xFF55 => { self.vram_dma_length } 0xFF56 => { self.infrared_com_port }, 0xFF70 => self.wram_bank, 0xFF80 ... 0xFFFE => { self.hiram[(addr - 0xFF80) as usize] }, 0xFFFF => { self.interrupt } _ => { println!("Read from {:04X} not supported.", addr); 0 } } } #[inline] pub fn write_byte(&mut self, addr: u16, val: u8) { // TODO: Make this more beautful /* 0000 7FFF Cartridge 8000 9FFF Video RAM A000 BFFF External RAM (Cartridge) C000 DFFF Work RAM E000 FCFF Copy of the work RAM FE00 FE9F OAM (Sprite Attribute Table) FEA0 FEFF Unused FF00 FF7F Hardware IO FF80 FFFE High RAM FFFF FFFF Interrupt switch */ match addr { 0x0000 ... 0x7FFF => self.cartridge.write_byte(addr, val), 0x8000 ... 0x9FFF => self.display.write_byte(addr, val), 0xA000 ... 0xBFFF => self.cartridge.write_byte(addr, val), 0xC000 ... 0xCFFF => { self.ram[(addr - 0xC000) as usize] = val; }, 0xD000 ... 0xDFFF => { self.ram[(addr - 0xD000) as usize + self.wram_bank as usize * 0x1000] = val; } 0xFE00 ... 0xFE9F => self.display.write_byte(addr, val), // OAM 0xFF00 => { // Joystick select self.joy_switch = val; } 0xFF01 ... 0xFF02 => self.serial.write_byte(addr, val), 0xFF04 ... 0xFF07 => self.timer.write_byte(addr, val), 0xFF0F => { self.interrupt_request_flags = val; } 0xFF10 ... 0xFF26 => { self.sound.write_byte(addr, val); }, 0xFF30 ... 0xFF3F => self.sound.write_byte(addr, val), // Exclude DMA transfer, we will do this below 0xFF40 ... 0xFF45 | 0xFF47 ... 0xFF4B => { self.display.write_byte(addr, val); }, 0xFF46 => { // println!("OAM DMA transfer"); for x in 0x00 .. 0x9F { let dma_b = self.read_byte(((val as u16) << 8) | x); self.write_byte(0xFE00 | x, dma_b); } } 0xFF50 => { println!("Disabling boot rom."); self.disable_bootrom = val; }, 0xFF51 => self.vram_dma_source_high = val, 0xFF52 => self.vram_dma_source_low = val & 0xF0, 0xFF53 => self.vram_dma_destination_high = val & 0x1F, 0xFF54 => self.vram_dma_destination_low = val & 0xF0, 0xFF55 => { let src: u16 = ((self.vram_dma_source_high as u16) << 8) | self.vram_dma_source_low as u16; let mut dst: u16 = ((self.vram_dma_destination_high as u16) << 8) | self.vram_dma_destination_low as u16; dst += 0x8000; println!("VRAM DMA transfer from {:04X} to {:04X}; {:02X}", src, dst, val); let len: u16 = ((val & 0x7F) + 1) as u16 * 0x10 - 1; for i in 0..len { let v = self.read_byte(src.wrapping_add(i)); self.write_byte(dst.wrapping_add(i), v); } // DMA done self.vram_dma_length = val | 0x80; } 0xFF56 => { self.infrared_com_port = val; }, 0xFF70 => { if self.wram_bank != val { println!("Switching wram bank to {:02X}", val); if val > 7 { panic!("R u sure this is correct?"); } if val == 0 { self.wram_bank = 1; } else { self.wram_bank = val; } } } 0xFF80 ... 0xFFFE => { self.hiram[(addr - 0xFF80) as usize] = val; }, 0xFFFF => { println!("Setting interrupt mask value {:02X}", val); self.interrupt = val; }, _ => { println!("Write {:02X} to {:04X} not supported.", val, addr); } } } #[inline] pub fn write_word(&mut self, addr: u16, val: u16) { self.write_byte(addr, val as u8); self.write_byte(addr + 1, (val >> 8) as u8); } #[inline] pub fn read_word(&self, addr: u16) -> u16 { self.read_byte(addr) as u16 | (self.read_byte(addr + 1) as u16) << 8 // (self.read_byte(addr) as u16) << 8 | (self.read_byte(addr + 1) as u16) } }