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
This commit is contained in:
Logan McNaughton
2025-02-11 15:22:35 +01:00
committed by GitHub
parent 6c1f8d0ab0
commit 2289971987
41 changed files with 942 additions and 518 deletions
-3
View File
@@ -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
Generated
+10
View File
@@ -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"
+1
View File
@@ -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"
+4 -8
View File
@@ -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);
}
+39 -15
View File
@@ -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()
+10 -1
View File
@@ -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
}
+49 -39
View File
@@ -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<u8>, device: &mut Device, fullscreen: bool) {
pub fn run_game(rom_contents: Vec<u8>, 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<u8>, 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<u8> {
swap_rom(contents)
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Device {
#[serde(skip)]
pub netplay: Option<netplay::Netplay>,
#[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,
},
}
}
+9 -9
View File
@@ -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(
+4 -3
View File
@@ -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,
+28 -2
View File
@@ -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<u8>,
#[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()];
+11 -31
View File
@@ -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<u8>,
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)
+40 -29
View File
@@ -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;
}
+42 -39
View File
@@ -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;
}
_ => {
+8 -10
View File
@@ -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 {
+17 -9
View File
@@ -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<String>,
pub talking: bool,
pub word_mappings: HashMap<String, String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Vec<String>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
#[serde(skip)]
pub gui_ctx: Option<egui::Context>,
}
@@ -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
}
+49 -48
View File
@@ -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<device::tlb::TlbLut>,
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
@@ -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)
}
+16 -5
View File
@@ -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 {
+8 -2
View File
@@ -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);
}
+17 -6
View File
@@ -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);
+39 -31
View File
@@ -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;
+1 -2
View File
@@ -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,
);
}
}
+10 -4
View File
@@ -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)
}
+1
View File
@@ -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],
}
+3 -4
View File
@@ -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 */
+24 -7
View File
@@ -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<usize>,
pub tx_buf: Option<usize>,
pub rx: Option<usize>,
pub rx_buf: Option<usize>,
#[serde(skip)]
pub process: Option<fn(&mut device::Device, usize)>,
pub pak_handler: Option<device::controller::PakHandler>,
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) {
+13 -5
View File
@@ -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);
+1
View File
@@ -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<u8>,
pub size: u32,
+1
View File
@@ -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,
+118 -59
View File
@@ -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);
}
+9 -9
View File
@@ -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;
+4 -5
View File
@@ -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,
)
}
+2 -2
View File
@@ -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,
+11 -12
View File
@@ -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<governor::DefaultDirectRateLimiter>,
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,
)
}
+30 -17
View File
@@ -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<String> = 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()
+1 -2
View File
@@ -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
+199
View File
@@ -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<const N: usize>;
impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor<N> {
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<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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::<u128>()? {
Some(value) => *item = unsafe { std::mem::transmute::<u128, __m128i>(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<S>(data: &__m128i, serializer: S) -> Result<S::Ok, S::Error>
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::<u128, __m128i>(bytes) })
}
pub fn serialize_m128i_array<S>(value: &[__m128i], serializer: S) -> Result<S::Ok, S::Error>
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<const N: usize>() -> [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]
}
+25 -13
View File
@@ -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)
}
}
+2 -4
View File
@@ -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<u8> = vec![0; min_latency as usize & !3];
+20 -53
View File
@@ -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")));
});
+34 -24
View File
@@ -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<u8>, bool),
pub sram: (Vec<u8>, 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<netplay::Netplay>) {
@@ -220,7 +228,7 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
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<netplay::Netplay>) {
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<netplay::Netplay>) {
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<u8> {
pub fn decompress_file(input: &[u8], name: &str) -> Vec<u8> {
let mut decompressed_file = Vec::new();
{
let mut reader = zip::ZipArchive::new(std::io::Cursor::new(input)).unwrap();
let mut file = reader.by_index(0).unwrap();
let mut file = reader.by_name(name).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
fn compress_file(input: &[u8]) -> Vec<u8> {
pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
let mut compressed_file = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut compressed_file));
writer
.start_file(
"save",
zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated),
)
.unwrap();
writer.write_all(input).unwrap();
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
}
+32 -6
View File
@@ -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);