use crate::{device, savestates, ui}; pub mod gbcart; pub mod mempak; pub mod rumble; pub mod transferpak; pub mod vru; pub const JCMD_STATUS: u8 = 0x00; pub const JCMD_CONTROLLER_READ: u8 = 0x01; const JCMD_PAK_READ: u8 = 0x02; const JCMD_PAK_WRITE: u8 = 0x03; pub const JCMD_RESET: u8 = 0xff; //const JDT_NONE: u16 = 0x0000; const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates */ //const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */ const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */ const PAK_CHUNK_SIZE: usize = 0x20; const CONT_STATUS_PAK_PRESENT: u8 = 1; const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2; const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT; #[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum PakType { None = 0, MemPak = 1, RumblePak = 2, TransferPak = 3, } #[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct PakHandler { #[serde(skip, default = "savestates::default_pak_handler")] pub read: fn(&mut device::Device, usize, u16, usize, usize), #[serde(skip, default = "savestates::default_pak_handler")] pub write: fn(&mut device::Device, usize, u16, usize, usize), pub pak_type: PakType, } pub fn process(device: &mut device::Device, channel: usize) { let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()]; match cmd { JCMD_RESET | JCMD_STATUS => { device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = CONT_FLAVOR as u8; device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 1] = (CONT_FLAVOR >> 8) as u8; if device.pif.channels[channel].pak_handler.is_none() { device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 2] = CONT_STATUS_PAK_NOT_PRESENT; } else { device.pif.ram[device.pif.channels[channel].rx_buf.unwrap() + 2] = CONT_STATUS_PAK_PRESENT; } } JCMD_CONTROLLER_READ => { let offset = device.pif.channels[channel].rx_buf.unwrap(); let input = if let Some(netplay) = &mut device.netplay { netplay.inputs[channel].0 } else { ui::input::get(&mut device.ui, channel) }; device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes()); if input.pak_change_pressed { // pak change button pressed if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() { device.pif.channels[channel].change_pak = device.pif.channels[channel].pak_handler.unwrap().pak_type; device.pif.channels[channel].pak_handler = None; device::events::create_event( device, device::events::EVENT_TYPE_PAK, device.cpu.clock_rate / 2, // 500ms ) } } } JCMD_PAK_READ => pak_read_block( device, device.pif.channels[channel].tx_buf.unwrap() + 1, device.pif.channels[channel].rx_buf.unwrap(), device.pif.channels[channel].rx_buf.unwrap() + 32, channel, ), JCMD_PAK_WRITE => pak_write_block( device, device.pif.channels[channel].tx_buf.unwrap() + 1, device.pif.channels[channel].tx_buf.unwrap() + 3, device.pif.channels[channel].rx_buf.unwrap(), channel, ), _ => eprintln!("unknown controller command {cmd}"), } } fn pak_read_block( device: &mut device::Device, addr_acrc: usize, data: usize, dcrc: usize, channel: usize, ) { let address = ((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16; let handler = device.pif.channels[channel].pak_handler; if let Some(handler) = handler { (handler.read)(device, channel, address, data, PAK_CHUNK_SIZE); device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE) } else { device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE) } } fn pak_write_block( device: &mut device::Device, addr_acrc: usize, data: usize, dcrc: usize, channel: usize, ) { let address = ((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16; let handler = device.pif.channels[channel].pak_handler; if let Some(handler) = handler { (handler.write)(device, channel, address, data, PAK_CHUNK_SIZE); device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE) } else { device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE) } } fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 { let mut i = 0; let mut crc = 0; while i <= size { let mut mask = 0x80; while mask >= 1 { let xor_tap = if crc & 0x80 != 0 { 0x85 } else { 0x00 }; crc <<= 1; if i != size && (device.pif.ram[data_offset + i] & mask) != 0 { crc |= 1; } crc ^= xor_tap; mask >>= 1 } i += 1; } crc } pub fn pak_switch_event(device: &mut device::Device) { for (i, channel) in device.pif.channels.iter_mut().enumerate() { if channel.change_pak != PakType::None { //stop rumble if it is on if let Some(netplay) = &device.netplay { if netplay.player_number == i { device::ui::input::set_rumble(&device.ui, 0, 0); } } else { device::ui::input::set_rumble(&device.ui, i, 0); } let new_pak_type = match channel.change_pak { PakType::MemPak => PakType::RumblePak, PakType::RumblePak => { if device.transferpaks[i].cart.rom.is_empty() { PakType::MemPak } else { PakType::TransferPak } } PakType::TransferPak => PakType::MemPak, _ => { panic!("Invalid pak type"); } }; if new_pak_type == PakType::MemPak { channel.pak_handler = Some(device::controller::PakHandler { read: device::controller::mempak::read, write: device::controller::mempak::write, pak_type: new_pak_type, }); ui::video::onscreen_message( &format!("P{} MemPak selected", i + 1), ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } else if new_pak_type == PakType::RumblePak { channel.pak_handler = Some(device::controller::PakHandler { read: device::controller::rumble::read, write: device::controller::rumble::write, pak_type: new_pak_type, }); ui::video::onscreen_message( &format!("P{} RumblePak selected", i + 1), ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } else if new_pak_type == PakType::TransferPak { channel.pak_handler = Some(device::controller::PakHandler { read: device::controller::transferpak::read, write: device::controller::transferpak::write, pak_type: new_pak_type, }); ui::video::onscreen_message( &format!("P{} TransferPak selected", i + 1), ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } channel.change_pak = PakType::None; } } }