Compare commits

...

61 Commits

Author SHA1 Message Date
Logan McNaughton 0aab9f71e1 cargo update 2026-01-29 21:58:06 +01:00
Logan McNaughton 7f84eb2aff add missing gamepad button types 2026-01-26 21:44:03 +01:00
Logan McNaughton b460d55714 use SDL_HAPTIC_INFINITY 2026-01-25 13:35:59 +01:00
Logan McNaughton ceaf56c563 bump to 1.1.13 2026-01-22 21:00:26 +01:00
Logan McNaughton 5937922558 fix issue with emux_xdetect 2026-01-22 20:44:17 +01:00
Logan McNaughton e9e71a6e02 Update to rust 1.93.0 (#659) 2026-01-22 20:24:41 +01:00
Logan McNaughton 9775f103e0 flush saves to disk (#658)
* flush saves to disk

* more

* more

* more

* more

* more

* more

* more

* more
2026-01-22 20:09:13 +01:00
Logan McNaughton bf01898c6f remove extra function 2026-01-22 09:22:47 +01:00
Logan McNaughton ce3b12bba9 print message for audio volume 2026-01-21 19:01:50 +01:00
Logan McNaughton 56ff2b260c adjust audio ratios 2026-01-21 12:13:19 +01:00
Logan McNaughton 367c22877b add system for micro adjustments to audio pitch 2026-01-21 11:47:33 +01:00
Logan McNaughton befbacc5b3 remove un-needed var 2026-01-20 15:31:03 +01:00
Logan McNaughton e1a6663bb5 remove needless copy 2026-01-20 13:21:44 +01:00
Logan McNaughton c5185e60c6 tweak ai dma calculation 2026-01-20 13:19:14 +01:00
Logan McNaughton 1aa879d759 tweak audio queue formula 2026-01-20 13:05:32 +01:00
Logan McNaughton bfad71f09f Update parallel-rdp (#656)
* Update parallel-rdp

* update cargo
2026-01-19 13:32:06 +01:00
Logan McNaughton 431b787998 update rfd 2026-01-05 19:27:16 +01:00
Logan McNaughton d48e3dc106 update rfd 2026-01-05 09:54:42 +01:00
Logan McNaughton 8be1ac7740 update SDL crate (#648)
* update SDL crate

* more
2026-01-03 10:07:27 +01:00
Logan McNaughton 56d0b1b2e2 update sse2neon to v1.9.1 2026-01-02 14:41:00 +01:00
Logan McNaughton 7f63bf1fde Update sse2neon to v1.9.0 (#642) 2025-12-22 10:08:09 +01:00
Logan McNaughton 05be960358 cargo update 2025-12-21 16:23:23 +01:00
dependabot[bot] 38a5079acf Bump actions/upload-artifact from 5 to 6 (#641)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 06:36:17 +01:00
Logan McNaughton 144e31c7fc bump to 1.1.12 2025-12-11 22:08:08 +01:00
Logan McNaughton e7a21171f4 fix windows build (#636) 2025-12-11 21:48:28 +01:00
Logan McNaughton 413da0178e update parallel rdp 2025-12-11 17:57:59 +01:00
Logan McNaughton d8c6669da1 Update to Rust 1.92.0 (#634) 2025-12-11 17:46:50 +01:00
Logan McNaughton c6fcfddf8c some cleanup 2025-12-10 15:12:56 +01:00
Logan McNaughton 0aa5c3e63a cleanup variable 2025-12-10 12:35:22 +01:00
Logan McNaughton 6f8433138d clippy fixes (#632)
* clippy fixes

* fix netplay browser
2025-12-10 10:00:42 +01:00
Logan McNaughton ea5bbfb218 only update controller assignment after user input (#629) 2025-12-06 13:42:10 +01:00
Logan McNaughton 10af598223 cargo update 2025-11-26 16:48:39 +01:00
dependabot[bot] c08511adc5 Bump actions/checkout from 5 to 6 (#622)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 04:24:48 +01:00
Logan McNaughton f116617ac3 remove redundant SDL_PumpEvents 2025-11-22 14:31:08 +01:00
Logan McNaughton 1141095b65 some code cleanup 2025-11-22 14:14:22 +01:00
Logan McNaughton 162a6df56d move if statement for better performance 2025-11-22 13:40:26 +01:00
Logan McNaughton 72f47e5aff addressing hack to fix compatibility with latest libdragon (#620) 2025-11-22 10:04:31 +01:00
Logan McNaughton 6d40f5a12d cargo update 2025-11-21 10:44:52 +01:00
Logan McNaughton 64e6d75db1 Update Granite (#618)
* Update Granite

* add file
2025-11-21 10:44:09 +01:00
Logan McNaughton 20761faf8c rust 1.91.1 2025-11-11 12:43:54 +01:00
Adam Steinberg 79eb34d83d unsolicited main.rs refactor (#614)
* main.rs refactor

* remove anyhow

* forgive me clippy, for I have sinned
2025-11-08 21:34:35 +01:00
Logan McNaughton c4057c2f21 bump to 1.1.11 2025-11-05 15:29:56 +01:00
Logan McNaughton 581cc0b9aa cargo update 2025-11-05 15:16:57 +01:00
Logan McNaughton 6f6e21c718 tweak build options 2025-11-05 15:12:12 +01:00
Logan McNaughton 181774a1de pass netplay_id to workflow 2025-11-04 15:38:30 +01:00
Logan McNaughton 002fbbe8a4 set netplay id from environment (#606) 2025-11-04 08:27:32 +01:00
Logan McNaughton ab630cc8cb Use skia backend for gui (#605)
* Use skia backend for gui

* more

* use clang-cl

* more

* more
2025-11-03 23:07:10 +01:00
Logan McNaughton 06f9c56a79 bump to 1.1.10 2025-10-30 22:52:34 +01:00
Logan McNaughton c3f4b11519 update sdl3 2025-10-30 22:47:45 +01:00
Logan McNaughton 921a0d5362 Rust 1.91 (#599) 2025-10-30 20:14:39 +01:00
Logan McNaughton 24fc5b9638 update parallel-rdp 2025-10-30 13:59:20 +01:00
dependabot[bot] 1559856483 Bump signpath/github-action-submit-signing-request from 1.3 to 2.0 (#597)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 1.3 to 2.0.
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v1.3...v2.0)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 08:36:02 +01:00
dependabot[bot] 1deb91fc1e Bump actions/upload-artifact from 4 to 5 (#596)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 06:01:31 +01:00
Logan McNaughton 726a58f854 cargo update 2025-10-26 13:45:56 +01:00
Logan McNaughton fe0297690f pump events during vi (#593)
* pump events during vi

* add var

* more

* more
2025-10-26 13:42:25 +01:00
Logan McNaughton 847df61526 Add pause between key bindings (#594) 2025-10-26 13:33:40 +01:00
Logan McNaughton 6de8128f5b cargo update 2025-10-12 12:30:57 +02:00
Logan McNaughton 6c09176d12 add .srm for GB saves 2025-10-11 19:32:35 +02:00
Logan McNaughton 2050098735 fix out of bounds access (#590) 2025-10-06 09:02:50 +02:00
Logan McNaughton d3ecc37da6 fix compare interrupt issue (#588) 2025-10-01 10:05:03 +02:00
Logan McNaughton a0237e9f7d bump to 1.1.9 2025-09-28 10:00:37 +02:00
39 changed files with 1987 additions and 1977 deletions
+16 -8
View File
@@ -29,7 +29,7 @@ jobs:
arch: linux-aarch64
llvm: ARM64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -48,7 +48,7 @@ jobs:
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
version: ${{ matrix.target }}-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
@@ -60,8 +60,10 @@ jobs:
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
env:
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
@@ -81,7 +83,7 @@ jobs:
# arch: windows-aarch64
# llvm: woa64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -102,15 +104,19 @@ jobs:
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
env:
CC: clang-cl
CXX: clang-cl
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload unsigned artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}.exe
- name: SignPath
if: ${{ github.ref_type == 'tag' }}
uses: signpath/github-action-submit-signing-request@v1.3
uses: signpath/github-action-submit-signing-request@v2.0
with:
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
@@ -121,6 +127,7 @@ jobs:
wait-for-completion: false
build-macos:
if: false
runs-on: macos-15
strategy:
fail-fast: false
@@ -131,7 +138,7 @@ jobs:
- target: aarch64-apple-darwin
arch: mac-aarch64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -145,8 +152,9 @@ jobs:
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
env:
AR: ar
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
lint-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -34,7 +34,7 @@ jobs:
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
version: lint-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: rustsec/audit-check@v2
Generated
+1478 -1515
View File
File diff suppressed because it is too large Load Diff
+13 -10
View File
@@ -1,27 +1,27 @@
[package]
name = "gopher64"
version = "1.1.8"
version = "1.1.13"
edition = "2024"
rust-version = "1.90"
rust-version = "1.93.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dirs = "6.0"
zip = "5.1"
zip = "7.0"
governor = "0.10"
sevenz-rust2 = { version = "0.19", default-features = false }
sevenz-rust2 = { version = "0.20", default-features = false }
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-big-array = "0.5"
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-femtovg-wgpu"] }
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia-vulkan"] }
open = "5.3"
sha2 = "0.10"
sdl3-sys = { version = "0.5", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.2", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] }
sdl3-sys = { version = "0.6", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.6", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
rfd = "0.17"
tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
@@ -34,7 +34,7 @@ fatfs = "0.3"
semver = "1.0"
postcard = { version = "1.1", features = ["use-std"] }
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
reqwest = { version = "0.13", default-features = false, features = ["http2","rustls","charset","json","query"] }
[build-dependencies]
winresource = "0.1"
@@ -45,7 +45,10 @@ cc = { version = "1.2", features = ["parallel"] }
[profile.release]
panic = "abort"
lto = "thin"
strip = true
[profile.release-with-debug]
inherits = "release"
debug = true
strip = false
panic = "unwind"
+4
View File
@@ -40,6 +40,7 @@ fn main() {
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_event.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/query_pool.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/render_pass.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/rtas.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/sampler.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/semaphore.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/semaphore_manager.cpp")
@@ -193,5 +194,8 @@ fn main() {
let git_hash = String::from_utf8(git_output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={git_hash}");
let netplay_id = std::env::var("NETPLAY_ID").unwrap_or("gopher64".to_string());
println!("cargo:rustc-env=NETPLAY_ID={netplay_id}");
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
}
+8 -4
View File
@@ -711,7 +711,8 @@ uint64_t rdp_process_commands()
uint32_t offset_address = (rdp_device.frame_buffer_info.framebuffer_address + pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
std::fill_n(rdram_dirty.begin() + offset_address, (pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3, true);
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
if (rdp_device.frame_buffer_info.depth_buffer_enabled)
@@ -719,7 +720,8 @@ uint64_t rdp_process_commands()
offset_address = (rdp_device.frame_buffer_info.depthbuffer_address + pixel_size(2, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
std::fill_n(rdram_dirty.begin() + offset_address, (pixel_size(2, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3, true);
uint32_t end_addr = std::min(offset_address + ((pixel_size(2, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
}
@@ -732,7 +734,8 @@ uint64_t rdp_process_commands()
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
uint32_t lower_right_t = (w2 & 0xFFF) >> 2;
std::fill_n(rdram_dirty.begin() + offset_address, (pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, (lower_right_t - upper_left_t) * rdp_device.frame_buffer_info.texture_width) + 7) >> 3, true);
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, (lower_right_t - upper_left_t) * rdp_device.frame_buffer_info.texture_width) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
break;
@@ -744,7 +747,8 @@ uint64_t rdp_process_commands()
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
uint32_t lower_right_s = ((w2 >> 12) & 0xFFF);
std::fill_n(rdram_dirty.begin() + offset_address, (pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, lower_right_s - upper_left_s) + 7) >> 3, true);
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, lower_right_s - upper_left_s) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
break;
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.90.0"
channel = "1.93.0"
+1 -7
View File
@@ -93,8 +93,7 @@ fn set_rng() -> rand_chacha::ChaCha8Rng {
fn init_rng(device: &mut Device) {
let mut rng_seed = set_rng().next_u64();
if device.netplay.is_some() {
let netplay = device.netplay.as_mut().unwrap();
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rng(netplay, rng_seed);
} else {
@@ -209,7 +208,6 @@ pub struct Device {
#[serde(skip, default = "set_rng")]
pub rng: rand_chacha::ChaCha8Rng,
pub vru: controller::vru::Vru,
pub vru_window: controller::vru::VruWindow,
pub transferpaks: [controller::transferpak::TransferPak; 4],
pub cheats: cheats::Cheats,
}
@@ -483,10 +481,6 @@ impl Device {
limit_freq: 2,
limit_freq_check: std::time::Instant::now(),
},
vru_window: controller::vru::VruWindow {
window_notifier: None,
word_receiver: None,
},
vru: controller::vru::Vru {
status: 0,
voice_state: 0,
+1 -2
View File
@@ -45,11 +45,10 @@ fn get_remaining_dma_length(device: &device::Device) -> u64 {
}
fn get_dma_duration(device: &device::Device) -> u64 {
let samples_per_sec = device.vi.clock / (1 + device.ai.regs[AI_DACRATE_REG as usize]) as u64;
let bytes_per_sample = 4; /* XXX: assume 16bit stereo - should depends on bitrate instead */
let length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
(length * device.cpu.clock_rate) / (bytes_per_sample * device.ai.freq)
}
fn do_dma(device: &mut device::Device) {
+1 -1
View File
@@ -147,7 +147,7 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize,
device.pif.ram[status] = 0x00;
device.ui.storage.saves.eeprom.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
}
fn time2data(device: &mut device::Device, offset: usize) {
+4 -4
View File
@@ -1,5 +1,6 @@
use crate::device;
use crate::netplay;
use crate::ui;
use sha2::{Digest, Sha256};
pub const CART_MASK: usize = 0xFFFFFFF;
@@ -63,7 +64,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
.data
.insert((masked_address + i) as u32, *item);
}
device.ui.storage.saves.romsave.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
}
device.cart.latch = value & mask;
@@ -94,7 +95,7 @@ pub fn dma_read(
.unwrap_or(&0),
);
}
device.ui.storage.saves.romsave.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
}
device::pi::calculate_cycles(device, 1, length)
@@ -135,8 +136,7 @@ pub fn dma_write(
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
device.cart.rtc_timestamp = now.naive_local().and_utc().timestamp();
if device.netplay.is_some() {
let netplay = device.netplay.as_mut().unwrap();
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
} else {
+3 -3
View File
@@ -208,7 +208,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
.copy_from_slice(&data);
i += 4;
}
device.ui.storage.saves.sdcard.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sdcard);
}
'U' => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
@@ -405,7 +405,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device::memory::masked_write_32(&mut data, value, mask);
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.storage.saves.eeprom.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
} else {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
@@ -429,7 +429,7 @@ pub fn dma_read(
let buffer = if cart_addr & 0x2000 != 0 {
device::cart::format_eeprom(device);
cart_addr &= SC64_EEPROM_MASK as u32;
device.ui.storage.saves.eeprom.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
&mut device.ui.storage.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
+5 -5
View File
@@ -107,7 +107,7 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.storage.saves.sram.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
}
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -167,7 +167,7 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
j += 1;
}
device.ui.storage.saves.sram.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
}
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
@@ -331,12 +331,12 @@ fn flashram_command(device: &mut device::Device, command: u32) {
for i in 0..128 * 128 {
device.ui.storage.saves.flash.data[offset + i] = 0xFF;
}
device.ui.storage.saves.flash.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.storage.saves.flash.data[i] = 0xFF;
}
device.ui.storage.saves.flash.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
} else {
panic!("Unexpected flash erase command")
}
@@ -356,7 +356,7 @@ fn flashram_command(device: &mut device::Device, command: u32) {
for i in 0..128 {
device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.storage.saves.flash.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
/* clear program busy flag, set program success flag, transition to status mode */
device.cart.flashram.status &= !0x01;
+10 -9
View File
@@ -56,15 +56,14 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = if device.netplay.is_none() {
ui::input::get(&device.ui, channel)
} else {
if device.netplay.as_ref().unwrap().player_number as usize == channel {
let input = if let Some(netplay) = &mut device.netplay {
if netplay.player_number as usize == channel {
let local_input = ui::input::get(&device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
netplay::send_input(netplay, local_input);
}
netplay::get_input(device, channel)
} else {
ui::input::get(&device.ui, channel)
};
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
@@ -162,10 +161,12 @@ 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 {
//stop rumble if it is on
if device.netplay.is_none() {
if let Some(netplay) = &device.netplay {
if netplay.player_number as usize == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
}
} else {
device::ui::input::set_rumble(&device.ui, i, 0);
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
}
let new_pak_type = match channel.change_pak {
+2 -1
View File
@@ -1,4 +1,5 @@
use crate::device;
use crate::ui;
pub const MEMPAK_SIZE: usize = 0x8000;
const MPK_PAGE_SIZE: usize = 256;
@@ -96,6 +97,6 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
device.ui.storage.saves.mempak.data[offset..offset + size]
.copy_from_slice(&device.pif.ram[data..data + size]);
device.ui.storage.saves.mempak.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Mempak);
}
}
+6 -3
View File
@@ -15,10 +15,13 @@ pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: us
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if address == 0xc000 {
let rumble = device.pif.ram[data + size - 1];
if device.netplay.is_none() {
if let Some(netplay) = &device.netplay {
if netplay.player_number as usize == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
} else {
device::ui::input::set_rumble(&device.ui, channel, rumble);
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
}
}
+2 -14
View File
@@ -29,14 +29,6 @@ pub struct Vru {
pub word_mappings: HashMap<String, String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
}
pub fn init(device: &mut device::Device) {
reset_vru(device);
create_word_mappings(device);
@@ -187,12 +179,8 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
}
JCMD_VRU_READ => {
let index = if device.vru_window.window_notifier.is_some() {
ui::vru::prompt_for_match(
&device.vru.words,
device.vru_window.window_notifier.as_ref().unwrap(),
device.vru_window.word_receiver.as_mut().unwrap(),
)
let index = if let Some(weak) = &device.ui.weak {
ui::vru::prompt_for_match(&device.vru.words, weak, device.vi.frame_time)
} else {
0x7FFF
};
+34 -32
View File
@@ -179,6 +179,8 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
device::exceptions::reserved_exception(device, 0);
}
pub fn emux_xdetect(_device: &mut device::Device, _opcode: u32) {}
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
match index {
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
@@ -261,38 +263,38 @@ pub fn add_cycles(device: &mut device::Device, cycles: u64) {
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop0.instrs = [
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
device::cop0::reserved, // 2
device::cop0::reserved, // 3
device::cop0::mtc0, // 4
device::cop0::dmtc0, // 5
device::cop0::reserved, // 6
device::cop0::reserved, // 7
device::cop0::reserved, // 8
device::cop0::reserved, // 9
device::cop0::reserved, // 10
device::cop0::reserved, // 11
device::cop0::reserved, // 12
device::cop0::reserved, // 13
device::cop0::reserved, // 14
device::cop0::reserved, // 15
device::cop0::execute_cp0, // 16
device::cop0::reserved, // 17
device::cop0::reserved, // 18
device::cop0::reserved, // 19
device::cop0::reserved, // 20
device::cop0::reserved, // 21
device::cop0::reserved, // 22
device::cop0::reserved, // 23
device::cop0::reserved, // 24
device::cop0::reserved, // 25
device::cop0::reserved, // 26
device::cop0::reserved, // 27
device::cop0::reserved, // 28
device::cop0::reserved, // 29
device::cop0::reserved, // 30
device::cop0::reserved, // 31
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
device::cop0::reserved, // 2
device::cop0::reserved, // 3
device::cop0::mtc0, // 4
device::cop0::dmtc0, // 5
device::cop0::reserved, // 6
device::cop0::reserved, // 7
device::cop0::reserved, // 8
device::cop0::reserved, // 9
device::cop0::reserved, // 10
device::cop0::reserved, // 11
device::cop0::reserved, // 12
device::cop0::reserved, // 13
device::cop0::reserved, // 14
device::cop0::reserved, // 15
device::cop0::execute_cp0, // 16
device::cop0::reserved, // 17
device::cop0::reserved, // 18
device::cop0::reserved, // 19
device::cop0::emux_xdetect, // 20
device::cop0::reserved, // 21
device::cop0::reserved, // 22
device::cop0::reserved, // 23
device::cop0::reserved, // 24
device::cop0::reserved, // 25
device::cop0::reserved, // 26
device::cop0::reserved, // 27
device::cop0::reserved, // 28
device::cop0::reserved, // 29
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
device.cpu.cop0.instrs2 = [
+4 -1
View File
@@ -1,4 +1,5 @@
use crate::device;
use crate::ui;
pub const EVENT_TYPE_NONE: usize = 0;
pub const EVENT_TYPE_AI: usize = 1;
@@ -12,7 +13,8 @@ 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;
pub const EVENT_TYPE_SAVE: usize = 12;
pub const EVENT_TYPE_COUNT: usize = 13;
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Event {
@@ -60,6 +62,7 @@ fn get_event_handler(name: usize) -> fn(&mut device::Device) {
EVENT_TYPE_COMPARE => device::cop0::compare_event,
EVENT_TYPE_VRU => device::controller::vru::vru_talking_event,
EVENT_TYPE_PAK => device::controller::pak_switch_event,
EVENT_TYPE_SAVE => ui::storage::save_event,
_ => dummy_event,
}
}
-1
View File
@@ -18,7 +18,6 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
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]
+7 -2
View File
@@ -62,11 +62,16 @@ pub fn masked_write_64(dst: &mut u64, value: u64, mask: u64) {
pub fn translate_address(
device: &mut device::Device,
address: u64,
mut address: u64,
access_type: AccessType,
) -> (u64, bool, bool) {
if (address & 0xc0000000) != 0x80000000 {
return device::tlb::get_physical_address(device, address, access_type);
if (address & 0xFFFFFFFF00000000) == 0x9000000000000000 {
// this is a hack to support 64-bit addresses used by some homebrew
address |= 0xA0000000;
} else {
return device::tlb::get_physical_address(device, address, access_type);
}
}
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
}
+12 -12
View File
@@ -218,11 +218,11 @@ pub fn process_ram(device: &mut device::Device) {
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] {
if let Some(netplay) = &device.netplay {
if netplay.player_data[i].reg_id != 0 {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
} else if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
}
@@ -268,16 +268,16 @@ pub fn init(device: &mut device::Device) {
connect_pif_channels(device);
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
if let Some(netplay) = &device.netplay {
if netplay.player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(default_handler);
}
} else if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(default_handler);
}
}
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
+9 -9
View File
@@ -70,10 +70,9 @@ fn set_vertical_interrupt(device: &mut device::Device) {
}
fn set_current_line(device: &mut device::Device) {
let delay = device.vi.delay;
let next_vi = device::events::get_event(device, device::events::EVENT_TYPE_VI);
if let Some(next_vi) = next_vi {
device.vi.regs[VI_CURRENT_REG as usize] = ((delay
device.vi.regs[VI_CURRENT_REG as usize] = ((device.vi.delay
- (next_vi.count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]))
/ device.vi.count_per_scanline)
as u32;
@@ -142,11 +141,11 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
let (mut speed_limiter_toggled, paused) = ui::video::check_callback(device);
if device.netplay.is_some() {
netplay::send_sync_check(device);
if device.vi.enable_speed_limiter == device.netplay.as_ref().unwrap().fast_forward {
if let Some(netplay) = &mut device.netplay {
netplay::send_sync_check(netplay, device.cpu.cop0.regs.as_ref());
if device.vi.enable_speed_limiter == netplay.fast_forward {
speed_limiter_toggled = true;
device.vi.enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
device.vi.enable_speed_limiter = !netplay.fast_forward;
}
}
@@ -158,9 +157,11 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
device.vi.vi_counter += 1;
if device.netplay.is_none() && paused {
ui::video::pause_loop();
ui::video::pause_loop(device.vi.frame_time);
}
unsafe { sdl3_sys::events::SDL_PumpEvents() };
/*
let vis = if device.cart.pal { 50 } else { 60 };
if device.vi.vi_counter % vis == 0 {
@@ -190,8 +191,7 @@ pub fn init(device: &mut device::Device) {
}
fn speed_limiter(device: &mut device::Device, speed_limiter_toggled: bool) {
let result = device.vi.limiter.as_ref().unwrap().check();
if let Err(outcome) = result {
if let Err(outcome) = device.vi.limiter.as_ref().unwrap().check() {
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
spin_sleep::sleep(dur);
if dur < device.vi.min_wait_time {
+69 -84
View File
@@ -9,6 +9,7 @@ mod netplay;
mod savestates;
mod ui;
use clap::Parser;
use std::io::Error;
use ui::gui;
/// N64 emulator
@@ -73,113 +74,97 @@ struct Args {
clear_input_bindings: bool,
}
#[tokio::main]
async fn main() {
fn main() -> std::io::Result<()> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.worker_threads(4)
.thread_name("n64")
.thread_stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.build()
.unwrap();
let dirs = ui::get_dirs();
let mut result = std::fs::create_dir_all(dirs.config_dir);
if result.is_err() {
panic!("could not create config dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
if result.is_err() {
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())
}
std::fs::create_dir_all(dirs.config_dir)?;
std::fs::create_dir_all(dirs.data_dir.join("saves"))?;
std::fs::create_dir_all(dirs.data_dir.join("states"))?;
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 && args.game.is_none() {
if let Some(game) = args.game {
let file_path = std::path::Path::new(&game);
let Some(rom_contents) = device::get_rom_contents(file_path) else {
return Err(Error::other(format!(
"Could not read ROM file: {}",
file_path.display()
)));
};
let handle = runtime.spawn(async move {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
let disable_expansion_pak = device.ui.config.emulation.disable_expansion_pak;
let game_cheats = {
let game_crc = ui::storage::get_game_crc(&rom_contents);
let cheats = ui::config::Cheats::new();
cheats.cheats.get(&game_crc).cloned().unwrap_or_default()
};
device::run_game(
&mut device,
rom_contents,
ui::gui::GameSettings {
fullscreen: args.fullscreen,
overclock,
disable_expansion_pak,
cheats: game_cheats,
},
);
});
runtime.block_on(handle).unwrap()
} else if std::env::args().count() > 1 {
let mut ui = ui::Ui::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut ui);
return;
return Ok(());
}
if args.port.is_some() {
let port = args.port.unwrap();
if !(1..=4).contains(&port) {
println!("Port must be betwen 1 and 4");
return;
}
if let Some(port) = args.port
&& !(1..=4).contains(&port)
{
return Err(Error::other("Port must be between 1 and 4"));
}
if args.list_controllers {
let controllers = ui::input::get_controller_names(&ui);
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {i}: {controller}");
}
return;
return Ok(());
}
if args.configure_input_profile.is_some() {
if let Some(profile) = args.configure_input_profile {
ui::input::configure_input_profile(
&mut ui,
args.configure_input_profile.unwrap(),
profile,
args.use_dinput,
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
);
return;
return Ok(());
}
if args.assign_controller.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::assign_controller(
&mut ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
if let Some(assign_controller) = args.assign_controller {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::assign_controller(&mut ui, assign_controller, port);
}
if args.bind_input_profile.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::bind_input_profile(
&mut ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
}
} else if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
if let Some(rom_contents) = device::get_rom_contents(file_path) {
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();
let overclock = device.ui.config.emulation.overclock;
let disable_expansion_pak = device.ui.config.emulation.disable_expansion_pak;
let game_cheats = {
let game_crc = ui::storage::get_game_crc(&rom_contents);
let cheats = ui::config::Cheats::new();
cheats.cheats.get(&game_crc).cloned().unwrap_or_default()
};
device::run_game(
&mut device,
rom_contents,
ui::gui::GameSettings {
fullscreen: args.fullscreen,
overclock,
disable_expansion_pak,
cheats: game_cheats,
},
);
})
.unwrap();
handle.join().unwrap();
} else {
println!("Could not read rom file");
return;
if let Some(profile) = args.bind_input_profile {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::bind_input_profile(&mut ui, profile, port);
}
} else {
gui::app_window();
runtime.block_on(async {
gui::app_window();
});
}
runtime.shutdown_timeout(std::time::Duration::from_secs(1));
Ok(())
}
+3 -4
View File
@@ -103,14 +103,13 @@ pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<
*save_data = response;
}
pub fn send_sync_check(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
pub fn send_sync_check(netplay: &mut Netplay, regs: &[u64]) {
if netplay.vi_counter.is_multiple_of(600) {
let mut request: Vec<u8> = [UDP_SYNC_DATA].to_vec();
request.extend_from_slice(&(netplay.vi_counter).to_be_bytes());
for i in 0..device::cop0::COP0_REGS_COUNT as usize {
request.extend_from_slice(&(device.cpu.cop0.regs[i] as u32).to_be_bytes());
for item in regs.iter() {
request.extend_from_slice(&(*item as u32).to_be_bytes());
}
netplay.udp_socket.send(&request).unwrap();
+15 -10
View File
@@ -53,6 +53,7 @@ pub struct Ui {
pub storage: Storage,
pub video: Video,
pub usb: Usb,
pub weak: Option<slint::Weak<gui::AppWindow>>,
}
impl Drop for Ui {
@@ -68,12 +69,15 @@ impl Drop for Ui {
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
unsafe {
let init = sdl3_sys::init::SDL_WasInit(0);
if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
if sdl3_sys::init::SDL_WasInit(flag) == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
.to_str()
.unwrap();
panic!("Could not initialize SDL subsystem: {flag}, {err}");
panic!(
"Could not initialize SDL subsystem: {}, {}",
u32::from(flag),
err
);
}
}
}
@@ -107,7 +111,7 @@ pub fn get_dirs() -> Dirs {
}
impl Ui {
fn construct_ui(joysticks: Vec<u32>, with_sdl: bool) -> Ui {
fn construct_ui(joysticks: Vec<sdl3_sys::everything::SDL_JoystickID>, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
@@ -153,27 +157,27 @@ impl Ui {
write_to_disk: true,
eeprom: storage::Save {
data: Vec::new(),
written: false,
write_pending: false,
},
sram: storage::Save {
data: Vec::new(),
written: false,
write_pending: false,
},
flash: storage::Save {
data: Vec::new(),
written: false,
write_pending: false,
},
mempak: storage::Save {
data: Vec::new(),
written: false,
write_pending: false,
},
sdcard: storage::Save {
data: Vec::new(),
written: false,
write_pending: false,
},
romsave: storage::RomSave {
data: std::collections::HashMap::new(),
written: false,
write_pending: false,
},
},
},
@@ -192,6 +196,7 @@ impl Ui {
usb_tx: None,
cart_rx: None,
},
weak: None,
dirs,
with_sdl,
}
+46 -8
View File
@@ -53,6 +53,7 @@ pub fn lower_audio_volume(ui: &mut ui::Ui) {
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
ui::video::onscreen_message(ui, &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0));
}
pub fn raise_audio_volume(ui: &mut ui::Ui) {
@@ -63,6 +64,28 @@ pub fn raise_audio_volume(ui: &mut ui::Ui) {
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
ui::video::onscreen_message(ui, &format!("Audio volume: {:.0}%", ui.audio.gain * 100.0));
}
fn adjust_audio_frequency(device: &device::Device, frequency: f32) {
if !device.vi.enable_speed_limiter {
return;
}
unsafe {
let current_ratio =
sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(device.ui.audio.audio_stream);
sdl3_sys::everything::SDL_SetAudioStreamFrequencyRatio(
device.ui.audio.audio_stream,
(current_ratio + frequency).clamp(0.995, 1.005),
);
/*
println!(
"Adjusted audio frequency ratio to {}",
sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(device.ui.audio.audio_stream)
);
*/
}
}
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
@@ -85,11 +108,11 @@ pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.audio_stream) } as f64;
let acceptable_latency = (device.ai.freq as f64 * 0.2) * 4.0;
let min_latency = (device.ai.freq as f64 * 0.02) * 4.0;
let samples_per_frame = device.ai.freq as f64 * device.vi.frame_time * 4.0;
let max_latency = samples_per_frame * 8.0;
if audio_queued < min_latency {
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if audio_queued < samples_per_frame {
let silence_buffer: Vec<u8> = vec![0; samples_per_frame as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio.audio_stream,
@@ -99,17 +122,32 @@ pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
} {
panic!("Could not play audio");
}
/*
println!(
"Audio underrun: queued {} samples, expected at least {} samples",
audio_queued, samples_per_frame
);
*/
adjust_audio_frequency(device, -0.0005);
}
if audio_queued < acceptable_latency
&& !unsafe {
if audio_queued < max_latency {
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
} {
panic!("Could not play audio");
}
{
panic!("Could not play audio");
} else {
/*
println!(
"Audio overrun: queued {} samples, expected at most {} samples",
audio_queued, max_latency
);
*/
adjust_audio_frequency(device, 0.0005);
}
}
+60 -137
View File
@@ -24,11 +24,6 @@ pub struct GbPaths {
pub ram: [Option<std::path::PathBuf>; 4],
}
pub struct VruChannel {
pub vru_window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
pub vru_word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
}
#[derive(Clone)]
pub struct GameSettings {
pub fullscreen: bool,
@@ -191,6 +186,7 @@ fn controller_window(
std::rc::Rc::new(controllers);
app.set_controller_names(slint::ModelRc::from(controller_names_model));
let controller_changed = slint::VecModel::default();
let selected_controllers = slint::VecModel::default();
for selected in config.input.controller_assignment.iter() {
let mut found = false;
@@ -204,11 +200,16 @@ fn controller_window(
if !found {
selected_controllers.push(0);
}
controller_changed.push(false);
}
let selected_controllers_model: std::rc::Rc<slint::VecModel<i32>> =
std::rc::Rc::new(selected_controllers);
app.set_selected_controller(slint::ModelRc::from(selected_controllers_model));
let controller_changed_model: std::rc::Rc<slint::VecModel<bool>> =
std::rc::Rc::new(controller_changed);
app.set_controller_changed(slint::ModelRc::from(controller_changed_model));
let weak_app = app.as_weak();
app.on_input_profile_button_clicked(move || {
let dialog = InputProfileDialog::new().unwrap();
@@ -269,8 +270,10 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
}
for (i, selected_controller) in app.get_selected_controller().iter().enumerate() {
config.input.controller_assignment[i] =
controller_paths[selected_controller as usize].clone();
if app.get_controller_changed().row_data(i).unwrap_or(false) {
config.input.controller_assignment[i] =
controller_paths[selected_controller as usize].clone();
}
}
}
@@ -329,120 +332,66 @@ pub fn app_window() {
save_settings(&app, &controller_paths);
}
fn setup_vru_word_watcher(
weak_vru: slint::Weak<AppWindow>,
vru_word_notifier: tokio::sync::mpsc::Sender<String>,
mut vru_window_receiver: tokio::sync::mpsc::Receiver<Option<Vec<String>>>,
) {
tokio::spawn(async move {
loop {
let notifier = vru_word_notifier.clone();
let notifier_closed = vru_word_notifier.clone();
let result = vru_window_receiver.recv().await;
if let Some(Some(words)) = result {
weak_vru
.upgrade_in_event_loop(move |_handle| {
let vru_dialog = VruDialog::new().unwrap();
let vru_dialog_weak = vru_dialog.as_weak();
vru_dialog.on_vru_button_clicked(move |chosen_word| {
notifier.try_send(chosen_word.to_string()).unwrap();
vru_dialog_weak.unwrap().window().hide().unwrap();
});
vru_dialog.window().on_close_requested(move || {
notifier_closed.try_send("".to_string()).unwrap();
slint::CloseRequestResponse::HideWindow
});
let words_vec = slint::VecModel::default();
for word in words {
words_vec.push(word.into());
}
let words_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(words_vec);
vru_dialog.set_words(slint::ModelRc::from(words_model));
vru_dialog.show().unwrap();
})
.unwrap();
} else {
return;
}
}
});
}
pub fn run_rom(
gb_paths: GbPaths,
file_path: std::path::PathBuf,
mut game_settings: GameSettings,
vru_channel: VruChannel,
netplay: Option<NetplayDevice>,
usb: ui::Usb,
weak: slint::Weak<AppWindow>,
) {
std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true))
.unwrap();
let mut device = device::Device::new();
device.ui.config.rom_dir = file_path.parent().unwrap().to_path_buf();
for i in 0..4 {
if gb_paths.rom[i].is_some() && gb_paths.ram[i].is_some() {
device.transferpaks[i].cart.rom =
std::fs::read(gb_paths.rom[i].as_ref().unwrap()).unwrap();
device.transferpaks[i].cart.ram =
std::fs::read(gb_paths.ram[i].as_ref().unwrap()).unwrap();
}
}
device.ui.usb.usb_tx = usb.usb_tx;
device.ui.usb.cart_rx = usb.cart_rx;
device.vru_window.window_notifier = vru_channel.vru_window_notifier;
device.vru_window.word_receiver = vru_channel.vru_word_receiver;
if let Some(rom_contents) = device::get_rom_contents(file_path.as_path()) {
if let Some(netplay_device) = netplay {
device.netplay = Some(netplay::init(
netplay_device.peer_addr,
netplay_device.player_number,
));
} else {
game_settings.cheats = {
let game_crc = ui::storage::get_game_crc(&rom_contents);
let cheats = ui::config::Cheats::new();
cheats.cheats.get(&game_crc).cloned().unwrap_or_default()
};
}
device::run_game(&mut device, rom_contents, game_settings);
if device.netplay.is_some() {
netplay::close(&mut device);
}
} else {
println!("Could not read rom file");
}
if let Some(vru_window_notifier) = device.vru_window.window_notifier {
vru_window_notifier.try_send(None).unwrap();
}
let rom_dir = device.ui.config.rom_dir.clone();
weak.upgrade_in_event_loop(move |handle| {
if let Some(rom_dir_str) = rom_dir.to_str() {
handle.set_rom_dir(rom_dir_str.into());
}
handle.set_game_running(false);
})
tokio::spawn(async move {
weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true))
.unwrap();
let mut device = device::Device::new();
device.ui.config.rom_dir = file_path.parent().unwrap().to_path_buf();
for i in 0..4 {
if gb_paths.rom[i].is_some() && gb_paths.ram[i].is_some() {
device.transferpaks[i].cart.rom =
std::fs::read(gb_paths.rom[i].as_ref().unwrap()).unwrap();
device.transferpaks[i].cart.ram =
std::fs::read(gb_paths.ram[i].as_ref().unwrap()).unwrap();
}
}
device.ui.usb.usb_tx = usb.usb_tx;
device.ui.usb.cart_rx = usb.cart_rx;
device.ui.weak = Some(weak.clone());
if let Some(rom_contents) = device::get_rom_contents(file_path.as_path()) {
if let Some(netplay_device) = netplay {
device.netplay = Some(netplay::init(
netplay_device.peer_addr,
netplay_device.player_number,
));
} else {
game_settings.cheats = {
let game_crc = ui::storage::get_game_crc(&rom_contents);
let cheats = ui::config::Cheats::new();
cheats.cheats.get(&game_crc).cloned().unwrap_or_default()
};
}
device::run_game(&mut device, rom_contents, game_settings);
if device.netplay.is_some() {
netplay::close(&mut device);
}
} else {
println!("Could not read rom file");
}
let rom_dir = device.ui.config.rom_dir.clone();
weak.upgrade_in_event_loop(move |handle| {
if let Some(rom_dir_str) = rom_dir.to_str() {
handle.set_rom_dir(rom_dir_str.into());
}
handle.set_game_running(false);
})
.unwrap();
});
}
fn open_rom(app: &AppWindow, usb: ui::Usb) {
@@ -472,31 +421,16 @@ fn open_rom(app: &AppWindow, usb: ui::Usb) {
select_gb_ram[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB RAM P{}", i + 1))
.add_filter("GB RAM files", &["sav", "ram", "SAV", "RAM"])
.add_filter("GB RAM files", &["sav", "ram", "srm", "SAV", "RAM", "SRM"])
.pick_file(),
);
}
}
#[allow(clippy::type_complexity)]
let (vru_window_notifier, vru_window_receiver): (
tokio::sync::mpsc::Sender<Option<Vec<String>>>,
tokio::sync::mpsc::Receiver<Option<Vec<String>>>,
) = tokio::sync::mpsc::channel(5);
let (vru_word_notifier, vru_word_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(5);
let fullscreen = app.get_fullscreen();
let overclock = app.get_overclock_n64_cpu();
let emulate_vru = app.get_emulate_vru();
let disable_expansion_pak = app.get_disable_expansion_pak();
if emulate_vru {
setup_vru_word_watcher(app.as_weak(), vru_word_notifier, vru_window_receiver);
}
let weak = app.as_weak();
tokio::spawn(async move {
if let Some(file) = select_rom.await {
@@ -525,17 +459,6 @@ fn open_rom(app: &AppWindow, usb: ui::Usb) {
disable_expansion_pak,
cheats: std::collections::HashMap::new(), // will be filled in later
},
if emulate_vru {
VruChannel {
vru_window_notifier: Some(vru_window_notifier),
vru_word_receiver: Some(vru_word_receiver),
}
} else {
VruChannel {
vru_window_notifier: None,
vru_word_receiver: None,
}
},
None,
usb,
weak,
+1
View File
@@ -47,6 +47,7 @@ export component AppWindow inherits Window {
in-out property rom_dir <=> SettingsData.rom_dir;
in-out property emulate_vru <=> ControllerData.emulate_vru;
in-out property controller_enabled <=> ControllerData.controller_enabled;
in-out property controller_changed <=> ControllerData.controller_changed;
in-out property transferpak <=> ControllerData.transferpak;
in-out property selected_profile_binding <=> ControllerData.selected_profile_binding;
in-out property input_profiles <=> ControllerData.input_profiles;
+2
View File
@@ -13,6 +13,7 @@ export global ControllerData {
in-out property <[string]> input_profiles;
in-out property <[string]> controller_names;
in-out property <[int]> selected_controller;
in-out property <[bool]> controller_changed;
in-out property <bool> blank_profiles;
}
@@ -114,6 +115,7 @@ export component ControllerConfig inherits Page {
current-index: ControllerData.selected_controller[player];
selected => {
ControllerData.selected_controller[player] = self.current-index;
ControllerData.controller_changed[player] = true;
}
}
}
+24 -8
View File
@@ -143,6 +143,17 @@ fn get_button_from_i32(button: i32) -> sdl3_sys::gamepad::SDL_GamepadButton {
12 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_DOWN,
13 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_LEFT,
14 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_RIGHT,
15 => sdl3_sys::gamepad::SDL_GamepadButton::MISC1,
16 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_PADDLE1,
17 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_PADDLE1,
18 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_PADDLE2,
19 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_PADDLE2,
20 => sdl3_sys::gamepad::SDL_GamepadButton::TOUCHPAD,
21 => sdl3_sys::gamepad::SDL_GamepadButton::MISC2,
22 => sdl3_sys::gamepad::SDL_GamepadButton::MISC3,
23 => sdl3_sys::gamepad::SDL_GamepadButton::MISC4,
24 => sdl3_sys::gamepad::SDL_GamepadButton::MISC5,
25 => sdl3_sys::gamepad::SDL_GamepadButton::MISC6,
_ => panic!("Invalid button"),
}
}
@@ -309,7 +320,7 @@ pub fn set_rumble(ui: &ui::Ui, channel: usize, rumble: u8) {
controller,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u32 * 60000,
(rumble & 1) as u32 * sdl3_sys::haptic::SDL_HAPTIC_INFINITY,
)
};
} else if !joystick.is_null() {
@@ -318,7 +329,7 @@ pub fn set_rumble(ui: &ui::Ui, channel: usize, rumble: u8) {
joystick,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u32 * 60000,
(rumble & 1) as u32 * sdl3_sys::haptic::SDL_HAPTIC_INFINITY,
)
};
}
@@ -385,8 +396,6 @@ pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<Option<String>> {
}
pub fn get(ui: &ui::Ui, channel: usize) -> InputData {
unsafe { sdl3_sys::events::SDL_PumpEvents() };
let profile_name = ui.config.input.input_profile_binding[channel].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
let mut keys = 0;
@@ -525,7 +534,7 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, d
title.as_ptr(),
640,
480,
0,
sdl3_sys::video::SDL_WindowFlags(0),
&mut window,
&mut renderer,
)
@@ -702,6 +711,13 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, d
}
}
}
ui::video::draw_text(
format!("Binding set for {key}").as_str(),
renderer,
text_engine,
font,
);
std::thread::sleep(std::time::Duration::from_millis(200));
}
close_controllers(open_joysticks, open_controllers);
@@ -936,7 +952,7 @@ pub fn init(ui: &mut ui::Ui) {
for i in 0..4 {
let controller_assignment = &ui.config.input.controller_assignment[i];
if controller_assignment.is_some() && ui.config.input.controller_enabled[i] {
let mut joystick_id = 0;
let mut joystick_id = sdl3_sys::everything::SDL_JoystickID(0);
let assigned_path = controller_assignment.as_ref().unwrap();
for joystick in ui.input.joysticks.iter() {
@@ -963,7 +979,7 @@ pub fn init(ui: &mut ui::Ui) {
if !profile.dinput {
let gamepad = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) };
if gamepad.is_null() {
println!("could not connect gamepad: {joystick_id}")
println!("could not connect gamepad: {}", u32::from(joystick_id))
} else {
ui.input.controllers[i].game_controller = gamepad;
let properties =
@@ -982,7 +998,7 @@ pub fn init(ui: &mut ui::Ui) {
} else {
let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) };
if joystick.is_null() {
println!("could not connect joystick: {joystick_id}")
println!("could not connect joystick: {}", u32::from(joystick_id))
} else {
ui.input.controllers[i].joystick = joystick;
let properties =
+30 -31
View File
@@ -2,7 +2,7 @@ use crate::device;
use crate::ui;
use crate::ui::gui::{
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, GbPaths,
NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, VruChannel, run_rom, save_settings,
NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, run_rom, save_settings,
};
use futures::{SinkExt, StreamExt};
use sha2::{Digest, Sha256};
@@ -111,7 +111,7 @@ impl NetplayPages for NetplayJoin {
fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slint::Weak<T>) {
let task = reqwest::Client::new()
.get("https://dispatch.gopher64.com/getRegions")
.header("netplay-id", EMU_NAME)
.header("netplay-id", env!("NETPLAY_ID"))
.send();
tokio::spawn(async move {
let mut local_servers: Vec<(String, String)> = vec![];
@@ -140,31 +140,34 @@ fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slin
}
let response = task.await;
if let Ok(response) = response
let public_servers = if let Ok(response) = response
&& let Ok(servers) = response.json::<Vec<String>>().await
{
weak.upgrade_in_event_loop(move |handle| {
let server_names: slint::VecModel<slint::SharedString> = slint::VecModel::default();
let server_urls: slint::VecModel<slint::SharedString> = slint::VecModel::default();
for local_server in local_servers {
server_names.push(local_server.0.into());
server_urls.push(local_server.1.into());
}
for server in servers {
server_names.push(server.clone().into());
server_urls.push(format!("dispatcher:{server}").into());
}
server_names.push("Custom".into());
handle.refresh_sessions();
let server_names_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_names);
let server_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_urls);
handle.set_server_names(slint::ModelRc::from(server_names_model));
handle.set_server_urls(slint::ModelRc::from(server_urls_model));
})
.unwrap();
}
servers
} else {
vec![]
};
weak.upgrade_in_event_loop(move |handle| {
let server_names: slint::VecModel<slint::SharedString> = slint::VecModel::default();
let server_urls: slint::VecModel<slint::SharedString> = slint::VecModel::default();
for local_server in local_servers {
server_names.push(local_server.0.into());
server_urls.push(local_server.1.into());
}
for server in public_servers {
server_names.push(server.clone().into());
server_urls.push(format!("dispatcher:{server}").into());
}
server_names.push("Custom".into());
handle.refresh_sessions();
let server_names_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_names);
let server_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_urls);
handle.set_server_names(slint::ModelRc::from(server_names_model));
handle.set_server_urls(slint::ModelRc::from(server_urls_model));
})
.unwrap();
});
}
@@ -296,7 +299,7 @@ pub fn setup_create_window(
let task = reqwest::Client::new()
.get("https://dispatch.gopher64.com/createServer")
.query(&[("region", server_url.strip_prefix("dispatcher:").unwrap())])
.header("netplay-id", EMU_NAME)
.header("netplay-id", env!("NETPLAY_ID"))
.send();
let netplay_read_sender = netplay_read_sender.clone();
let netplay_write_receiver = netplay_write_receiver.resubscribe();
@@ -448,7 +451,7 @@ fn show_netplay_error(message: String) {
fn update_sessions(weak: slint::Weak<NetplayJoin>) {
let task = reqwest::Client::new()
.get("https://dispatch.gopher64.com/getServers")
.header("netplay-id", EMU_NAME)
.header("netplay-id", env!("NETPLAY_ID"))
.send();
tokio::spawn(async move {
let mut dispatcher_servers = std::collections::HashMap::new();
@@ -1081,10 +1084,6 @@ fn setup_wait_window(
disable_expansion_pak: game_settings.disable_expansion_pak,
cheats: game_settings.cheats,
},
VruChannel {
vru_window_notifier: None,
vru_word_receiver: None,
},
Some(NetplayDevice {
peer_addr: socket_addr,
player_number: player_number as u8,
+69 -34
View File
@@ -28,13 +28,13 @@ pub struct Paths {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Save {
pub data: Vec<u8>,
pub written: bool,
pub write_pending: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct RomSave {
pub data: std::collections::HashMap<u32, u8>,
pub written: bool,
pub write_pending: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -354,15 +354,10 @@ pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
compressed_file
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap();
std::fs::write(ui.storage.paths.romsave_file_path.clone(), data).unwrap();
}
fn writeback_sdcard(device: &mut device::Device) {
let length;
let save_data: &[u8];
if device.ui.storage.saves.eeprom.written {
if device.ui.storage.saves.eeprom.write_pending {
if device
.ui
.storage
@@ -374,12 +369,15 @@ fn writeback_sdcard(device: &mut device::Device) {
length = 4;
}
save_data = device.ui.storage.saves.eeprom.data.as_ref();
} else if device.ui.storage.saves.sram.written {
device.ui.storage.saves.eeprom.write_pending = false;
} else if device.ui.storage.saves.sram.write_pending {
length = device.ui.storage.saves.sram.data.len() / 512;
save_data = device.ui.storage.saves.sram.data.as_ref();
} else if device.ui.storage.saves.flash.written {
device.ui.storage.saves.sram.write_pending = false;
} else if device.ui.storage.saves.flash.write_pending {
length = device.ui.storage.saves.flash.data.len() / 512;
save_data = device.ui.storage.saves.flash.data.as_ref();
device.ui.storage.saves.flash.write_pending = false;
} else {
return;
}
@@ -389,71 +387,108 @@ fn writeback_sdcard(device: &mut device::Device) {
device.ui.storage.saves.sdcard.data[offset..offset + 512]
.copy_from_slice(&save_data[i * 512..(i + 1) * 512]);
}
device.ui.storage.saves.sdcard.written = true;
device.ui.storage.saves.sdcard.write_pending = true;
}
pub fn write_saves(device: &mut device::Device) {
if device.netplay.is_none() || device.netplay.as_ref().unwrap().player_number == 0 {
if device.ui.storage.saves.write_to_disk {
if device.ui.storage.saves.eeprom.written {
write_save(&device.ui, SaveTypes::Eeprom16k)
if device.ui.storage.saves.eeprom.write_pending {
write_save(&mut device.ui, SaveTypes::Eeprom16k)
}
if device.ui.storage.saves.sram.written {
write_save(&device.ui, SaveTypes::Sram)
if device.ui.storage.saves.sram.write_pending {
write_save(&mut device.ui, SaveTypes::Sram)
}
if device.ui.storage.saves.flash.written {
write_save(&device.ui, SaveTypes::Flash)
if device.ui.storage.saves.flash.write_pending {
write_save(&mut device.ui, SaveTypes::Flash)
}
if device.ui.storage.saves.romsave.written {
write_save(&device.ui, SaveTypes::Romsave)
if device.ui.storage.saves.romsave.write_pending {
write_save(&mut device.ui, SaveTypes::Romsave)
}
} else {
writeback_sdcard(device)
}
if device.ui.storage.saves.mempak.written {
write_save(&device.ui, SaveTypes::Mempak)
if device.ui.storage.saves.mempak.write_pending {
write_save(&mut device.ui, SaveTypes::Mempak)
}
if device.ui.storage.saves.sdcard.written {
write_save(&device.ui, SaveTypes::Sdcard)
if device.ui.storage.saves.sdcard.write_pending {
write_save(&mut device.ui, SaveTypes::Sdcard)
}
}
}
fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
pub fn save_event(device: &mut device::Device) {
write_saves(device);
}
pub fn schedule_save(device: &mut device::Device, save_type: SaveTypes) {
match save_type {
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
device.ui.storage.saves.eeprom.write_pending = true;
}
SaveTypes::Sram => {
device.ui.storage.saves.sram.write_pending = true;
}
SaveTypes::Flash => {
device.ui.storage.saves.flash.write_pending = true;
}
SaveTypes::Mempak => {
device.ui.storage.saves.mempak.write_pending = true;
}
SaveTypes::Sdcard => {
device.ui.storage.saves.sdcard.write_pending = true;
}
SaveTypes::Romsave => {
device.ui.storage.saves.romsave.write_pending = true;
}
}
device::events::create_event(
device,
device::events::EVENT_TYPE_SAVE,
device.cpu.clock_rate, // 1 second
);
}
fn write_save(ui: &mut ui::Ui, save_type: SaveTypes) {
let path: &std::path::Path;
let data: &Vec<u8>;
let rom_save_data: Vec<u8>;
match save_type {
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
path = ui.storage.paths.eep_file_path.as_ref();
data = ui.storage.saves.eeprom.data.as_ref();
ui.storage.saves.eeprom.write_pending = false;
}
SaveTypes::Sram => {
path = ui.storage.paths.sra_file_path.as_ref();
data = ui.storage.saves.sram.data.as_ref();
ui.storage.saves.sram.write_pending = false;
}
SaveTypes::Flash => {
path = ui.storage.paths.fla_file_path.as_ref();
data = ui.storage.saves.flash.data.as_ref();
ui.storage.saves.flash.write_pending = false;
}
SaveTypes::Mempak => {
path = ui.storage.paths.pak_file_path.as_ref();
data = ui.storage.saves.mempak.data.as_ref();
ui.storage.saves.mempak.write_pending = false;
}
SaveTypes::Sdcard => {
path = ui.storage.paths.sdcard_file_path.as_ref();
data = ui.storage.saves.sdcard.data.as_ref();
ui.storage.saves.sdcard.write_pending = false;
}
SaveTypes::Romsave => {
write_rom_save(ui);
return;
path = ui.storage.paths.romsave_file_path.as_ref();
rom_save_data = postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap();
data = rom_save_data.as_ref();
ui.storage.saves.romsave.write_pending = false;
}
}
let result = std::fs::write(path, data);
if result.is_err() {
panic!(
"could not save {} {}",
path.display(),
result.err().unwrap()
)
}
let save_data = data.clone();
let save_path = path.to_path_buf();
tokio::spawn(async move {
tokio::fs::write(save_path, save_data).await.unwrap();
});
}
-4
View File
@@ -53,10 +53,6 @@ fn upload_rom(
disable_expansion_pak: handle.get_disable_expansion_pak(),
cheats: std::collections::HashMap::new(), // will be filled in later
},
ui::gui::VruChannel {
vru_window_notifier: None,
vru_word_receiver: None,
},
None,
ui::Usb {
usb_tx: Some(usb_tx),
+2 -2
View File
@@ -133,11 +133,11 @@ pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
}
}
pub fn pause_loop() {
pub fn pause_loop(frame_time: f64) {
let mut paused = true;
let mut frame_advance = false;
while paused && !frame_advance {
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
std::thread::sleep(std::time::Duration::from_secs_f64(frame_time));
unsafe { sdl3_sys::events::SDL_PumpEvents() };
let callback = unsafe { rdp_check_callback() };
paused = callback.paused;
+40 -6
View File
@@ -1,16 +1,50 @@
use crate::gui;
use slint::ComponentHandle;
pub fn prompt_for_match(
words: &[String],
window_notifier: &tokio::sync::mpsc::Sender<Option<Vec<String>>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
weak_vru: &slint::Weak<gui::AppWindow>,
frame_time: f64,
) -> u16 {
let mut dedup_words = words.to_owned();
dedup_words.sort();
dedup_words.dedup();
window_notifier.try_send(Some(dedup_words)).unwrap();
let mut result = word_index_receiver.try_recv();
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
weak_vru
.upgrade_in_event_loop(move |_handle| {
let vru_dialog = gui::VruDialog::new().unwrap();
let vru_dialog_weak = vru_dialog.as_weak();
let tx_clicked = tx.clone();
vru_dialog.on_vru_button_clicked(move |chosen_word| {
tx_clicked.try_send(chosen_word.to_string()).unwrap();
vru_dialog_weak.unwrap().window().hide().unwrap();
});
vru_dialog.window().on_close_requested(move || {
tx.try_send("".to_string()).unwrap();
slint::CloseRequestResponse::HideWindow
});
let words_vec = slint::VecModel::default();
for word in dedup_words {
words_vec.push(word.into());
}
let words_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(words_vec);
vru_dialog.set_words(slint::ModelRc::from(words_model));
vru_dialog.show().unwrap();
})
.unwrap();
let mut result = rx.try_recv();
while result.is_err() {
result = word_index_receiver.try_recv();
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
result = rx.try_recv();
std::thread::sleep(std::time::Duration::from_secs_f64(frame_time));
unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the OS doesn't complain about the game window being frozen
}
for (i, v) in words.iter().enumerate() {