453 lines
16 KiB
Rust
453 lines
16 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, Copy, Clone, PartialEq)]
|
|
pub enum TickResult {
|
|
Continue,
|
|
Shutdown,
|
|
}
|
|
|
|
#[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::SoundManager,
|
|
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::SoundManager::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) -> TickResult {
|
|
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();
|
|
return TickResult::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;
|
|
}
|
|
TickResult::Continue
|
|
}
|
|
|
|
#[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.sound_object.lock().unwrap().read_byte(addr),
|
|
0xFF30..=0xFF3F => self.sound.sound_object.lock().unwrap().read_byte(addr),
|
|
0xFF40..=0xFF4B => self.display.read_byte(addr),
|
|
0xFF4F => 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,
|
|
0xFF6A | 0xFF6B => self.display.read_byte(addr),
|
|
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
|
|
.sound_object
|
|
.lock()
|
|
.unwrap()
|
|
.write_byte(addr, val);
|
|
}
|
|
0xFF30..=0xFF3F => self
|
|
.sound
|
|
.sound_object
|
|
.lock()
|
|
.unwrap()
|
|
.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);
|
|
}
|
|
}
|
|
0xFF4F => self.display.write_byte(addr, val),
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
0xFF68 | 0xFF69 => self.display.write_byte(addr, val),
|
|
0xFF6A | 0xFF6B => self.display.write_byte(addr, 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)
|
|
}
|
|
}
|