Compare commits

...

9 Commits

Author SHA1 Message Date
Logan McNaughton 26022fff72 cargo update 2025-08-21 15:03:50 +02:00
Logan McNaughton 658dede9e9 use dependabot to update rust (#552) 2025-08-20 10:26:26 +02:00
Logan McNaughton 6ad1a31e34 correct joystick hat diagonal input (#551) 2025-08-20 09:15:41 +02:00
Logan McNaughton 4476d476de add frame advance (#549) 2025-08-18 10:42:29 +02:00
Logan McNaughton 8037fb426e UNF loader support (#526) 2025-08-18 08:44:12 +02:00
Logan McNaughton f0381a2cd1 add uppercase file extensions (#548) 2025-08-17 21:01:23 +02:00
Logan McNaughton c8e70e6125 cargo update 2025-08-17 20:56:02 +02:00
dependabot[bot] e9664b1087 Bump actions/checkout from 4 to 5 (#545)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [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/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  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-08-12 13:02:31 +02:00
Logan McNaughton 3f72700da0 bump to 1.1.4 (#541) 2025-08-07 19:21:54 +02:00
20 changed files with 608 additions and 186 deletions
+5 -1
View File
@@ -3,7 +3,11 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
- package-ecosystem: "rust-toolchain"
directory: "/"
schedule:
interval: "weekly"
# - package-ecosystem: "cargo"
# directory: "/"
# schedule:
+3 -3
View File
@@ -29,7 +29,7 @@ jobs:
arch: linux-aarch64
llvm: ARM64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -81,7 +81,7 @@ jobs:
# arch: windows-aarch64
# llvm: woa64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -131,7 +131,7 @@ jobs:
- target: aarch64-apple-darwin
arch: mac-aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
lint-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: recursive
- uses: rustsec/audit-check@v2
Generated
+166 -164
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gopher64"
version = "1.1.3"
version = "1.1.4"
edition = "2024"
rust-version = "1.89"
+4
View File
@@ -207,6 +207,9 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
case SDL_SCANCODE_RIGHTBRACKET:
callback.raise_volume = true;
break;
case SDL_SCANCODE_SLASH:
callback.frame_advance = true;
break;
case SDL_SCANCODE_0:
case SDL_SCANCODE_1:
case SDL_SCANCODE_2:
@@ -536,6 +539,7 @@ CALL_BACK rdp_check_callback()
callback.load_state = false;
callback.lower_volume = false;
callback.raise_volume = false;
callback.frame_advance = false;
return return_value;
}
+1
View File
@@ -34,6 +34,7 @@ extern "C"
bool lower_volume;
bool raise_volume;
bool paused;
bool frame_advance;
uint32_t save_state_slot;
} CALL_BACK;
+1
View File
@@ -347,6 +347,7 @@ impl Device {
sector: 0,
buffer: vec![0; 8192],
writeback_sector: vec![0; 256],
usb_buffer: vec![],
},
flashram: cart::sram::Flashram {
status: 0,
+1 -1
View File
@@ -2,7 +2,7 @@ use crate::device;
use crate::netplay;
use sha2::{Digest, Sha256};
const CART_MASK: usize = 0xFFFFFFF;
pub const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = [0; 4];
+127 -2
View File
@@ -28,6 +28,7 @@ pub struct Sc64 {
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
pub writeback_sector: Vec<u32>,
pub usb_buffer: Vec<u8>,
}
fn format_sdcard(device: &mut device::Device) {
@@ -209,8 +210,132 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
device.ui.storage.saves.sdcard.written = true;
}
'U' | 'u' => {} // USB status, ignored
'M' | 'm' => {} // USB read/write, ignored
'U' => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
}
'u' => {
// used to notify the game that there is data to read
if let Some(cart_rx) = device.ui.usb.cart_rx.as_mut() {
match cart_rx.try_recv() {
Ok(data) => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] =
data.data_type & 0xFF; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] =
data.data_size & 0xFFFFFF; // length
device.cart.sc64.usb_buffer = data.data; // store the data to be read
}
Err(err) => {
match err {
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
panic!("cart_rx lagged: {err}");
}
_ => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
}
}
}
}
} else {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
}
}
'M' => {
// Send data from from flashcart to USB
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length =
device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize & 0xFFFFFF;
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
let mut usb_buffer = vec![0; length];
let mut i = 0;
if address < device.rdram.size as u64 {
while i < length {
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
.rdram
.mem
.get((address as usize + i) ^ device.byte_swap)
.unwrap_or(&0);
i += 1;
}
} else if address >= device::memory::MM_CART_ROM as u64
&& address < device::memory::MM_PIF_MEM as u64
{
while i < length {
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
.ui
.storage
.saves
.romsave
.data
.get(
&(((address as usize + i)
& device::cart::rom::CART_MASK)
as u32),
)
.unwrap_or(
device
.cart
.rom
.get(
(address as usize + i)
& device::cart::rom::CART_MASK,
)
.unwrap_or(&0),
);
i += 1;
}
} else {
panic!("Unknown address {address:#x} for SC64 M command");
}
ui::usb::send_to_usb(
usb_tx,
ui::usb::UsbData {
data: usb_buffer,
data_type: device.cart.sc64.regs[SC64_DATA1_REG as usize] >> 24,
data_size: device.cart.sc64.regs[SC64_DATA1_REG as usize]
& 0xFFFFFF,
},
);
}
}
'm' => {
// Receive data from USB to flashcart
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length = device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize;
let mut i = 0;
if address < device.rdram.size as u64 {
while i < length {
*device
.rdram
.mem
.get_mut((address as usize + i) ^ device.byte_swap)
.unwrap_or(&mut 0) =
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0);
i += 1;
}
} else if address >= device::memory::MM_CART_ROM as u64
&& address < device::memory::MM_PIF_MEM as u64
{
while i < length {
device.ui.storage.saves.romsave.data.insert(
((address as usize + i) & device::cart::rom::CART_MASK) as u32,
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0),
);
i += 1;
}
} else {
panic!("Unknown address {address:#x} for SC64 m command");
}
}
'w' => {
// SD card writeback pending
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
+11
View File
@@ -5,6 +5,7 @@ pub mod gui;
pub mod input;
pub mod netplay;
pub mod storage;
pub mod usb;
pub mod video;
pub mod vru;
@@ -38,6 +39,11 @@ pub struct Video {
pub fullscreen: bool,
}
pub struct Usb {
pub usb_tx: Option<tokio::sync::broadcast::Sender<usb::UsbData>>,
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
}
pub struct Ui {
pub dirs: Dirs,
pub config: config::Config,
@@ -48,6 +54,7 @@ pub struct Ui {
pub input: Input,
pub storage: Storage,
pub video: Video,
pub usb: Usb,
}
impl Drop for Ui {
@@ -185,6 +192,10 @@ impl Ui {
window: std::ptr::null_mut(),
fullscreen: false,
},
usb: Usb {
usb_tx: None,
cart_rx: None,
},
dirs,
with_sdl,
}
+2
View File
@@ -54,6 +54,7 @@ pub struct Video {
pub struct Emulation {
pub overclock: bool,
pub disable_expansion_pak: bool,
pub usb: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -150,6 +151,7 @@ impl Config {
emulation: Emulation {
overclock: false,
disable_expansion_pak: false,
usb: false,
},
rom_dir: std::path::PathBuf::new(),
}
+36 -7
View File
@@ -5,7 +5,9 @@ use slint::Model;
slint::include_modules!();
pub const N64_EXTENSIONS: [&str; 5] = ["n64", "v64", "z64", "7z", "zip"];
pub const N64_EXTENSIONS: [&str; 12] = [
"n64", "v64", "z64", "7z", "zip", "bin", "N64", "V64", "Z64", "7Z", "ZIP", "BIN",
];
#[derive(serde::Deserialize)]
struct GithubData {
@@ -62,15 +64,23 @@ fn check_latest_version(weak: slint::Weak<AppWindow>) {
});
}
fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>]) {
fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>], usb: ui::Usb) {
let dirs = ui::get_dirs();
let weak = app.as_weak();
let controller_paths = controller_paths.to_owned();
app.on_open_rom_button_clicked(move || {
let controller_paths = controller_paths.clone();
let mut usb_tx = None;
if let Some(usb_tx_inner) = usb.usb_tx.as_ref() {
usb_tx = Some(usb_tx_inner.clone());
}
let mut cart_rx = None;
if let Some(cart_rx_inner) = usb.cart_rx.as_ref() {
cart_rx = Some(cart_rx_inner.resubscribe());
}
weak.upgrade_in_event_loop(move |handle| {
save_settings(&handle, &controller_paths);
open_rom(&handle)
open_rom(&handle, ui::Usb { usb_tx, cart_rx })
})
.unwrap();
});
@@ -96,6 +106,7 @@ fn settings_window(app: &AppWindow, config: &ui::config::Config) {
app.set_apply_crt_shader(config.video.crt);
app.set_overclock_n64_cpu(config.emulation.overclock);
app.set_disable_expansion_pak(config.emulation.disable_expansion_pak);
app.set_emulate_usb(config.emulation.usb);
let combobox_value = match config.video.upscale {
1 => 0,
2 => 1,
@@ -230,6 +241,7 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
config.video.crt = app.get_apply_crt_shader();
config.emulation.overclock = app.get_overclock_n64_cpu();
config.emulation.disable_expansion_pak = app.get_disable_expansion_pak();
config.emulation.usb = app.get_emulate_usb();
let upscale_values = [1, 2, 4];
config.video.upscale = upscale_values[app.get_resolution() as usize];
@@ -280,6 +292,11 @@ fn about_window(app: &AppWindow) {
pub fn app_window() {
let app = AppWindow::new().unwrap();
about_window(&app);
let mut usb = ui::Usb {
usb_tx: None,
cart_rx: None,
};
let mut shutdown_tx = None;
let mut controller_paths;
{
let game_ui = ui::Ui::new();
@@ -289,11 +306,18 @@ pub fn app_window() {
controller_paths.insert(0, None);
settings_window(&app, &game_ui.config);
controller_window(&app, &game_ui.config, &controller_names, &controller_paths);
if game_ui.config.emulation.usb {
(shutdown_tx, usb) = ui::usb::init(app.as_weak());
}
}
local_game_window(&app, &controller_paths);
local_game_window(&app, &controller_paths, usb);
ui::netplay::netplay_window(&app, &controller_paths);
ui::cheats::cheats_window(&app);
app.run().unwrap();
if let Some(shutdown_tx) = &shutdown_tx {
ui::usb::close(shutdown_tx);
}
save_settings(&app, &controller_paths);
}
@@ -347,6 +371,7 @@ pub fn run_rom(
mut game_settings: GameSettings,
vru_channel: VruChannel,
netplay: Option<NetplayDevice>,
usb: ui::Usb,
weak: slint::Weak<AppWindow>,
) {
std::thread::Builder::new()
@@ -369,6 +394,9 @@ pub fn run_rom(
}
}
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;
@@ -409,7 +437,7 @@ pub fn run_rom(
.unwrap();
}
fn open_rom(app: &AppWindow) {
fn open_rom(app: &AppWindow, usb: ui::Usb) {
let rom_dir = app.get_rom_dir();
let select_rom = if !rom_dir.is_empty()
&& let Ok(exists) = std::fs::exists(&rom_dir)
@@ -430,13 +458,13 @@ fn open_rom(app: &AppWindow) {
select_gb_rom[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB ROM P{}", i + 1))
.add_filter("GB ROM files", &["gb", "gbc"])
.add_filter("GB ROM files", &["gb", "gbc", "GB", "GBC"])
.pick_file(),
);
select_gb_ram[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB RAM P{}", i + 1))
.add_filter("GB RAM files", &["sav", "ram"])
.add_filter("GB RAM files", &["sav", "ram", "SAV", "RAM"])
.pick_file(),
);
}
@@ -501,6 +529,7 @@ fn open_rom(app: &AppWindow) {
}
},
None,
usb,
weak,
);
}
+1
View File
@@ -43,6 +43,7 @@ export component AppWindow inherits Window {
in-out property overclock_n64_cpu <=> SettingsData.overclock_n64_cpu;
in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak;
in-out property resolution <=> SettingsData.resolution;
in-out property emulate_usb <=> SettingsData.emulate_usb;
in-out property rom_dir <=> SettingsData.rom_dir;
in-out property emulate_vru <=> ControllerData.emulate_vru;
in-out property controller_enabled <=> ControllerData.controller_enabled;
+12
View File
@@ -12,6 +12,7 @@ export global SettingsData {
in-out property <bool> overclock_n64_cpu;
in-out property <bool> disable_expansion_pak;
in-out property <int> resolution;
in-out property <bool> emulate_usb;
in-out property <string> rom_dir;
}
@@ -105,5 +106,16 @@ export component Settings inherits Page {
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Emulate UNFLoader (reload emulator after changing)");
checked: SettingsData.emulate_usb;
toggled => {
SettingsData.emulate_usb = self.checked;
}
}
}
}
}
+6 -4
View File
@@ -225,8 +225,9 @@ fn set_buttons_from_joystick(
let profile_joystick_hat = profile.joystick_hat[i];
if profile_joystick_hat.enabled
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) }
== profile_joystick_hat.direction
&& (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) }
& profile_joystick_hat.direction)
!= 0
{
*keys |= 1 << i;
}
@@ -326,8 +327,9 @@ fn change_paks(
pressed =
unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.id) };
} else if joystick_hat.enabled && !joystick.is_null() {
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) }
== joystick_hat.direction;
pressed = (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) }
& joystick_hat.direction)
!= 0;
} else if key.enabled {
pressed = unsafe { *keyboard_state.offset(key.id as isize) };
}
+4
View File
@@ -1129,6 +1129,10 @@ fn setup_wait_window(
peer_addr: socket_addr,
player_number: player_number as u8,
}),
ui::Usb {
usb_tx: None,
cart_rx: None,
},
weak_app,
);
})
+224
View File
@@ -0,0 +1,224 @@
use crate::ui;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
const DATATYPE_TCPTEST: u32 = 0x07;
const DATATYPE_ROMUPLOAD: u32 = 0x08;
#[derive(Clone, Debug)]
pub struct UsbData {
pub data: Vec<u8>,
pub data_type: u32,
pub data_size: u32,
}
fn respond_to_handshake(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, data: Vec<u8>) {
if let Ok(data) = String::from_utf8(data)
&& data == "N64"
{
ui::usb::send_to_usb(
usb_tx,
ui::usb::UsbData {
data: [b'N', b'6', b'4'].to_vec(),
data_type: DATATYPE_TCPTEST,
data_size: 3,
},
);
}
}
fn upload_rom(
weak: slint::Weak<ui::gui::AppWindow>,
rom: Vec<u8>,
usb_tx: &tokio::sync::broadcast::Sender<UsbData>,
cart_rx: &tokio::sync::broadcast::Receiver<UsbData>,
) {
let weak_clone = weak.clone();
let usb_tx = usb_tx.clone();
let cart_rx = cart_rx.resubscribe();
weak.upgrade_in_event_loop(move |handle| {
let dir = std::env::temp_dir();
let rom_path = dir.join("rom_upload.bin");
std::fs::write(rom_path.clone(), rom).unwrap();
ui::gui::run_rom(
ui::gui::GbPaths {
rom: [None, None, None, None],
ram: [None, None, None, None],
},
rom_path,
ui::gui::GameSettings {
fullscreen: handle.get_fullscreen(),
overclock: handle.get_overclock_n64_cpu(),
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),
cart_rx: Some(cart_rx),
},
weak_clone,
);
})
.unwrap();
}
async fn handle_connection(
conn: tokio::net::TcpStream,
mut shutdown_rx: tokio::sync::watch::Receiver<()>,
mut usb_rx: tokio::sync::broadcast::Receiver<UsbData>,
usb_tx: tokio::sync::broadcast::Sender<UsbData>,
cart_tx: tokio::sync::broadcast::Sender<UsbData>,
cart_rx: tokio::sync::broadcast::Receiver<UsbData>,
weak: slint::Weak<ui::gui::AppWindow>,
) {
let (mut incoming, mut outgoing) = conn.into_split();
let mut shutdown_rx_clone = shutdown_rx.clone();
tokio::spawn(async move {
loop {
tokio::select! {
data = usb_rx.recv() => {
match data {
Ok(data) => {
let mut output: Vec<u8> = vec![];
output.extend_from_slice(&data.data_type.to_be_bytes());
output.extend_from_slice(&data.data_size.to_be_bytes());
output.extend_from_slice(&data.data);
if outgoing.write_all(&output).await.is_err() {
break;
}
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
panic!("usb_rx lagged");
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
break;
}
}
}
_ = shutdown_rx_clone.changed() => {
break;
}
}
}
});
let mut incoming_buffer = vec![0u8; 4096];
let mut data_type: Option<u32> = None;
let mut data_size: Option<u32> = None;
let mut usb_buffer: Vec<u8> = vec![];
loop {
tokio::select! {
result = incoming.read(&mut incoming_buffer) => {
match result {
Ok(0) => {
break;
}
Ok(n) => {
usb_buffer.extend_from_slice(&incoming_buffer[0..n]);
if data_type.is_none() {
if usb_buffer.len() < 4 {
continue;
} else {
data_type = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
usb_buffer.drain(0..4);
}
}
if data_type.is_some() && data_size.is_none() {
if usb_buffer.len() < 4 {
continue;
} else {
data_size = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
usb_buffer.drain(0..4);
}
}
if let Some(d_type) = data_type && let Some(d_size) = data_size {
let length = d_size as usize;
if usb_buffer.len() >= length {
let usb_data = UsbData {
data: usb_buffer[0..length].to_vec(),
data_type: d_type,
data_size: d_size,
};
usb_buffer.drain(0..length);
if usb_data.data_type == DATATYPE_TCPTEST {
respond_to_handshake(&usb_tx,usb_data.data);
} else if usb_data.data_type == DATATYPE_ROMUPLOAD {
upload_rom(weak.clone(), usb_data.data,&usb_tx,&cart_rx);
} else {
cart_tx.send(usb_data).unwrap();
}
data_type = None;
data_size = None;
}
}
}
Err(_e) => {
break;
}
}
}
_ = shutdown_rx.changed() => {
break;
}
}
}
}
pub fn init(
weak: slint::Weak<ui::gui::AppWindow>,
) -> (Option<tokio::sync::watch::Sender<()>>, ui::Usb) {
let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(());
let (usb_tx, usb_rx): (
tokio::sync::broadcast::Sender<UsbData>,
tokio::sync::broadcast::Receiver<UsbData>,
) = tokio::sync::broadcast::channel(1024);
let (cart_tx, cart_rx): (
tokio::sync::broadcast::Sender<UsbData>,
tokio::sync::broadcast::Receiver<UsbData>,
) = tokio::sync::broadcast::channel(1024);
let usb_tx_clone = usb_tx.clone();
let cart_rx_clone = cart_rx.resubscribe();
tokio::spawn(async move {
let listener = tokio::net::TcpListener::bind("localhost:64000")
.await
.unwrap();
loop {
tokio::select! {
res = listener.accept() => {
if let Ok((c,_)) = res {
handle_connection(c,shutdown_rx.clone(),usb_rx.resubscribe(),usb_tx.clone(),cart_tx.clone(),cart_rx.resubscribe(),weak.clone()).await;
} else {
break;
}
}
_ = shutdown_rx.changed() => {
break;
}
}
}
});
(
Some(shutdown_tx),
ui::Usb {
usb_tx: Some(usb_tx_clone),
cart_rx: Some(cart_rx_clone),
},
)
}
pub fn close(shutdown_tx: &tokio::sync::watch::Sender<()>) {
let _ = shutdown_tx.send(());
}
pub fn send_to_usb(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, buffer: UsbData) {
usb_tx.send(buffer).unwrap();
}
+1 -1
View File
@@ -144,7 +144,7 @@ pub fn check_callback(device: &mut device::Device) -> bool {
speed_limiter_toggled = true;
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
}
while callback.paused {
while callback.paused && !callback.frame_advance {
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
unsafe { sdl3_sys::events::SDL_PumpEvents() };
callback = unsafe { rdp_check_callback() };