mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58eaad1742 | |||
| a8e7c3c136 | |||
| f4099cfcce | |||
| 5b3e2a25de | |||
| 55ab1cb60b | |||
| 778a0a4015 | |||
| 96d2a72850 | |||
| 4c7361f963 | |||
| 80fc5f8ea2 | |||
| f0eb5764c8 | |||
| 480af8d36e | |||
| ad4eddb408 | |||
| 166b7daa9d | |||
| 920a0323c0 | |||
| 14e45be300 | |||
| fba6a4a6d4 | |||
| 1efdeafc31 | |||
| a7c2c26d78 | |||
| 7d63a694cc | |||
| 5c98a08fe4 | |||
| 39ffad3af3 | |||
| ca2a3d423b | |||
| fe5346235e | |||
| f84444a12e | |||
| 026e4c0e74 | |||
| 363ed0953a | |||
| cb9309d7e4 | |||
| 1b1313003c | |||
| 7219a4445c | |||
| 687badc142 | |||
| 3e2189463f | |||
| ed8b4e8e02 | |||
| 611121568e | |||
| 27922fbf9c | |||
| 71b7baf613 | |||
| f4714a7813 | |||
| 4730b7f293 |
+13
-10
@@ -30,16 +30,16 @@ jobs:
|
||||
rustflags: ""
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y dist-upgrade
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
version: ${{ matrix.target }}-1.0
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
@@ -82,12 +82,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Build
|
||||
run: |
|
||||
brew install molten-vk
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -21,15 +21,16 @@ jobs:
|
||||
components: clippy, rustfmt
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
version: lint-1.0
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rust-lang/rustfmt@v1
|
||||
- name: Lint
|
||||
|
||||
@@ -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
+5127
File diff suppressed because it is too large
Load Diff
+6
-8
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "0.1.23"
|
||||
version = "1.0.3"
|
||||
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"
|
||||
|
||||
@@ -158,4 +158,12 @@ fn main() {
|
||||
simd_build.include(".");
|
||||
simd_build.compile("simd");
|
||||
}
|
||||
|
||||
if os == "macos" {
|
||||
// Add Homebrew lib path for Apple Silicon Macs
|
||||
println!("cargo:rustc-link-search=native=/opt/homebrew/lib");
|
||||
// Add Homebrew lib path for Intel Macs
|
||||
println!("cargo:rustc-link-search=native=/usr/local/lib");
|
||||
println!("cargo:rustc-link-lib=static=MoltenVK");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,8 +383,6 @@ uint64_t rdp_process_commands()
|
||||
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
|
||||
return interrupt_timer;
|
||||
|
||||
*gfx_info.DPC_STATUS_REG |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
|
||||
|
||||
uint32_t offset = DP_CURRENT;
|
||||
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
|
||||
{
|
||||
@@ -446,8 +444,6 @@ uint64_t rdp_process_commands()
|
||||
height = 240;
|
||||
}
|
||||
interrupt_timer = width * height * 4;
|
||||
|
||||
*gfx_info.DPC_STATUS_REG &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
|
||||
}
|
||||
|
||||
cmd_cur += cmd_length;
|
||||
@@ -456,7 +452,6 @@ uint64_t rdp_process_commands()
|
||||
cmd_ptr = 0;
|
||||
cmd_cur = 0;
|
||||
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
|
||||
*gfx_info.DPC_STATUS_REG |= DP_STATUS_CBUF_READY;
|
||||
|
||||
return interrupt_timer;
|
||||
}
|
||||
|
||||
+22
-12
@@ -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,8 @@ 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],
|
||||
tlb_lut_w: vec![
|
||||
@@ -288,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 },
|
||||
},
|
||||
@@ -367,6 +368,8 @@ impl Device {
|
||||
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
|
||||
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
|
||||
mem: [0; 0x2000],
|
||||
last_status_value: 0,
|
||||
run_after_dma: false,
|
||||
fifo: [rsp_interface::RspDma {
|
||||
dir: rsp_interface::DmaDir::None,
|
||||
length: 0,
|
||||
@@ -385,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,
|
||||
@@ -412,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,
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ fn do_dma(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
|
||||
dma_event,
|
||||
);
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
}
|
||||
|
||||
fn fifo_push(device: &mut device::Device) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+6
-5
@@ -55,14 +55,14 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
|
||||
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
|
||||
const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_BD: u64 = 1 << 31;
|
||||
|
||||
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
|
||||
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
|
||||
|
||||
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
|
||||
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
|
||||
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
|
||||
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
|
||||
@@ -97,6 +97,8 @@ 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,
|
||||
}
|
||||
|
||||
fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -214,6 +216,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
compare_event,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
}
|
||||
COP0_STATUS_REG => {
|
||||
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
|
||||
@@ -231,9 +234,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
}
|
||||
|
||||
fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
|
||||
|
||||
device.cpu.cop0.pending_compare_interrupt = true;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
|
||||
@@ -8,7 +8,7 @@ pub enum EventType {
|
||||
PI,
|
||||
DP,
|
||||
SP,
|
||||
InterruptCheck,
|
||||
Interrupt,
|
||||
SPDma,
|
||||
Compare,
|
||||
Vru,
|
||||
@@ -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,15 +1,6 @@
|
||||
use crate::device;
|
||||
|
||||
pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& (device::cop0::COP0_STATUS_IE
|
||||
| device::cop0::COP0_STATUS_EXL
|
||||
@@ -20,6 +11,38 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if device.mi.regs[device::mi::MI_INTR_REG as usize]
|
||||
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
|
||||
!= 0
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
|
||||
} else if device.cpu.cop0.pending_compare_interrupt {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if device.cpu.cop0.is_event {
|
||||
interrupt_exception(device);
|
||||
} else {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Interrupt,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
interrupt_exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt_exception(device: &mut device::Device) {
|
||||
exception_general(device, 0x180);
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -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)
|
||||
|
||||
+3
-31
@@ -2,8 +2,8 @@ use crate::device;
|
||||
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
const MI_INTR_REG: u32 = 2;
|
||||
const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_REGS_COUNT: u32 = 4;
|
||||
|
||||
/* read */
|
||||
@@ -64,11 +64,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
|
||||
}
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
} else {
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
@@ -149,37 +145,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::InterruptCheck,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
device::exceptions::check_pending_interrupts,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
|
||||
}
|
||||
|
||||
+49
-11
@@ -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
|
||||
}
|
||||
@@ -149,10 +159,15 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device.pi.regs[PI_STATUS_REG as usize] = 0
|
||||
}
|
||||
}
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM1_PGS_REG | PI_BSD_DOM1_RLS_REG
|
||||
| PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG | PI_BSD_DOM2_PGS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xFF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_PGS_REG | PI_BSD_DOM2_PGS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_RLS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0x3, mask)
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
@@ -189,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
-1
@@ -52,7 +52,17 @@ pub fn read_regs_dpc(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
|
||||
DPC_CURRENT_REG => {
|
||||
if device.rdp.wait_frozen {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rdp.regs_dpc[reg as usize]
|
||||
}
|
||||
_ => device.rdp.regs_dpc[reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -93,6 +103,7 @@ 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;
|
||||
|
||||
if timer != 0 {
|
||||
device::events::create_event(
|
||||
@@ -174,5 +185,7 @@ 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::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ pub fn read_regs(
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
match reg as u32 {
|
||||
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
|
||||
0x80 => 0x00000000, //Row, needed for libdragon
|
||||
_ => device.rdram.regs[chip_id as usize][reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
+44
-14
@@ -81,6 +81,8 @@ pub struct Rsp {
|
||||
pub regs2: [u32; SP_REGS2_COUNT as usize],
|
||||
pub mem: [u8; 0x2000],
|
||||
pub fifo: [RspDma; 2],
|
||||
pub last_status_value: u32,
|
||||
pub run_after_dma: bool,
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -249,7 +251,11 @@ fn fifo_pop(device: &mut device::Device) {
|
||||
do_dma(device, device.rsp.fifo[0])
|
||||
} else {
|
||||
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
|
||||
if device.rsp.run_after_dma {
|
||||
device.rsp.run_after_dma = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +266,30 @@ pub fn read_regs(
|
||||
) -> u32 {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
|
||||
if device.rsp.regs[reg as usize] != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
SP_STATUS_REG => {
|
||||
let value = device.rsp.regs[reg as usize]
|
||||
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
|
||||
if value == device.rsp.last_status_value && value != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.last_status_value = value;
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
SP_SEMAPHORE_REG => {
|
||||
let value = device.rsp.regs[reg as usize];
|
||||
if value == 1 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
device.rsp.cpu.cycle_counter += 4; // needed for DK64
|
||||
}
|
||||
device.rsp.regs[reg as usize] = 1;
|
||||
value
|
||||
}
|
||||
@@ -284,9 +312,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
SP_SEMAPHORE_REG => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
|
||||
}
|
||||
_ => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +361,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
}
|
||||
/* set SP interrupt */
|
||||
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
}
|
||||
|
||||
/* clear / set single step */
|
||||
@@ -421,20 +447,25 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
|
||||
device.rsp.cpu.broken = false;
|
||||
device.rsp.cpu.halted = false;
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_task(device: &mut device::Device) {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
device.rsp.cpu.sync_point = false;
|
||||
device.rsp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
|
||||
device.rsp.run_after_dma = true
|
||||
} else {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rsp_event,
|
||||
)
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rsp_event,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn rsp_event(device: &mut device::Device) {
|
||||
@@ -450,7 +481,6 @@ fn rsp_event(device: &mut device::Device) {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
|
||||
return;
|
||||
}
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device)
|
||||
}
|
||||
|
||||
|
||||
@@ -565,8 +565,6 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device::memory::AccessSize::Word,
|
||||
)
|
||||
}
|
||||
device.rsp.cpu.cycle_counter += 4; // needed for DK64
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -592,7 +590,6 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
!device::rsp_interface::SP_STATUS_HALT; // set halt when event happens
|
||||
device.rsp.cpu.halted = true // the RSP can halt itself by setting SP_SET_HALT
|
||||
}
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+13
-10
@@ -11,12 +11,13 @@ const UDP_SYNC_DATA: u8 = 4;
|
||||
|
||||
//TCP packet formats
|
||||
const TCP_SEND_SAVE: u8 = 1;
|
||||
const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_SEND_SETTINGS: u8 = 3;
|
||||
//const TCP_RECEIVE_SETTINGS: u8 = 4;
|
||||
const TCP_REGISTER_PLAYER: u8 = 5;
|
||||
const TCP_GET_REGISTRATION: u8 = 6;
|
||||
const TCP_DISCONNECT_NOTICE: u8 = 7;
|
||||
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
|
||||
|
||||
const CS4: u32 = 32;
|
||||
|
||||
@@ -29,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,
|
||||
}
|
||||
|
||||
@@ -46,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
|
||||
}
|
||||
@@ -57,19 +58,21 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
|
||||
request.push(0); // null terminate string
|
||||
request.extend_from_slice(&(size as u32).to_be_bytes());
|
||||
|
||||
let mut send_data = save_data.to_owned();
|
||||
send_data.resize(size, 0); // pad with zeros if needed
|
||||
request.extend(send_data);
|
||||
if size > 0 {
|
||||
request.extend(save_data.to_owned());
|
||||
}
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>, size: usize) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE].to_vec();
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
|
||||
request.extend_from_slice(save_type.as_bytes());
|
||||
request.push(0); // null terminate string
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
|
||||
let mut response: Vec<u8> = vec![0; size];
|
||||
let mut size: [u8; 4] = [0; 4];
|
||||
netplay.tcp_stream.read_exact(&mut size).unwrap();
|
||||
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
|
||||
netplay.tcp_stream.read_exact(&mut response).unwrap();
|
||||
*save_data = response;
|
||||
}
|
||||
@@ -216,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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
+138
-45
@@ -1,6 +1,7 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum SaveTypes {
|
||||
@@ -9,6 +10,7 @@ pub enum SaveTypes {
|
||||
Sram,
|
||||
Flash,
|
||||
Mempak,
|
||||
Sdcard,
|
||||
Romsave,
|
||||
}
|
||||
|
||||
@@ -17,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,
|
||||
}
|
||||
|
||||
@@ -27,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
|
||||
@@ -88,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>) {
|
||||
@@ -138,12 +181,13 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
if mempak.is_ok() {
|
||||
ui.saves.mempak.0 = mempak.unwrap();
|
||||
}
|
||||
if netplay.is_none() {
|
||||
// can't do romsaves with the current netplay implementation
|
||||
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();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,55 +197,97 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&ui.saves.eeprom.0,
|
||||
device::cart::EEPROM_MAX_SIZE,
|
||||
ui.saves.eeprom.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&ui.saves.sram.0,
|
||||
device::sram::SRAM_SIZE,
|
||||
ui.saves.sram.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&ui.saves.flash.0,
|
||||
device::sram::FLASHRAM_SIZE,
|
||||
ui.saves.flash.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&ui.saves.mempak.0,
|
||||
device::controller::mempak::MEMPAK_SIZE * 4,
|
||||
ui.saves.mempak.0.len(),
|
||||
);
|
||||
|
||||
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() {
|
||||
compressed_romsave =
|
||||
compress_file(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap());
|
||||
}
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"rom",
|
||||
&compressed_romsave,
|
||||
compressed_romsave.len(),
|
||||
);
|
||||
} else {
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&mut ui.saves.eeprom.0,
|
||||
device::cart::EEPROM_MAX_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&mut ui.saves.sram.0,
|
||||
device::sram::SRAM_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&mut ui.saves.flash.0,
|
||||
device::sram::FLASHRAM_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&mut ui.saves.mempak.0,
|
||||
device::controller::mempak::MEMPAK_SIZE * 4,
|
||||
);
|
||||
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 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();
|
||||
@@ -221,7 +307,10 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
|
||||
if ui.saves.mempak.1 {
|
||||
write_save(ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.romsave.1 && netplay.is_none() {
|
||||
if ui.saves.sdcard.1 {
|
||||
write_save(ui, SaveTypes::Sdcard)
|
||||
}
|
||||
if ui.saves.romsave.1 {
|
||||
write_save(ui, SaveTypes::Romsave)
|
||||
}
|
||||
}
|
||||
@@ -247,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;
|
||||
|
||||
+3
-3
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user