diff --git a/Cargo.toml b/Cargo.toml index 4e0288c..15b6754 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ mimalloc = "0.1" regex = "1.11" tungstenite = "0.26" socket2 = "0.5" +fatfs = "0.3" postcard = { version = "1.1", features = ["use-std"] } clap = { version = "4.4", features = ["derive"] } diff --git a/src/device.rs b/src/device.rs index 6561041..50b4f5e 100644 --- a/src/device.rs +++ b/src/device.rs @@ -19,7 +19,6 @@ use std::io::Read; pub mod ai; pub mod cache; pub mod cart; -pub mod cart_rom; pub mod controller; pub mod cop0; pub mod cop1; @@ -42,13 +41,12 @@ pub mod rsp_interface; pub mod rsp_su_instructions; pub mod rsp_vu_instructions; pub mod si; -pub mod sram; pub mod tlb; pub mod unmapped; pub mod vi; pub fn run_game(rom_contents: Vec, device: &mut Device, fullscreen: bool) { - cart_rom::init(device, rom_contents); // cart needs to come before rdram + cart::rom::init(device, rom_contents); // cart needs to come before rdram // rdram pointer is shared with parallel-rdp rdram::init(device); @@ -70,7 +68,7 @@ pub fn run_game(rom_contents: Vec, device: &mut Device, fullscreen: bool) { ui::storage::init(&mut device.ui, &device.cart.rom); ui::storage::load_saves(&mut device.ui, &mut device.netplay); - cart_rom::load_rom_save(device); + cart::rom::load_rom_save(device); cpu::run(device); @@ -163,18 +161,19 @@ pub struct Device { byte_swap: usize, pub cpu: cpu::Cpu, pif: pif::Pif, - cart: cart_rom::Cart, + cart: cart::rom::Cart, memory: memory::Memory, pub rsp: rsp_interface::Rsp, pub rdp: rdp::Rdp, pub rdram: rdram::Rdram, mi: mi::Mi, pi: pi::Pi, + sc64: cart::sc64::Sc64, pub vi: vi::Vi, ai: ai::Ai, si: si::Si, ri: ri::Ri, - flashram: sram::Flashram, + flashram: cart::sram::Flashram, pub vru: controller::vru::Vru, } @@ -290,13 +289,13 @@ impl Device { change_pak: controller::PakType::None, }; 5], }, - cart: cart_rom::Cart { + cart: cart::rom::Cart { rom: Vec::new(), is_viewer_buffer: [0; 0xFFFF], pal: false, latch: 0, cic_seed: 0, - cic_type: cart_rom::CicType::CicNus6102, + cic_type: cart::rom::CicType::CicNus6102, rdram_size_offset: 0, rtc: cart::AfRtc { control: 0x0200 }, }, @@ -389,6 +388,13 @@ impl Device { pi: pi::Pi { regs: [0; pi::PI_REGS_COUNT as usize], }, + sc64: cart::sc64::Sc64 { + regs: [0; cart::sc64::SC64_REGS_COUNT as usize], + regs_locked: true, + cfg: [0; cart::sc64::SC64_CFG_COUNT as usize], + sector: 0, + buffer: [0; 8192], + }, ai: ai::Ai { regs: [0; ai::AI_REGS_COUNT as usize], last_read: 0, @@ -416,12 +422,12 @@ impl Device { limiter: None, vi_counter: 0, }, - flashram: sram::Flashram { + flashram: cart::sram::Flashram { status: 0, erase_page: 0, page_buf: [0xff; 128], - silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID], - mode: sram::FlashramMode::ReadArray, + silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID], + mode: cart::sram::FlashramMode::ReadArray, }, vru: controller::vru::Vru { status: 0, diff --git a/src/device/cart.rs b/src/device/cart.rs index e59bc44..f004899 100644 --- a/src/device/cart.rs +++ b/src/device/cart.rs @@ -4,6 +4,10 @@ use chrono::Timelike; use crate::device; use crate::ui; +pub mod rom; +pub mod sc64; +pub mod sram; + const JCMD_STATUS: u8 = 0x00; const JCMD_EEPROM_READ: u8 = 0x04; const JCMD_EEPROM_WRITE: u8 = 0x05; diff --git a/src/device/cart_rom.rs b/src/device/cart/rom.rs similarity index 100% rename from src/device/cart_rom.rs rename to src/device/cart/rom.rs diff --git a/src/device/cart/sc64.rs b/src/device/cart/sc64.rs new file mode 100644 index 0000000..d60cc17 --- /dev/null +++ b/src/device/cart/sc64.rs @@ -0,0 +1,233 @@ +use crate::device; + +pub const SDCARD_SIZE: usize = 0x4000000; + +const SC64_SCR_REG: u32 = 0; +const SC64_DATA0_REG: u32 = 1; +const SC64_DATA1_REG: u32 = 2; +const SC64_IDENTIFIER_REG: u32 = 3; +const SC64_KEY_REG: u32 = 4; +//const SC64_IRQ_REG: u32 = 5; +//const SC64_AUX_REG: u32 = 6; +pub const SC64_REGS_COUNT: u32 = 7; + +pub const SC64_CFG_COUNT: u32 = 15; + +const SC64_BUFFER_MASK: usize = 0x1FFF; + +pub struct Sc64 { + pub buffer: [u8; 8192], + pub regs: [u32; SC64_REGS_COUNT as usize], + pub regs_locked: bool, + pub cfg: [u32; SC64_CFG_COUNT as usize], + pub sector: u32, +} + +fn format_sdcard(device: &mut device::Device) { + if device.ui.saves.sdcard.0.is_empty() { + device.ui.saves.sdcard.0.resize(SDCARD_SIZE, 0); + let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.0); + fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap(); + } +} + +pub fn read_regs( + device: &mut device::Device, + address: u64, + _access_size: device::memory::AccessSize, +) -> u32 { + if device.sc64.regs_locked { + return 0; + } + let reg = (address & 0xFFFF) >> 2; + match reg as u32 { + SC64_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.sc64.regs[reg as usize], + SC64_IDENTIFIER_REG => 0x53437632, + _ => panic!("unknown read reg {} address {:x}", reg, address), + } +} + +pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) { + let reg = (address & 0xFFFF) >> 2; + match reg as u32 { + SC64_KEY_REG => { + device::memory::masked_write_32(&mut device.sc64.regs[reg as usize], value, mask); + if device.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F { + device.sc64.regs_locked = false; + } else if device.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF { + device.sc64.regs_locked = true; + } + } + SC64_DATA0_REG | SC64_DATA1_REG => { + if !device.sc64.regs_locked { + device::memory::masked_write_32(&mut device.sc64.regs[reg as usize], value, mask); + } + } + SC64_SCR_REG => { + if !device.sc64.regs_locked { + match char::from_u32(value & mask).unwrap() { + 'c' => { + // get config + device.sc64.regs[SC64_DATA1_REG as usize] = + device.sc64.cfg[device.sc64.regs[SC64_DATA0_REG as usize] as usize] + } + 'C' => { + // set config + std::mem::swap( + &mut device.sc64.cfg + [device.sc64.regs[SC64_DATA0_REG as usize] as usize], + &mut device.sc64.regs[SC64_DATA1_REG as usize], + ); + } + 'i' => { + // sd card operation + match device.sc64.regs[SC64_DATA1_REG as usize] { + 0 => { //Init SD card + } + 1 => { //Deinit SD card + } + _ => { + panic!( + "unknown sc64 sd card operation: {}", + device.sc64.regs[SC64_DATA1_REG as usize] + ) + } + } + } + 'I' => { + // set sd sector + device.sc64.sector = device.sc64.regs[SC64_DATA0_REG as usize]; + } + 's' => { + format_sdcard(device); + // read sd card + let address = device.sc64.regs[SC64_DATA0_REG as usize] as u64; + let offset = (device.sc64.sector * 512) as usize; + let length = (device.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize; + let mut i = 0; + + while i < length { + if offset + i < device.ui.saves.sdcard.0.len() { + let data = u32::from_be_bytes( + device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)] + .try_into() + .unwrap(), + ); + + device.memory.memory_map_write[(address >> 16) as usize]( + device, + address + i as u64, + data, + 0xFFFFFFFF, + ); + } else { + panic!("sd card read out of bounds") + } + i += 4; + } + } + 'S' => { + format_sdcard(device); + // write sd card + let address = device.sc64.regs[SC64_DATA0_REG as usize] as u64; + let offset = (device.sc64.sector * 512) as usize; + let length = (device.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize; + let mut i = 0; + + while i < length { + if offset + i < device.ui.saves.sdcard.0.len() { + let data = device.memory.memory_map_read[(address >> 16) as usize]( + device, + address + i as u64, + device::memory::AccessSize::Word, + ) + .to_be_bytes(); + device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)] + .copy_from_slice(&data); + } else { + panic!("sd card write out of bounds") + } + i += 4; + } + device.ui.saves.sdcard.1 = true; + } + _ => { + panic!( + "unknown sc64 command: {}", + char::from_u32(value & mask).unwrap() + ) + } + } + } + } + _ => panic!( + "unknown write reg {} address {:x} value {}", + reg, + address, + char::from_u32(value & mask).unwrap() + ), + } +} + +pub fn read_mem( + device: &mut device::Device, + address: u64, + _access_size: device::memory::AccessSize, +) -> u32 { + let masked_address = address as usize & SC64_BUFFER_MASK; + u32::from_be_bytes( + device.sc64.buffer[masked_address..masked_address + 4] + .try_into() + .unwrap(), + ) +} + +pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) { + let masked_address = address as usize & SC64_BUFFER_MASK; + let mut data = u32::from_be_bytes( + device.sc64.buffer[masked_address..masked_address + 4] + .try_into() + .unwrap(), + ); + device::memory::masked_write_32(&mut data, value, mask); + device.sc64.buffer[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes()); +} + +pub fn dma_read( + device: &mut device::Device, + mut cart_addr: u32, + mut dram_addr: u32, + length: u32, +) -> u64 { + dram_addr &= device::rdram::RDRAM_MASK as u32; + cart_addr &= SC64_BUFFER_MASK as u32; + let mut i = dram_addr; + let mut j = cart_addr; + + while i < dram_addr + length && i < device.rdram.size { + device.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap]; + i += 1; + j += 1; + } + + device::pi::calculate_cycles(device, 1, length) +} + +pub fn dma_write( + device: &mut device::Device, + mut cart_addr: u32, + mut dram_addr: u32, + length: u32, +) -> u64 { + dram_addr &= device::rdram::RDRAM_MASK as u32; + cart_addr &= SC64_BUFFER_MASK as u32; + let mut i = dram_addr; + let mut j = cart_addr; + + while i < dram_addr + length && i < device.rdram.size { + device.rdram.mem[i as usize ^ device.byte_swap] = device.sc64.buffer[j as usize]; + i += 1; + j += 1; + } + device::pi::calculate_cycles(device, 1, length) +} diff --git a/src/device/sram.rs b/src/device/cart/sram.rs similarity index 100% rename from src/device/sram.rs rename to src/device/cart/sram.rs diff --git a/src/device/memory.rs b/src/device/memory.rs index d54e979..192ada4 100644 --- a/src/device/memory.rs +++ b/src/device/memory.rs @@ -19,6 +19,8 @@ pub const MM_CART_ROM: usize = 0x10000000; const MM_PIF_MEM: usize = 0x1fc00000; //const MM_DOM1_ADDR3: usize = 0x1fd00000; const MM_IS_VIEWER: usize = 0x13ff0000; +pub const MM_SC64_BUFFER: usize = 0x1ffe0000; +const MM_SC64_REGS: usize = 0x1fff0000; #[derive(PartialEq)] pub enum AccessType { @@ -142,18 +144,24 @@ pub fn init(device: &mut device::Device) { device.memory.memory_map_read[i] = device::si::read_regs; device.memory.memory_map_write[i] = device::si::write_regs; } else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) { - device.memory.memory_map_read[i] = device::sram::read_mem; - device.memory.memory_map_write[i] = device::sram::write_mem; + device.memory.memory_map_read[i] = device::cart::sram::read_mem; + device.memory.memory_map_write[i] = device::cart::sram::write_mem; } else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 { - device.memory.fast_read[i] = device::cart_rom::read_mem_fast; - device.memory.memory_map_read[i] = device::cart_rom::read_mem; - device.memory.memory_map_write[i] = device::cart_rom::write_mem; + device.memory.fast_read[i] = device::cart::rom::read_mem_fast; + device.memory.memory_map_read[i] = device::cart::rom::read_mem; + device.memory.memory_map_write[i] = device::cart::rom::write_mem; } else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 { device.memory.memory_map_read[i] = device::is_viewer::read_mem; device.memory.memory_map_write[i] = device::is_viewer::write_mem; } else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 { device.memory.memory_map_read[i] = device::pif::read_mem; device.memory.memory_map_write[i] = device::pif::write_mem; + } else if i >= MM_SC64_BUFFER >> 16 && i <= (MM_SC64_BUFFER + 0xFFFF) >> 16 { + device.memory.memory_map_read[i] = device::cart::sc64::read_mem; + device.memory.memory_map_write[i] = device::cart::sc64::write_mem; + } else if i >= MM_SC64_REGS >> 16 && i <= (MM_SC64_REGS + 0xFFFF) >> 16 { + device.memory.memory_map_read[i] = device::cart::sc64::read_regs; + device.memory.memory_map_write[i] = device::cart::sc64::write_regs; } } device::cache::init(device) diff --git a/src/device/pi.rs b/src/device/pi.rs index e2adb9c..2e94a50 100644 --- a/src/device/pi.rs +++ b/src/device/pi.rs @@ -114,15 +114,18 @@ fn dma_write(device: &mut device::Device) { fn get_handler(address: u32) -> PiHandler { let mut handler = PiHandler { - read: device::cart_rom::dma_read, - write: device::cart_rom::dma_write, + read: device::cart::rom::dma_read, + write: device::cart::rom::dma_write, }; - if address >= device::memory::MM_CART_ROM as u32 { - handler.read = device::cart_rom::dma_read; - handler.write = device::cart_rom::dma_write; + if address >= device::memory::MM_SC64_BUFFER as u32 { + handler.read = device::cart::sc64::dma_read; + handler.write = device::cart::sc64::dma_write; + } else if address >= device::memory::MM_CART_ROM as u32 { + handler.read = device::cart::rom::dma_read; + handler.write = device::cart::rom::dma_write; } else if address >= device::memory::MM_DOM2_ADDR2 as u32 { - handler.read = device::sram::dma_read; - handler.write = device::sram::dma_write; + handler.read = device::cart::sram::dma_read; + handler.write = device::cart::sram::dma_write; } else { panic!("unknown pi handler") } diff --git a/src/ui.rs b/src/ui.rs index 96ee1d6..4783b98 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -121,6 +121,7 @@ impl Ui { fla_file_path: std::path::PathBuf::new(), sra_file_path: std::path::PathBuf::new(), pak_file_path: std::path::PathBuf::new(), + sdcard_file_path: std::path::PathBuf::new(), romsave_file_path: std::path::PathBuf::new(), }, saves: storage::Saves { @@ -128,6 +129,7 @@ impl Ui { sram: (Vec::new(), false), flash: (Vec::new(), false), mempak: (Vec::new(), false), + sdcard: (Vec::new(), false), romsave: (std::collections::HashMap::new(), false), }, pak_audio: audio::PakAudio { diff --git a/src/ui/gui/gui_netplay.rs b/src/ui/gui/gui_netplay.rs index 0bcdf73..c1a88b6 100644 --- a/src/ui/gui/gui_netplay.rs +++ b/src/ui/gui/gui_netplay.rs @@ -394,12 +394,11 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) { fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender) { let rom_contents = device::get_rom_contents(file.as_path()); if !rom_contents.is_empty() { - let hash = device::cart_rom::calculate_hash(&rom_contents); + let hash = device::cart::rom::calculate_hash(&rom_contents); let game_name = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14]) .unwrap() .trim() - .replace('\0', "") - .to_string(); + .replace('\0', ""); tx.send(( hash, game_name, diff --git a/src/ui/storage.rs b/src/ui/storage.rs index 0fd320d..beafdf5 100644 --- a/src/ui/storage.rs +++ b/src/ui/storage.rs @@ -8,6 +8,7 @@ pub enum SaveTypes { Sram, Flash, Mempak, + Sdcard, Romsave, } @@ -16,6 +17,7 @@ pub struct Paths { pub sra_file_path: std::path::PathBuf, pub fla_file_path: std::path::PathBuf, pub pak_file_path: std::path::PathBuf, + pub sdcard_file_path: std::path::PathBuf, pub romsave_file_path: std::path::PathBuf, } @@ -26,6 +28,7 @@ pub struct Saves { pub sram: (Vec, bool), pub flash: (Vec, bool), pub mempak: (Vec, bool), + pub sdcard: (Vec, bool), pub romsave: (std::collections::HashMap, bool), } @@ -102,35 +105,50 @@ fn get_save_type(rom: &[u8], game_id: &str) -> Vec { } pub fn init(ui: &mut ui::Ui, rom: &[u8]) { - let id = ui.game_id.as_str(); - ui.save_type = get_save_type(rom, id); + ui.save_type = get_save_type(rom, &ui.game_id); let base_path = ui.dirs.data_dir.join("saves"); + let game_name = std::str::from_utf8(&rom[0x20..0x20 + 0x14]) + .unwrap() + .trim() + .replace('\0', ""); + + let prefix = if game_name.is_empty() { + &ui.game_id + } else { + &game_name + }; + ui.paths.eep_file_path.clone_from(&base_path); ui.paths .eep_file_path - .push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".eep"); + .push(prefix.to_owned() + "-" + &ui.game_hash + ".eep"); ui.paths.sra_file_path.clone_from(&base_path); ui.paths .sra_file_path - .push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".sra"); + .push(prefix.to_owned() + "-" + &ui.game_hash + ".sra"); ui.paths.fla_file_path.clone_from(&base_path); ui.paths .fla_file_path - .push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".fla"); + .push(prefix.to_owned() + "-" + &ui.game_hash + ".fla"); ui.paths.pak_file_path.clone_from(&base_path); ui.paths .pak_file_path - .push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".mpk"); + .push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk"); + + ui.paths.sdcard_file_path.clone_from(&base_path); + ui.paths + .sdcard_file_path + .push(prefix.to_owned() + "-" + &ui.game_hash + ".img"); ui.paths.romsave_file_path.clone_from(&base_path); ui.paths .romsave_file_path - .push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".romsave"); + .push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave"); } pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { @@ -151,6 +169,10 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { if mempak.is_ok() { ui.saves.mempak.0 = mempak.unwrap(); } + let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path); + if sdcard.is_ok() { + ui.saves.sdcard.0 = sdcard.unwrap(); + } let romsave = std::fs::read(&mut ui.paths.romsave_file_path); if romsave.is_ok() { ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap(); @@ -183,6 +205,12 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { &ui.saves.mempak.0, ui.saves.mempak.0.len(), ); + netplay::send_save( + netplay.as_mut().unwrap(), + "img", + &ui.saves.sdcard.0, + ui.saves.sdcard.0.len(), + ); let mut romsave_bytes: Vec = vec![]; if !ui.saves.romsave.0.is_empty() { romsave_bytes = postcard::to_stdvec(&ui.saves.romsave.0).unwrap(); @@ -198,6 +226,7 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0); netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0); netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0); + netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut ui.saves.sdcard.0); let mut romsave_bytes: Vec = vec![]; netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut romsave_bytes); if !romsave_bytes.is_empty() { @@ -226,6 +255,9 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option) { if ui.saves.mempak.1 { write_save(ui, SaveTypes::Mempak) } + if ui.saves.sdcard.1 && netplay.is_none() { + write_save(ui, SaveTypes::Sdcard) + } if ui.saves.romsave.1 { write_save(ui, SaveTypes::Romsave) } @@ -252,6 +284,10 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) { path = ui.paths.pak_file_path.as_ref(); data = ui.saves.mempak.0.as_ref(); } + SaveTypes::Sdcard => { + path = ui.paths.sdcard_file_path.as_ref(); + data = ui.saves.sdcard.0.as_ref(); + } SaveTypes::Romsave => { write_rom_save(ui); return;