Compare commits

...

26 Commits

Author SHA1 Message Date
Logan McNaughton 421d533479 update cargo packages (#231) 2025-02-09 11:45:36 +01:00
Logan McNaughton ab5ef57106 further function cleanup 2025-02-09 11:43:10 +01:00
Logan McNaughton 37b41c0f66 cleanup vi calc functions 2025-02-08 22:41:28 +01:00
Logan McNaughton 4c50203ad3 modify rdp sync code (#230) 2025-02-08 21:39:48 +01:00
Logan McNaughton 271e4be68c disable rdp sync optimization to be safe (#229) 2025-02-08 21:20:59 +01:00
Logan McNaughton a9afd6bb5c Improve RDP emulation performance (#228)
* Improve RDP emulation performance

* more

* more

* more

* more
2025-02-07 22:29:20 +01:00
Logan McNaughton cd2b3dd143 update cargo lock 2025-02-06 14:31:11 +01:00
Logan McNaughton ab31b44aac Bump to 1.0.4 (#227) 2025-02-05 22:19:47 +01:00
Logan McNaughton 58eaad1742 remove some dependencies (#226)
* remove some dependencies

* more

* more

* more

* more
2025-02-05 19:31:38 +01:00
Logan McNaughton a8e7c3c136 add Cargo.lock (#225)
* add Cargo.lock

* more

* more
2025-02-05 17:31:17 +01:00
Logan McNaughton f4099cfcce further sc64 cleanup (#224)
* further sc64 cleanup

* further sc64 cleanup
2025-02-05 13:39:24 +01:00
Logan McNaughton 5b3e2a25de Fix out-of-bounds ROM dma (#223)
* Fix out-of-bounds ROM dma

* Fix out-of-bounds ROM dma

* more
2025-02-05 13:18:44 +01:00
Logan McNaughton 55ab1cb60b Fix address masking in sc64 read/write (#222)
* fix address masking for sc64

* more

* more
2025-02-05 10:29:07 +01:00
dependabot[bot] 778a0a4015 Update eframe requirement from 0.30 to 0.31 (#221)
Updates the requirements on [eframe](https://github.com/emilk/egui) to permit the latest version.
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.30.0...0.31.0)

---
updated-dependencies:
- dependency-name: eframe
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-05 06:40:50 +01:00
Logan McNaughton 96d2a72850 compress sd and rom saves during netplay (#220)
* compress sd and rom saves during netplay

* compress sd and rom saves during netplay
2025-02-04 21:21:47 +01:00
Logan McNaughton 4c7361f963 No need to check for ROM bounds on write (#219)
* resize ROM when writing if needed

* more
2025-02-04 19:50:19 +01:00
Logan McNaughton 80fc5f8ea2 collapse variable 2025-02-04 15:42:29 +01:00
Logan McNaughton f0eb5764c8 emulate summercart64 ROM_WRITE_ENABLE flag (#218)
* emulate summercart64 ROM_WRITE_ENABLE flag

* clean up names
2025-02-04 15:37:55 +01:00
Logan McNaughton 480af8d36e Ignore summercart64 USB commands (#217) 2025-02-04 13:14:57 +01:00
Logan McNaughton ad4eddb408 Fix crash when game name cannot be parsed (#216) 2025-02-04 13:01:33 +01:00
Logan McNaughton 166b7daa9d Bump to 1.0.3 (#215) 2025-02-03 18:50:20 +01:00
Logan McNaughton 920a0323c0 cleanup 2025-02-03 16:49:42 +01:00
Logan McNaughton 14e45be300 SD card emulation (#214)
* SD card emulation

* more

* more

* more

* more

* more

* more

* more

* better save name

* better save name

* better save name

* more
2025-02-03 16:41:46 +01:00
Logan McNaughton fba6a4a6d4 support for Advanced Homebrew ROM Header (#213)
* support for Advanced Homebrew ROM Header

* more

* more
2025-02-03 10:22:21 +01:00
Logan McNaughton 1efdeafc31 bump to 1.0.2 (#212) 2025-02-02 12:54:48 +01:00
Logan McNaughton a7c2c26d78 minor tweak to interrupt system (#211) 2025-02-02 11:23:32 +01:00
27 changed files with 5806 additions and 229 deletions
-4
View File
@@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
Generated
+5146
View File
File diff suppressed because it is too large Load Diff
+6 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.0.1"
version = "1.0.4"
edition = "2021"
rust-version = "1.82"
@@ -14,24 +14,22 @@ sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.30"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
sha2 = "0.10"
rusttype = "0.9"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = "0.15"
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
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"] }
[dependencies.reqwest]
version = "0.12"
default-features = false
features = ["http2","rustls-tls","charset","json","blocking"]
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winres = "0.1"
+7 -18
View File
@@ -330,40 +330,29 @@ bool rdp_update_screen()
return emu_running;
}
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale, uint32_t width)
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale)
{
if (xscale == 0)
return 320;
uint32_t start = ((hstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t start = (hstart >> 16) & 0x3FF;
uint32_t end = (hstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
delta = 0;
uint32_t scale = (xscale & 0xFFF);
if (delta == 0)
{
delta = width;
}
return (delta * scale) / 0x400;
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
{
if (yscale == 0)
return 240;
uint32_t start = ((vstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t start = (vstart >> 16) & 0x3FF;
uint32_t end = (vstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
delta = 0;
uint32_t scale = (yscale & 0xFFF);
return (delta * scale) / 0x800;
@@ -433,7 +422,7 @@ uint64_t rdp_process_commands()
{
processor->wait_for_timeline(processor->signal_timeline());
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG, *gfx_info.VI_WIDTH_REG);
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG);
if (width == 0)
{
width = 320;
@@ -443,7 +432,7 @@ uint64_t rdp_process_commands()
{
height = 240;
}
interrupt_timer = width * height * 4;
interrupt_timer = width * height;
}
cmd_cur += cmd_length;
-1
View File
@@ -20,7 +20,6 @@ extern "C"
uint32_t *VI_V_START_REG;
uint32_t *VI_X_SCALE_REG;
uint32_t *VI_Y_SCALE_REG;
uint32_t *VI_WIDTH_REG;
} GFX_INFO;
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
+20 -12
View File
@@ -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<u8>, 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);
@@ -68,9 +66,9 @@ pub fn run_game(rom_contents: Vec<u8>, device: &mut Device, fullscreen: bool) {
vi::init(device);
cpu::init(device);
ui::storage::init(&mut device.ui);
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,
}
@@ -196,6 +195,7 @@ impl Device {
regs: [0; cop0::COP0_REGS_COUNT as usize],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
@@ -289,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 },
},
@@ -381,6 +381,7 @@ impl Device {
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
wait_frozen: false,
last_status_value: 0,
},
mi: mi::Mi {
regs: [0; mi::MI_REGS_COUNT as usize],
@@ -388,6 +389,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,
@@ -415,12 +423,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,
+4
View File
@@ -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;
@@ -56,7 +56,25 @@ pub fn read_mem(
}
}
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
let masked_address = address as usize & CART_MASK;
let mut data = u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.rom[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
for i in 0..4 {
device.ui.saves.romsave.0.insert(
(masked_address + i) as u32,
device.cart.rom[masked_address + i],
);
}
device.ui.saves.romsave.1 = true;
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
@@ -76,23 +94,27 @@ pub fn dma_read(
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
if device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
device.ui.saves.romsave.0.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device
.ui
.saves
.romsave
.0
.insert(cart_addr + i, device.cart.rom[(cart_addr + i) as usize]);
device.ui.saves.romsave.1 = true;
}
}
}
device.ui.saves.romsave.1 = true;
device::pi::calculate_cycles(device, 1, length)
}
@@ -183,6 +205,7 @@ fn set_cic(device: &mut device::Device) {
device.cart.cic_type = CicType::CicNus5167;
device.cart.cic_seed = 0xdd;
device.cart.rdram_size_offset = 0x318;
device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
}
_ => {
device.cart.cic_type = CicType::CicNus6102;
+238
View File
@@ -0,0 +1,238 @@
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_ROM_WRITE_ENABLE: u32 = 1;
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 & 0x1FFFFFFF;
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::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
} 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 & 0x1FFFFFFF;
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::data_read(
device,
address + i as u64,
device::memory::AccessSize::Word,
false,
)
.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;
}
'U' => {} // USB_WRITE_STATUS, ignored
'M' => {} // USB_WRITE, ignored
_ => {
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)
}
+3 -3
View File
@@ -26,8 +26,8 @@ pub struct Vru {
pub words: Vec<String>,
pub talking: bool,
pub word_mappings: HashMap<String, String>,
pub window_notifier: Option<std::sync::mpsc::Sender<Vec<String>>>,
pub word_receiver: Option<std::sync::mpsc::Receiver<String>>,
pub window_notifier: Option<tokio::sync::mpsc::Sender<Vec<String>>>,
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
pub gui_ctx: Option<egui::Context>,
}
@@ -187,7 +187,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
ui::vru::prompt_for_match(
&device.vru.words,
device.vru.window_notifier.as_ref().unwrap(),
device.vru.word_receiver.as_ref().unwrap(),
device.vru.word_receiver.as_mut().unwrap(),
device.vru.gui_ctx.as_ref().unwrap(),
)
} else {
+1
View File
@@ -97,6 +97,7 @@ pub struct Cop0 {
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
+2
View File
@@ -53,7 +53,9 @@ pub fn trigger_event(device: &mut device::Device) {
device.cpu.events[next_event_name].enabled = false;
let handler = device.cpu.events[next_event_name].handler;
device.cpu.cop0.is_event = true;
handler(device);
device.cpu.cop0.is_event = false;
set_next_event(device);
}
+1 -1
View File
@@ -30,7 +30,7 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] == device::cop0::COP0_CAUSE_IP7 {
if device.cpu.cop0.is_event {
interrupt_exception(device);
} else {
device::events::create_event(
+14 -6
View File
@@ -16,9 +16,11 @@ const MM_SI_REGS: usize = 0x04800000;
//const MM_DOM2_ADDR1: usize = 0x05000000;
pub const MM_DOM2_ADDR2: usize = 0x08000000;
pub const MM_CART_ROM: usize = 0x10000000;
const MM_PIF_MEM: usize = 0x1fc00000;
pub 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)
+42 -9
View File
@@ -114,17 +114,27 @@ 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;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
handler.read = device::sram::dma_read;
handler.write = device::sram::dma_write;
if address >= device::memory::MM_SC64_BUFFER as u32
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) 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
&& address < device::memory::MM_PIF_MEM 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
&& address < device::memory::MM_CART_ROM as u32
{
handler.read = device::cart::sram::dma_read;
handler.write = device::cart::sram::dma_write;
} else {
panic!("unknown pi handler")
handler.read = unknown_dma_read;
handler.write = unknown_dma_write;
}
handler
}
@@ -194,3 +204,26 @@ pub fn dma_event(device: &mut device::Device) {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
fn unknown_dma_read(
device: &mut device::Device,
mut _cart_addr: u32,
mut _dram_addr: u32,
length: u32,
) -> u64 {
device::pi::calculate_cycles(device, 1, length)
}
fn unknown_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;
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+14 -2
View File
@@ -45,6 +45,7 @@ pub struct Rdp {
pub regs_dpc: [u32; DPC_REGS_COUNT as usize],
pub regs_dps: [u32; DPS_REGS_COUNT as usize],
pub wait_frozen: bool,
pub last_status_value: u32,
}
pub fn read_regs_dpc(
@@ -54,6 +55,15 @@ pub fn read_regs_dpc(
) -> u32 {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
DPC_STATUS_REG => {
let value =
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
if value == device.rdp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
device.rdp.last_status_value = value;
device.rdp.regs_dpc[reg as usize]
}
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
DPC_CURRENT_REG => {
if device.rdp.wait_frozen {
@@ -103,7 +113,8 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY;
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
if timer != 0 {
device::events::create_event(
@@ -185,7 +196,8 @@ pub fn init(device: &mut device::Device) {
}
fn rdp_interrupt_event(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY);
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+1 -1
View File
@@ -5,7 +5,7 @@ use governor::clock::Clock;
const VI_STATUS_REG: u32 = 0;
//const VI_ORIGIN_REG: u32 = 1;
pub const VI_WIDTH_REG: u32 = 2;
//const VI_WIDTH_REG: u32 = 2;
//const VI_V_INTR_REG: u32 = 3;
const VI_CURRENT_REG: u32 = 4;
//const VI_BURST_REG: u32 = 5;
+2 -1
View File
@@ -64,7 +64,8 @@ struct Args {
clear_input_bindings: bool,
}
fn main() {
#[tokio::main]
async fn main() {
let dirs = ui::get_dirs();
let mut result = std::fs::create_dir_all(dirs.config_dir);
+3 -3
View File
@@ -30,7 +30,7 @@ pub struct Netplay {
status: u8,
buffer_target: u8,
pub fast_forward: bool,
pub error_notifier: std::sync::mpsc::Sender<String>,
pub error_notifier: tokio::sync::mpsc::Sender<String>,
pub gui_ctx: egui::Context,
}
@@ -47,7 +47,7 @@ struct InputEvent {
}
fn send_error(netplay: &mut Netplay, error: String) {
netplay.error_notifier.send(error).unwrap();
netplay.error_notifier.try_send(error).unwrap();
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
}
@@ -219,7 +219,7 @@ pub fn init(
mut peer_addr: std::net::SocketAddr,
session: ui::gui::gui_netplay::NetplayRoom,
player_number: u8,
error_notifier: std::sync::mpsc::Sender<String>,
error_notifier: tokio::sync::mpsc::Sender<String>,
gui_ctx: egui::Context,
) -> Netplay {
peer_addr.set_port(session.port.unwrap() as u16);
+2
View File
@@ -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 {
+1 -1
View File
@@ -116,7 +116,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let min_latency = (device.ui.audio_freq * 0.02) * 4.0;
if audio_queued < min_latency {
let silence_buffer: Vec<u8> = vec![0; (min_latency - audio_queued) as usize & !3];
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
+109 -89
View File
@@ -21,9 +21,9 @@ pub struct GopherEguiApp {
emulate_vru: bool,
dinput: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
netplay_error_receiver: Option<std::sync::mpsc::Receiver<String>>,
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
netplay_error_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
pub netplay: gui_netplay::GuiNetplay,
}
@@ -84,7 +84,7 @@ impl GopherEguiApp {
controller_paths: Vec<String>,
controller_names: Vec<String>,
) -> GopherEguiApp {
add_japanese_font(&cc.egui_ctx);
add_fonts(&cc.egui_ctx);
let config = ui::config::Config::new();
let mut selected_controller = [-1, -1, -1, -1];
@@ -236,7 +236,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.vru_word_notifier
.as_ref()
.unwrap()
.send(v.clone())
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
@@ -248,7 +248,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.vru_word_notifier
.as_ref()
.unwrap()
.send(String::from(""))
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
}
@@ -285,19 +285,19 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
let (netplay_error_notifier, netplay_error_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
let (vru_window_notifier, vru_window_receiver): (
std::sync::mpsc::Sender<Vec<String>>,
std::sync::mpsc::Receiver<Vec<String>>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<Vec<String>>,
tokio::sync::mpsc::Receiver<Vec<String>>,
) = tokio::sync::mpsc::channel(1);
let (vru_word_notifier, vru_word_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
if netplay {
app.netplay_error_receiver = Some(netplay_error_receiver);
@@ -312,83 +312,89 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
std::thread::spawn(move || {
let file = if !netplay {
rfd::FileDialog::new().pick_file()
} else {
None
};
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
let mut task = None;
if !netplay {
task = Some(rfd::AsyncFileDialog::new().pick_file());
}
tokio::spawn(async move {
let file = if !netplay { task.unwrap().await } else { None };
if cfg!(target_os = "macos") && file.is_some() {
// mac os requires the process to be started on the main thread
// this means that netplay and VRU emulation will not work on mac os
{
let mut config = ui::config::Config::new();
save_config(&mut config, controller_paths, save_config_items);
}
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
if fullscreen {
command.arg("--fullscreen");
}
command.arg(file.unwrap().as_path());
std::thread::Builder::new()
.name("n64".to_string())
.spawn(move || {
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
let status = command.status().expect("failed to execute process");
if !status.success() {
panic!("process exited with: {}", status);
}
} else if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
if cfg!(target_os = "macos") && file.is_some() {
// mac os requires the process to be started on the main thread
// this means that netplay and VRU emulation will not work on mac os
{
let mut config = ui::config::Config::new();
save_config(&mut config, controller_paths, save_config_items);
}
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
if fullscreen {
command.arg("--fullscreen");
}
command.arg(file.unwrap().path());
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
let status = command.status().expect("failed to execute process");
if !status.success() {
panic!("process exited with: {}", status);
}
} else if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device::run_game(rom_contents, &mut device, fullscreen);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device::run_game(rom_contents, &mut device, fullscreen);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
let rom_contents = device::get_rom_contents(file.unwrap().as_path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
})
.unwrap();
});
}
@@ -407,7 +413,7 @@ impl eframe::App for GopherEguiApp {
}
if self.netplay_error_receiver.is_some() {
let result = self.netplay_error_receiver.as_ref().unwrap().try_recv();
let result = self.netplay_error_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.netplay.error = result.unwrap();
}
@@ -518,7 +524,7 @@ impl eframe::App for GopherEguiApp {
});
if self.emulate_vru && self.vru_window_receiver.is_some() {
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
let result = self.vru_window_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.show_vru_dialog = true;
self.vru_word_list = result.unwrap();
@@ -531,7 +537,21 @@ impl eframe::App for GopherEguiApp {
}
}
fn add_japanese_font(ctx: &egui::Context) {
fn add_fonts(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"regular_font",
egui::FontData::from_static(include_bytes!("../../data/Roboto-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Highest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Highest,
},
],
));
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
+33 -37
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::ui::gui;
use crate::ui::gui::GopherEguiApp;
use crate::ui::{self, gui};
use eframe::egui;
use sha2::{Digest, Sha256};
@@ -41,8 +41,8 @@ pub struct GuiNetplay {
pub socket:
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
pub server_receiver:
Option<std::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<std::sync::mpsc::Receiver<GameInfo>>,
Option<tokio::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<GameInfo>>,
pub broadcast_socket: Option<std::net::UdpSocket>,
pub broadcast_timer: Option<std::time::Instant>,
}
@@ -99,16 +99,14 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.server_receiver.is_none() {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.server_receiver = Some(rx);
let gui_ctx = ctx.clone();
std::thread::spawn(move || {
if let Ok(response) =
reqwest::blocking::get("https://m64p.s3.amazonaws.com/servers.json")
.unwrap()
.json::<std::collections::HashMap<String, String>>()
{
tx.send(response).unwrap();
let task = reqwest::get("https://m64p.s3.amazonaws.com/servers.json");
tokio::spawn(async move {
let response = task.await;
if let Ok(response) = response {
tx.send(response.json().await.unwrap()).await.unwrap();
gui_ctx.request_repaint();
}
});
@@ -143,7 +141,7 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.server_receiver.is_some() {
let result = app.netplay.server_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.server_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.servers.extend(result.unwrap());
app.netplay.server_receiver = None;
@@ -181,15 +179,16 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.netplay.create_rom_label = "Open ROM".to_string();
}
if ui.button(&app.netplay.create_rom_label).clicked() {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.create_rom_label = "Inspecting ROM".to_string();
std::thread::spawn(move || {
let file = rfd::FileDialog::new().pick_file();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx);
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
@@ -197,6 +196,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
"Open ROM".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
@@ -217,7 +217,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
get_servers(app, ctx);
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
@@ -391,22 +391,15 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
}
fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.as_path());
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
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();
tx.send((
hash,
game_name,
file.file_name().unwrap().to_string_lossy().to_string(),
rom_contents,
))
.unwrap();
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
tx.send((hash, game_name, file.file_name(), rom_contents))
.await
.unwrap();
} else {
tx.send((
"".to_string(),
@@ -414,6 +407,7 @@ fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo
"Invalid ROM".to_string(),
vec![],
))
.await
.unwrap();
}
}
@@ -444,7 +438,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
@@ -585,15 +579,16 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
{
app.netplay.error = "Session requires a password".to_string();
} else {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.join_rom_label = "Inspecting ROM".to_string();
std::thread::spawn(move || {
let file = rfd::FileDialog::new().pick_file();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx);
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
@@ -601,6 +596,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
"No ROM selected".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
+118 -16
View File
@@ -1,5 +1,7 @@
use crate::netplay;
use crate::ui;
use std::io::Read;
use std::io::Write;
#[derive(PartialEq)]
pub enum SaveTypes {
@@ -8,6 +10,7 @@ pub enum SaveTypes {
Sram,
Flash,
Mempak,
Sdcard,
Romsave,
}
@@ -16,6 +19,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,10 +30,25 @@ pub struct Saves {
pub sram: (Vec<u8>, bool),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub sdcard: (Vec<u8>, bool),
pub romsave: (std::collections::HashMap<u32, u8>, bool),
}
fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
let header_type = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap());
if header_type.is_ok() && header_type.unwrap() == "ED" {
let save_type = rom[0x3F] >> 4;
match save_type {
0 => return vec![],
1 => return vec![SaveTypes::Eeprom4k],
2 => return vec![SaveTypes::Eeprom16k],
3 => return vec![SaveTypes::Sram],
4 => panic!("Unsupported save type: {}", save_type),
5 => return vec![SaveTypes::Flash],
6 => panic!("Unsupported save type: {}", save_type),
_ => panic!("Unknown save type: {}", save_type),
}
}
match game_id {
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
@@ -87,36 +106,61 @@ fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
}
}
pub fn init(ui: &mut ui::Ui) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
pub fn get_game_name(rom: &[u8]) -> String {
let mut game_name = "".to_owned();
let header_value = std::str::from_utf8(&rom[0x20..0x34]);
if header_value.is_ok() {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re
.replace_all(header_value.unwrap(), "")
.trim()
.replace('\0', "");
}
game_name
}
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.save_type = get_save_type(rom, &ui.game_id);
let base_path = ui.dirs.data_dir.join("saves");
let game_name = get_game_name(rom);
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<netplay::Netplay>) {
@@ -137,6 +181,10 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
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();
@@ -169,30 +217,77 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
&ui.saves.mempak.0,
ui.saves.mempak.0.len(),
);
let mut romsave_bytes: Vec<u8> = vec![];
let mut compressed_sd = Vec::new();
if !ui.saves.sdcard.0.is_empty() {
compressed_sd = compress_file(&ui.saves.sdcard.0);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"img",
&compressed_sd,
compressed_sd.len(),
);
let mut compressed_romsave = Vec::new();
if !ui.saves.romsave.0.is_empty() {
romsave_bytes = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
compressed_romsave =
compress_file(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap());
}
netplay::send_save(
netplay.as_mut().unwrap(),
"rom",
&romsave_bytes,
romsave_bytes.len(),
&compressed_romsave,
compressed_romsave.len(),
);
} else {
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
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);
let mut romsave_bytes: Vec<u8> = vec![];
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut romsave_bytes);
if !romsave_bytes.is_empty() {
let mut compressed_sd = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
if !compressed_sd.is_empty() {
ui.saves.sdcard.0 = decompress_file(&compressed_sd);
}
let mut compressed_romsave = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut compressed_romsave);
if !compressed_romsave.is_empty() {
let romsave_bytes = decompress_file(&compressed_romsave);
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
}
fn decompress_file(input: &[u8]) -> Vec<u8> {
let mut decompressed_file = Vec::new();
{
let mut reader = zip::ZipArchive::new(std::io::Cursor::new(input)).unwrap();
let mut file = reader.by_index(0).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
fn compress_file(input: &[u8]) -> Vec<u8> {
let mut compressed_file = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut compressed_file));
writer
.start_file(
"save",
zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated),
)
.unwrap();
writer.write_all(input).unwrap();
}
compressed_file
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
@@ -212,6 +307,9 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.sdcard.1 {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
}
@@ -238,6 +336,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;
-1
View File
@@ -37,7 +37,6 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
VI_V_START_REG: &mut device.vi.regs[device::vi::VI_V_START_REG as usize],
VI_X_SCALE_REG: &mut device.vi.regs[device::vi::VI_X_SCALE_REG as usize],
VI_Y_SCALE_REG: &mut device.vi.regs[device::vi::VI_Y_SCALE_REG as usize],
VI_WIDTH_REG: &mut device.vi.regs[device::vi::VI_WIDTH_REG as usize],
};
unsafe {
+3 -3
View File
@@ -4,14 +4,14 @@ use crate::ui;
pub fn prompt_for_match(
words: &[String],
window_notifier: &std::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &std::sync::mpsc::Receiver<String>,
window_notifier: &tokio::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
gui_ctx: &egui::Context,
) -> u16 {
let mut dedup_words = words.to_owned();
dedup_words.sort();
dedup_words.dedup();
window_notifier.send(dedup_words).unwrap();
window_notifier.try_send(dedup_words).unwrap();
gui_ctx.request_repaint(); // this is so the window pops up right away
let mut result = word_index_receiver.try_recv();
while result.is_err() {