386 lines
14 KiB
Rust
386 lines
14 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::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<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)
|
|
}
|
|
}
|