Compare commits

..

52 Commits

Author SHA1 Message Date
Logan McNaughton 500b4bb505 only try to assign enabled controllers (#248) 2025-02-13 21:05:22 +01:00
Logan McNaughton b01bbaa82f update cargo packages 2025-02-13 21:04:15 +01:00
Logan McNaughton 2010b82c8e remove dead code 2025-02-13 11:57:16 +01:00
Logan McNaughton 74c65750a3 add some sdl window flags (#247) 2025-02-13 11:40:06 +01:00
Logan McNaughton 2d09425ca1 fix rsp reserved commands (#246) 2025-02-12 22:04:30 +01:00
Logan McNaughton d0965c03e1 dynamically calculate speed limit frequency (#245)
* dynamically calculate speed limit frequency

* dynamically calculate speed limit frequency

* dynamically calculate speed limit frequency

* more
2025-02-12 21:18:54 +01:00
Logan McNaughton 6ee55ce4ca add a way to show internal FPS (#244) 2025-02-12 17:53:17 +01:00
Logan McNaughton 63b2ed69e4 don't modify cart.rom (#243)
* don't modify cart.rom

* don't modify cart.rom

* don't modify cart.rom

* more

* more
2025-02-12 16:09:42 +01:00
Logan McNaughton 0faf33fcfa PAL aspect ratio adjustment (#242) 2025-02-12 10:14:06 +01:00
Logan McNaughton b67268ff62 fix PAL aspect ratio (#241) 2025-02-12 09:19:39 +01:00
dependabot[bot] bda50f8d9d Bump clap from 4.5.28 to 4.5.29 (#240)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.28 to 4.5.29.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.28...clap_complete-v4.5.29)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-12 06:13:45 +01:00
Logan McNaughton ea012b4587 bump to 1.0.6 (#239) 2025-02-11 15:48:46 +01:00
Logan McNaughton a3e67d7ad9 use cache-apt-pkgs-action@v1 (#238) 2025-02-11 15:32:02 +01:00
Logan McNaughton 8084a8cb80 update cargo packages (#237) 2025-02-11 15:26:25 +01:00
Logan McNaughton 2289971987 Savestate support (#236)
* add save state support

* bind to f5

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* arm

* arm

* arm

* arm

* arm

* more

* more

* more

* more

* more

* more
2025-02-11 15:22:35 +01:00
Logan McNaughton 6c1f8d0ab0 Remove fat LTO (#235) 2025-02-10 21:00:38 +01:00
Logan McNaughton 7f6de5119f use git hash for netplay sync (#234)
* use git hash for netplay sync

* more
2025-02-10 17:02:09 +01:00
Logan McNaughton 1c168c4bcd add some CPU read cycles (#233)
* add some CPU read cycles

* more

* more

* more

* use scissor region for rdp interrupt calc
2025-02-09 17:34:29 +01:00
Logan McNaughton 99511b7cc6 bump to 1.0.5 (#232) 2025-02-09 11:56:47 +01:00
Logan McNaughton 421d533479 update cargo packages (#231) 2025-02-09 11:45:36 +01:00
Logan McNaughton ab5ef57106 further function cleanup 2025-02-09 11:43:10 +01:00
Logan McNaughton 37b41c0f66 cleanup vi calc functions 2025-02-08 22:41:28 +01:00
Logan McNaughton 4c50203ad3 modify rdp sync code (#230) 2025-02-08 21:39:48 +01:00
Logan McNaughton 271e4be68c disable rdp sync optimization to be safe (#229) 2025-02-08 21:20:59 +01:00
Logan McNaughton a9afd6bb5c Improve RDP emulation performance (#228)
* Improve RDP emulation performance

* more

* more

* more

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

* more

* more

* more

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

* more

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

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

* Fix out-of-bounds ROM dma

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

* more

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

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

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

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

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

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

* more

* more

* more

* more

* more

* more

* more

* better save name

* better save name

* better save name

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

* more

* more
2025-02-03 10:22:21 +01:00
Logan McNaughton 1efdeafc31 bump to 1.0.2 (#212) 2025-02-02 12:54:48 +01:00
Logan McNaughton a7c2c26d78 minor tweak to interrupt system (#211) 2025-02-02 11:23:32 +01:00
Logan McNaughton 7d63a694cc fix compare interrupt (#210) 2025-02-01 20:47:26 +01:00
Logan McNaughton 5c98a08fe4 remove schedule_rcp_interrupt (#208)
* remove schedule_rcp_interrupt

* more

* more

* more
2025-02-01 19:16:38 +01:00
Logan McNaughton 39ffad3af3 Bump to 1.0.1 (#206) 2025-01-30 21:08:05 +01:00
Logan McNaughton ca2a3d423b Bump to 1.0.0 (#205) 2025-01-30 19:33:47 +01:00
Logan McNaughton fe5346235e fix freeze in JFG (#204)
* fix freeze in JFG

* more
2025-01-30 19:32:56 +01:00
Logan McNaughton f84444a12e remove rdp_full_sync (#203)
* remove rdp_full_sync

* swap

* swap
2025-01-30 14:42:04 +01:00
Logan McNaughton 026e4c0e74 Bump to 0.1.26 (#202) 2025-01-29 21:06:36 +01:00
48 changed files with 6906 additions and 885 deletions
+1 -4
View File
@@ -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
+1 -1
View File
@@ -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 \
-4
View File
@@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
Generated
+5156
View File
File diff suppressed because it is too large Load Diff
+8 -10
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "0.1.25"
version = "1.0.6"
edition = "2021"
rust-version = "1.82"
@@ -14,24 +14,23 @@ sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.30"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
sha2 = "0.10"
rusttype = "0.9"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = "0.15"
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
regex = "1.11"
tungstenite = "0.26"
socket2 = "0.5"
fatfs = "0.3"
postcard = { version = "1.1", features = ["use-std"] }
clap = { version = "4.4", features = ["derive"] }
[dependencies.reqwest]
version = "0.12"
default-features = false
features = ["http2","rustls-tls","charset","json","blocking"]
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winres = "0.1"
@@ -39,5 +38,4 @@ bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
lto = true
codegen-units = 1
+11 -8
View File
@@ -98,8 +98,9 @@ fn main() {
.allowlist_function("rdp_close")
.allowlist_function("rdp_set_vi_register")
.allowlist_function("rdp_update_screen")
.allowlist_function("rdp_full_sync")
.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");
@@ -160,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);
}
+52 -71
View File
@@ -60,9 +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 uint64_t sync_signal;
static uint32_t region;
static const unsigned cmd_len_lut[64] = {
1,
@@ -135,18 +135,25 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
{
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
{
emu_running = false;
callback.emu_running = false;
}
else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running)
else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running)
{
wsi_platform->do_resize();
}
else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen)
else if (event->type == SDL_EVENT_KEY_DOWN)
{
switch (event->key.scancode)
{
case SDL_SCANCODE_ESCAPE:
emu_running = false;
if (fullscreen)
callback.emu_running = false;
break;
case SDL_SCANCODE_F5:
callback.save_state = true;
break;
case SDL_SCANCODE_F7:
callback.load_state = true;
break;
default:
break;
@@ -156,6 +163,22 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
return 0;
}
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale)
{
gfx_info = _gfx_info;
if (processor)
{
delete processor;
}
RDP::CommandProcessorFlags flags = 0;
if (_upscale)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
}
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
}
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
{
window = (SDL_Window *)_window;
@@ -185,13 +208,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
{
rdp_close();
}
RDP::CommandProcessorFlags flags = 0;
if (_upscale)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
}
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
rdp_new_processor(gfx_info, _upscale);
if (!processor->device_is_supported())
{
@@ -202,7 +219,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
}
wsi->begin_frame();
emu_running = true;
callback.emu_running = true;
}
void rdp_close()
@@ -228,8 +245,8 @@ void rdp_close()
static void calculate_viewport(float *x, float *y, float *width, float *height)
{
const int32_t display_width = 320;
const int32_t display_height = 240;
const int32_t display_width = gfx_info.PAL ? 384 : 320;
const int32_t display_height = gfx_info.PAL ? 288 : 240;
int w, h;
SDL_GetWindowSize(window, &w, &h);
@@ -322,52 +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, uint32_t width)
CALL_BACK rdp_check_callback()
{
if (xscale == 0)
return 320;
uint32_t start = ((hstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t end = (hstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
uint32_t scale = (xscale & 0xFFF);
if (delta == 0)
{
delta = width;
}
return (delta * scale) / 0x400;
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
{
if (yscale == 0)
return 240;
uint32_t start = ((vstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t end = (vstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
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()
@@ -418,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];
@@ -430,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)
{
sync_signal = processor->signal_timeline();
processor->wait_for_timeline(processor->signal_timeline());
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG, *gfx_info.VI_WIDTH_REG);
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 * 4;
interrupt_timer = region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
}
cmd_cur += cmd_length;
@@ -456,8 +442,3 @@ uint64_t rdp_process_commands()
return interrupt_timer;
}
void rdp_full_sync()
{
processor->wait_for_timeline(sync_signal);
}
+11 -7
View File
@@ -16,19 +16,23 @@ 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;
uint32_t *VI_WIDTH_REG;
bool PAL;
} 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_full_sync();
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale);
#ifdef __cplusplus
}
-1
View File
@@ -61,7 +61,6 @@ void SDL_WSIPlatform::poll_input()
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
{
SDL_PumpEvents();
}
void SDL_WSIPlatform::set_window(SDL_Window *_window)
+60 -36
View File
@@ -10,16 +10,12 @@ 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;
pub mod cart;
pub mod cart_rom;
pub mod controller;
pub mod cop0;
pub mod cop1;
@@ -42,19 +38,18 @@ pub mod rsp_interface;
pub mod rsp_su_instructions;
pub mod rsp_vu_instructions;
pub mod si;
pub mod sram;
pub mod tlb;
pub mod unmapped;
pub mod vi;
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device, fullscreen: bool) {
cart_rom::init(device, rom_contents); // cart needs to come before rdram
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);
@@ -63,14 +58,14 @@ 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);
cpu::init(device);
ui::storage::init(&mut device.ui);
ui::storage::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
cart_rom::load_rom_save(device);
cpu::run(device);
@@ -157,25 +152,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,
pub mi: mi::Mi,
pub pi: pi::Pi,
pub vi: vi::Vi,
ai: ai::Ai,
si: si::Si,
ri: ri::Ri,
flashram: 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 {
@@ -191,11 +195,15 @@ 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],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
tlb_lut_w: vec![
@@ -270,10 +278,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],
@@ -288,15 +295,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],
@@ -380,6 +401,7 @@ impl Device {
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
wait_frozen: false,
last_status_value: 0,
},
mi: mi::Mi {
regs: [0; mi::MI_REGS_COUNT as usize],
@@ -390,6 +412,7 @@ impl Device {
ai: ai::Ai {
regs: [0; ai::AI_REGS_COUNT as usize],
last_read: 0,
freq: 33600,
delayed_carry: false,
fifo: [ai::AiDma {
address: 0,
@@ -413,13 +436,17 @@ impl Device {
count_per_scanline: 0,
limiter: None,
vi_counter: 0,
last_origin: 0,
internal_frame_counter: 0,
min_wait_time: std::time::Duration::from_secs(1),
frame_time: 0.0,
limit_freq: 2,
limit_freq_check: std::time::Instant::now(),
},
flashram: sram::Flashram {
status: 0,
erase_page: 0,
page_buf: [0xff; 128],
silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID],
mode: sram::FlashramMode::ReadArray,
vru_window: controller::vru::VruWindow {
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
vru: controller::vru::Vru {
status: 0,
@@ -430,9 +457,6 @@ impl Device {
words: Vec::new(),
talking: false,
word_mappings: HashMap::new(),
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
}
}
+14 -12
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
const AI_DRAM_ADDR_REG: u32 = 0;
const AI_LEN_REG: u32 = 1;
@@ -12,14 +11,16 @@ pub const AI_REGS_COUNT: u32 = 6;
const AI_STATUS_BUSY: u32 = 0x40000000;
const AI_STATUS_FULL: u32 = 0x80000000;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ai {
pub regs: [u32; AI_REGS_COUNT as usize],
pub fifo: [AiDma; 2],
pub last_read: u64,
pub delayed_carry: bool,
pub freq: u64,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct AiDma {
pub address: u64,
pub length: u64,
@@ -31,7 +32,7 @@ fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
return 0;
}
let next_ai_event = device::events::get_event(device, device::events::EventType::AI);
let next_ai_event = device::events::get_event(device, device::events::EVENT_TYPE_AI);
if next_ai_event.is_none() {
return 0;
}
@@ -64,11 +65,10 @@ 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::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
}
fn fifo_push(device: &mut device::Device) {
@@ -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
View File
@@ -1,15 +1,16 @@
use crate::device;
use crate::{device, savestates};
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct ICache {
pub valid: bool,
pub tag: u32,
pub index: u16,
pub words: [u32; 8],
#[serde(skip, default = "savestates::default_instructions")]
pub instruction: [fn(&mut device::Device, u32); 8],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct DCache {
pub valid: bool,
pub dirty: bool,
+32 -2
View File
@@ -1,8 +1,11 @@
use chrono::Datelike;
use chrono::Timelike;
use crate::device;
use crate::ui;
use crate::{device, ui};
pub mod rom;
pub mod sc64;
pub mod sram;
const JCMD_STATUS: u8 = 0x00;
const JCMD_EEPROM_READ: u8 = 0x04;
@@ -18,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,
}
@@ -27,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()];
@@ -1,25 +1,22 @@
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,
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = device.cart.rom[address..address + 4].try_into().unwrap();
for i in 0..4 {
if device
.ui
.saves
.romsave
.0
.contains_key(&(address as u32 + i))
{
data[i as usize] = device.ui.saves.romsave.0[&(address as u32 + i)];
}
}
u32::from_be_bytes(data)
}
pub fn read_mem_fast(
@@ -28,11 +25,7 @@ pub fn read_mem_fast(
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & CART_MASK;
u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
read_cart_word(device, masked_address)
}
pub fn read_mem(
@@ -48,15 +41,26 @@ pub fn read_mem(
device.cart.latch
} else {
let masked_address = address as usize & CART_MASK;
u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
read_cart_word(device, masked_address)
}
}
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
let masked_address = address as usize & CART_MASK;
let mut data = read_cart_word(device, masked_address);
device::memory::masked_write_32(&mut data, value, mask);
for (i, item) in data.to_be_bytes().iter().enumerate() {
device
.ui
.saves
.romsave
.0
.insert((masked_address + i) as u32, *item);
}
device.ui.saves.romsave.1 = true;
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
@@ -64,9 +68,8 @@ pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u
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,
);
}
@@ -76,23 +79,22 @@ pub fn dma_read(
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
if device.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;
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.ui.saves.romsave.0.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device.ui.saves.romsave.0.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device.ui.saves.romsave.1 = true;
}
}
}
device.ui.saves.romsave.1 = true;
device::pi::calculate_cycles(device, 1, length)
}
@@ -108,7 +110,11 @@ pub fn dma_write(
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && j < device.cart.rom.len() as u32 {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
if device.ui.saves.romsave.0.contains_key(&j) {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.0[&j];
} else {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
}
i += 1;
j += 1;
}
@@ -133,15 +139,6 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
}
}
pub fn load_rom_save(device: &mut device::Device) {
if device.ui.saves.romsave.0.is_empty() {
return;
}
for (key, value) in device.ui.saves.romsave.0.iter() {
device.cart.rom[*key as usize] = *value
}
}
fn set_system_region(device: &mut device::Device, country: u8) {
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
for i in pal_codes {
@@ -155,37 +152,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.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)
+250
View File
@@ -0,0 +1,250 @@
use crate::device;
pub const SDCARD_SIZE: usize = 0x4000000;
const SC64_SCR_REG: u32 = 0;
const SC64_DATA0_REG: u32 = 1;
const SC64_DATA1_REG: u32 = 2;
const SC64_IDENTIFIER_REG: u32 = 3;
const SC64_KEY_REG: u32 = 4;
//const SC64_IRQ_REG: u32 = 5;
//const SC64_AUX_REG: u32 = 6;
pub const SC64_REGS_COUNT: u32 = 7;
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
pub const SC64_CFG_COUNT: u32 = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
#[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,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
}
fn format_sdcard(device: &mut device::Device) {
if device.ui.saves.sdcard.0.is_empty() {
device.ui.saves.sdcard.0.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.0);
fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap();
}
}
pub fn read_regs(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
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.cart.sc64.regs[reg as usize],
SC64_IDENTIFIER_REG => 0x53437632,
_ => panic!("unknown read reg {} address {:#x}", reg, address),
}
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_KEY_REG => {
device::memory::masked_write_32(&mut device.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.cart.sc64.regs_locked {
device::memory::masked_write_32(
&mut device.cart.sc64.regs[reg as usize],
value,
mask,
);
}
}
SC64_SCR_REG => {
if !device.cart.sc64.regs_locked {
match char::from_u32(value & mask).unwrap() {
'c' => {
// get config
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.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.cart.sc64.regs[SC64_DATA1_REG as usize] {
0 => { //Init SD card
}
1 => { //Deinit SD card
}
_ => {
panic!(
"unknown sc64 sd card operation: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
)
}
}
}
'I' => {
// set sd sector
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG as usize];
}
's' => {
format_sdcard(device);
// read sd card
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 {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = u32::from_be_bytes(
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.try_into()
.unwrap(),
);
device::memory::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
} else {
panic!("sd card read out of bounds")
}
i += 4;
}
}
'S' => {
format_sdcard(device);
// write sd card
let address =
device.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 {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = device::memory::data_read(
device,
address + i as u64,
device::memory::AccessSize::Word,
false,
)
.to_be_bytes();
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.copy_from_slice(&data);
} else {
panic!("sd card write out of bounds")
}
i += 4;
}
device.ui.saves.sdcard.1 = true;
}
'U' => {} // USB_WRITE_STATUS, ignored
'M' => {} // USB_WRITE, ignored
_ => {
panic!(
"unknown sc64 command: {}",
char::from_u32(value & mask).unwrap()
)
}
}
}
}
_ => panic!(
"unknown write reg {} address {:#x} value {}",
reg,
address,
char::from_u32(value & mask).unwrap()
),
}
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & SC64_BUFFER_MASK;
u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.sc64.buffer[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
}
pub fn dma_read(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.cart.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn dma_write(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.sc64.buffer[j as usize];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
+42 -39
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
const SRAM_MASK: usize = 0xFFFF;
pub const SRAM_SIZE: usize = 0x8000;
@@ -9,7 +8,7 @@ pub const MX29L1100_ID: u32 = 0x00c2001e;
const MX29L0000_ID: u32 = 0x00c20000;
const MX29L0001_ID: u32 = 0x00c20001;
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FlashramMode {
ReadArray,
ReadSiliconId,
@@ -19,10 +18,12 @@ pub enum FlashramMode {
PageProgram,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Flashram {
pub status: u32,
pub mode: FlashramMode,
pub erase_page: u16,
#[serde(with = "serde_big_array::BigArray")]
pub page_buf: [u8; 128],
pub silicon_id: [u32; 2],
}
@@ -52,10 +53,11 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
}
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* read Status register */
device.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.flashram.mode == FlashramMode::ReadArray {
device.cart.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.cart.flashram.mode == FlashramMode::ReadArray
{
/* flashram MMIO read are not supported except for the "dummy" read @0x0000 done before DMA.
* returns a "dummy" value. */
return 0;
@@ -97,9 +99,9 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
}
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* clear/set Status register */
device.flashram.status = (value & mask) & 0xff;
device.cart.flashram.status = (value & mask) & 0xff;
} else if (address & 0x1ffff) == 0x10000 {
/* set command */
format_flash(device);
@@ -122,9 +124,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::events::create_event(
device,
device::events::EventType::PI,
device::events::EVENT_TYPE_PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
device::pi::dma_event,
);
}
@@ -150,11 +151,11 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
if (cart_addr & 0x1ffff) == 0x00000
&& length == 128
&& device.flashram.mode == FlashramMode::PageProgram
&& device.cart.flashram.mode == FlashramMode::PageProgram
{
/* load page buf using DMA */
for i in 0..length {
device.flashram.page_buf[i as usize] =
device.cart.flashram.page_buf[i as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
}
} else {
@@ -203,20 +204,22 @@ fn dma_write_flash(
if (cart_addr & 0x1ffff) == 0x00000
&& length == 8
&& device.flashram.mode == FlashramMode::ReadSiliconId
&& device.cart.flashram.mode == FlashramMode::ReadSiliconId
{
/* read Silicon ID using DMA */
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[0].to_ne_bytes());
.copy_from_slice(&device.cart.flashram.silicon_id[0].to_ne_bytes());
dram_addr += 4;
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000 && device.flashram.mode == FlashramMode::ReadArray {
.copy_from_slice(&device.cart.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000
&& device.cart.flashram.mode == FlashramMode::ReadArray
{
format_flash(device);
/* adjust flashram address before starting DMA. */
if device.flashram.silicon_id[1] == MX29L1100_ID
|| device.flashram.silicon_id[1] == MX29L0000_ID
|| device.flashram.silicon_id[1] == MX29L0001_ID
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
|| device.cart.flashram.silicon_id[1] == MX29L0001_ID
{
/* "old" flash needs special address adjusting */
cart_addr = (cart_addr & 0xffff) * 2;
@@ -250,27 +253,27 @@ fn flashram_command(device: &mut device::Device, command: u32) {
match command & 0xff000000 {
0x3c000000 => {
/* set chip erase mode */
device.flashram.mode = FlashramMode::ChipErase;
device.cart.flashram.mode = FlashramMode::ChipErase;
}
0x4b000000 => {
/* set sector erase mode, set erase sector */
device.flashram.mode = FlashramMode::SectorErase;
device.flashram.erase_page = command as u16;
device.cart.flashram.mode = FlashramMode::SectorErase;
device.cart.flashram.erase_page = command as u16;
}
0x78000000 => {
/* set erase busy flag */
device.flashram.status |= 0x02;
device.cart.flashram.status |= 0x02;
/* do chip/sector erase */
if device.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128;
if device.cart.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128;
for i in 0..128 * 128 {
device.ui.saves.flash.0[offset + i] = 0xFF;
}
device.ui.saves.flash.1 = true
} else if device.flashram.mode == FlashramMode::ChipErase {
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.saves.flash.0[i] = 0xFF;
}
@@ -280,47 +283,47 @@ fn flashram_command(device: &mut device::Device, command: u32) {
}
/* clear erase busy flag, set erase success flag, transition to status mode */
device.flashram.status &= !0x02;
device.flashram.status |= 0x08;
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.status &= !0x02;
device.cart.flashram.status |= 0x08;
device.cart.flashram.mode = FlashramMode::Status;
}
0xa5000000 => {
/* set program busy flag */
device.flashram.status |= 0x01;
device.cart.flashram.status |= 0x01;
/* program selected page */
let offset: usize = (command & 0xffff) as usize * 128;
for i in 0..128 {
device.ui.saves.flash.0[offset + i] = device.flashram.page_buf[i];
device.ui.saves.flash.0[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.saves.flash.1 = true;
/* clear program busy flag, set program success flag, transition to status mode */
device.flashram.status &= !0x01;
device.flashram.status |= 0x04;
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.status &= !0x01;
device.cart.flashram.status |= 0x04;
device.cart.flashram.mode = FlashramMode::Status;
}
0xb4000000 => {
/* set page program mode */
device.flashram.mode = FlashramMode::PageProgram;
device.cart.flashram.mode = FlashramMode::PageProgram;
}
0xd2000000 => {
/* set status mode */
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.mode = FlashramMode::Status;
}
0xe1000000 => {
/* set silicon_id mode */
device.flashram.mode = FlashramMode::ReadSiliconId;
device.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
device.cart.flashram.mode = FlashramMode::ReadSiliconId;
device.cart.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
}
0xf0000000 => {
/* set read mode */
device.flashram.mode = FlashramMode::ReadArray;
device.cart.flashram.mode = FlashramMode::ReadArray;
}
_ => {
+8 -10
View File
@@ -1,6 +1,4 @@
use crate::device;
use crate::netplay;
use crate::ui;
use crate::{device, netplay, savestates, ui};
pub mod mempak;
pub mod rumble;
@@ -21,16 +19,18 @@ const CONT_STATUS_PAK_PRESENT: u8 = 1;
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2;
const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
#[derive(Copy, Clone, PartialEq)]
#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum PakType {
None = 0,
MemPak = 1,
RumblePak = 2,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PakHandler {
#[serde(skip, default = "savestates::default_pak_handler")]
pub read: fn(&mut device::Device, usize, u16, usize, usize),
#[serde(skip, default = "savestates::default_pak_handler")]
pub write: fn(&mut device::Device, usize, u16, usize, usize),
pub pak_type: PakType,
}
@@ -67,17 +67,15 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
if input.1 {
// pak change button pressed
if device::events::get_event(device, device::events::EventType::PakSwitch).is_none()
{
if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() {
device.pif.channels[channel].change_pak =
device.pif.channels[channel].pak_handler.unwrap().pak_type;
device.pif.channels[channel].pak_handler = None;
device::events::create_event(
device,
device::events::EventType::PakSwitch,
device::events::EVENT_TYPE_PAK,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ (device.cpu.clock_rate), // 1 second
pak_switch_event,
)
}
}
@@ -158,7 +156,7 @@ fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
crc
}
fn pak_switch_event(device: &mut device::Device) {
pub fn pak_switch_event(device: &mut device::Device) {
for (i, channel) in device.pif.channels.iter_mut().enumerate() {
if channel.change_pak != PakType::None {
if channel.change_pak == PakType::RumblePak {
+19 -11
View File
@@ -17,17 +17,26 @@ const VOICE_STATUS_BUSY: u8 = 0x05;
const JDT_VRU: u16 = 0x0100; /* VRU */
const CONT_FLAVOR: u16 = JDT_VRU;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Vru {
pub status: u8,
pub voice_state: u8,
pub load_offset: u8,
pub voice_init: u8,
#[serde(with = "serde_big_array::BigArray")]
pub word_buffer: [u16; 40],
pub words: Vec<String>,
pub talking: bool,
pub word_mappings: HashMap<String, String>,
pub window_notifier: Option<std::sync::mpsc::Sender<Vec<String>>>,
pub word_receiver: Option<std::sync::mpsc::Receiver<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_ref().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
}
+55 -53
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
pub const COP0_INDEX_REG: u32 = 0;
const COP0_RANDOM_REG: u32 = 1;
@@ -55,14 +55,14 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_BD: u64 = 1 << 31;
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
@@ -88,15 +88,20 @@ 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>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
fn mfc0(device: &mut device::Device, opcode: u32) {
@@ -209,11 +214,11 @@ 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;
}
COP0_STATUS_REG => {
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
@@ -230,15 +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) {
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
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);
}
@@ -257,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
@@ -362,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;
@@ -372,10 +379,5 @@ pub fn init(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
device::events::create_event(
device,
device::events::EventType::Compare,
u32::MAX as u64,
compare_event,
)
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
}
+16 -5
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
//const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
//const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
@@ -24,6 +24,7 @@ const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop1 {
pub fcr0: u32,
pub fcr31: u32,
@@ -31,11 +32,17 @@ pub struct Cop1 {
//pub flush_mode: u32,
pub fgr32: [[u8; 4]; 32],
pub fgr64: [[u8; 8]; 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub b_instrs: [fn(&mut device::Device, u32); 4],
#[serde(skip, default = "savestates::default_instructions")]
pub s_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub d_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub w_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub l_instrs: [fn(&mut device::Device, u32); 64],
}
@@ -448,10 +455,7 @@ pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 {
}
}
pub fn init(device: &mut device::Device) {
set_fgr_registers(device, 0);
device.cpu.cop1.fcr0 = 0b101000000000;
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop1.b_instrs = [
device::fpu_instructions::bc1f, // 0
device::fpu_instructions::bc1t, // 1
@@ -763,6 +767,13 @@ pub fn init(device: &mut device::Device) {
]
}
pub fn init(device: &mut device::Device) {
set_fgr_registers(device, 0);
device.cpu.cop1.fcr0 = 0b101000000000;
map_instructions(device);
}
pub fn set_fgr_registers(device: &mut device::Device, status_reg: u64) {
// this method doesn't account for undocumented behaviour (accessing odd numbered registers in half mode)
if (status_reg & device::cop0::COP0_STATUS_FR) == 0 {
+8 -2
View File
@@ -1,6 +1,8 @@
use crate::device;
use crate::{device, savestates};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop2 {
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
pub reg_latch: u64,
}
@@ -76,7 +78,7 @@ fn unusable(device: &mut device::Device, _opcode: u32) {
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2)
}
pub fn init(device: &mut device::Device) {
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop2.instrs = [
device::cop2::mfc2, // 0
device::cop2::dmfc2, // 1
@@ -112,3 +114,7 @@ pub fn init(device: &mut device::Device) {
device::cop2::reserved, // 31
]
}
pub fn init(device: &mut device::Device) {
map_instructions(device);
}
+17 -6
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::{device, savestates};
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum State {
Step,
Take,
@@ -11,10 +11,13 @@ pub enum State {
Exception,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: State,
pub pc: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cpu {
pub cop0: device::cop0::Cop0,
pub cop1: device::cop1::Cop1,
@@ -28,10 +31,13 @@ pub struct Cpu {
pub running: bool,
pub llbit: bool,
pub clock_rate: u64,
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub special_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
pub events: [device::events::Event; device::events::EventType::Count as usize],
pub events: [device::events::Event; device::events::EVENT_TYPE_COUNT],
pub next_event_count: u64,
pub next_event: usize,
}
@@ -47,9 +53,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
}
}
pub fn init(device: &mut device::Device) {
device.cpu.clock_rate = 93750000;
pub fn map_instructions(device: &mut device::Device) {
device.cpu.instrs = [
device::cop0::reserved, // SPECIAL
device::cop0::reserved, // REGIMM
@@ -216,6 +220,13 @@ pub fn init(device: &mut device::Device) {
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
}
pub fn init(device: &mut device::Device) {
device.cpu.clock_rate = 93750000;
map_instructions(device);
device::cop0::init(device);
device::cop1::init(device);
device::cop2::init(device);
+41 -31
View File
@@ -1,59 +1,69 @@
use crate::device;
#[derive(PartialEq, Copy, Clone)]
pub enum EventType {
AI,
SI,
VI,
PI,
DP,
SP,
InterruptCheck,
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;
set_next_event(device);
}
+31 -9
View File
@@ -1,15 +1,6 @@
use crate::device;
pub fn check_pending_interrupts(device: &mut device::Device) {
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& (device::cop0::COP0_STATUS_IE
| device::cop0::COP0_STATUS_EXL
@@ -20,6 +11,37 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.mi.regs[device::mi::MI_INTR_REG as usize]
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
!= 0
{
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
} else if device.cpu.cop0.pending_compare_interrupt {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if device.cpu.cop0.is_event {
interrupt_exception(device);
} else {
device::events::create_event(
device,
device::events::EVENT_TYPE_INT,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
);
}
}
pub fn interrupt_exception(device: &mut device::Device) {
exception_general(device, 0x180);
}
+24 -10
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
const MM_RDRAM_DRAM: usize = 0x00000000;
const MM_RDRAM_REGS: usize = 0x03f00000;
@@ -16,9 +16,11 @@ const MM_SI_REGS: usize = 0x04800000;
//const MM_DOM2_ADDR1: usize = 0x05000000;
pub const MM_DOM2_ADDR2: usize = 0x08000000;
pub const MM_CART_ROM: usize = 0x10000000;
const MM_PIF_MEM: usize = 0x1fc00000;
pub const MM_PIF_MEM: usize = 0x1fc00000;
//const MM_DOM1_ADDR3: usize = 0x1fd00000;
const MM_IS_VIEWER: usize = 0x13ff0000;
pub const MM_SC64_BUFFER: usize = 0x1ffe0000;
const MM_SC64_REGS: usize = 0x1fff0000;
#[derive(PartialEq)]
pub enum AccessType {
@@ -26,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],
}
@@ -142,19 +151,24 @@ pub fn init(device: &mut device::Device) {
device.memory.memory_map_read[i] = device::si::read_regs;
device.memory.memory_map_write[i] = device::si::write_regs;
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
device.memory.memory_map_read[i] = device::sram::read_mem;
device.memory.memory_map_write[i] = device::sram::write_mem;
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
device.memory.fast_read[i] = device::cart_rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart_rom::read_mem;
device.memory.memory_map_write[i] = device::cart_rom::write_mem;
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::pif::read_mem;
device.memory.memory_map_write[i] = device::pif::write_mem;
} else if i >= MM_SC64_BUFFER >> 16 && i <= (MM_SC64_BUFFER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_mem;
device.memory.memory_map_write[i] = device::cart::sc64::write_mem;
} else if i >= MM_SC64_REGS >> 16 && i <= (MM_SC64_REGS + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_regs;
device.memory.memory_map_write[i] = device::cart::sc64::write_regs;
}
}
device::cache::init(device)
}
+5 -31
View File
@@ -2,8 +2,8 @@ use crate::device;
const MI_INIT_MODE_REG: u32 = 0;
const MI_VERSION_REG: u32 = 1;
const MI_INTR_REG: u32 = 2;
const MI_INTR_MASK_REG: u32 = 3;
pub const MI_INTR_REG: u32 = 2;
pub const MI_INTR_MASK_REG: u32 = 3;
pub const MI_REGS_COUNT: u32 = 4;
/* read */
@@ -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]
}
@@ -64,11 +66,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
}
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
} else {
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
@@ -149,37 +147,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::events::create_event(
device,
device::events::EventType::InterruptCheck,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
device::exceptions::check_pending_interrupts,
)
}
pub fn init(device: &mut device::Device) {
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
}
+46 -13
View File
@@ -26,6 +26,7 @@ const PI_STATUS_INTERRUPT: u32 = 1 << 3;
const PI_STATUS_RESET: u32 = 1 << 0;
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pi {
pub regs: [u32; PI_REGS_COUNT as usize],
}
@@ -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 */
@@ -114,17 +114,27 @@ fn dma_write(device: &mut device::Device) {
fn get_handler(address: u32) -> PiHandler {
let mut handler = PiHandler {
read: device::cart_rom::dma_read,
write: device::cart_rom::dma_write,
read: device::cart::rom::dma_read,
write: device::cart::rom::dma_write,
};
if address >= device::memory::MM_CART_ROM as u32 {
handler.read = device::cart_rom::dma_read;
handler.write = device::cart_rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
handler.read = device::sram::dma_read;
handler.write = device::sram::dma_write;
if address >= device::memory::MM_SC64_BUFFER as u32
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) as u32
{
handler.read = device::cart::sc64::dma_read;
handler.write = device::cart::sc64::dma_write;
} else if address >= device::memory::MM_CART_ROM as u32
&& address < device::memory::MM_PIF_MEM as u32
{
handler.read = device::cart::rom::dma_read;
handler.write = device::cart::rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32
&& address < device::memory::MM_CART_ROM as u32
{
handler.read = device::cart::sram::dma_read;
handler.write = device::cart::sram::dma_write;
} else {
panic!("unknown pi handler")
handler.read = unknown_dma_read;
handler.write = unknown_dma_write;
}
handler
}
@@ -194,3 +204,26 @@ pub fn dma_event(device: &mut device::Device) {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
fn unknown_dma_read(
device: &mut device::Device,
mut _cart_addr: u32,
mut _dram_addr: u32,
length: u32,
) -> u64 {
device::pi::calculate_cycles(device, 1, length)
}
fn unknown_dma_write(
device: &mut device::Device,
mut _cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+24 -7
View File
@@ -1,18 +1,22 @@
mod rom;
use crate::device;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pif {
#[serde(with = "serde_big_array::BigArray")]
pub rom: [u8; 1984],
#[serde(with = "serde_big_array::BigArray")]
pub ram: [u8; 64],
pub channels: [PifChannel; 5],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PifChannel {
pub tx: Option<usize>,
pub tx_buf: Option<usize>,
pub rx: Option<usize>,
pub rx_buf: Option<usize>,
#[serde(skip)]
pub process: Option<fn(&mut device::Device, usize)>,
pub pak_handler: Option<device::controller::PakHandler>,
pub change_pak: device::controller::PakType,
@@ -65,9 +69,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device.si.dma_dir = device::si::DmaDir::Write;
device::events::create_event(
device,
device::events::EventType::SI,
device::events::EVENT_TYPE_SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 3200,
device::si::dma_event,
); //based on https://github.com/rasky/n64-systembench
device.si.regs[device::si::SI_STATUS_REG as usize] |=
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
@@ -217,6 +220,22 @@ pub fn process_ram(device: &mut device::Device) {
device.pif.ram[0x3F] &= !clrmask
}
pub fn connect_pif_channels(device: &mut device::Device) {
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@@ -232,22 +251,20 @@ pub fn init(device: &mut device::Device) {
pak_type: device::controller::PakType::MemPak,
};
connect_pif_channels(device);
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].pak_handler = Some(mempak_handler);
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(mempak_handler);
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].pak_handler = None;
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
fn process_cic_challenge(device: &mut device::Device) {
+30 -9
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, savestates, ui};
pub const DPC_START_REG: u32 = 0;
pub const DPC_END_REG: u32 = 1;
@@ -41,10 +40,12 @@ 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],
pub wait_frozen: bool,
pub last_status_value: u32,
}
pub fn read_regs_dpc(
@@ -52,8 +53,19 @@ 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 => {
let value =
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
if value == device.rdp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
device.rdp.last_status_value = value;
device.rdp.regs_dpc[reg as usize]
}
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
DPC_CURRENT_REG => {
if device.rdp.wait_frozen {
device.rsp.cpu.sync_point = true;
@@ -102,14 +114,23 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY;
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
if timer != 0 {
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,
)
}
}
@@ -119,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]
}
@@ -183,10 +205,9 @@ 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) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY);
ui::video::full_sync();
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);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+2
View File
@@ -15,6 +15,7 @@ pub const RDRAM_REGS_COUNT: u32 = 10;
pub const RDRAM_MASK: usize = 0xFFFFFF;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rdram {
pub mem: Vec<u8>,
pub size: u32,
@@ -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 {
+2
View File
@@ -10,6 +10,7 @@ pub const RI_REFRESH_REG: u32 = 4;
//const RI_WERROR_REG: u32 = 7;
pub const RI_REGS_COUNT: u32 = 8;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ri {
pub regs: [u32; RI_REGS_COUNT as usize],
pub ram_init: bool,
@@ -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;
+182 -123
View File
@@ -2,28 +2,32 @@
use device::__m128i;
#[cfg(target_arch = "aarch64")]
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
use crate::device;
use crate::{device, savestates};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: device::cpu::State,
pub pc: u32,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Instructions {
#[serde(skip, default = "savestates::default_instruction")]
pub func: fn(&mut device::Device, u32),
pub opcode: u32,
}
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum InstructionType {
Su,
Vu,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cpu {
#[serde(with = "serde_big_array::BigArray")]
pub instructions: [Instructions; 0x1000 / 4],
pub last_instruction_type: InstructionType,
pub instruction_type: InstructionType,
@@ -33,29 +37,79 @@ pub struct Cpu {
pub halted: bool,
pub sync_point: bool,
pub cycle_counter: u64,
#[serde(
serialize_with = "savestates::serialize_m128i_array",
deserialize_with = "savestates::deserialize_m128i_array"
)]
pub shuffle: [__m128i; 16],
pub gpr: [u32; 32],
#[serde(
serialize_with = "savestates::serialize_m128i_array",
deserialize_with = "savestates::deserialize_m128i_array"
)]
pub vpr: [__m128i; 32],
#[serde(with = "serde_big_array::BigArray")]
pub reciprocals: [u16; 512],
#[serde(with = "serde_big_array::BigArray")]
pub inverse_square_roots: [u16; 512],
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcol: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcoh: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vccl: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcch: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vce: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub accl: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub accm: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub acch: __m128i,
pub divdp: bool,
pub divin: i16,
pub divout: i16,
#[serde(skip, default = "savestates::default_instructions")]
pub special_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub cop0_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub cop2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub lwc2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub swc2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub vec_instrs: [fn(&mut device::Device, u32); 64],
}
@@ -165,62 +219,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
}
}
pub fn init(device: &mut device::Device) {
device.rsp.cpu.reciprocals[0] = u16::MAX;
let mut index = 1;
while index < 512 {
let a = (index + 512) as u64;
let b = (1_u64 << 34) / a;
device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16;
index += 1;
}
index = 0;
while index < 512 {
let mut shift = 0;
if index % 2 == 1 {
shift = 1
}
let a = ((index + 512) >> shift) as u64;
let mut b = (1 << 17) as u64;
//find the largest b where b < 1.0 / sqrt(a)
while a * (b + 1) * (b + 1) < (1_u64 << 44) {
b += 1;
}
device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16;
index += 1;
}
device.rsp.cpu.shuffle = unsafe {
[
//vector
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
//scalar quarter
_mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466
_mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577
//scalar half
_mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444
_mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555
_mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777
//scalar whole
_mm_set_epi8(
15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14,
), //00000000
_mm_set_epi8(
13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12,
), //11111111
_mm_set_epi8(
11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10,
), //22222222
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333
_mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444
_mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555
_mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666
_mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777
]
};
pub fn map_instructions(device: &mut device::Device) {
device.rsp.cpu.instrs = [
device::rsp_su_instructions::reserved, // SPECIAL
device::rsp_su_instructions::reserved, // REGIMM
@@ -289,70 +288,70 @@ pub fn init(device: &mut device::Device) {
];
device.rsp.cpu.special_instrs = [
device::rsp_su_instructions::sll, // 0
device::rsp_su_instructions::reserved, // 1
device::rsp_su_instructions::srl, // 2
device::rsp_su_instructions::sra, // 3
device::rsp_su_instructions::sllv, // 4
device::rsp_su_instructions::reserved, // 5
device::rsp_su_instructions::srlv, // 6
device::rsp_su_instructions::srav, // 7
device::rsp_su_instructions::jr, // 8
device::rsp_su_instructions::jalr, // 9
device::rsp_su_instructions::reserved, // 10
device::rsp_su_instructions::reserved, // 11
device::rsp_su_instructions::reserved, // 12
device::rsp_su_instructions::break_, // 13
device::rsp_su_instructions::reserved, // 14
device::rsp_su_instructions::reserved, // 15
device::rsp_su_instructions::reserved, // 16
device::rsp_su_instructions::reserved, // 17
device::rsp_su_instructions::reserved, // 18
device::rsp_su_instructions::reserved, // 19
device::rsp_su_instructions::reserved, // 20
device::rsp_su_instructions::reserved, // 21
device::rsp_su_instructions::reserved, // 22
device::rsp_su_instructions::reserved, // 23
device::rsp_su_instructions::reserved, // 24
device::rsp_su_instructions::reserved, // 25
device::rsp_su_instructions::reserved, // 26
device::rsp_su_instructions::reserved, // 27
device::rsp_su_instructions::reserved, // 28
device::rsp_su_instructions::reserved, // 29
device::rsp_su_instructions::reserved, // 30
device::rsp_su_instructions::reserved, // 31
device::rsp_su_instructions::add, // 32
device::rsp_su_instructions::addu, // 33
device::rsp_su_instructions::sub, // 34
device::rsp_su_instructions::subu, // 35
device::rsp_su_instructions::and, // 36
device::rsp_su_instructions::or, // 37
device::rsp_su_instructions::xor, // 38
device::rsp_su_instructions::nor, // 39
device::rsp_su_instructions::reserved, // 40
device::rsp_su_instructions::reserved, // 41
device::rsp_su_instructions::slt, // 42
device::rsp_su_instructions::sltu, // 43
device::rsp_su_instructions::reserved, // 44
device::rsp_su_instructions::reserved, // 45
device::rsp_su_instructions::reserved, // 46
device::rsp_su_instructions::reserved, // 47
device::rsp_su_instructions::reserved, // 48
device::rsp_su_instructions::reserved, // 49
device::rsp_su_instructions::reserved, // 50
device::rsp_su_instructions::reserved, // 51
device::rsp_su_instructions::reserved, // 52
device::rsp_su_instructions::reserved, // 53
device::rsp_su_instructions::reserved, // 54
device::rsp_su_instructions::reserved, // 55
device::rsp_su_instructions::reserved, // 56
device::rsp_su_instructions::reserved, // 57
device::rsp_su_instructions::reserved, // 58
device::rsp_su_instructions::reserved, // 59
device::rsp_su_instructions::reserved, // 60
device::rsp_su_instructions::reserved, // 61
device::rsp_su_instructions::reserved, // 62
device::rsp_su_instructions::reserved, // 63
device::rsp_su_instructions::sll, // 0
device::rsp_su_instructions::special_reserved, // 1
device::rsp_su_instructions::srl, // 2
device::rsp_su_instructions::sra, // 3
device::rsp_su_instructions::sllv, // 4
device::rsp_su_instructions::special_reserved, // 5
device::rsp_su_instructions::srlv, // 6
device::rsp_su_instructions::srav, // 7
device::rsp_su_instructions::jr, // 8
device::rsp_su_instructions::jalr, // 9
device::rsp_su_instructions::special_reserved, // 10
device::rsp_su_instructions::special_reserved, // 11
device::rsp_su_instructions::special_reserved, // 12
device::rsp_su_instructions::break_, // 13
device::rsp_su_instructions::special_reserved, // 14
device::rsp_su_instructions::special_reserved, // 15
device::rsp_su_instructions::special_reserved, // 16
device::rsp_su_instructions::special_reserved, // 17
device::rsp_su_instructions::special_reserved, // 18
device::rsp_su_instructions::special_reserved, // 19
device::rsp_su_instructions::special_reserved, // 20
device::rsp_su_instructions::special_reserved, // 21
device::rsp_su_instructions::special_reserved, // 22
device::rsp_su_instructions::special_reserved, // 23
device::rsp_su_instructions::special_reserved, // 24
device::rsp_su_instructions::special_reserved, // 25
device::rsp_su_instructions::special_reserved, // 26
device::rsp_su_instructions::special_reserved, // 27
device::rsp_su_instructions::special_reserved, // 28
device::rsp_su_instructions::special_reserved, // 29
device::rsp_su_instructions::special_reserved, // 30
device::rsp_su_instructions::special_reserved, // 31
device::rsp_su_instructions::add, // 32
device::rsp_su_instructions::addu, // 33
device::rsp_su_instructions::sub, // 34
device::rsp_su_instructions::subu, // 35
device::rsp_su_instructions::and, // 36
device::rsp_su_instructions::or, // 37
device::rsp_su_instructions::xor, // 38
device::rsp_su_instructions::nor, // 39
device::rsp_su_instructions::special_reserved, // 40
device::rsp_su_instructions::special_reserved, // 41
device::rsp_su_instructions::slt, // 42
device::rsp_su_instructions::sltu, // 43
device::rsp_su_instructions::special_reserved, // 44
device::rsp_su_instructions::special_reserved, // 45
device::rsp_su_instructions::special_reserved, // 46
device::rsp_su_instructions::special_reserved, // 47
device::rsp_su_instructions::special_reserved, // 48
device::rsp_su_instructions::special_reserved, // 49
device::rsp_su_instructions::special_reserved, // 50
device::rsp_su_instructions::special_reserved, // 51
device::rsp_su_instructions::special_reserved, // 52
device::rsp_su_instructions::special_reserved, // 53
device::rsp_su_instructions::special_reserved, // 54
device::rsp_su_instructions::special_reserved, // 55
device::rsp_su_instructions::special_reserved, // 56
device::rsp_su_instructions::special_reserved, // 57
device::rsp_su_instructions::special_reserved, // 58
device::rsp_su_instructions::special_reserved, // 59
device::rsp_su_instructions::special_reserved, // 60
device::rsp_su_instructions::special_reserved, // 61
device::rsp_su_instructions::special_reserved, // 62
device::rsp_su_instructions::special_reserved, // 63
];
device.rsp.cpu.regimm_instrs = [
@@ -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);
}
+15 -11
View File
@@ -60,14 +60,14 @@ const SP_SET_SIG7: u32 = 1 << 24;
const RSP_MEM_MASK: usize = 0x1FFF;
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct RspDma {
pub dir: DmaDir,
pub length: u32,
@@ -75,10 +75,12 @@ pub struct RspDma {
pub dramaddr: u32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rsp {
pub cpu: device::rsp_cpu::Cpu,
pub regs: [u32; SP_REGS_COUNT as usize],
pub regs2: [u32; SP_REGS2_COUNT as usize],
#[serde(with = "serde_big_array::BigArray")]
pub mem: [u8; 0x2000],
pub fifo: [RspDma; 2],
pub last_status_value: u32,
@@ -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
}
@@ -361,7 +366,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
}
/* set SP interrupt */
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
}
/* clear / set single step */
@@ -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 -2
View File
@@ -1204,6 +1204,9 @@ pub fn stv(device: &mut device::Device, opcode: u32) {
}
}
pub fn reserved(_device: &mut device::Device, _opcode: u32) {
panic!("rsp su reserved")
pub fn special_reserved(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rd(opcode) as usize] =
device.rsp.cpu.gpr[rs(opcode) as usize] >> (device.rsp.cpu.gpr[rs(opcode) as usize] & 31)
}
pub fn reserved(_device: &mut device::Device, _opcode: u32) {}
+5 -5
View File
@@ -14,13 +14,14 @@ pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Si {
pub regs: [u32; SI_REGS_COUNT as usize],
pub dma_dir: DmaDir,
@@ -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
View File
@@ -1,12 +1,12 @@
use crate::device;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct TlbLut {
pub address: u64,
pub cached: bool,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct TlbEntry {
pub mask: u64,
pub vpn2: u64,
+78 -43
View File
@@ -1,68 +1,80 @@
use crate::device;
use crate::netplay;
use crate::ui;
use crate::{device, netplay, ui};
use governor::clock::Clock;
const VI_STATUS_REG: u32 = 0;
//const VI_ORIGIN_REG: u32 = 1;
pub const VI_WIDTH_REG: u32 = 2;
const VI_ORIGIN_REG: u32 = 1;
//const VI_WIDTH_REG: u32 = 2;
//const VI_V_INTR_REG: u32 = 3;
const VI_CURRENT_REG: u32 = 4;
//const VI_BURST_REG: u32 = 5;
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,
pub last_origin: u32,
pub internal_frame_counter: u64,
pub min_wait_time: std::time::Duration,
pub frame_time: f64,
pub limit_freq: u64,
#[serde(skip)]
#[serde(default = "std::time::Instant::now")]
pub limit_freq_check: std::time::Instant,
}
//static mut FRAME_COUNTER: u64 = 0;
const MAX_LIMIT_FREQ: u64 = 3;
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
* 2.0;
device.vi.frame_time = 1.0 / expected_refresh_rate;
device.vi.delay = (device.cpu.clock_rate as f64 / expected_refresh_rate) as u64;
device.vi.count_per_scanline =
device.vi.delay / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as u64;
create_limiter(device);
}
fn create_limiter(device: &mut device::Device) {
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
(1.0 / expected_refresh_rate) * LIMIT_BUFFER as f64,
device.vi.frame_time * device.vi.limit_freq as f64,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
device.vi.limiter = Some(governor::RateLimiter::direct(quota));
let _ = device.vi.limiter.as_ref().unwrap().check();
//println!("new limit freq: {}", device.vi.limit_freq);
}
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 +101,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]
}
@@ -109,6 +122,13 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
set_expected_refresh_rate(device);
}
}
VI_ORIGIN_REG => {
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
if device.vi.regs[reg as usize] != device.vi.last_origin {
device.vi.last_origin = device.vi.regs[reg as usize];
device.vi.internal_frame_counter += 1;
}
}
_ => {
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
}
@@ -116,14 +136,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();
/*
unsafe {
FRAME_COUNTER += 1;
}
*/
pub fn vertical_interrupt_event(device: &mut device::Device) {
ui::video::update_screen();
ui::video::check_callback(device);
let mut enable_speed_limiter = true;
if device.netplay.is_some() {
@@ -132,10 +147,18 @@ fn vertical_interrupt_event(device: &mut device::Device) {
}
device.vi.vi_counter += 1;
if device.vi.vi_counter % LIMIT_BUFFER == 0 && enable_speed_limiter {
if device.vi.vi_counter % device.vi.limit_freq == 0 && enable_speed_limiter {
speed_limiter(device);
}
/*
let vis = if device.cart.pal { 50 } else { 60 };
if device.vi.vi_counter % vis == 0 {
println!("FPS: {}", device.vi.internal_frame_counter);
device.vi.internal_frame_counter = 0;
}
*/
/* toggle vi field if in interlaced mode */
device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1;
@@ -143,9 +166,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,
)
}
@@ -155,27 +177,40 @@ pub fn init(device: &mut device::Device) {
} else {
device.vi.clock = 48681812
}
/*
std::thread::spawn(move || {
let mut last_count = 0;
loop {
unsafe {
println!("{:?}", FRAME_COUNTER - last_count);
last_count = FRAME_COUNTER;
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
*/
}
fn speed_limiter(device: &device::Device) {
fn speed_limiter(device: &mut device::Device) {
let result = device.vi.limiter.as_ref().unwrap().check();
if result.is_err() {
let outcome = result.unwrap_err();
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
spin_sleep::sleep(dur);
if dur < device.vi.min_wait_time {
device.vi.min_wait_time = dur;
}
let _ = device.vi.limiter.as_ref().unwrap().check();
} else {
device.vi.min_wait_time = std::time::Duration::from_secs(0);
}
if std::time::Instant::now()
.duration_since(device.vi.limit_freq_check)
.as_secs_f64()
> 1.0
{
if device.vi.min_wait_time.as_secs_f64() == 0.0 && device.vi.limit_freq < MAX_LIMIT_FREQ {
device.vi.limit_freq += 1;
create_limiter(device);
} else if device.vi.min_wait_time.as_secs_f64() > device.vi.frame_time
&& device.vi.limit_freq > 1
{
device.vi.limit_freq -= 1;
create_limiter(device);
}
//println!("limit freq: {}", device.vi.limit_freq);
device.vi.min_wait_time = std::time::Duration::from_secs(1);
device.vi.limit_freq_check = std::time::Instant::now();
}
}
+32 -18
View File
@@ -5,6 +5,7 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod device;
mod netplay;
mod savestates;
mod ui;
use clap::Parser;
use ui::gui;
@@ -64,7 +65,8 @@ struct Args {
clear_input_bindings: bool,
}
fn main() {
#[tokio::main]
async fn main() {
let dirs = ui::get_dirs();
let mut result = std::fs::create_dir_all(dirs.config_dir);
@@ -77,7 +79,11 @@ 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() {
@@ -90,11 +96,11 @@ 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() {
@@ -105,7 +111,7 @@ 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);
}
@@ -117,7 +123,7 @@ fn main() {
return;
}
ui::input::assign_controller(
&mut device.ui,
&mut ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
@@ -129,7 +135,7 @@ fn main() {
return;
}
ui::input::bind_input_profile(
&mut device.ui,
&mut ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
@@ -137,22 +143,30 @@ 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()
+4 -5
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
use eframe::egui;
use std::io::{Read, Write};
//UDP packet formats
@@ -30,7 +29,7 @@ pub struct Netplay {
status: u8,
buffer_target: u8,
pub fast_forward: bool,
pub error_notifier: std::sync::mpsc::Sender<String>,
pub error_notifier: tokio::sync::mpsc::Sender<String>,
pub gui_ctx: egui::Context,
}
@@ -47,7 +46,7 @@ struct InputEvent {
}
fn send_error(netplay: &mut Netplay, error: String) {
netplay.error_notifier.send(error).unwrap();
netplay.error_notifier.try_send(error).unwrap();
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
}
@@ -219,7 +218,7 @@ pub fn init(
mut peer_addr: std::net::SocketAddr,
session: ui::gui::gui_netplay::NetplayRoom,
player_number: u8,
error_notifier: std::sync::mpsc::Sender<String>,
error_notifier: tokio::sync::mpsc::Sender<String>,
gui_ctx: egui::Context,
) -> Netplay {
peer_addr.set_port(session.port.unwrap() as u16);
+199
View File
@@ -0,0 +1,199 @@
use crate::{device, ui};
#[cfg(target_arch = "aarch64")]
use device::__m128i;
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeSeq, Serializer};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
struct M128iArrayVisitor<const N: usize>;
impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor<N> {
type Value = [__m128i; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(&format!("an array of {} 128-bit integers", N))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut arr: [__m128i; N] = [device::zero_m128i(); N];
for (index, item) in arr.iter_mut().enumerate().take(N) {
match seq.next_element::<u128>()? {
Some(value) => *item = unsafe { std::mem::transmute::<u128, __m128i>(value) },
None => return Err(serde::de::Error::invalid_length(index, &self)),
}
}
Ok(arr)
}
}
pub fn deserialize_m128i_array<'de, D, const N: usize>(
deserializer: D,
) -> Result<[__m128i; N], D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(M128iArrayVisitor)
}
pub fn serialize_m128i<S>(data: &__m128i, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes: u128 = unsafe { std::mem::transmute(*data) };
bytes.serialize(serializer)
}
pub fn deserialize_m128i<'de, D>(deserializer: D) -> Result<__m128i, D::Error>
where
D: Deserializer<'de>,
{
let bytes = u128::deserialize(deserializer)?;
Ok(unsafe { std::mem::transmute::<u128, __m128i>(bytes) })
}
pub fn serialize_m128i_array<S>(value: &[__m128i], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(value.len()))?;
for item in value {
let bytes: u128 = unsafe { std::mem::transmute(*item) };
seq.serialize_element(&bytes)?;
}
seq.end()
}
pub fn create_savestate(device: &device::Device) {
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
(&postcard::to_stdvec(&device.ui.saves).unwrap(), "saves"),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(device.ui.paths.savestate_file_path.clone(), compressed_file).unwrap();
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&mut device.ui.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
device.ui.saves = postcard::from_bytes(&save_bytes).unwrap();
device.cpu = state.cpu;
device.pif = state.pif;
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
for line_index in 0..512 {
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device);
}
}
pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) {
device::controller::mempak::read
}
pub fn default_instruction() -> fn(&mut device::Device, u32) {
device::cop0::reserved
}
pub fn default_instructions<const N: usize>() -> [fn(&mut device::Device, u32); N]
where
[fn(&mut device::Device, u32); N]: Sized,
{
[device::cop0::reserved; N]
}
pub fn default_memory_read(
) -> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::rdram::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::rdram::write_mem; 0x2000]
}
+27 -13
View File
@@ -26,17 +26,20 @@ pub struct Ui {
pub window: *mut sdl3_sys::video::SDL_Window,
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_freq: f64,
pub audio_device: u32,
pub num_joysticks: i32,
pub fullscreen: bool,
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
pub with_sdl: bool,
}
impl Drop for Ui {
fn drop(&mut self) {
unsafe {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
if self.with_sdl {
unsafe {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
}
}
}
@@ -78,14 +81,7 @@ pub fn get_dirs() -> Dirs {
}
impl Ui {
pub fn new() -> Ui {
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
let mut num_joysticks = 0;
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
if joysticks.is_null() {
panic!("Could not get joystick list");
}
fn construct_ui(num_joysticks: i32, joysticks: *mut u32, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
@@ -121,13 +117,16 @@ impl Ui {
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
sdcard_file_path: std::path::PathBuf::new(),
romsave_file_path: std::path::PathBuf::new(),
savestate_file_path: std::path::PathBuf::new(),
},
saves: storage::Saves {
eeprom: (Vec::new(), false),
sram: (Vec::new(), false),
flash: (Vec::new(), false),
mempak: (Vec::new(), false),
sdcard: (Vec::new(), false),
romsave: (std::collections::HashMap::new(), false),
},
pak_audio: audio::PakAudio {
@@ -137,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)
}
}
+3 -5
View File
@@ -49,8 +49,6 @@ pub fn init(ui: &mut ui::Ui, frequency: u64) {
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
panic!("Could not bind audio stream");
}
ui.audio_freq = audio_spec.freq as f64;
}
pub fn close(ui: &mut ui::Ui) {
@@ -112,11 +110,11 @@ 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 - audio_queued) as usize & !3];
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
+106 -119
View File
@@ -1,6 +1,4 @@
use crate::device;
use crate::netplay;
use crate::ui;
use crate::{device, netplay, ui};
use eframe::egui;
pub mod gui_netplay;
@@ -21,9 +19,9 @@ pub struct GopherEguiApp {
emulate_vru: bool,
dinput: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
netplay_error_receiver: Option<std::sync::mpsc::Receiver<String>>,
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
netplay_error_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
pub netplay: gui_netplay::GuiNetplay,
}
@@ -84,7 +82,7 @@ impl GopherEguiApp {
controller_paths: Vec<String>,
controller_names: Vec<String>,
) -> GopherEguiApp {
add_japanese_font(&cc.egui_ctx);
add_fonts(&cc.egui_ctx);
let config = ui::config::Config::new();
let mut selected_controller = [-1, -1, -1, -1];
@@ -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()
@@ -236,7 +219,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.vru_word_notifier
.as_ref()
.unwrap()
.send(v.clone())
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
@@ -248,7 +231,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.vru_word_notifier
.as_ref()
.unwrap()
.send(String::from(""))
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
}
@@ -285,19 +268,19 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
let (netplay_error_notifier, netplay_error_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
let (vru_window_notifier, vru_window_receiver): (
std::sync::mpsc::Sender<Vec<String>>,
std::sync::mpsc::Receiver<Vec<String>>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<Vec<String>>,
tokio::sync::mpsc::Receiver<Vec<String>>,
) = tokio::sync::mpsc::channel(1);
let (vru_word_notifier, vru_word_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
if netplay {
app.netplay_error_receiver = Some(netplay_error_receiver);
@@ -312,83 +295,75 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
std::thread::spawn(move || {
let file = if !netplay {
rfd::FileDialog::new().pick_file()
} else {
None
};
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
let mut task = None;
if !netplay {
task = Some(rfd::AsyncFileDialog::new().pick_file());
}
tokio::spawn(async move {
let file = if !netplay { task.unwrap().await } else { None };
if cfg!(target_os = "macos") && file.is_some() {
// mac os requires the process to be started on the main thread
// this means that netplay and VRU emulation will not work on mac os
{
let mut config = ui::config::Config::new();
save_config(&mut config, controller_paths, save_config_items);
}
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
if fullscreen {
command.arg("--fullscreen");
}
command.arg(file.unwrap().as_path());
std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
let status = command.status().expect("failed to execute process");
if !status.success() {
panic!("process exited with: {}", status);
}
} else if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device::run_game(rom_contents, &mut device, fullscreen);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device.ui.fullscreen = fullscreen;
device::run_game(rom_contents, &mut device);
netplay::close(&mut device);
} else {
if emulate_vru {
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.ui.fullscreen = fullscreen;
device::run_game(rom_contents, &mut device);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
let rom_contents = device::get_rom_contents(file.unwrap().as_path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(rom_contents, &mut device, fullscreen);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
})
.unwrap();
});
}
@@ -407,7 +382,7 @@ impl eframe::App for GopherEguiApp {
}
if self.netplay_error_receiver.is_some() {
let result = self.netplay_error_receiver.as_ref().unwrap().try_recv();
let result = self.netplay_error_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.netplay.error = result.unwrap();
}
@@ -431,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;
@@ -446,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;
@@ -507,18 +480,18 @@ 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")));
});
if self.emulate_vru && self.vru_window_receiver.is_some() {
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
let result = self.vru_window_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.show_vru_dialog = true;
self.vru_word_list = result.unwrap();
@@ -531,7 +504,21 @@ impl eframe::App for GopherEguiApp {
}
}
fn add_japanese_font(ctx: &egui::Context) {
fn add_fonts(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"regular_font",
egui::FontData::from_static(include_bytes!("../../data/Roboto-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Highest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Highest,
},
],
));
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
+35 -39
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::ui::gui;
use crate::ui::gui::GopherEguiApp;
use crate::ui::{self, gui};
use eframe::egui;
use sha2::{Digest, Sha256};
@@ -41,8 +41,8 @@ pub struct GuiNetplay {
pub socket:
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
pub server_receiver:
Option<std::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<std::sync::mpsc::Receiver<GameInfo>>,
Option<tokio::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<GameInfo>>,
pub broadcast_socket: Option<std::net::UdpSocket>,
pub broadcast_timer: Option<std::time::Instant>,
}
@@ -99,16 +99,14 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.server_receiver.is_none() {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.server_receiver = Some(rx);
let gui_ctx = ctx.clone();
std::thread::spawn(move || {
if let Ok(response) =
reqwest::blocking::get("https://m64p.s3.amazonaws.com/servers.json")
.unwrap()
.json::<std::collections::HashMap<String, String>>()
{
tx.send(response).unwrap();
let task = reqwest::get("https://m64p.s3.amazonaws.com/servers.json");
tokio::spawn(async move {
let response = task.await;
if let Ok(response) = response {
tx.send(response.json().await.unwrap()).await.unwrap();
gui_ctx.request_repaint();
}
});
@@ -143,7 +141,7 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.server_receiver.is_some() {
let result = app.netplay.server_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.server_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.servers.extend(result.unwrap());
app.netplay.server_receiver = None;
@@ -181,15 +179,16 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
app.netplay.create_rom_label = "Open ROM".to_string();
}
if ui.button(&app.netplay.create_rom_label).clicked() {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.create_rom_label = "Inspecting ROM".to_string();
std::thread::spawn(move || {
let file = rfd::FileDialog::new().pick_file();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx);
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
@@ -197,6 +196,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
"Open ROM".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
@@ -217,7 +217,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
get_servers(app, ctx);
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
@@ -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,
@@ -391,22 +391,15 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
}
}
fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.as_path());
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
let hash = device::cart_rom::calculate_hash(&rom_contents);
let game_name = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
.unwrap()
.trim()
.replace('\0', "")
.to_string();
tx.send((
hash,
game_name,
file.file_name().unwrap().to_string_lossy().to_string(),
rom_contents,
))
.unwrap();
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
tx.send((hash, game_name, file.file_name(), rom_contents))
.await
.unwrap();
} else {
tx.send((
"".to_string(),
@@ -414,6 +407,7 @@ fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo
"Invalid ROM".to_string(),
vec![],
))
.await
.unwrap();
}
}
@@ -444,7 +438,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.request_repaint();
}
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
@@ -455,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,
@@ -585,15 +579,16 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
{
app.netplay.error = "Session requires a password".to_string();
} else {
let (tx, rx) = std::sync::mpsc::channel();
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.join_rom_label = "Inspecting ROM".to_string();
std::thread::spawn(move || {
let file = rfd::FileDialog::new().pick_file();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx);
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
@@ -601,6 +596,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
"No ROM selected".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
+1 -1
View File
@@ -705,7 +705,7 @@ pub fn init(ui: &mut ui::Ui) {
let mut taken = [false; 4];
for i in 0..4 {
let controller_assignment = &ui.config.input.controller_assignment[i];
if controller_assignment.is_some() {
if controller_assignment.is_some() && ui.config.input.controller_enabled[i] {
let mut joystick_id = 0;
let assigned_path = controller_assignment.as_ref().unwrap();
+136 -24
View File
@@ -1,5 +1,6 @@
use crate::netplay;
use crate::ui;
use crate::{netplay, ui};
use std::io::Read;
use std::io::Write;
#[derive(PartialEq)]
pub enum SaveTypes {
@@ -8,6 +9,7 @@ pub enum SaveTypes {
Sram,
Flash,
Mempak,
Sdcard,
Romsave,
}
@@ -16,20 +18,38 @@ pub struct Paths {
pub sra_file_path: std::path::PathBuf,
pub fla_file_path: std::path::PathBuf,
pub pak_file_path: std::path::PathBuf,
pub sdcard_file_path: std::path::PathBuf,
pub romsave_file_path: std::path::PathBuf,
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),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub sdcard: (Vec<u8>, bool),
pub romsave: (std::collections::HashMap<u32, u8>, bool),
}
fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
let header_type = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap());
if header_type.is_ok() && header_type.unwrap() == "ED" {
let save_type = rom[0x3F] >> 4;
match save_type {
0 => return vec![],
1 => return vec![SaveTypes::Eeprom4k],
2 => return vec![SaveTypes::Eeprom16k],
3 => return vec![SaveTypes::Sram],
4 => panic!("Unsupported save type: {}", save_type),
5 => return vec![SaveTypes::Flash],
6 => panic!("Unsupported save type: {}", save_type),
_ => panic!("Unknown save type: {}", save_type),
}
}
match game_id {
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
@@ -87,36 +107,68 @@ fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
}
}
pub fn init(ui: &mut ui::Ui) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
pub fn get_game_name(rom: &[u8]) -> String {
let mut game_name = "".to_owned();
let header_value = std::str::from_utf8(&rom[0x20..0x34]);
if header_value.is_ok() {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re
.replace_all(header_value.unwrap(), "")
.trim()
.replace('\0', "");
}
game_name
}
let base_path = ui.dirs.data_dir.join("saves");
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.save_type = get_save_type(rom, &ui.game_id);
ui.paths.eep_file_path.clone_from(&base_path);
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);
let prefix = if game_name.is_empty() {
&ui.game_id
} else {
&game_name
};
ui.paths.eep_file_path.clone_from(&saves_path);
ui.paths
.eep_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".eep");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
ui.paths.sra_file_path.clone_from(&base_path);
ui.paths.sra_file_path.clone_from(&saves_path);
ui.paths
.sra_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".sra");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
ui.paths.fla_file_path.clone_from(&base_path);
ui.paths.fla_file_path.clone_from(&saves_path);
ui.paths
.fla_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".fla");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
ui.paths.pak_file_path.clone_from(&base_path);
ui.paths.pak_file_path.clone_from(&saves_path);
ui.paths
.pak_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".mpk");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
ui.paths.romsave_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(&saves_path);
ui.paths
.romsave_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".romsave");
.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>) {
@@ -137,6 +189,10 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
if mempak.is_ok() {
ui.saves.mempak.0 = mempak.unwrap();
}
let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.saves.sdcard.0 = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
@@ -169,30 +225,79 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
&ui.saves.mempak.0,
ui.saves.mempak.0.len(),
);
let mut romsave_bytes: Vec<u8> = vec![];
let mut compressed_sd = Vec::new();
if !ui.saves.sdcard.0.is_empty() {
compressed_sd = compress_file(&[(&ui.saves.sdcard.0, "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"img",
&compressed_sd,
compressed_sd.len(),
);
let mut compressed_romsave = Vec::new();
if !ui.saves.romsave.0.is_empty() {
romsave_bytes = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
compressed_romsave =
compress_file(&[(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap(), "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"rom",
&romsave_bytes,
romsave_bytes.len(),
&compressed_romsave,
compressed_romsave.len(),
);
} else {
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0);
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0);
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0);
let mut romsave_bytes: Vec<u8> = vec![];
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut romsave_bytes);
if !romsave_bytes.is_empty() {
let mut compressed_sd = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
if !compressed_sd.is_empty() {
ui.saves.sdcard.0 = decompress_file(&compressed_sd, "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, "save");
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
}
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_name(name).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
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));
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
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
@@ -212,6 +317,9 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.sdcard.1 {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
}
@@ -238,6 +346,10 @@ fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.0.as_ref();
}
SaveTypes::Sdcard => {
path = ui.paths.sdcard_file_path.as_ref();
data = ui.saves.sdcard.0.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);
return;
+45 -17
View File
@@ -1,29 +1,37 @@
#![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;
}
flags |= sdl3_sys::video::SDL_WINDOW_INPUT_FOCUS;
device.ui.window =
unsafe { sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), 640, 480, flags) };
let mut window_width = 640;
let mut window_height = 480;
if device.cart.pal {
window_width = 768;
window_height = 576;
}
device.ui.window = unsafe {
sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), window_width, window_height, flags)
};
if device.ui.window.is_null() {
panic!("Could not create window");
}
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.window) } {
panic!("Could not show window");
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
@@ -33,11 +41,7 @@ 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],
VI_WIDTH_REG: &mut device.vi.regs[device::vi::VI_WIDTH_REG as usize],
PAL: device.cart.pal,
};
unsafe {
@@ -46,7 +50,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,
)
}
}
@@ -58,11 +62,39 @@ 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],
PAL: device.cart.pal,
};
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);
@@ -73,10 +105,6 @@ pub fn process_rdp_list() -> u64 {
unsafe { rdp_process_commands() }
}
pub fn full_sync() {
unsafe { rdp_full_sync() }
}
pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) {
let text_size = 32;
let scale = rusttype::Scale::uniform(text_size as f32);
+3 -3
View File
@@ -4,14 +4,14 @@ use crate::ui;
pub fn prompt_for_match(
words: &[String],
window_notifier: &std::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &std::sync::mpsc::Receiver<String>,
window_notifier: &tokio::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
gui_ctx: &egui::Context,
) -> u16 {
let mut dedup_words = words.to_owned();
dedup_words.sort();
dedup_words.dedup();
window_notifier.send(dedup_words).unwrap();
window_notifier.try_send(dedup_words).unwrap();
gui_ctx.request_repaint(); // this is so the window pops up right away
let mut result = word_index_receiver.try_recv();
while result.is_err() {