From 22899719876caa82b0609b45dc178eccc969d548 Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:22:35 +0100 Subject: [PATCH] Savestate support (#236) * add save state support * bind to f5 * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * more * arm * arm * arm * arm * arm * more * more * more * more * more * more --- .github/workflows/build.yml | 3 - Cargo.lock | 10 ++ Cargo.toml | 1 + build.rs | 12 +-- parallel-rdp/interface.cpp | 54 +++++++--- parallel-rdp/interface.hpp | 11 +- src/device.rs | 88 +++++++++------- src/device/ai.rs | 18 ++-- src/device/cache.rs | 7 +- src/device/cart.rs | 30 +++++- src/device/cart/rom.rs | 42 ++------ src/device/cart/sc64.rs | 69 +++++++----- src/device/cart/sram.rs | 81 +++++++------- src/device/controller.rs | 18 ++-- src/device/controller/vru.rs | 26 +++-- src/device/cop0.rs | 97 ++++++++--------- src/device/cop1.rs | 21 +++- src/device/cop2.rs | 10 +- src/device/cpu.rs | 23 ++-- src/device/events.rs | 70 ++++++------ src/device/exceptions.rs | 3 +- src/device/memory.rs | 14 ++- src/device/mi.rs | 1 + src/device/pi.rs | 7 +- src/device/pif.rs | 31 ++++-- src/device/rdp.rs | 18 +++- src/device/rdram.rs | 1 + src/device/ri.rs | 1 + src/device/rsp_cpu.rs | 177 ++++++++++++++++++++----------- src/device/rsp_interface.rs | 18 ++-- src/device/si.rs | 9 +- src/device/tlb.rs | 4 +- src/device/vi.rs | 23 ++-- src/main.rs | 47 ++++++--- src/netplay.rs | 3 +- src/savestates.rs | 199 +++++++++++++++++++++++++++++++++++ src/ui.rs | 38 ++++--- src/ui/audio.rs | 6 +- src/ui/gui.rs | 73 ++++--------- src/ui/storage.rs | 58 +++++----- src/ui/video.rs | 38 +++++-- 41 files changed, 942 insertions(+), 518 deletions(-) create mode 100644 src/savestates.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5b426d..7941678 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,15 +82,12 @@ 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 diff --git a/Cargo.lock b/Cargo.lock index 8a0f9b4..fb70400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1501,6 +1501,7 @@ dependencies = [ "rusttype", "sdl3-sys", "serde", + "serde-big-array", "serde_json", "sevenz-rust", "sha2", @@ -3299,6 +3300,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.217" diff --git a/Cargo.toml b/Cargo.toml index 43f8f34..9d79162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ sevenz-rust = "0.6" chrono = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +serde-big-array = "0.5" eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] } sha2 = "0.10" rusttype = "0.9" diff --git a/build.rs b/build.rs index 08b1e6c..43eb5b7 100644 --- a/build.rs +++ b/build.rs @@ -99,6 +99,8 @@ fn main() { .allowlist_function("rdp_set_vi_register") .allowlist_function("rdp_update_screen") .allowlist_function("rdp_process_commands") + .allowlist_function("rdp_check_callback") + .allowlist_function("rdp_new_processor") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); @@ -159,14 +161,6 @@ fn main() { 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"); - } - let git_output = std::process::Command::new("git") .args(["rev-parse", "HEAD"]) .output() @@ -174,4 +168,6 @@ fn main() { let git_hash = String::from_utf8(git_output.stdout).unwrap(); println!("cargo:rustc-env=GIT_HASH={}", git_hash); + + println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024); } diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index 819c4c4..5930ec2 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -60,7 +60,7 @@ static WSI *wsi; static uint32_t cmd_data[0x00040000 >> 2]; static int cmd_cur; static int cmd_ptr; -static bool emu_running; +static CALL_BACK callback; static GFX_INFO gfx_info; static uint32_t region; @@ -135,18 +135,25 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { - emu_running = false; + callback.emu_running = false; } - else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running) + else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running) { wsi_platform->do_resize(); } - else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen) + else if (event->type == SDL_EVENT_KEY_DOWN) { switch (event->key.scancode) { case SDL_SCANCODE_ESCAPE: - emu_running = false; + if (fullscreen) + callback.emu_running = false; + break; + case SDL_SCANCODE_F5: + callback.save_state = true; + break; + case SDL_SCANCODE_F7: + callback.load_state = true; break; default: break; @@ -156,6 +163,22 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) return 0; } +void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale) +{ + gfx_info = _gfx_info; + if (processor) + { + delete processor; + } + RDP::CommandProcessorFlags flags = 0; + if (_upscale) + { + flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT; + flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT; + } + processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags); +} + void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen) { window = (SDL_Window *)_window; @@ -185,13 +208,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc { rdp_close(); } - RDP::CommandProcessorFlags flags = 0; - if (_upscale) - { - flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT; - flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT; - } - processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags); + rdp_new_processor(gfx_info, _upscale); if (!processor->device_is_supported()) { @@ -202,7 +219,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc } wsi->begin_frame(); - emu_running = true; + callback.emu_running = true; } void rdp_close() @@ -322,13 +339,20 @@ void rdp_set_vi_register(uint32_t reg, uint32_t value) processor->set_vi_register(RDP::VIRegister(reg), value); } -bool rdp_update_screen() +void rdp_update_screen() { auto &device = wsi->get_device(); render_frame(device); wsi->end_frame(); wsi->begin_frame(); - return emu_running; +} + +CALL_BACK rdp_check_callback() +{ + CALL_BACK return_value = callback; + callback.save_state = false; + callback.load_state = false; + return return_value; } uint64_t rdp_process_commands() diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index ba8832c..7c8a51b 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -18,11 +18,20 @@ extern "C" uint32_t *DPC_STATUS_REG; } GFX_INFO; + typedef struct + { + bool emu_running; + bool save_state; + bool load_state; + } CALL_BACK; + void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen); void rdp_close(); void rdp_set_vi_register(uint32_t reg, uint32_t value); - bool rdp_update_screen(); + void rdp_update_screen(); + CALL_BACK rdp_check_callback(); uint64_t rdp_process_commands(); + void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale); #ifdef __cplusplus } diff --git a/src/device.rs b/src/device.rs index d639059..9f95b35 100644 --- a/src/device.rs +++ b/src/device.rs @@ -10,11 +10,8 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs")); #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; -use crate::netplay; -use crate::ui; -use std::collections::HashMap; -use std::fs; -use std::io::Read; +use crate::{netplay, ui}; +use std::{collections::HashMap, fs, io::Read}; pub mod ai; pub mod cache; @@ -45,14 +42,14 @@ pub mod tlb; pub mod unmapped; pub mod vi; -pub fn run_game(rom_contents: Vec, device: &mut Device, fullscreen: bool) { +pub fn run_game(rom_contents: Vec, device: &mut Device) { cart::rom::init(device, rom_contents); // cart needs to come before rdram // rdram pointer is shared with parallel-rdp rdram::init(device); - ui::audio::init(&mut device.ui, 33600); - ui::video::init(device, fullscreen); + ui::audio::init(&mut device.ui, device.ai.freq); + ui::video::init(device); ui::input::init(&mut device.ui); mi::init(device); @@ -61,6 +58,7 @@ pub fn run_game(rom_contents: Vec, device: &mut Device, fullscreen: bool) { controller::vru::init(device); } memory::init(device); + cache::init(device); rsp_interface::init(device); rdp::init(device); vi::init(device); @@ -155,26 +153,34 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec { swap_rom(contents) } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Device { + #[serde(skip)] pub netplay: Option, + #[serde(skip, default = "ui::Ui::default")] pub ui: ui::Ui, byte_swap: usize, + pub save_state: bool, + pub load_state: bool, pub cpu: cpu::Cpu, - pif: pif::Pif, - cart: cart::rom::Cart, - memory: memory::Memory, + pub pif: pif::Pif, + pub cart: cart::Cart, + pub 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 mi: mi::Mi, + pub pi: pi::Pi, pub vi: vi::Vi, - ai: ai::Ai, - si: si::Si, - ri: ri::Ri, - flashram: cart::sram::Flashram, + pub ai: ai::Ai, + pub si: si::Si, + pub ri: ri::Ri, pub vru: controller::vru::Vru, + pub vru_window: controller::vru::VruWindow, +} + +pub fn zero_m128i() -> __m128i { + unsafe { _mm_setzero_si128() } } impl Device { @@ -190,6 +196,8 @@ impl Device { netplay: None, ui: ui::Ui::new(), byte_swap, + save_state: false, + load_state: false, cpu: cpu::Cpu { cop0: cop0::Cop0 { regs: [0; cop0::COP0_REGS_COUNT as usize], @@ -271,10 +279,9 @@ impl Device { events: [events::Event { enabled: false, count: u64::MAX, - handler: events::dummy_event, - }; events::EventType::Count as usize], + }; events::EVENT_TYPE_COUNT], next_event_count: u64::MAX, - next_event: 0, + next_event: events::EVENT_TYPE_NONE, }, pif: pif::Pif { rom: [0; 1984], @@ -289,15 +296,29 @@ impl Device { change_pak: controller::PakType::None, }; 5], }, - cart: cart::rom::Cart { + cart: cart::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::CicType::CicNus6102, rdram_size_offset: 0, rtc: cart::AfRtc { control: 0x0200 }, + 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], + }, + flashram: cart::sram::Flashram { + status: 0, + erase_page: 0, + page_buf: [0xff; 128], + silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID], + mode: cart::sram::FlashramMode::ReadArray, + }, }, memory: memory::Memory { fast_read: [unmapped::read_mem; 0x2000], @@ -389,16 +410,10 @@ 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, + freq: 33600, delayed_carry: false, fifo: [ai::AiDma { address: 0, @@ -423,12 +438,10 @@ impl Device { limiter: None, vi_counter: 0, }, - flashram: cart::sram::Flashram { - status: 0, - erase_page: 0, - page_buf: [0xff; 128], - silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID], - mode: cart::sram::FlashramMode::ReadArray, + vru_window: controller::vru::VruWindow { + window_notifier: None, + word_receiver: None, + gui_ctx: None, }, vru: controller::vru::Vru { status: 0, @@ -439,9 +452,6 @@ impl Device { words: Vec::new(), talking: false, word_mappings: HashMap::new(), - window_notifier: None, - word_receiver: None, - gui_ctx: None, }, } } diff --git a/src/device/ai.rs b/src/device/ai.rs index 8cc0607..58c44f3 100644 --- a/src/device/ai.rs +++ b/src/device/ai.rs @@ -1,5 +1,4 @@ -use crate::device; -use crate::ui; +use crate::{device, ui}; const AI_DRAM_ADDR_REG: u32 = 0; const AI_LEN_REG: u32 = 1; @@ -12,14 +11,16 @@ pub const AI_REGS_COUNT: u32 = 6; const AI_STATUS_BUSY: u32 = 0x40000000; const AI_STATUS_FULL: u32 = 0x80000000; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Ai { pub regs: [u32; AI_REGS_COUNT as usize], pub fifo: [AiDma; 2], pub last_read: u64, pub delayed_carry: bool, + pub freq: u64, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct AiDma { pub address: u64, pub length: u64, @@ -31,7 +32,7 @@ fn get_remaining_dma_length(device: &mut device::Device) -> u64 { return 0; } - let next_ai_event = device::events::get_event(device, device::events::EventType::AI); + let next_ai_event = device::events::get_event(device, device::events::EVENT_TYPE_AI); if next_ai_event.is_none() { return 0; } @@ -64,9 +65,8 @@ fn do_dma(device: &mut device::Device) { /* schedule end of dma event */ device::events::create_event( device, - device::events::EventType::AI, + device::events::EVENT_TYPE_AI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration, - dma_event, ); device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI); } @@ -145,9 +145,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u AI_STATUS_REG => device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_AI), AI_DACRATE_REG => { if device.ai.regs[reg as usize] != value & mask { - let frequency = device.vi.clock / (1 + (value & mask)) as u64; + device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64; ui::audio::close(&mut device.ui); - ui::audio::init(&mut device.ui, frequency) + ui::audio::init(&mut device.ui, device.ai.freq) } device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask) } @@ -155,7 +155,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u } } -fn dma_event(device: &mut device::Device) { +pub fn dma_event(device: &mut device::Device) { if device.ai.last_read != 0 { let diff = device.ai.fifo[0].length - device.ai.last_read; ui::audio::play_audio( diff --git a/src/device/cache.rs b/src/device/cache.rs index 1b2e1da..c89b17f 100644 --- a/src/device/cache.rs +++ b/src/device/cache.rs @@ -1,15 +1,16 @@ -use crate::device; +use crate::{device, savestates}; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct ICache { pub valid: bool, pub tag: u32, pub index: u16, pub words: [u32; 8], + #[serde(skip, default = "savestates::default_instructions")] pub instruction: [fn(&mut device::Device, u32); 8], } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct DCache { pub valid: bool, pub dirty: bool, diff --git a/src/device/cart.rs b/src/device/cart.rs index f004899..55d1e32 100644 --- a/src/device/cart.rs +++ b/src/device/cart.rs @@ -1,8 +1,7 @@ use chrono::Datelike; use chrono::Timelike; -use crate::device; -use crate::ui; +use crate::{device, ui}; pub mod rom; pub mod sc64; @@ -22,6 +21,17 @@ const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */ const EEPROM_BLOCK_SIZE: usize = 8; pub const EEPROM_MAX_SIZE: usize = 0x800; +#[derive(serde::Serialize, serde::Deserialize)] +pub enum CicType { + CicNus6101, + CicNus6102, + CicNus6103, + CicNus6105, + CicNus6106, + CicNus5167, +} + +#[derive(serde::Serialize, serde::Deserialize)] pub struct AfRtc { pub control: u16, } @@ -31,6 +41,22 @@ fn byte2bcd(mut n: u32) -> u8 { (((n / 10) << 4) | (n % 10)) as u8 } +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Cart { + #[serde(skip)] + pub rom: Vec, + #[serde(with = "serde_big_array::BigArray")] + pub is_viewer_buffer: [u8; 0xFFFF], + pub pal: bool, + pub latch: u32, + pub cic_type: CicType, + pub cic_seed: u8, + pub rdram_size_offset: usize, + pub rtc: AfRtc, + pub sc64: sc64::Sc64, + pub flashram: sram::Flashram, +} + pub fn process(device: &mut device::Device, channel: usize) { let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()]; diff --git a/src/device/cart/rom.rs b/src/device/cart/rom.rs index be261c6..be5ef93 100644 --- a/src/device/cart/rom.rs +++ b/src/device/cart/rom.rs @@ -1,26 +1,7 @@ use crate::device; use sha2::{Digest, Sha256}; -pub enum CicType { - CicNus6101, - CicNus6102, - CicNus6103, - CicNus6105, - CicNus6106, - CicNus5167, -} - const CART_MASK: usize = 0xFFFFFFF; -pub struct Cart { - pub rom: Vec, - pub is_viewer_buffer: [u8; 0xFFFF], - pub pal: bool, - pub latch: u32, - pub cic_type: CicType, - pub cic_seed: u8, - pub rdram_size_offset: usize, - pub rtc: device::cart::AfRtc, -} pub fn read_mem_fast( device: &mut device::Device, @@ -57,7 +38,7 @@ pub fn read_mem( } 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 { + if device.cart.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] @@ -82,9 +63,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3 let cycles = device::pi::calculate_cycles(device, 1, 4); device::events::create_event( device, - device::events::EventType::PI, + device::events::EVENT_TYPE_PI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles, - device::pi::dma_event, ); } @@ -94,7 +74,7 @@ pub fn dma_read( mut dram_addr: u32, length: u32, ) -> u64 { - if device.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 { + if device.cart.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; @@ -177,38 +157,38 @@ fn set_cic(device: &mut device::Device) { let hash = calculate_hash(&device.cart.rom[0x40..0x1000]); match hash.as_str() { "B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => { - device.cart.cic_type = CicType::CicNus6101; + device.cart.cic_type = device::cart::CicType::CicNus6101; device.cart.cic_seed = 0x3F; device.cart.rdram_size_offset = 0x318; } "61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => { - device.cart.cic_type = CicType::CicNus6102; + device.cart.cic_type = device::cart::CicType::CicNus6102; device.cart.cic_seed = 0x3F; device.cart.rdram_size_offset = 0x318; } "BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => { - device.cart.cic_type = CicType::CicNus6103; + device.cart.cic_type = device::cart::CicType::CicNus6103; device.cart.cic_seed = 0x78; device.cart.rdram_size_offset = 0x318; } "04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => { - device.cart.cic_type = CicType::CicNus6105; + device.cart.cic_type = device::cart::CicType::CicNus6105; device.cart.cic_seed = 0x91; device.cart.rdram_size_offset = 0x3F0; } "36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => { - device.cart.cic_type = CicType::CicNus6106; + device.cart.cic_type = device::cart::CicType::CicNus6106; device.cart.cic_seed = 0x85; device.cart.rdram_size_offset = 0x318; } "53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => { - device.cart.cic_type = CicType::CicNus5167; + device.cart.cic_type = device::cart::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.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1; } _ => { - device.cart.cic_type = CicType::CicNus6102; + device.cart.cic_type = device::cart::CicType::CicNus6102; device.cart.cic_seed = 0x3F; device.cart.rdram_size_offset = 0x318; println!("unknown IPL3 {}", hash) diff --git a/src/device/cart/sc64.rs b/src/device/cart/sc64.rs index 8adb1cf..b035750 100644 --- a/src/device/cart/sc64.rs +++ b/src/device/cart/sc64.rs @@ -16,7 +16,9 @@ pub const SC64_CFG_COUNT: u32 = 15; const SC64_BUFFER_MASK: usize = 0x1FFF; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Sc64 { + #[serde(with = "serde_big_array::BigArray")] pub buffer: [u8; 8192], pub regs: [u32; SC64_REGS_COUNT as usize], pub regs_locked: bool, @@ -38,12 +40,12 @@ pub fn read_regs( _access_size: device::memory::AccessSize, ) -> u32 { device::cop0::add_cycles(device, 20); - if device.sc64.regs_locked { + if device.cart.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_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.cart.sc64.regs[reg as usize], SC64_IDENTIFIER_REG => 0x53437632, _ => panic!("unknown read reg {} address {:#x}", reg, address), } @@ -53,37 +55,41 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u 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; + device::memory::masked_write_32(&mut device.cart.sc64.regs[reg as usize], value, mask); + if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F { + device.cart.sc64.regs_locked = false; + } else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF { + device.cart.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); + if !device.cart.sc64.regs_locked { + device::memory::masked_write_32( + &mut device.cart.sc64.regs[reg as usize], + value, + mask, + ); } } SC64_SCR_REG => { - if !device.sc64.regs_locked { + if !device.cart.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] + device.cart.sc64.regs[SC64_DATA1_REG as usize] = device.cart.sc64.cfg + [device.cart.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], + &mut device.cart.sc64.cfg + [device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize], + &mut device.cart.sc64.regs[SC64_DATA1_REG as usize], ); } 'i' => { // sd card operation - match device.sc64.regs[SC64_DATA1_REG as usize] { + match device.cart.sc64.regs[SC64_DATA1_REG as usize] { 0 => { //Init SD card } 1 => { //Deinit SD card @@ -91,21 +97,23 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u _ => { panic!( "unknown sc64 sd card operation: {}", - device.sc64.regs[SC64_DATA1_REG as usize] + device.cart.sc64.regs[SC64_DATA1_REG as usize] ) } } } 'I' => { // set sd sector - device.sc64.sector = device.sc64.regs[SC64_DATA0_REG as usize]; + device.cart.sc64.sector = device.cart.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 address = + device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF; + let offset = (device.cart.sc64.sector * 512) as usize; + let length = + (device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize; let mut i = 0; while i < length { @@ -132,9 +140,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u '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 address = + device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF; + let offset = (device.cart.sc64.sector * 512) as usize; + let length = + (device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize; let mut i = 0; while i < length { @@ -182,7 +192,7 @@ pub fn read_mem( ) -> u32 { let masked_address = address as usize & SC64_BUFFER_MASK; u32::from_be_bytes( - device.sc64.buffer[masked_address..masked_address + 4] + device.cart.sc64.buffer[masked_address..masked_address + 4] .try_into() .unwrap(), ) @@ -191,12 +201,13 @@ pub fn read_mem( 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] + device.cart.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()); + device.cart.sc64.buffer[masked_address..masked_address + 4] + .copy_from_slice(&data.to_be_bytes()); } pub fn dma_read( @@ -211,7 +222,7 @@ pub fn dma_read( 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]; + device.cart.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap]; i += 1; j += 1; } @@ -231,7 +242,7 @@ pub fn dma_write( 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]; + device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.sc64.buffer[j as usize]; i += 1; j += 1; } diff --git a/src/device/cart/sram.rs b/src/device/cart/sram.rs index e54589a..67d7146 100644 --- a/src/device/cart/sram.rs +++ b/src/device/cart/sram.rs @@ -1,5 +1,4 @@ -use crate::device; -use crate::ui; +use crate::{device, ui}; const SRAM_MASK: usize = 0xFFFF; pub const SRAM_SIZE: usize = 0x8000; @@ -9,7 +8,7 @@ pub const MX29L1100_ID: u32 = 0x00c2001e; const MX29L0000_ID: u32 = 0x00c20000; const MX29L0001_ID: u32 = 0x00c20001; -#[derive(PartialEq)] +#[derive(PartialEq, serde::Serialize, serde::Deserialize)] pub enum FlashramMode { ReadArray, ReadSiliconId, @@ -19,10 +18,12 @@ pub enum FlashramMode { PageProgram, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Flashram { pub status: u32, pub mode: FlashramMode, pub erase_page: u16, + #[serde(with = "serde_big_array::BigArray")] pub page_buf: [u8; 128], pub silicon_id: [u32; 2], } @@ -52,10 +53,11 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 { } fn read_mem_flash(device: &device::Device, address: u64) -> u32 { - if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status { + if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status { /* read Status register */ - device.flashram.status - } else if (address & 0x1ffff) == 0x0000 && device.flashram.mode == FlashramMode::ReadArray { + device.cart.flashram.status + } else if (address & 0x1ffff) == 0x0000 && device.cart.flashram.mode == FlashramMode::ReadArray + { /* flashram MMIO read are not supported except for the "dummy" read @0x0000 done before DMA. * returns a "dummy" value. */ return 0; @@ -97,9 +99,9 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u } fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) { - if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status { + if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status { /* clear/set Status register */ - device.flashram.status = (value & mask) & 0xff; + device.cart.flashram.status = (value & mask) & 0xff; } else if (address & 0x1ffff) == 0x10000 { /* set command */ format_flash(device); @@ -122,9 +124,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3 let cycles = device::pi::calculate_cycles(device, 2, 4); device::events::create_event( device, - device::events::EventType::PI, + device::events::EVENT_TYPE_PI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles, - device::pi::dma_event, ); } @@ -150,11 +151,11 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l if (cart_addr & 0x1ffff) == 0x00000 && length == 128 - && device.flashram.mode == FlashramMode::PageProgram + && device.cart.flashram.mode == FlashramMode::PageProgram { /* load page buf using DMA */ for i in 0..length { - device.flashram.page_buf[i as usize] = + device.cart.flashram.page_buf[i as usize] = device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap]; } } else { @@ -203,20 +204,22 @@ fn dma_write_flash( if (cart_addr & 0x1ffff) == 0x00000 && length == 8 - && device.flashram.mode == FlashramMode::ReadSiliconId + && device.cart.flashram.mode == FlashramMode::ReadSiliconId { /* read Silicon ID using DMA */ device.rdram.mem[dram_addr as usize..dram_addr as usize + 4] - .copy_from_slice(&device.flashram.silicon_id[0].to_ne_bytes()); + .copy_from_slice(&device.cart.flashram.silicon_id[0].to_ne_bytes()); dram_addr += 4; device.rdram.mem[dram_addr as usize..dram_addr as usize + 4] - .copy_from_slice(&device.flashram.silicon_id[1].to_ne_bytes()); - } else if (cart_addr & 0x1ffff) < 0x10000 && device.flashram.mode == FlashramMode::ReadArray { + .copy_from_slice(&device.cart.flashram.silicon_id[1].to_ne_bytes()); + } else if (cart_addr & 0x1ffff) < 0x10000 + && device.cart.flashram.mode == FlashramMode::ReadArray + { format_flash(device); /* adjust flashram address before starting DMA. */ - if device.flashram.silicon_id[1] == MX29L1100_ID - || device.flashram.silicon_id[1] == MX29L0000_ID - || device.flashram.silicon_id[1] == MX29L0001_ID + if device.cart.flashram.silicon_id[1] == MX29L1100_ID + || device.cart.flashram.silicon_id[1] == MX29L0000_ID + || device.cart.flashram.silicon_id[1] == MX29L0001_ID { /* "old" flash needs special address adjusting */ cart_addr = (cart_addr & 0xffff) * 2; @@ -250,27 +253,27 @@ fn flashram_command(device: &mut device::Device, command: u32) { match command & 0xff000000 { 0x3c000000 => { /* set chip erase mode */ - device.flashram.mode = FlashramMode::ChipErase; + device.cart.flashram.mode = FlashramMode::ChipErase; } 0x4b000000 => { /* set sector erase mode, set erase sector */ - device.flashram.mode = FlashramMode::SectorErase; - device.flashram.erase_page = command as u16; + device.cart.flashram.mode = FlashramMode::SectorErase; + device.cart.flashram.erase_page = command as u16; } 0x78000000 => { /* set erase busy flag */ - device.flashram.status |= 0x02; + device.cart.flashram.status |= 0x02; /* do chip/sector erase */ - if device.flashram.mode == FlashramMode::SectorErase { - let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128; + if device.cart.flashram.mode == FlashramMode::SectorErase { + let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128; for i in 0..128 * 128 { device.ui.saves.flash.0[offset + i] = 0xFF; } device.ui.saves.flash.1 = true - } else if device.flashram.mode == FlashramMode::ChipErase { + } else if device.cart.flashram.mode == FlashramMode::ChipErase { for i in 0..FLASHRAM_SIZE { device.ui.saves.flash.0[i] = 0xFF; } @@ -280,47 +283,47 @@ fn flashram_command(device: &mut device::Device, command: u32) { } /* clear erase busy flag, set erase success flag, transition to status mode */ - device.flashram.status &= !0x02; - device.flashram.status |= 0x08; - device.flashram.mode = FlashramMode::Status; + device.cart.flashram.status &= !0x02; + device.cart.flashram.status |= 0x08; + device.cart.flashram.mode = FlashramMode::Status; } 0xa5000000 => { /* set program busy flag */ - device.flashram.status |= 0x01; + device.cart.flashram.status |= 0x01; /* program selected page */ let offset: usize = (command & 0xffff) as usize * 128; for i in 0..128 { - device.ui.saves.flash.0[offset + i] = device.flashram.page_buf[i]; + device.ui.saves.flash.0[offset + i] = device.cart.flashram.page_buf[i]; } device.ui.saves.flash.1 = true; /* clear program busy flag, set program success flag, transition to status mode */ - device.flashram.status &= !0x01; - device.flashram.status |= 0x04; - device.flashram.mode = FlashramMode::Status; + device.cart.flashram.status &= !0x01; + device.cart.flashram.status |= 0x04; + device.cart.flashram.mode = FlashramMode::Status; } 0xb4000000 => { /* set page program mode */ - device.flashram.mode = FlashramMode::PageProgram; + device.cart.flashram.mode = FlashramMode::PageProgram; } 0xd2000000 => { /* set status mode */ - device.flashram.mode = FlashramMode::Status; + device.cart.flashram.mode = FlashramMode::Status; } 0xe1000000 => { /* set silicon_id mode */ - device.flashram.mode = FlashramMode::ReadSiliconId; - device.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */ + device.cart.flashram.mode = FlashramMode::ReadSiliconId; + device.cart.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */ } 0xf0000000 => { /* set read mode */ - device.flashram.mode = FlashramMode::ReadArray; + device.cart.flashram.mode = FlashramMode::ReadArray; } _ => { diff --git a/src/device/controller.rs b/src/device/controller.rs index 7bb2b22..6cf5b5f 100644 --- a/src/device/controller.rs +++ b/src/device/controller.rs @@ -1,6 +1,4 @@ -use crate::device; -use crate::netplay; -use crate::ui; +use crate::{device, netplay, savestates, ui}; pub mod mempak; pub mod rumble; @@ -21,16 +19,18 @@ const CONT_STATUS_PAK_PRESENT: u8 = 1; const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2; const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT; -#[derive(Copy, Clone, PartialEq)] +#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum PakType { None = 0, MemPak = 1, RumblePak = 2, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct PakHandler { + #[serde(skip, default = "savestates::default_pak_handler")] pub read: fn(&mut device::Device, usize, u16, usize, usize), + #[serde(skip, default = "savestates::default_pak_handler")] pub write: fn(&mut device::Device, usize, u16, usize, usize), pub pak_type: PakType, } @@ -67,17 +67,15 @@ pub fn process(device: &mut device::Device, channel: usize) { device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes()); if input.1 { // pak change button pressed - if device::events::get_event(device, device::events::EventType::PakSwitch).is_none() - { + if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() { device.pif.channels[channel].change_pak = device.pif.channels[channel].pak_handler.unwrap().pak_type; device.pif.channels[channel].pak_handler = None; device::events::create_event( device, - device::events::EventType::PakSwitch, + device::events::EVENT_TYPE_PAK, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + (device.cpu.clock_rate), // 1 second - pak_switch_event, ) } } @@ -158,7 +156,7 @@ fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 { crc } -fn pak_switch_event(device: &mut device::Device) { +pub fn pak_switch_event(device: &mut device::Device) { for (i, channel) in device.pif.channels.iter_mut().enumerate() { if channel.change_pak != PakType::None { if channel.change_pak == PakType::RumblePak { diff --git a/src/device/controller/vru.rs b/src/device/controller/vru.rs index a197f38..b82d434 100644 --- a/src/device/controller/vru.rs +++ b/src/device/controller/vru.rs @@ -17,17 +17,26 @@ const VOICE_STATUS_BUSY: u8 = 0x05; const JDT_VRU: u16 = 0x0100; /* VRU */ const CONT_FLAVOR: u16 = JDT_VRU; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Vru { pub status: u8, pub voice_state: u8, pub load_offset: u8, pub voice_init: u8, + #[serde(with = "serde_big_array::BigArray")] pub word_buffer: [u16; 40], pub words: Vec, pub talking: bool, pub word_mappings: HashMap, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct VruWindow { + #[serde(skip)] pub window_notifier: Option>>, + #[serde(skip)] pub word_receiver: Option>, + #[serde(skip)] pub gui_ctx: Option, } @@ -160,14 +169,13 @@ pub fn process(device: &mut device::Device, channel: usize) { device.vru.voice_init = 2; device::events::create_event( device, - device::events::EventType::Vru, + device::events::EVENT_TYPE_VRU, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + (device.cpu.clock_rate * 2), // 2 seconds - vru_talking_event, ) } else if device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] == 0xEF { device.vru.talking = false; - device::events::remove_event(device, device::events::EventType::Vru); + device::events::remove_event(device, device::events::EVENT_TYPE_VRU); } else if device.pif.ram[device.pif.channels[channel].tx_buf.unwrap() + 3] == 0x2 { device.vru.voice_init = 0; device.vru.words.clear(); @@ -178,17 +186,17 @@ pub fn process(device: &mut device::Device, channel: usize) { let offset = device.pif.channels[channel].tx_buf.unwrap() + 1; if u16::from_ne_bytes(device.pif.ram[offset..offset + 2].try_into().unwrap()) == 0 { device.vru.talking = false; - device::events::remove_event(device, device::events::EventType::Vru); + device::events::remove_event(device, device::events::EVENT_TYPE_VRU); } device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0; } JCMD_VRU_READ => { - let index = if device.vru.window_notifier.is_some() { + let index = if device.vru_window.window_notifier.is_some() { ui::vru::prompt_for_match( &device.vru.words, - device.vru.window_notifier.as_ref().unwrap(), - device.vru.word_receiver.as_mut().unwrap(), - device.vru.gui_ctx.as_ref().unwrap(), + device.vru_window.window_notifier.as_ref().unwrap(), + device.vru_window.word_receiver.as_mut().unwrap(), + device.vru_window.gui_ctx.as_ref().unwrap(), ) } else { 0x7FFF @@ -250,7 +258,7 @@ pub fn process(device: &mut device::Device, channel: usize) { } } -fn vru_talking_event(device: &mut device::Device) { +pub fn vru_talking_event(device: &mut device::Device) { device.vru.talking = false } diff --git a/src/device/cop0.rs b/src/device/cop0.rs index 36820f5..280b590 100644 --- a/src/device/cop0.rs +++ b/src/device/cop0.rs @@ -1,4 +1,4 @@ -use crate::device; +use crate::{device, savestates}; pub const COP0_INDEX_REG: u32 = 0; const COP0_RANDOM_REG: u32 = 1; @@ -88,11 +88,14 @@ const COP0_XCONTEXT_REG_MASK: u64 = const COP0_PARITYERR_REG_MASK: u64 = 0b00000000000000000000000011111111; const COP0_TAGLO_REG_MASK: u64 = 0b00001111111111111111111111000000; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Cop0 { pub reg_latch: u64, pub regs: [u64; COP0_REGS_COUNT as usize], pub reg_write_masks: [u64; COP0_REGS_COUNT as usize], + #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub instrs2: [fn(&mut device::Device, u32); 32], pub tlb_lut_r: Vec, pub tlb_lut_w: Vec, @@ -211,9 +214,8 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) device::events::create_event( device, - device::events::EventType::Compare, + device::events::EVENT_TYPE_COMPARE, device.cpu.cop0.regs[COP0_COUNT_REG as usize] + ((compare_event_diff as u64) << 1), - compare_event, ); device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7; device.cpu.cop0.pending_compare_interrupt = false; @@ -233,13 +235,12 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) device::exceptions::check_pending_interrupts(device); } -fn compare_event(device: &mut device::Device) { +pub fn compare_event(device: &mut device::Device) { device.cpu.cop0.pending_compare_interrupt = true; device::events::create_event( device, - device::events::EventType::Compare, + device::events::EVENT_TYPE_COMPARE, device.cpu.next_event_count + (u32::MAX as u64), - compare_event, ); device::exceptions::check_pending_interrupts(device); } @@ -258,42 +259,7 @@ pub fn add_cycles(device: &mut device::Device, cycles: u64) { device.cpu.cop0.regs[COP0_COUNT_REG as usize] += cycles // COUNT_REG is shifted right 1 bit when read by MFC0 } -pub fn init(device: &mut device::Device) { - device.cpu.cop0.reg_write_masks = [ - COP0_INDEX_REG_MASK, - 0, // Random, read only - COP0_ENTRYLO_REG_MASK, - COP0_ENTRYLO_REG_MASK, - COP0_CONTEXT_REG_MASK, - COP0_PAGEMASK_REG_MASK, - COP0_WIRED_REG_MASK, - 0, // 7 - 0, // BadVAddr, read only - u32::MAX as u64, // count - COP0_ENTRYHI_REG_MASK, - u32::MAX as u64, // compare - COP0_STATUS_REG_MASK, - COP0_CAUSE_REG_MASK, - u64::MAX, // EPC - 0, // previd, read only - COP0_CONFIG_REG_MASK, - COP0_LLADDR_REG_MASK, - COP0_WATCHLO_REG_MASK, - COP0_WATCHHI_REG_MASK, - COP0_XCONTEXT_REG_MASK, - 0, // 21 - 0, // 22 - 0, // 23 - 0, // 24 - 0, // 25 - COP0_PARITYERR_REG_MASK, - 0, // cache error - COP0_TAGLO_REG_MASK, - 0, // taghi - u64::MAX, // ErrorPC - 0, // 31 - ]; - +pub fn map_instructions(device: &mut device::Device) { device.cpu.cop0.instrs = [ device::cop0::mfc0, // 0 device::cop0::dmfc0, // 1 @@ -363,6 +329,46 @@ pub fn init(device: &mut device::Device) { device::cop0::reserved, // 30 device::cop0::reserved, // 31 ]; +} + +pub fn init(device: &mut device::Device) { + device.cpu.cop0.reg_write_masks = [ + COP0_INDEX_REG_MASK, + 0, // Random, read only + COP0_ENTRYLO_REG_MASK, + COP0_ENTRYLO_REG_MASK, + COP0_CONTEXT_REG_MASK, + COP0_PAGEMASK_REG_MASK, + COP0_WIRED_REG_MASK, + 0, // 7 + 0, // BadVAddr, read only + u32::MAX as u64, // count + COP0_ENTRYHI_REG_MASK, + u32::MAX as u64, // compare + COP0_STATUS_REG_MASK, + COP0_CAUSE_REG_MASK, + u64::MAX, // EPC + 0, // previd, read only + COP0_CONFIG_REG_MASK, + COP0_LLADDR_REG_MASK, + COP0_WATCHLO_REG_MASK, + COP0_WATCHHI_REG_MASK, + COP0_XCONTEXT_REG_MASK, + 0, // 21 + 0, // 22 + 0, // 23 + 0, // 24 + 0, // 25 + COP0_PARITYERR_REG_MASK, + 0, // cache error + COP0_TAGLO_REG_MASK, + 0, // taghi + u64::MAX, // ErrorPC + 0, // 31 + ]; + + map_instructions(device); + // taken from VR4300 manual device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 0b00000000000000000000000000011111; device.cpu.cop0.regs[COP0_CONFIG_REG as usize] = 0b01110000000001101110010001100000; @@ -373,10 +379,5 @@ pub fn init(device: &mut device::Device) { device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF; device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0; - device::events::create_event( - device, - device::events::EventType::Compare, - u32::MAX as u64, - compare_event, - ) + device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64) } diff --git a/src/device/cop1.rs b/src/device/cop1.rs index 8a4ee8a..01f53d2 100644 --- a/src/device/cop1.rs +++ b/src/device/cop1.rs @@ -1,4 +1,4 @@ -use crate::device; +use crate::{device, savestates}; //const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2; //const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3; @@ -24,6 +24,7 @@ const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000; const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000; const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Cop1 { pub fcr0: u32, pub fcr31: u32, @@ -31,11 +32,17 @@ pub struct Cop1 { //pub flush_mode: u32, pub fgr32: [[u8; 4]; 32], pub fgr64: [[u8; 8]; 32], + #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub b_instrs: [fn(&mut device::Device, u32); 4], + #[serde(skip, default = "savestates::default_instructions")] pub s_instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub d_instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub w_instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub l_instrs: [fn(&mut device::Device, u32); 64], } @@ -448,10 +455,7 @@ pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 { } } -pub fn init(device: &mut device::Device) { - set_fgr_registers(device, 0); - device.cpu.cop1.fcr0 = 0b101000000000; - +pub fn map_instructions(device: &mut device::Device) { device.cpu.cop1.b_instrs = [ device::fpu_instructions::bc1f, // 0 device::fpu_instructions::bc1t, // 1 @@ -763,6 +767,13 @@ pub fn init(device: &mut device::Device) { ] } +pub fn init(device: &mut device::Device) { + set_fgr_registers(device, 0); + device.cpu.cop1.fcr0 = 0b101000000000; + + map_instructions(device); +} + pub fn set_fgr_registers(device: &mut device::Device, status_reg: u64) { // this method doesn't account for undocumented behaviour (accessing odd numbered registers in half mode) if (status_reg & device::cop0::COP0_STATUS_FR) == 0 { diff --git a/src/device/cop2.rs b/src/device/cop2.rs index ddf5763..92e3c7a 100644 --- a/src/device/cop2.rs +++ b/src/device/cop2.rs @@ -1,6 +1,8 @@ -use crate::device; +use crate::{device, savestates}; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Cop2 { + #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 32], pub reg_latch: u64, } @@ -76,7 +78,7 @@ fn unusable(device: &mut device::Device, _opcode: u32) { device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2) } -pub fn init(device: &mut device::Device) { +pub fn map_instructions(device: &mut device::Device) { device.cpu.cop2.instrs = [ device::cop2::mfc2, // 0 device::cop2::dmfc2, // 1 @@ -112,3 +114,7 @@ pub fn init(device: &mut device::Device) { device::cop2::reserved, // 31 ] } + +pub fn init(device: &mut device::Device) { + map_instructions(device); +} diff --git a/src/device/cpu.rs b/src/device/cpu.rs index afaa6db..a72ddd9 100644 --- a/src/device/cpu.rs +++ b/src/device/cpu.rs @@ -1,6 +1,6 @@ -use crate::device; +use crate::{device, savestates}; -#[derive(PartialEq)] +#[derive(PartialEq, serde::Serialize, serde::Deserialize)] pub enum State { Step, Take, @@ -11,10 +11,13 @@ pub enum State { Exception, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct BranchState { pub state: State, pub pc: u64, } + +#[derive(serde::Serialize, serde::Deserialize)] pub struct Cpu { pub cop0: device::cop0::Cop0, pub cop1: device::cop1::Cop1, @@ -28,10 +31,13 @@ pub struct Cpu { pub running: bool, pub llbit: bool, pub clock_rate: u64, + #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub special_instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub regimm_instrs: [fn(&mut device::Device, u32); 32], - pub events: [device::events::Event; device::events::EventType::Count as usize], + pub events: [device::events::Event; device::events::EVENT_TYPE_COUNT], pub next_event_count: u64, pub next_event: usize, } @@ -47,9 +53,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De } } -pub fn init(device: &mut device::Device) { - device.cpu.clock_rate = 93750000; - +pub fn map_instructions(device: &mut device::Device) { device.cpu.instrs = [ device::cop0::reserved, // SPECIAL device::cop0::reserved, // REGIMM @@ -216,6 +220,13 @@ pub fn init(device: &mut device::Device) { device::cop0::reserved, // 30 device::cop0::reserved, // 31 ]; +} + +pub fn init(device: &mut device::Device) { + device.cpu.clock_rate = 93750000; + + map_instructions(device); + device::cop0::init(device); device::cop1::init(device); device::cop2::init(device); diff --git a/src/device/events.rs b/src/device/events.rs index 3aa48ed..10a3e83 100644 --- a/src/device/events.rs +++ b/src/device/events.rs @@ -1,58 +1,66 @@ use crate::device; -#[derive(PartialEq, Copy, Clone)] -pub enum EventType { - AI, - SI, - VI, - PI, - DP, - SP, - Interrupt, - SPDma, - Compare, - Vru, - PakSwitch, - Count, -} +pub const EVENT_TYPE_NONE: usize = 0; +pub const EVENT_TYPE_AI: usize = 1; +pub const EVENT_TYPE_SI: usize = 2; +pub const EVENT_TYPE_VI: usize = 3; +pub const EVENT_TYPE_PI: usize = 4; +pub const EVENT_TYPE_DP: usize = 5; +pub const EVENT_TYPE_SP: usize = 6; +pub const EVENT_TYPE_INT: usize = 7; +pub const EVENT_TYPE_SPDMA: usize = 8; +pub const EVENT_TYPE_COMPARE: usize = 9; +pub const EVENT_TYPE_VRU: usize = 10; +pub const EVENT_TYPE_PAK: usize = 11; +pub const EVENT_TYPE_COUNT: usize = 12; -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct Event { pub enabled: bool, pub count: u64, - pub handler: fn(&mut device::Device), } -pub fn create_event( - device: &mut device::Device, - name: EventType, - when: u64, - handler: fn(&mut device::Device), -) { - device.cpu.events[name as usize] = Event { +pub fn create_event(device: &mut device::Device, name: usize, when: u64) { + device.cpu.events[name] = Event { enabled: true, count: when, - handler, }; set_next_event(device); } -pub fn get_event(device: &mut device::Device, name: EventType) -> Option<&mut Event> { - if device.cpu.events[name as usize].enabled { - return Some(&mut device.cpu.events[name as usize]); +pub fn get_event(device: &mut device::Device, name: usize) -> Option<&mut Event> { + if device.cpu.events[name].enabled { + return Some(&mut device.cpu.events[name]); } None } -pub fn remove_event(device: &mut device::Device, name: EventType) { - device.cpu.events[name as usize].enabled = false; +pub fn remove_event(device: &mut device::Device, name: usize) { + device.cpu.events[name].enabled = false; +} + +fn get_event_handler(name: usize) -> fn(&mut device::Device) { + match name { + EVENT_TYPE_AI => device::ai::dma_event, + EVENT_TYPE_SI => device::si::dma_event, + EVENT_TYPE_VI => device::vi::vertical_interrupt_event, + EVENT_TYPE_PI => device::pi::dma_event, + EVENT_TYPE_DP => device::rdp::rdp_interrupt_event, + EVENT_TYPE_SP => device::rsp_interface::rsp_event, + EVENT_TYPE_INT => device::exceptions::interrupt_exception, + EVENT_TYPE_SPDMA => device::rsp_interface::fifo_pop, + EVENT_TYPE_COMPARE => device::cop0::compare_event, + EVENT_TYPE_VRU => device::controller::vru::vru_talking_event, + EVENT_TYPE_PAK => device::controller::pak_switch_event, + _ => dummy_event, + } } pub fn trigger_event(device: &mut device::Device) { let next_event_name = device.cpu.next_event; device.cpu.events[next_event_name].enabled = false; - let handler = device.cpu.events[next_event_name].handler; + let handler = get_event_handler(next_event_name); device.cpu.cop0.is_event = true; handler(device); device.cpu.cop0.is_event = false; diff --git a/src/device/exceptions.rs b/src/device/exceptions.rs index 1e72d2c..40353b0 100644 --- a/src/device/exceptions.rs +++ b/src/device/exceptions.rs @@ -35,9 +35,8 @@ pub fn check_pending_interrupts(device: &mut device::Device) { } else { device::events::create_event( device, - device::events::EventType::Interrupt, + device::events::EVENT_TYPE_INT, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize], - interrupt_exception, ); } } diff --git a/src/device/memory.rs b/src/device/memory.rs index a5707d7..3b87d7c 100644 --- a/src/device/memory.rs +++ b/src/device/memory.rs @@ -1,4 +1,4 @@ -use crate::device; +use crate::{device, savestates}; const MM_RDRAM_DRAM: usize = 0x00000000; const MM_RDRAM_REGS: usize = 0x03f00000; @@ -28,20 +28,27 @@ pub enum AccessType { Read, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] pub enum AccessSize { - // None = 0, + #[default] + None = 0, Word = 4, Dword = 8, Dcache = 16, Icache = 32, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Memory { + #[serde(skip, default = "savestates::default_memory_read")] pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops + #[serde(skip, default = "savestates::default_memory_read")] pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], + #[serde(skip, default = "savestates::default_memory_write")] pub memory_map_write: [fn(&mut device::Device, u64, u32, u32); 0x2000], + #[serde(with = "serde_big_array::BigArray")] pub icache: [device::cache::ICache; 512], + #[serde(with = "serde_big_array::BigArray")] pub dcache: [device::cache::DCache; 512], } @@ -164,5 +171,4 @@ pub fn init(device: &mut device::Device) { device.memory.memory_map_write[i] = device::cart::sc64::write_regs; } } - device::cache::init(device) } diff --git a/src/device/mi.rs b/src/device/mi.rs index 81f02d7..82f1121 100644 --- a/src/device/mi.rs +++ b/src/device/mi.rs @@ -44,6 +44,7 @@ const MI_SET_RDRAM: u32 = 1 << 13; const MI_INIT_LENGTH_MASK: u32 = 0b1111111; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Mi { pub regs: [u32; MI_REGS_COUNT as usize], } diff --git a/src/device/pi.rs b/src/device/pi.rs index 691411e..039b93d 100644 --- a/src/device/pi.rs +++ b/src/device/pi.rs @@ -26,6 +26,7 @@ const PI_STATUS_INTERRUPT: u32 = 1 << 3; const PI_STATUS_RESET: u32 = 1 << 0; const PI_STATUS_CLR_INTR: u32 = 1 << 1; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Pi { pub regs: [u32; PI_REGS_COUNT as usize], } @@ -66,9 +67,8 @@ fn dma_read(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::PI, + device::events::EVENT_TYPE_PI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles, - dma_event, ); /* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */ @@ -99,9 +99,8 @@ fn dma_write(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::PI, + device::events::EVENT_TYPE_PI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles, - dma_event, ); /* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */ diff --git a/src/device/pif.rs b/src/device/pif.rs index 8bab677..8a494e3 100644 --- a/src/device/pif.rs +++ b/src/device/pif.rs @@ -1,18 +1,22 @@ mod rom; use crate::device; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Pif { + #[serde(with = "serde_big_array::BigArray")] pub rom: [u8; 1984], + #[serde(with = "serde_big_array::BigArray")] pub ram: [u8; 64], pub channels: [PifChannel; 5], } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct PifChannel { pub tx: Option, pub tx_buf: Option, pub rx: Option, pub rx_buf: Option, + #[serde(skip)] pub process: Option, pub pak_handler: Option, pub change_pak: device::controller::PakType, @@ -65,9 +69,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3 device.si.dma_dir = device::si::DmaDir::Write; device::events::create_event( device, - device::events::EventType::SI, + device::events::EVENT_TYPE_SI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 3200, - device::si::dma_event, ); //based on https://github.com/rasky/n64-systembench device.si.regs[device::si::SI_STATUS_REG as usize] |= device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY @@ -217,6 +220,22 @@ pub fn process_ram(device: &mut device::Device) { device.pif.ram[0x3F] &= !clrmask } +pub fn connect_pif_channels(device: &mut device::Device) { + for i in 0..4 { + if device.netplay.is_none() { + if device.ui.config.input.controller_enabled[i] { + device.pif.channels[i].process = Some(device::controller::process); + } + } else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 { + device.pif.channels[i].process = Some(device::controller::process); + } + } + if device.ui.config.input.emulate_vru { + device.pif.channels[3].process = Some(device::controller::vru::process); + } + device.pif.channels[4].process = Some(device::cart::process) +} + pub fn init(device: &mut device::Device) { if device.cart.pal { device.pif.rom = rom::PAL_PIF_ROM; @@ -232,22 +251,20 @@ pub fn init(device: &mut device::Device) { pak_type: device::controller::PakType::MemPak, }; + connect_pif_channels(device); + for i in 0..4 { if device.netplay.is_none() { if device.ui.config.input.controller_enabled[i] { device.pif.channels[i].pak_handler = Some(mempak_handler); - device.pif.channels[i].process = Some(device::controller::process); } } else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 { device.pif.channels[i].pak_handler = Some(mempak_handler); - device.pif.channels[i].process = Some(device::controller::process); } } if device.ui.config.input.emulate_vru { device.pif.channels[3].pak_handler = None; - device.pif.channels[3].process = Some(device::controller::vru::process); } - device.pif.channels[4].process = Some(device::cart::process) } fn process_cic_challenge(device: &mut device::Device) { diff --git a/src/device/rdp.rs b/src/device/rdp.rs index 9df454c..cd35441 100644 --- a/src/device/rdp.rs +++ b/src/device/rdp.rs @@ -1,5 +1,4 @@ -use crate::device; -use crate::ui; +use crate::{device, savestates, ui}; pub const DPC_START_REG: u32 = 0; pub const DPC_END_REG: u32 = 1; @@ -41,6 +40,7 @@ const DPC_CLR_PIPE_CTR: u32 = 1 << 7; const DPC_CLR_CMD_CTR: u32 = 1 << 8; const DPC_CLR_CLOCK_CTR: u32 = 1 << 9; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Rdp { pub regs_dpc: [u32; DPC_REGS_COUNT as usize], pub regs_dps: [u32; DPS_REGS_COUNT as usize], @@ -118,11 +118,19 @@ fn run_rdp(device: &mut device::Device) { DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY; if timer != 0 { + if device.save_state { + // Right after full sync, good time for save state + device.save_state = false; + savestates::create_savestate(device); + } else if device.load_state { + // Right after full sync, good time for save state + device.load_state = false; + savestates::load_savestate(device); + } device::events::create_event( device, - device::events::EventType::DP, + device::events::EVENT_TYPE_DP, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer, - rdp_interrupt_event, ) } } @@ -197,7 +205,7 @@ pub fn init(device: &mut device::Device) { DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY; } -fn rdp_interrupt_event(device: &mut device::Device) { +pub 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 | DPC_STATUS_CMD_BUSY); diff --git a/src/device/rdram.rs b/src/device/rdram.rs index 37d2615..a94a468 100644 --- a/src/device/rdram.rs +++ b/src/device/rdram.rs @@ -15,6 +15,7 @@ pub const RDRAM_REGS_COUNT: u32 = 10; pub const RDRAM_MASK: usize = 0xFFFFFF; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Rdram { pub mem: Vec, pub size: u32, diff --git a/src/device/ri.rs b/src/device/ri.rs index 60056b4..b6b5cec 100644 --- a/src/device/ri.rs +++ b/src/device/ri.rs @@ -10,6 +10,7 @@ pub const RI_REFRESH_REG: u32 = 4; //const RI_WERROR_REG: u32 = 7; pub const RI_REGS_COUNT: u32 = 8; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Ri { pub regs: [u32; RI_REGS_COUNT as usize], pub ram_init: bool, diff --git a/src/device/rsp_cpu.rs b/src/device/rsp_cpu.rs index 3d41440..451f397 100644 --- a/src/device/rsp_cpu.rs +++ b/src/device/rsp_cpu.rs @@ -2,28 +2,32 @@ use device::__m128i; #[cfg(target_arch = "aarch64")] include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs")); -use crate::device; +use crate::{device, savestates}; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::*; +#[derive(serde::Serialize, serde::Deserialize)] pub struct BranchState { pub state: device::cpu::State, pub pc: u32, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct Instructions { + #[serde(skip, default = "savestates::default_instruction")] pub func: fn(&mut device::Device, u32), pub opcode: u32, } -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub enum InstructionType { Su, Vu, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Cpu { + #[serde(with = "serde_big_array::BigArray")] pub instructions: [Instructions; 0x1000 / 4], pub last_instruction_type: InstructionType, pub instruction_type: InstructionType, @@ -33,29 +37,79 @@ pub struct Cpu { pub halted: bool, pub sync_point: bool, pub cycle_counter: u64, + #[serde( + serialize_with = "savestates::serialize_m128i_array", + deserialize_with = "savestates::deserialize_m128i_array" + )] pub shuffle: [__m128i; 16], pub gpr: [u32; 32], + #[serde( + serialize_with = "savestates::serialize_m128i_array", + deserialize_with = "savestates::deserialize_m128i_array" + )] pub vpr: [__m128i; 32], + #[serde(with = "serde_big_array::BigArray")] pub reciprocals: [u16; 512], + #[serde(with = "serde_big_array::BigArray")] pub inverse_square_roots: [u16; 512], + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub vcol: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub vcoh: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub vccl: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub vcch: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub vce: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub accl: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub accm: __m128i, + #[serde( + serialize_with = "savestates::serialize_m128i", + deserialize_with = "savestates::deserialize_m128i" + )] pub acch: __m128i, pub divdp: bool, pub divin: i16, pub divout: i16, + #[serde(skip, default = "savestates::default_instructions")] pub special_instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub regimm_instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub cop0_instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub cop2_instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub lwc2_instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub swc2_instrs: [fn(&mut device::Device, u32); 32], + #[serde(skip, default = "savestates::default_instructions")] pub instrs: [fn(&mut device::Device, u32); 64], + #[serde(skip, default = "savestates::default_instructions")] pub vec_instrs: [fn(&mut device::Device, u32); 64], } @@ -165,62 +219,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De } } -pub fn init(device: &mut device::Device) { - device.rsp.cpu.reciprocals[0] = u16::MAX; - let mut index = 1; - while index < 512 { - let a = (index + 512) as u64; - let b = (1_u64 << 34) / a; - device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16; - index += 1; - } - - index = 0; - while index < 512 { - let mut shift = 0; - if index % 2 == 1 { - shift = 1 - } - let a = ((index + 512) >> shift) as u64; - let mut b = (1 << 17) as u64; - //find the largest b where b < 1.0 / sqrt(a) - while a * (b + 1) * (b + 1) < (1_u64 << 44) { - b += 1; - } - device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16; - index += 1; - } - - device.rsp.cpu.shuffle = unsafe { - [ - //vector - _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 - _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 - //scalar quarter - _mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466 - _mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577 - //scalar half - _mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444 - _mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555 - _mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666 - _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777 - //scalar whole - _mm_set_epi8( - 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, - ), //00000000 - _mm_set_epi8( - 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, - ), //11111111 - _mm_set_epi8( - 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, - ), //22222222 - _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333 - _mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444 - _mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555 - _mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666 - _mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777 - ] - }; +pub fn map_instructions(device: &mut device::Device) { device.rsp.cpu.instrs = [ device::rsp_su_instructions::reserved, // SPECIAL device::rsp_su_instructions::reserved, // REGIMM @@ -597,3 +596,63 @@ pub fn init(device: &mut device::Device) { device::rsp_vu_instructions::vnop, // 63 ] } + +pub fn init(device: &mut device::Device) { + device.rsp.cpu.reciprocals[0] = u16::MAX; + let mut index = 1; + while index < 512 { + let a = (index + 512) as u64; + let b = (1_u64 << 34) / a; + device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16; + index += 1; + } + + index = 0; + while index < 512 { + let mut shift = 0; + if index % 2 == 1 { + shift = 1 + } + let a = ((index + 512) >> shift) as u64; + let mut b = (1 << 17) as u64; + //find the largest b where b < 1.0 / sqrt(a) + while a * (b + 1) * (b + 1) < (1_u64 << 44) { + b += 1; + } + device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16; + index += 1; + } + + device.rsp.cpu.shuffle = unsafe { + [ + //vector + _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + //scalar quarter + _mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466 + _mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577 + //scalar half + _mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444 + _mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555 + _mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777 + //scalar whole + _mm_set_epi8( + 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, + ), //00000000 + _mm_set_epi8( + 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, + ), //11111111 + _mm_set_epi8( + 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, + ), //22222222 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333 + _mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444 + _mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555 + _mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666 + _mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777 + ] + }; + + map_instructions(device); +} diff --git a/src/device/rsp_interface.rs b/src/device/rsp_interface.rs index 1c643ca..bad92e8 100644 --- a/src/device/rsp_interface.rs +++ b/src/device/rsp_interface.rs @@ -60,14 +60,14 @@ const SP_SET_SIG7: u32 = 1 << 24; const RSP_MEM_MASK: usize = 0x1FFF; -#[derive(PartialEq, Copy, Clone)] +#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)] pub enum DmaDir { None, Write, Read, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct RspDma { pub dir: DmaDir, pub length: u32, @@ -75,10 +75,12 @@ pub struct RspDma { pub dramaddr: u32, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Rsp { pub cpu: device::rsp_cpu::Cpu, pub regs: [u32; SP_REGS_COUNT as usize], pub regs2: [u32; SP_REGS2_COUNT as usize], + #[serde(with = "serde_big_array::BigArray")] pub mem: [u8; 0x2000], pub fifo: [RspDma; 2], pub last_status_value: u32, @@ -201,11 +203,10 @@ fn do_dma(device: &mut device::Device, dma: RspDma) { device::events::create_event( device, - device::events::EventType::SPDma, + device::events::EVENT_TYPE_SPDMA, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device::rdram::rdram_calculate_cycles((count * length) as u64) + 9, - device::rsp_interface::fifo_pop, ); } @@ -241,7 +242,7 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) { } } -fn fifo_pop(device: &mut device::Device) { +pub fn fifo_pop(device: &mut device::Device) { if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 { device.rsp.fifo[0].dir = device.rsp.fifo[1].dir; device.rsp.fifo[0].length = device.rsp.fifo[1].length; @@ -350,7 +351,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) { device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT } if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 { - device::events::remove_event(device, device::events::EventType::SP); + device::events::remove_event(device, device::events::EVENT_TYPE_SP); device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT } @@ -465,14 +466,13 @@ fn do_task(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::SP, + device::events::EVENT_TYPE_SP, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer, - rsp_event, ) } } -fn rsp_event(device: &mut device::Device) { +pub fn rsp_event(device: &mut device::Device) { if device.rsp.cpu.broken { device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT | SP_STATUS_BROKE; diff --git a/src/device/si.rs b/src/device/si.rs index 4751ebc..50b2adb 100644 --- a/src/device/si.rs +++ b/src/device/si.rs @@ -14,13 +14,14 @@ pub const SI_STATUS_IO_BUSY: u32 = 1 << 1; //const SI_STATUS_DMA_ERROR: u32 = 1 << 3; const SI_STATUS_INTERRUPT: u32 = 1 << 12; -#[derive(PartialEq)] +#[derive(PartialEq, serde::Serialize, serde::Deserialize)] pub enum DmaDir { None, Write, Read, } +#[derive(serde::Serialize, serde::Deserialize)] pub struct Si { pub regs: [u32; SI_REGS_COUNT as usize], pub dma_dir: DmaDir, @@ -44,9 +45,8 @@ fn dma_read(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::SI, + device::events::EVENT_TYPE_SI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + duration, - dma_event, ) } @@ -59,9 +59,8 @@ fn dma_write(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::SI, + device::events::EVENT_TYPE_SI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench - dma_event, ) } diff --git a/src/device/tlb.rs b/src/device/tlb.rs index e966684..5c0cd89 100644 --- a/src/device/tlb.rs +++ b/src/device/tlb.rs @@ -1,12 +1,12 @@ use crate::device; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct TlbLut { pub address: u64, pub cached: bool, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct TlbEntry { pub mask: u64, pub vpn2: u64, diff --git a/src/device/vi.rs b/src/device/vi.rs index 9b825e2..cb884b1 100644 --- a/src/device/vi.rs +++ b/src/device/vi.rs @@ -1,6 +1,4 @@ -use crate::device; -use crate::netplay; -use crate::ui; +use crate::{device, netplay, ui}; use governor::clock::Clock; const VI_STATUS_REG: u32 = 0; @@ -19,11 +17,13 @@ const VI_H_SYNC_REG: u32 = 7; //const VI_Y_SCALE_REG: u32 = 13; pub const VI_REGS_COUNT: u32 = 14; +#[derive(serde::Serialize, serde::Deserialize)] pub struct Vi { pub regs: [u32; VI_REGS_COUNT as usize], pub clock: u64, pub delay: u64, pub field: u32, + #[serde(skip)] pub limiter: Option, pub count_per_scanline: u64, pub vi_counter: u64, @@ -33,7 +33,7 @@ pub struct Vi { const LIMIT_BUFFER: u64 = 3; -fn set_expected_refresh_rate(device: &mut device::Device) { +pub fn set_expected_refresh_rate(device: &mut device::Device) { let expected_refresh_rate = device.vi.clock as f64 / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as f64 / ((device.vi.regs[VI_H_SYNC_REG as usize] & 0xFFF) + 1) as f64 @@ -50,19 +50,18 @@ fn set_expected_refresh_rate(device: &mut device::Device) { } fn set_vertical_interrupt(device: &mut device::Device) { - if device::events::get_event(device, device::events::EventType::VI).is_none() { + if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() { device::events::create_event( device, - device::events::EventType::VI, + device::events::EVENT_TYPE_VI, device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.vi.delay, - vertical_interrupt_event, ) } } fn set_current_line(device: &mut device::Device) { let delay = device.vi.delay; - let next_vi = device::events::get_event(device, device::events::EventType::VI); + let next_vi = device::events::get_event(device, device::events::EVENT_TYPE_VI); if next_vi.is_some() { device.vi.regs[VI_CURRENT_REG as usize] = ((delay - (next_vi.unwrap().count @@ -117,8 +116,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u ui::video::set_register(reg as u32, device.vi.regs[reg as usize]) } -fn vertical_interrupt_event(device: &mut device::Device) { - device.cpu.running = ui::video::update_screen(); +pub fn vertical_interrupt_event(device: &mut device::Device) { + ui::video::update_screen(); + ui::video::check_callback(device); /* unsafe { @@ -144,9 +144,8 @@ fn vertical_interrupt_event(device: &mut device::Device) { device::events::create_event( device, - device::events::EventType::VI, + device::events::EVENT_TYPE_VI, device.cpu.next_event_count + device.vi.delay, - vertical_interrupt_event, ) } diff --git a/src/main.rs b/src/main.rs index 34afd2f..f877979 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; mod device; mod netplay; +mod savestates; mod ui; use clap::Parser; use ui::gui; @@ -78,7 +79,11 @@ async fn main() { } result = std::fs::create_dir_all(dirs.data_dir.join("saves")); if result.is_err() { - panic!("could not create data dir: {}", result.err().unwrap()) + panic!("could not create save dir: {}", result.err().unwrap()) + } + result = std::fs::create_dir_all(dirs.data_dir.join("states")); + if result.is_err() { + panic!("could not create state dir: {}", result.err().unwrap()) } let running_file = dirs.cache_dir.join("game_running"); if running_file.exists() { @@ -91,11 +96,11 @@ async fn main() { let args = Args::parse(); let args_as_strings: Vec = std::env::args().collect(); let args_count = args_as_strings.len(); - if args_count > 1 { - let mut device = device::Device::new(); + if args_count > 1 && args.game.is_none() { + let mut ui = ui::Ui::new(); if args.clear_input_bindings { - ui::input::clear_bindings(&mut device.ui); + ui::input::clear_bindings(&mut ui); return; } if args.port.is_some() { @@ -106,7 +111,7 @@ async fn main() { } } if args.list_controllers { - let controllers = gui::get_controller_names(&device.ui); + let controllers = gui::get_controller_names(&ui); for (i, controller) in controllers.iter().enumerate() { println!("Controller {}: {}", i, controller); } @@ -118,7 +123,7 @@ async fn main() { return; } ui::input::assign_controller( - &mut device.ui, + &mut ui, args.assign_controller.unwrap(), args.port.unwrap(), ); @@ -130,7 +135,7 @@ async fn main() { return; } ui::input::bind_input_profile( - &mut device.ui, + &mut ui, args.bind_input_profile.unwrap(), args.port.unwrap(), ); @@ -138,22 +143,30 @@ async fn main() { } if args.configure_input_profile.is_some() { ui::input::configure_input_profile( - &mut device.ui, + &mut ui, args.configure_input_profile.unwrap(), args.use_dinput, ); return; } - - if args.game.is_some() { - let file_path = std::path::Path::new(args.game.as_ref().unwrap()); - let rom_contents = device::get_rom_contents(file_path); - if rom_contents.is_empty() { - println!("Could not read rom file"); - return; - } - device::run_game(rom_contents, &mut device, args.fullscreen); + } else if args.game.is_some() { + let file_path = std::path::Path::new(args.game.as_ref().unwrap()); + let rom_contents = device::get_rom_contents(file_path); + if rom_contents.is_empty() { + println!("Could not read rom file"); + return; } + let handle = std::thread::Builder::new() + .name("n64".to_string()) + .stack_size(env!("N64_STACK_SIZE").parse().unwrap()) + .spawn(move || { + let mut device = device::Device::new(); + device.ui.fullscreen = args.fullscreen; + device::run_game(rom_contents, &mut device); + }) + .unwrap(); + + handle.join().unwrap(); } else { let options = eframe::NativeOptions { viewport: eframe::egui::ViewportBuilder::default() diff --git a/src/netplay.rs b/src/netplay.rs index ad65398..4272e99 100644 --- a/src/netplay.rs +++ b/src/netplay.rs @@ -1,5 +1,4 @@ -use crate::device; -use crate::ui; +use crate::{device, ui}; use eframe::egui; use std::io::{Read, Write}; //UDP packet formats diff --git a/src/savestates.rs b/src/savestates.rs new file mode 100644 index 0000000..bb80b84 --- /dev/null +++ b/src/savestates.rs @@ -0,0 +1,199 @@ +use crate::{device, ui}; +#[cfg(target_arch = "aarch64")] +use device::__m128i; +use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; +use serde::ser::{Serialize, SerializeSeq, Serializer}; +#[cfg(target_arch = "x86_64")] +use std::arch::x86_64::*; + +struct M128iArrayVisitor; + +impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor { + type Value = [__m128i; N]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(&format!("an array of {} 128-bit integers", N)) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut arr: [__m128i; N] = [device::zero_m128i(); N]; + for (index, item) in arr.iter_mut().enumerate().take(N) { + match seq.next_element::()? { + Some(value) => *item = unsafe { std::mem::transmute::(value) }, + None => return Err(serde::de::Error::invalid_length(index, &self)), + } + } + Ok(arr) + } +} + +pub fn deserialize_m128i_array<'de, D, const N: usize>( + deserializer: D, +) -> Result<[__m128i; N], D::Error> +where + D: serde::Deserializer<'de>, +{ + deserializer.deserialize_seq(M128iArrayVisitor) +} + +pub fn serialize_m128i(data: &__m128i, serializer: S) -> Result +where + S: Serializer, +{ + let bytes: u128 = unsafe { std::mem::transmute(*data) }; + bytes.serialize(serializer) +} + +pub fn deserialize_m128i<'de, D>(deserializer: D) -> Result<__m128i, D::Error> +where + D: Deserializer<'de>, +{ + let bytes = u128::deserialize(deserializer)?; + Ok(unsafe { std::mem::transmute::(bytes) }) +} + +pub fn serialize_m128i_array(value: &[__m128i], serializer: S) -> Result +where + S: serde::Serializer, +{ + let mut seq = serializer.serialize_seq(Some(value.len()))?; + + for item in value { + let bytes: u128 = unsafe { std::mem::transmute(*item) }; + seq.serialize_element(&bytes)?; + } + + seq.end() +} + +pub fn create_savestate(device: &device::Device) { + let data: &[(&[u8], &str)] = &[ + (&postcard::to_stdvec(device).unwrap(), "device"), + (&postcard::to_stdvec(&device.ui.saves).unwrap(), "saves"), + ]; + let compressed_file = ui::storage::compress_file(data); + std::fs::write(device.ui.paths.savestate_file_path.clone(), compressed_file).unwrap(); +} + +pub fn load_savestate(device: &mut device::Device) { + let savestate = std::fs::read(&mut device.ui.paths.savestate_file_path); + if savestate.is_ok() { + let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device"); + let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves"); + let state: device::Device = postcard::from_bytes(&device_bytes).unwrap(); + + device.ui.saves = postcard::from_bytes(&save_bytes).unwrap(); + + device.cpu = state.cpu; + device.pif = state.pif; + + let rom = device.cart.rom.clone(); + device.cart = state.cart; + device.cart.rom = rom; + + device.memory = state.memory; + device.rsp = state.rsp; + device.rdp = state.rdp; + device.rdram = state.rdram; + device.mi = state.mi; + device.pi = state.pi; + device.vi = state.vi; + device.ai = state.ai; + device.si = state.si; + device.ri = state.ri; + device.vru = state.vru; + + device::memory::init(device); + device::vi::set_expected_refresh_rate(device); + device::cpu::map_instructions(device); + device::cop0::map_instructions(device); + device::cop1::map_instructions(device); + device::cop2::map_instructions(device); + device::rsp_cpu::map_instructions(device); + + let mut mem_addr = 0x1000; + while mem_addr < 0x2000 { + let data = + u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap()); + device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func = + device::rsp_cpu::decode_opcode(device, data); + device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data; + mem_addr += 4; + } + + for line_index in 0..512 { + device.memory.icache[line_index].instruction[0] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]); + device.memory.icache[line_index].instruction[1] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]); + device.memory.icache[line_index].instruction[2] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]); + device.memory.icache[line_index].instruction[3] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]); + device.memory.icache[line_index].instruction[4] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]); + device.memory.icache[line_index].instruction[5] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]); + device.memory.icache[line_index].instruction[6] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]); + device.memory.icache[line_index].instruction[7] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]); + } + + device::pif::connect_pif_channels(device); + for i in 0..4 { + if device.pif.channels[i].pak_handler.is_some() { + if device.pif.channels[i].pak_handler.unwrap().pak_type + == device::controller::PakType::RumblePak + { + let rumblepak_handler = device::controller::PakHandler { + read: device::controller::rumble::read, + write: device::controller::rumble::write, + pak_type: device::controller::PakType::RumblePak, + }; + device.pif.channels[i].pak_handler = Some(rumblepak_handler); + } else if device.pif.channels[i].pak_handler.unwrap().pak_type + == device::controller::PakType::MemPak + { + let mempak_handler = device::controller::PakHandler { + read: device::controller::mempak::read, + write: device::controller::mempak::write, + pak_type: device::controller::PakType::MemPak, + }; + device.pif.channels[i].pak_handler = Some(mempak_handler); + } + } + } + + ui::audio::close(&mut device.ui); + ui::audio::init(&mut device.ui, device.ai.freq); + ui::video::load_state(device); + } +} + +pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) { + device::controller::mempak::read +} + +pub fn default_instruction() -> fn(&mut device::Device, u32) { + device::cop0::reserved +} + +pub fn default_instructions() -> [fn(&mut device::Device, u32); N] +where + [fn(&mut device::Device, u32); N]: Sized, +{ + [device::cop0::reserved; N] +} + +pub fn default_memory_read( +) -> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] { + [device::rdram::read_mem; 0x2000] +} + +pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] { + [device::rdram::write_mem; 0x2000] +} diff --git a/src/ui.rs b/src/ui.rs index 4783b98..79929cd 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -26,17 +26,20 @@ pub struct Ui { pub window: *mut sdl3_sys::video::SDL_Window, pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, - pub audio_freq: f64, pub audio_device: u32, pub num_joysticks: i32, + pub fullscreen: bool, pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID, + pub with_sdl: bool, } impl Drop for Ui { fn drop(&mut self) { - unsafe { - sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void); - sdl3_sys::init::SDL_Quit(); + if self.with_sdl { + unsafe { + sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void); + sdl3_sys::init::SDL_Quit(); + } } } } @@ -78,14 +81,7 @@ pub fn get_dirs() -> Dirs { } impl Ui { - pub fn new() -> Ui { - sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); - let mut num_joysticks = 0; - let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) }; - if joysticks.is_null() { - panic!("Could not get joystick list"); - } - + fn construct_ui(num_joysticks: i32, joysticks: *mut u32, with_sdl: bool) -> Ui { let dirs = get_dirs(); Ui { @@ -123,6 +119,7 @@ impl Ui { pak_file_path: std::path::PathBuf::new(), sdcard_file_path: std::path::PathBuf::new(), romsave_file_path: std::path::PathBuf::new(), + savestate_file_path: std::path::PathBuf::new(), }, saves: storage::Saves { eeprom: (Vec::new(), false), @@ -139,11 +136,26 @@ impl Ui { window: std::ptr::null_mut(), audio_stream: std::ptr::null_mut(), pak_audio_stream: std::ptr::null_mut(), - audio_freq: 0.0, audio_device: 0, + fullscreen: false, num_joysticks, joysticks, dirs, + with_sdl, } } + + pub fn default() -> Ui { + Self::construct_ui(0, std::ptr::null_mut(), false) + } + + pub fn new() -> Ui { + sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD); + let mut num_joysticks = 0; + let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) }; + if joysticks.is_null() { + panic!("Could not get joystick list"); + } + Self::construct_ui(num_joysticks, joysticks, true) + } } diff --git a/src/ui/audio.rs b/src/ui/audio.rs index fd309d9..cc02755 100644 --- a/src/ui/audio.rs +++ b/src/ui/audio.rs @@ -49,8 +49,6 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) { if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } { panic!("Could not bind audio stream"); } - - ui.audio_freq = audio_spec.freq as f64; } pub fn close(ui: &mut ui::Ui) { @@ -112,8 +110,8 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) { let audio_queued = unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64; - let acceptable_latency = (device.ui.audio_freq * 0.2) * 4.0; - let min_latency = (device.ui.audio_freq * 0.02) * 4.0; + let acceptable_latency = (device.ai.freq as f64 * 0.2) * 4.0; + let min_latency = (device.ai.freq as f64 * 0.02) * 4.0; if audio_queued < min_latency { let silence_buffer: Vec = vec![0; min_latency as usize & !3]; diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 9dd3823..daf6571 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -1,6 +1,4 @@ -use crate::device; -use crate::netplay; -use crate::ui; +use crate::{device, netplay, ui}; use eframe::egui; pub mod gui_netplay; @@ -181,23 +179,8 @@ fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) { let profile_name = app.profile_name.clone(); let dinput = app.dinput; std::thread::spawn(move || { - if cfg!(target_os = "macos") { - let mut command = - std::process::Command::new(std::env::current_exe().unwrap()); - if dinput { - command.arg("--use-dinput"); - } - command.arg("--configure-input-profile"); - command.arg(profile_name); - - let status = command.status().expect("failed to execute process"); - if !status.success() { - panic!("process exited with: {}", status); - } - } else { - let mut game_ui = ui::Ui::new(); - ui::input::configure_input_profile(&mut game_ui, profile_name, dinput); - } + let mut game_ui = ui::Ui::new(); + ui::input::configure_input_profile(&mut game_ui, profile_name, dinput); }); app.configure_profile = false; if !app.profile_name.is_empty() @@ -322,6 +305,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { std::thread::Builder::new() .name("n64".to_string()) + .stack_size(env!("N64_STACK_SIZE").parse().unwrap()) .spawn(move || { let save_config_items = SaveConfig { selected_controller, @@ -333,24 +317,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { emulate_vru, }; - 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 status = command.status().expect("failed to execute process"); - if !status.success() { - panic!("process exited with: {}", status); - } - } else if file.is_some() || netplay { + if file.is_some() || netplay { let running_file = cache_dir.join("game_running"); if running_file.exists() { println!("Game already running"); @@ -372,20 +339,22 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) { netplay_error_notifier, gui_ctx, )); - device::run_game(rom_contents, &mut device, fullscreen); + device.ui.fullscreen = fullscreen; + device::run_game(rom_contents, &mut device); 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); + device.vru_window.window_notifier = Some(vru_window_notifier); + device.vru_window.word_receiver = Some(vru_word_receiver); + device.vru_window.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); + device.ui.fullscreen = fullscreen; + device::run_game(rom_contents, &mut device); } } let result = std::fs::remove_file(running_file); @@ -437,8 +406,7 @@ impl eframe::App for GopherEguiApp { if ui.button("Open ROM").clicked() { open_rom(self, ctx); } - if !cfg!(target_os = "macos") - && ui.button("Netplay: Create Session").clicked() + if ui.button("Netplay: Create Session").clicked() && !self.dirs.cache_dir.join("game_running").exists() { self.netplay.create = true; @@ -452,8 +420,7 @@ impl eframe::App for GopherEguiApp { self.configure_profile = true; } - if !cfg!(target_os = "macos") - && ui.button("Netplay: Join Session").clicked() + if ui.button("Netplay: Join Session").clicked() && !self.dirs.cache_dir.join("game_running").exists() { self.netplay.join = true; @@ -513,12 +480,12 @@ impl eframe::App for GopherEguiApp { ui.checkbox(&mut self.integer_scaling, "Integer Scaling"); ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)"); ui.add_space(32.0); - if !cfg!(target_os = "macos") { - ui.checkbox( - &mut self.emulate_vru, - "Emulate VRU (connects VRU to controller port 4)", - ); - } + + ui.checkbox( + &mut self.emulate_vru, + "Emulate VRU (connects VRU to controller port 4)", + ); + ui.add_space(32.0); ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION"))); }); diff --git a/src/ui/storage.rs b/src/ui/storage.rs index c10a72c..f9f9d69 100644 --- a/src/ui/storage.rs +++ b/src/ui/storage.rs @@ -1,5 +1,4 @@ -use crate::netplay; -use crate::ui; +use crate::{netplay, ui}; use std::io::Read; use std::io::Write; @@ -21,10 +20,12 @@ pub struct Paths { pub pak_file_path: std::path::PathBuf, pub sdcard_file_path: std::path::PathBuf, pub romsave_file_path: std::path::PathBuf, + pub savestate_file_path: std::path::PathBuf, } // the bool indicates whether the save has been written to // if that is the case, it will be flushed to the disk when the program closes +#[derive(serde::Serialize, serde::Deserialize)] pub struct Saves { pub eeprom: (Vec, bool), pub sram: (Vec, bool), @@ -122,7 +123,9 @@ pub fn get_game_name(rom: &[u8]) -> String { 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 saves_path = ui.dirs.data_dir.join("saves"); + + let states_path = ui.dirs.data_dir.join("states"); let game_name = get_game_name(rom); @@ -132,35 +135,40 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) { &game_name }; - ui.paths.eep_file_path.clone_from(&base_path); + ui.paths.eep_file_path.clone_from(&saves_path); ui.paths .eep_file_path .push(prefix.to_owned() + "-" + &ui.game_hash + ".eep"); - ui.paths.sra_file_path.clone_from(&base_path); + ui.paths.sra_file_path.clone_from(&saves_path); ui.paths .sra_file_path .push(prefix.to_owned() + "-" + &ui.game_hash + ".sra"); - ui.paths.fla_file_path.clone_from(&base_path); + ui.paths.fla_file_path.clone_from(&saves_path); ui.paths .fla_file_path .push(prefix.to_owned() + "-" + &ui.game_hash + ".fla"); - ui.paths.pak_file_path.clone_from(&base_path); + ui.paths.pak_file_path.clone_from(&saves_path); ui.paths .pak_file_path .push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk"); - ui.paths.sdcard_file_path.clone_from(&base_path); + ui.paths.sdcard_file_path.clone_from(&saves_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.clone_from(&saves_path); ui.paths .romsave_file_path .push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave"); + + ui.paths.savestate_file_path.clone_from(&states_path); + ui.paths + .savestate_file_path + .push(prefix.to_owned() + "-" + &ui.game_hash + ".state"); } pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { @@ -220,7 +228,7 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { let mut compressed_sd = Vec::new(); if !ui.saves.sdcard.0.is_empty() { - compressed_sd = compress_file(&ui.saves.sdcard.0); + compressed_sd = compress_file(&[(&ui.saves.sdcard.0, "save")]); } netplay::send_save( netplay.as_mut().unwrap(), @@ -232,7 +240,7 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { 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()); + compress_file(&[(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap(), "save")]); } netplay::send_save( netplay.as_mut().unwrap(), @@ -249,41 +257,43 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option) { 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); + ui.saves.sdcard.0 = decompress_file(&compressed_sd, "save"); } 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); + let romsave_bytes = decompress_file(&compressed_romsave, "save"); ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap(); } } } } -fn decompress_file(input: &[u8]) -> Vec { +pub fn decompress_file(input: &[u8], name: &str) -> Vec { 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(); + let mut file = reader.by_name(name).unwrap(); file.read_to_end(&mut decompressed_file).unwrap(); } decompressed_file } -fn compress_file(input: &[u8]) -> Vec { +pub fn compress_file(data: &[(&[u8], &str)]) -> Vec { 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(); + for item in data { + writer + .start_file( + item.1, + zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Zstd), + ) + .unwrap(); + writer.write_all(item.0).unwrap(); + } } compressed_file } diff --git a/src/ui/video.rs b/src/ui/video.rs index ee4000d..067afd6 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -1,16 +1,15 @@ #![allow(non_snake_case)] include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs")); -use crate::device; -use crate::ui; +use crate::{device, ui}; -pub fn init(device: &mut device::Device, fullscreen: bool) { +pub fn init(device: &mut device::Device) { ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO); let title = std::ffi::CString::new("gopher64").unwrap(); let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN; - if fullscreen { + if device.ui.fullscreen { flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN; } else { flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE; @@ -41,7 +40,7 @@ pub fn init(device: &mut device::Device, fullscreen: bool) { gfx_info, device.ui.config.video.upscale, device.ui.config.video.integer_scaling, - fullscreen, + device.ui.fullscreen, ) } } @@ -53,11 +52,38 @@ pub fn close(ui: &ui::Ui) { } } -pub fn update_screen() -> bool { +pub fn update_screen() { // when the window is closed, running is set to false unsafe { rdp_update_screen() } } +pub fn load_state(device: &mut device::Device) { + let gfx_info = GFX_INFO { + RDRAM: device.rdram.mem.as_mut_ptr(), + DMEM: device.rsp.mem.as_mut_ptr(), + RDRAM_SIZE: device.rdram.size, + DPC_CURRENT_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_CURRENT_REG as usize], + DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize], + DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize], + DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize], + }; + unsafe { + rdp_new_processor(gfx_info, device.ui.config.video.upscale); + } +} + +pub fn check_callback(device: &mut device::Device) { + let callback = unsafe { rdp_check_callback() }; + device.cpu.running = callback.emu_running; + if device.netplay.is_none() { + if callback.save_state { + device.save_state = true; + } else if callback.load_state { + device.load_state = true; + } + } +} + pub fn set_register(reg: u32, value: u32) { unsafe { rdp_set_vi_register(reg, value);