rustyboy/src/interconnect.rs

432 lines
15 KiB
Rust

// 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::cartridge;
use super::display;
use super::serial;
use super::sound;
use super::timer;
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<String>) -> 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)
}
}