mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 01:25:20 +02:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3e67d7ad9 | |||
| 8084a8cb80 | |||
| 2289971987 | |||
| 6c1f8d0ab0 | |||
| 7f6de5119f | |||
| 1c168c4bcd | |||
| 99511b7cc6 |
@@ -30,7 +30,7 @@ jobs:
|
||||
rustflags: ""
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
@@ -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
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
components: clippy, rustfmt
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1
|
||||
with:
|
||||
packages: build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
|
||||
Generated
+21
-11
@@ -448,9 +448,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.11+1.0.8"
|
||||
version = "0.1.12+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||
checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -616,9 +616,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.53"
|
||||
version = "0.1.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e24a03c8b52922d68a1589ad61032f2c1aa5a8158d2aa0d93c6e9534944bbad6"
|
||||
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -797,9 +797,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.7.0"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
|
||||
checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
@@ -1482,7 +1482,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "gopher64"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -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"
|
||||
@@ -3795,9 +3805,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.23"
|
||||
version = "0.22.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
|
||||
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -4756,9 +4766,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
|
||||
checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
|
||||
@@ -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"
|
||||
@@ -37,5 +38,4 @@ bindgen = "0.71"
|
||||
cc = { version = "1.2", features = ["parallel"] }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
|
||||
@@ -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,11 +161,13 @@ 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()
|
||||
.unwrap();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+49
-51
@@ -60,8 +60,9 @@ 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;
|
||||
|
||||
static const unsigned cmd_len_lut[64] = {
|
||||
1,
|
||||
@@ -134,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;
|
||||
@@ -155,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;
|
||||
@@ -184,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())
|
||||
{
|
||||
@@ -201,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()
|
||||
@@ -321,41 +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;
|
||||
}
|
||||
|
||||
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale)
|
||||
CALL_BACK rdp_check_callback()
|
||||
{
|
||||
uint32_t start = (hstart >> 16) & 0x3FF;
|
||||
uint32_t end = (hstart & 0x3FF);
|
||||
uint32_t delta;
|
||||
if (end > start)
|
||||
delta = end - start;
|
||||
else
|
||||
delta = 0;
|
||||
uint32_t scale = (xscale & 0xFFF);
|
||||
|
||||
return (delta * scale) / 0x400;
|
||||
}
|
||||
|
||||
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
|
||||
{
|
||||
uint32_t start = (vstart >> 16) & 0x3FF;
|
||||
uint32_t end = (vstart & 0x3FF);
|
||||
uint32_t delta;
|
||||
if (end > start)
|
||||
delta = end - start;
|
||||
else
|
||||
delta = 0;
|
||||
uint32_t scale = (yscale & 0xFFF);
|
||||
|
||||
return (delta * scale) / 0x800;
|
||||
CALL_BACK return_value = callback;
|
||||
callback.save_state = false;
|
||||
callback.load_state = false;
|
||||
return return_value;
|
||||
}
|
||||
|
||||
uint64_t rdp_process_commands()
|
||||
@@ -406,6 +403,7 @@ uint64_t rdp_process_commands()
|
||||
while (cmd_cur - cmd_ptr < 0)
|
||||
{
|
||||
uint32_t w1 = cmd_data[2 * cmd_cur];
|
||||
uint32_t w2 = cmd_data[2 * cmd_cur + 1];
|
||||
uint32_t command = (w1 >> 24) & 63;
|
||||
int cmd_length = cmd_len_lut[command];
|
||||
|
||||
@@ -418,21 +416,21 @@ uint64_t rdp_process_commands()
|
||||
if (command >= 8)
|
||||
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
|
||||
|
||||
if (RDP::Op(command) == RDP::Op::SetScissor)
|
||||
{
|
||||
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
|
||||
uint32_t upper_left_y = (w1 & 0xFFF) >> 2;
|
||||
uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2;
|
||||
uint32_t lower_right_y = (w2 & 0xFFF) >> 2;
|
||||
region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
|
||||
}
|
||||
if (RDP::Op(command) == RDP::Op::SyncFull)
|
||||
{
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
|
||||
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG);
|
||||
if (width == 0)
|
||||
{
|
||||
width = 320;
|
||||
}
|
||||
uint32_t height = viCalculateVerticalHeight(*gfx_info.VI_V_START_REG, *gfx_info.VI_Y_SCALE_REG);
|
||||
if (height == 0)
|
||||
{
|
||||
height = 240;
|
||||
}
|
||||
interrupt_timer = width * height;
|
||||
interrupt_timer = region;
|
||||
if (interrupt_timer == 0)
|
||||
interrupt_timer = 5000;
|
||||
}
|
||||
|
||||
cmd_cur += cmd_length;
|
||||
|
||||
@@ -16,17 +16,22 @@ extern "C"
|
||||
uint32_t *DPC_START_REG;
|
||||
uint32_t *DPC_END_REG;
|
||||
uint32_t *DPC_STATUS_REG;
|
||||
uint32_t *VI_H_START_REG;
|
||||
uint32_t *VI_V_START_REG;
|
||||
uint32_t *VI_X_SCALE_REG;
|
||||
uint32_t *VI_Y_SCALE_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
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+13
-11
@@ -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);
|
||||
}
|
||||
@@ -111,7 +111,7 @@ pub fn read_regs(
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
let return_value = match reg as u32 {
|
||||
AI_LEN_REG => {
|
||||
let value = get_remaining_dma_length(device);
|
||||
if value < device.ai.last_read {
|
||||
@@ -128,7 +128,9 @@ pub fn read_regs(
|
||||
value as u32
|
||||
}
|
||||
_ => device.ai.regs[reg as usize],
|
||||
}
|
||||
};
|
||||
device::cop0::add_cycles(device, 20);
|
||||
return_value
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -143,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)
|
||||
}
|
||||
@@ -153,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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
+41
-29
@@ -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,
|
||||
@@ -37,12 +39,13 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if device.sc64.regs_locked {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
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),
|
||||
}
|
||||
@@ -52,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
|
||||
@@ -90,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 {
|
||||
@@ -131,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 {
|
||||
@@ -181,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(),
|
||||
)
|
||||
@@ -190,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(
|
||||
@@ -210,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;
|
||||
}
|
||||
@@ -230,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
@@ -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;
|
||||
}
|
||||
|
||||
_ => {
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
}
|
||||
@@ -53,6 +54,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.mi.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -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],
|
||||
}
|
||||
@@ -40,6 +41,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
PI_WR_LEN_REG | PI_RD_LEN_REG => 0x7F,
|
||||
@@ -65,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 */
|
||||
@@ -98,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
@@ -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) {
|
||||
|
||||
+15
-5
@@ -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],
|
||||
@@ -53,6 +53,7 @@ pub fn read_regs_dpc(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
DPC_STATUS_REG => {
|
||||
@@ -117,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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -131,6 +140,7 @@ pub fn read_regs_dps(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.rdp.regs_dps[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -195,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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -73,6 +74,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let chip_id = (address >> 13) & 3;
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
match reg as u32 {
|
||||
|
||||
@@ -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,
|
||||
@@ -20,6 +21,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
|
||||
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
|
||||
device.ri.ram_init = true;
|
||||
|
||||
+118
-59
@@ -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);
|
||||
}
|
||||
|
||||
+14
-10
@@ -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,
|
||||
@@ -101,8 +103,10 @@ pub fn read_mem_fast(
|
||||
pub fn read_mem(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, access_size as u64 / 4);
|
||||
|
||||
let masked_address = address as usize & RSP_MEM_MASK;
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[masked_address..masked_address + 4]
|
||||
@@ -199,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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,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;
|
||||
@@ -264,6 +267,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
|
||||
@@ -321,6 +325,7 @@ pub fn read_regs2(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.rsp.regs2[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -346,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
|
||||
}
|
||||
|
||||
@@ -461,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;
|
||||
|
||||
|
||||
+5
-5
@@ -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,
|
||||
@@ -31,6 +32,7 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.si.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
@@ -43,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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -58,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
@@ -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,
|
||||
|
||||
+16
-16
@@ -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;
|
||||
@@ -12,18 +10,20 @@ const VI_CURRENT_REG: u32 = 4;
|
||||
const VI_V_SYNC_REG: u32 = 6;
|
||||
const VI_H_SYNC_REG: u32 = 7;
|
||||
//const VI_LEAP_REG: u32 = 8;
|
||||
pub const VI_H_START_REG: u32 = 9;
|
||||
pub const VI_V_START_REG: u32 = 10;
|
||||
//const VI_H_START_REG: u32 = 9;
|
||||
//const VI_V_START_REG: u32 = 10;
|
||||
//const VI_V_BURST_REG: u32 = 11;
|
||||
pub const VI_X_SCALE_REG: u32 = 12;
|
||||
pub const VI_Y_SCALE_REG: u32 = 13;
|
||||
//const VI_X_SCALE_REG: u32 = 12;
|
||||
//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
|
||||
@@ -89,6 +88,7 @@ pub fn read_regs(
|
||||
if reg as u32 == VI_CURRENT_REG {
|
||||
set_current_line(device)
|
||||
}
|
||||
device::cop0::add_cycles(device, 20);
|
||||
device.vi.regs[reg as usize]
|
||||
}
|
||||
|
||||
@@ -116,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 {
|
||||
@@ -143,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
@@ -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
@@ -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
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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")));
|
||||
});
|
||||
|
||||
@@ -284,7 +284,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_create_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
client_sha: Some(env!("GIT_HASH").to_string()),
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
@@ -449,7 +449,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let netplay_message = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
player_name: Some(app.netplay.player_name.clone()),
|
||||
client_sha: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
client_sha: Some(env!("GIT_HASH").to_string()),
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
accept: None,
|
||||
|
||||
+34
-24
@@ -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
-10
@@ -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;
|
||||
@@ -33,10 +32,6 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
|
||||
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],
|
||||
VI_H_START_REG: &mut device.vi.regs[device::vi::VI_H_START_REG as usize],
|
||||
VI_V_START_REG: &mut device.vi.regs[device::vi::VI_V_START_REG as usize],
|
||||
VI_X_SCALE_REG: &mut device.vi.regs[device::vi::VI_X_SCALE_REG as usize],
|
||||
VI_Y_SCALE_REG: &mut device.vi.regs[device::vi::VI_Y_SCALE_REG as usize],
|
||||
};
|
||||
|
||||
unsafe {
|
||||
@@ -45,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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -57,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);
|
||||
|
||||
Reference in New Issue
Block a user