mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 09:35:21 +02:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6820da889c | |||
| 6292459f8b | |||
| 416d43dee5 | |||
| b0e9c29a05 | |||
| 02c8661b4e | |||
| fdf493bfc3 | |||
| 8cbe1ca5b2 | |||
| dae67b7e8b | |||
| 5436b8c0fe | |||
| 8366ab0cad | |||
| 7d805ac69b | |||
| 2c0710a44f |
@@ -24,7 +24,8 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Install SDL2
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
cat <<EOF > deb822sources
|
||||
@@ -82,13 +83,26 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get -y dist-upgrade
|
||||
sudo apt-get -y install aptitude
|
||||
sudo aptitude -y install libsdl2-dev:arm64 g++-aarch64-linux-gnu
|
||||
- name: Install SDL2
|
||||
sudo aptitude -y install build-essential:arm64 git:arm64 make:arm64 \
|
||||
pkg-config:arm64 cmake:arm64 ninja-build:arm64 gnome-desktop-testing:arm64 libasound2-dev:arm64 libpulse-dev:arm64 \
|
||||
libaudio-dev:arm64 libjack-dev:arm64 libsndio-dev:arm64 libx11-dev:arm64 libxext-dev:arm64 \
|
||||
libxrandr-dev:arm64 libxcursor-dev:arm64 libxfixes-dev:arm64 libxi-dev:arm64 libxss-dev:arm64 \
|
||||
libxkbcommon-dev:arm64 libdrm-dev:arm64 libgbm-dev:arm64 libgl1-mesa-dev:arm64 libgles2-mesa-dev:arm64 \
|
||||
libegl1-mesa-dev:arm64 libdbus-1-dev:arm64 libibus-1.0-dev:arm64 libudev-dev:arm64 fcitx-libs-dev:arm64 \
|
||||
libpipewire-0.3-dev:arm64 libwayland-dev:arm64 libdecor-0-dev:arm64 liburing-dev:arm64 g++-aarch64-linux-gnu
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
if: matrix.target == 'x86_64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y dist-upgrade
|
||||
sudo apt-get -y install libsdl2-dev
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-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
|
||||
- name: Set aarch64 linker
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
|
||||
@@ -102,7 +116,7 @@ jobs:
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2025
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -19,6 +19,17 @@ jobs:
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
components: clippy, rustfmt
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install build-essential git make \
|
||||
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
|
||||
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-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
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rust-lang/rustfmt@v1
|
||||
- name: Lint
|
||||
|
||||
+3
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "0.1.20"
|
||||
version = "0.1.22"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
|
||||
@@ -18,12 +18,13 @@ eframe = "0.30"
|
||||
sha2 = "0.10"
|
||||
rusttype = "0.9"
|
||||
tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
|
||||
sdl2-sys = "0.37"
|
||||
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tungstenite = "0.26"
|
||||
socket2 = "0.5"
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
|
||||
[dependencies.reqwest]
|
||||
@@ -31,11 +32,6 @@ version = "0.12"
|
||||
default-features = false
|
||||
features = ["http2","rustls-tls","charset","json"]
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.37"
|
||||
default-features = false
|
||||
features = ["static-link","bundled"]
|
||||
|
||||
[dependencies.rfd]
|
||||
version = "0.15"
|
||||
default-features = false
|
||||
|
||||
@@ -31,7 +31,7 @@ Options:
|
||||
```
|
||||
## discord
|
||||
|
||||
There is no dedicated server at this point, but there is a #gopher64 channel on the simple64 server: https://discord.gg/R2cz7S94vD
|
||||
https://discord.gg/9RGXq8W8JQ
|
||||
|
||||
## controls
|
||||
|
||||
@@ -41,6 +41,10 @@ You can create you own mappings by running `./gopher64 --configure-input-profile
|
||||
|
||||
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
|
||||
|
||||
## netplay
|
||||
|
||||
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
|
||||
|
||||
## portable mode
|
||||
|
||||
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
|
||||
|
||||
@@ -52,7 +52,10 @@ fn main() {
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/util")
|
||||
.includes(std::env::var("DEP_SDL2_INCLUDE"));
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
|
||||
.join("include"),
|
||||
);
|
||||
|
||||
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
+17
-21
@@ -129,28 +129,19 @@ static const unsigned cmd_len_lut[64] = {
|
||||
1,
|
||||
};
|
||||
|
||||
int sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
{
|
||||
if (event->type == SDL_WINDOWEVENT)
|
||||
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
|
||||
{
|
||||
switch (event->window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
emu_running = false;
|
||||
break;
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
if (emu_running)
|
||||
{
|
||||
wsi_platform->do_resize();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emu_running = false;
|
||||
}
|
||||
else if (fullscreen && event->type == SDL_KEYDOWN)
|
||||
else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running)
|
||||
{
|
||||
switch (event->key.keysym.scancode)
|
||||
wsi_platform->do_resize();
|
||||
}
|
||||
else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen)
|
||||
{
|
||||
switch (event->key.scancode)
|
||||
{
|
||||
case SDL_SCANCODE_ESCAPE:
|
||||
emu_running = false;
|
||||
@@ -166,7 +157,12 @@ int sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
|
||||
{
|
||||
window = (SDL_Window *)_window;
|
||||
SDL_AddEventWatch(sdl_event_filter, nullptr);
|
||||
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
|
||||
if (!result)
|
||||
{
|
||||
printf("Could not add event watch.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
gfx_info = _gfx_info;
|
||||
fullscreen = _fullscreen;
|
||||
@@ -394,8 +390,8 @@ uint64_t rdp_process_commands()
|
||||
do
|
||||
{
|
||||
offset &= 0xFF8;
|
||||
cmd_data[2 * cmd_ptr + 0] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
|
||||
cmd_data[2 * cmd_ptr + 1] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
|
||||
cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
|
||||
cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
|
||||
offset += sizeof(uint64_t);
|
||||
cmd_ptr++;
|
||||
} while (--length > 0);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "wsi_platform.hpp"
|
||||
#include <SDL_vulkan.h>
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
|
||||
{
|
||||
VkSurfaceKHR surface = nullptr;
|
||||
SDL_bool result = SDL_Vulkan_CreateSurface(window, instance, &surface);
|
||||
if (result != SDL_TRUE)
|
||||
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
|
||||
if (result != true)
|
||||
{
|
||||
printf("Error creating surface\n");
|
||||
}
|
||||
@@ -14,18 +14,23 @@ VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevi
|
||||
|
||||
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
|
||||
{
|
||||
SDL_Vulkan_DestroySurface(instance, surface, NULL);
|
||||
}
|
||||
|
||||
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
|
||||
{
|
||||
|
||||
unsigned int extensionCount = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
|
||||
std::vector<const char *> extensionNames(extensionCount);
|
||||
SDL_bool result = SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames.data());
|
||||
if (result != SDL_TRUE)
|
||||
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
|
||||
if (extensions == NULL)
|
||||
{
|
||||
printf("Error creating surface\n");
|
||||
printf("Error getting instance extensions\n");
|
||||
}
|
||||
|
||||
std::vector<const char *> extensionNames;
|
||||
for (int i = 0; i < extensionCount; ++i)
|
||||
{
|
||||
extensionNames.push_back(extensions[i]);
|
||||
}
|
||||
return extensionNames;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "wsi.hpp"
|
||||
#include <SDL.h>
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
class SDL_WSIPlatform : public Vulkan::WSIPlatform
|
||||
{
|
||||
|
||||
+5
-1
@@ -79,7 +79,9 @@ pub fn run_game(
|
||||
|
||||
cpu::run(device);
|
||||
|
||||
ui::video::close();
|
||||
ui::input::close(&mut device.ui);
|
||||
ui::video::close(&device.ui);
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::storage::write_saves(&device.ui, &device.netplay);
|
||||
}
|
||||
|
||||
@@ -323,6 +325,7 @@ impl Device {
|
||||
rdram: rdram::Rdram {
|
||||
mem: vec![],
|
||||
size: 0x800000,
|
||||
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
|
||||
},
|
||||
rsp: rsp_interface::Rsp {
|
||||
cpu: rsp_cpu::Cpu {
|
||||
@@ -403,6 +406,7 @@ impl Device {
|
||||
},
|
||||
ri: ri::Ri {
|
||||
regs: [0; ri::RI_REGS_COUNT as usize],
|
||||
ram_init: false,
|
||||
},
|
||||
vi: vi::Vi {
|
||||
regs: [0; vi::VI_REGS_COUNT as usize],
|
||||
|
||||
@@ -144,6 +144,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
AI_DACRATE_REG => {
|
||||
if device.ai.regs[reg as usize] != value & mask {
|
||||
let frequency = device.vi.clock / (1 + (value & mask)) as u64;
|
||||
ui::audio::close(&mut device.ui);
|
||||
ui::audio::init(&mut device.ui, frequency)
|
||||
}
|
||||
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
|
||||
|
||||
+33
-5
@@ -1,11 +1,24 @@
|
||||
use crate::device;
|
||||
use std::alloc::{alloc_zeroed, Layout};
|
||||
|
||||
//const RDRAM_CONFIG_REG: u32 = 0;
|
||||
//const RDRAM_DEVICE_ID_REG: u32 = 1;
|
||||
//const RDRAM_DELAY_REG: u32 = 2;
|
||||
const RDRAM_MODE_REG: u32 = 3;
|
||||
//const RDRAM_REF_INTERVAL_REG: u32 = 4;
|
||||
//const RDRAM_REF_ROW_REG: u32 = 5;
|
||||
//const RDRAM_RAS_INTERVAL_REG: u32 = 6;
|
||||
//const RDRAM_MIN_INTERVAL_REG: u32 = 7;
|
||||
//const RDRAM_ADDR_SELECT_REG: u32 = 8;
|
||||
//const RDRAM_DEVICE_MANUF_REG: u32 = 9;
|
||||
pub const RDRAM_REGS_COUNT: u32 = 10;
|
||||
|
||||
pub const RDRAM_MASK: usize = 0xFFFFFF;
|
||||
|
||||
pub struct Rdram {
|
||||
pub mem: Vec<u8>,
|
||||
pub size: u32,
|
||||
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -56,16 +69,26 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
_device: &mut device::Device,
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
println!("Warning: read from RDRAM reg {:#x}", address);
|
||||
0
|
||||
let chip_id = address >> 13 & 3;
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
match reg as u32 {
|
||||
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
|
||||
_ => device.rdram.regs[chip_id as usize][reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {
|
||||
panic!("rdram write reg");
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let chip_id = address >> 13 & 3;
|
||||
let reg = (address & 0x3FF) >> 2;
|
||||
device::memory::masked_write_32(
|
||||
&mut device.rdram.regs[chip_id as usize][reg as usize],
|
||||
value,
|
||||
mask,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
@@ -79,6 +102,11 @@ pub fn init(device: &mut device::Device) {
|
||||
// hack, skip RDRAM initialization
|
||||
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
|
||||
.copy_from_slice(&device.rdram.size.to_ne_bytes());
|
||||
|
||||
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
|
||||
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
|
||||
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
|
||||
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
|
||||
}
|
||||
|
||||
pub fn rdram_calculate_cycles(length: u64) -> u64 {
|
||||
|
||||
+8
-6
@@ -1,10 +1,10 @@
|
||||
use crate::device;
|
||||
|
||||
//const RI_MODE_REG: u32 = 0;
|
||||
//const RI_CONFIG_REG: u32 = 1;
|
||||
pub const RI_MODE_REG: u32 = 0;
|
||||
pub const RI_CONFIG_REG: u32 = 1;
|
||||
//const RI_CURRENT_LOAD_REG: u32 = 2;
|
||||
const RI_SELECT_REG: u32 = 3;
|
||||
//const RI_REFRESH_REG: u32 = 4;
|
||||
pub const RI_SELECT_REG: u32 = 3;
|
||||
pub const RI_REFRESH_REG: u32 = 4;
|
||||
//const RI_LATENCY_REG: u32 = 5;
|
||||
//const RI_ERROR_REG: u32 = 6;
|
||||
//const RI_WERROR_REG: u32 = 7;
|
||||
@@ -12,6 +12,7 @@ pub const RI_REGS_COUNT: u32 = 8;
|
||||
|
||||
pub struct Ri {
|
||||
pub regs: [u32; RI_REGS_COUNT as usize],
|
||||
pub ram_init: bool,
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
@@ -19,8 +20,9 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG {
|
||||
return 0x14; // hack, skip RDRAM initialization
|
||||
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;
|
||||
}
|
||||
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
|
||||
}
|
||||
|
||||
+12
-3
@@ -7,6 +7,7 @@ mod device;
|
||||
mod netplay;
|
||||
mod ui;
|
||||
use clap::Parser;
|
||||
use ui::gui;
|
||||
|
||||
/// N64 emulator
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -47,7 +48,7 @@ struct Args {
|
||||
value_name = "CONTROLLER_NUMBER",
|
||||
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
|
||||
)]
|
||||
assign_controller: Option<u32>,
|
||||
assign_controller: Option<i32>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
@@ -119,7 +120,10 @@ async fn main() {
|
||||
}
|
||||
}
|
||||
if args.list_controllers {
|
||||
ui::input::list_controllers(&device.ui);
|
||||
let controllers = gui::get_controller_names(&device.ui);
|
||||
for (i, controller) in controllers.iter().enumerate() {
|
||||
println!("Controller {}: {}", i, controller);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if args.assign_controller.is_some() {
|
||||
@@ -166,7 +170,12 @@ async fn main() {
|
||||
}
|
||||
} else {
|
||||
let options = eframe::NativeOptions {
|
||||
viewport: eframe::egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]),
|
||||
viewport: eframe::egui::ViewportBuilder::default()
|
||||
.with_inner_size([640.0, 480.0])
|
||||
.with_icon(
|
||||
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
|
||||
.unwrap(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
eframe::run_native(
|
||||
|
||||
+18
-8
@@ -18,6 +18,8 @@ const TCP_REGISTER_PLAYER: u8 = 5;
|
||||
const TCP_GET_REGISTRATION: u8 = 6;
|
||||
const TCP_DISCONNECT_NOTICE: u8 = 7;
|
||||
|
||||
const CS4: u32 = 32;
|
||||
|
||||
pub struct Netplay {
|
||||
udp_socket: std::net::UdpSocket,
|
||||
tcp_stream: std::net::TcpStream,
|
||||
@@ -103,15 +105,14 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
|
||||
let mut input = None;
|
||||
|
||||
let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10);
|
||||
let mut request_timer = std::time::Instant::now() + std::time::Duration::from_millis(5);
|
||||
request_input(netplay, channel);
|
||||
let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5);
|
||||
while input.is_none() {
|
||||
process_incoming(netplay); // we execute process_incoming before request_input so that we send an accurate buffer count
|
||||
if std::time::Instant::now() > request_timer {
|
||||
// sends a request packet every 5ms
|
||||
request_input(netplay, channel);
|
||||
request_timer = std::time::Instant::now() + std::time::Duration::from_millis(5);
|
||||
}
|
||||
process_incoming(netplay);
|
||||
input = netplay.player_data[channel]
|
||||
.input_events
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
@@ -219,13 +220,22 @@ pub fn init(
|
||||
gui_ctx: egui::Context,
|
||||
) -> Netplay {
|
||||
peer_addr.set_port(session.port.unwrap() as u16);
|
||||
let udp_socket = if peer_addr.is_ipv4() {
|
||||
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address")
|
||||
let udp_socket;
|
||||
if peer_addr.is_ipv4() {
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address");
|
||||
socket2::SockRef::from(&udp_socket)
|
||||
.set_tos(CS4 << 2)
|
||||
.unwrap();
|
||||
} else {
|
||||
std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address")
|
||||
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
|
||||
.expect("couldn't bind to address");
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
socket2::SockRef::from(&udp_socket)
|
||||
.set_tclass_v6(CS4 << 2)
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
udp_socket.connect(peer_addr).unwrap();
|
||||
udp_socket.set_nonblocking(true).unwrap();
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ pub mod video;
|
||||
pub mod vru;
|
||||
|
||||
pub struct Ui {
|
||||
pub keyboard_state: *const bool,
|
||||
pub controllers: [input::Controllers; 4],
|
||||
pub config_file_path: std::path::PathBuf,
|
||||
pub config: config::Config,
|
||||
@@ -15,19 +16,22 @@ pub struct Ui {
|
||||
pub game_hash: String,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub sdl_context: Option<sdl2::Sdl>,
|
||||
pub video_subsystem: Option<sdl2::VideoSubsystem>,
|
||||
pub audio_subsystem: Option<sdl2::AudioSubsystem>,
|
||||
pub pak_audio: Option<audio::PakAudio>,
|
||||
pub joystick_subsystem: Option<sdl2::JoystickSubsystem>,
|
||||
#[allow(dead_code)]
|
||||
pub controller_subsystem: Option<sdl2::GameControllerSubsystem>,
|
||||
pub window: Option<sdl2::video::Window>,
|
||||
pub audio_device: Option<sdl2::audio::AudioQueue<i16>>,
|
||||
pub pak_audio: audio::PakAudio,
|
||||
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 joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
write_config(self);
|
||||
}
|
||||
}
|
||||
@@ -37,13 +41,23 @@ fn write_config(ui: &Ui) {
|
||||
serde_json::to_writer_pretty(f, &ui.config).unwrap();
|
||||
}
|
||||
|
||||
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) {
|
||||
panic!("Could not initialize SDL subsystem: {}", flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn new(config_dir: std::path::PathBuf) -> Ui {
|
||||
let sdl_context = sdl2::init().unwrap();
|
||||
let video_subsystem = sdl_context.video().unwrap();
|
||||
let audio_subsystem = sdl_context.audio().unwrap();
|
||||
let joystick_subsystem = sdl_context.joystick().unwrap();
|
||||
let controller_subsystem = sdl_context.game_controller().unwrap();
|
||||
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");
|
||||
}
|
||||
|
||||
let config_file_path = config_dir.join("config.json");
|
||||
let config_file = std::fs::read(config_file_path.clone());
|
||||
@@ -58,26 +72,27 @@ impl Ui {
|
||||
Ui {
|
||||
controllers: [
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
input::Controllers {
|
||||
game_controller: None,
|
||||
joystick: None,
|
||||
game_controller: std::ptr::null_mut(),
|
||||
joystick: std::ptr::null_mut(),
|
||||
rumble: false,
|
||||
},
|
||||
],
|
||||
keyboard_state: std::ptr::null_mut(),
|
||||
config_file_path,
|
||||
config: config_map,
|
||||
save_type: vec![],
|
||||
@@ -97,14 +112,17 @@ impl Ui {
|
||||
mempak: (Vec::new(), false),
|
||||
romsave: (serde_json::Map::new(), false),
|
||||
},
|
||||
sdl_context: Some(sdl_context),
|
||||
video_subsystem: Some(video_subsystem),
|
||||
audio_subsystem: Some(audio_subsystem),
|
||||
joystick_subsystem: Some(joystick_subsystem),
|
||||
controller_subsystem: Some(controller_subsystem),
|
||||
window: None,
|
||||
audio_device: None,
|
||||
pak_audio: None,
|
||||
pak_audio: audio::PakAudio {
|
||||
mempak: include_bytes!("../data/mempak.wav").to_vec(),
|
||||
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
|
||||
},
|
||||
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,
|
||||
num_joysticks,
|
||||
joysticks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+100
-56
@@ -2,74 +2,101 @@ use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub struct PakAudio {
|
||||
mempak: Vec<u8>,
|
||||
rumblepak: Vec<u8>,
|
||||
pub mempak: Vec<u8>,
|
||||
pub rumblepak: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
let desired_spec = sdl2::audio::AudioSpecDesired {
|
||||
freq: Some(frequency as i32),
|
||||
channels: Some(2),
|
||||
samples: None,
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
|
||||
|
||||
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: frequency as i32,
|
||||
channels: 2,
|
||||
};
|
||||
ui.audio_device = Some(
|
||||
ui.audio_subsystem
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.open_queue::<i16, _>(None, &desired_spec)
|
||||
.unwrap(),
|
||||
);
|
||||
let audio_device = ui.audio_device.as_ref().unwrap();
|
||||
audio_device.resume();
|
||||
|
||||
let mempak_audio = Box::new(sdl2::audio::AudioSpecWAV::load_wav_rw(
|
||||
&mut sdl2::rwops::RWops::from_bytes(include_bytes!("../../data/mempak.wav"))
|
||||
.expect("Could not mempak WAV file"),
|
||||
))
|
||||
.expect("Could not load mempak WAV file");
|
||||
let rumblepak_audio = Box::new(
|
||||
sdl2::audio::AudioSpecWAV::load_wav_rw(
|
||||
&mut sdl2::rwops::RWops::from_bytes(include_bytes!("../../data/rumblepak.wav"))
|
||||
.expect("Could not load rumblepak WAV file"),
|
||||
ui.audio_device = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDevice(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&audio_spec,
|
||||
)
|
||||
.expect("Could not load rumblepak WAV file"),
|
||||
);
|
||||
};
|
||||
if ui.audio_device == 0 {
|
||||
panic!("Could not open audio device");
|
||||
}
|
||||
|
||||
let cvt = sdl2::audio::AudioCVT::new(
|
||||
mempak_audio.format,
|
||||
mempak_audio.channels,
|
||||
mempak_audio.freq,
|
||||
audio_device.spec().format,
|
||||
audio_device.spec().channels,
|
||||
audio_device.spec().freq,
|
||||
)
|
||||
.expect("Could not create AudioCVT");
|
||||
let mut dst = Default::default();
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
|
||||
} {
|
||||
panic!("Could not get audio device format");
|
||||
}
|
||||
|
||||
ui.pak_audio = Some(PakAudio {
|
||||
mempak: cvt.convert(mempak_audio.buffer().to_vec()),
|
||||
rumblepak: cvt.convert(rumblepak_audio.buffer().to_vec()),
|
||||
});
|
||||
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
|
||||
if ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
|
||||
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 24000,
|
||||
channels: 1,
|
||||
};
|
||||
|
||||
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
|
||||
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) {
|
||||
unsafe {
|
||||
if !ui.audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
|
||||
ui.audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
if !ui.pak_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream);
|
||||
ui.pak_audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
|
||||
ui.audio_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
|
||||
if ui.pak_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sound;
|
||||
if pak == device::controller::PakType::RumblePak {
|
||||
sound = &ui.pak_audio.as_ref().unwrap().rumblepak;
|
||||
sound = &ui.pak_audio.rumblepak;
|
||||
} else if pak == device::controller::PakType::MemPak {
|
||||
sound = &ui.pak_audio.as_ref().unwrap().mempak;
|
||||
sound = &ui.pak_audio.mempak;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let audio_device = ui.audio_device.as_ref().unwrap();
|
||||
let i16_buffer: Vec<i16> = sound
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
|
||||
.collect();
|
||||
audio_device.queue_audio(&i16_buffer).unwrap();
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.pak_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
|
||||
let audio_device = device.ui.audio_device.as_ref().unwrap();
|
||||
if device.ui.audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
|
||||
let mut i = 0;
|
||||
while i < length as usize / 2 {
|
||||
@@ -83,16 +110,33 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
|
||||
i += 2;
|
||||
}
|
||||
|
||||
let audio_queued = audio_device.size() as f64;
|
||||
let acceptable_latency = (audio_device.spec().freq as f64 * 0.2) * 4.0;
|
||||
let min_latency = (audio_device.spec().freq as f64 * 0.02) * 4.0;
|
||||
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;
|
||||
|
||||
if audio_queued < min_latency {
|
||||
let silence_buffer: Vec<i16> = vec![0; ((min_latency - audio_queued) * 2.0) as usize & !1];
|
||||
audio_device.queue_audio(&silence_buffer).unwrap();
|
||||
let silence_buffer: Vec<u8> = vec![0; (min_latency - audio_queued) as usize & !3];
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
silence_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
silence_buffer.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
if audio_queued < acceptable_latency {
|
||||
audio_device.queue_audio(&primary_buffer).unwrap();
|
||||
if audio_queued < acceptable_latency
|
||||
&& !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
device.ui.audio_stream,
|
||||
primary_buffer.as_ptr() as *const std::ffi::c_void,
|
||||
primary_buffer.len() as i32 * 2,
|
||||
)
|
||||
}
|
||||
{
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ pub struct InputProfile {
|
||||
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [(bool, u32); ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [(bool, u32, u8); ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [(bool, u32, i16); ui::input::PROFILE_SIZE],
|
||||
pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
|
||||
pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
|
||||
pub dinput: bool,
|
||||
}
|
||||
|
||||
|
||||
+39
-26
@@ -10,7 +10,7 @@ pub struct GopherEguiApp {
|
||||
data_dir: std::path::PathBuf,
|
||||
configure_profile: bool,
|
||||
profile_name: String,
|
||||
controllers: Vec<String>,
|
||||
controller_names: Vec<String>,
|
||||
selected_controller: [i32; 4],
|
||||
selected_profile: [String; 4],
|
||||
input_profiles: Vec<String>,
|
||||
@@ -46,17 +46,38 @@ fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
|
||||
profiles
|
||||
}
|
||||
|
||||
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
|
||||
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
|
||||
let mut controllers: Vec<String> = vec![];
|
||||
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
for i in 0..num_joysticks {
|
||||
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
|
||||
for offset in 0..game_ui.num_joysticks as isize {
|
||||
let name = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
|
||||
*(game_ui.joysticks.offset(offset)),
|
||||
))
|
||||
};
|
||||
controllers.push(name.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
controllers
|
||||
}
|
||||
|
||||
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
|
||||
let mut controller_paths: Vec<String> = vec![];
|
||||
|
||||
for offset in 0..game_ui.num_joysticks as isize {
|
||||
let path = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
|
||||
*(game_ui.joysticks.offset(offset)),
|
||||
))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
controller_paths.push(path);
|
||||
}
|
||||
|
||||
controller_paths
|
||||
}
|
||||
|
||||
impl GopherEguiApp {
|
||||
pub fn new(
|
||||
cc: &eframe::CreationContext<'_>,
|
||||
@@ -66,12 +87,8 @@ impl GopherEguiApp {
|
||||
) -> GopherEguiApp {
|
||||
add_japanese_font(&cc.egui_ctx);
|
||||
let game_ui = ui::Ui::new(config_dir.clone());
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
let mut guids: Vec<String> = vec![];
|
||||
for i in 0..num_joysticks {
|
||||
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
|
||||
}
|
||||
let controller_paths = get_controller_paths(&game_ui);
|
||||
|
||||
let mut selected_controller = [-1, -1, -1, -1];
|
||||
for (pos, item) in game_ui
|
||||
.config
|
||||
@@ -81,9 +98,9 @@ impl GopherEguiApp {
|
||||
.enumerate()
|
||||
{
|
||||
if item.is_some() {
|
||||
for (guid_pos, guid) in guids.iter().enumerate() {
|
||||
if item.as_deref().unwrap() == *guid {
|
||||
selected_controller[pos] = guid_pos as i32;
|
||||
for (path_pos, path) in controller_paths.iter().enumerate() {
|
||||
if item.as_deref().unwrap() == *path {
|
||||
selected_controller[pos] = path_pos as i32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +114,7 @@ impl GopherEguiApp {
|
||||
profile_name: "".to_string(),
|
||||
selected_profile: game_ui.config.input.input_profile_binding.clone(),
|
||||
selected_controller,
|
||||
controllers: get_controllers(&game_ui),
|
||||
controller_names: get_controller_names(&game_ui),
|
||||
input_profiles: get_input_profiles(&game_ui),
|
||||
controller_enabled: game_ui.config.input.controller_enabled,
|
||||
upscale: game_ui.config.video.upscale,
|
||||
@@ -116,15 +133,11 @@ impl GopherEguiApp {
|
||||
}
|
||||
|
||||
fn save_config(game_ui: &mut ui::Ui, save_config_items: SaveConfig) {
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
let controller_paths = get_controller_paths(game_ui);
|
||||
for (pos, item) in save_config_items.selected_controller.iter().enumerate() {
|
||||
if *item != -1 {
|
||||
game_ui.config.input.controller_assignment[pos] = Some(
|
||||
joystick_subsystem
|
||||
.device_guid(*item as u32)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
game_ui.config.input.controller_assignment[pos] =
|
||||
Some(controller_paths[*item as usize].clone());
|
||||
} else {
|
||||
game_ui.config.input.controller_assignment[pos] = None
|
||||
}
|
||||
@@ -437,7 +450,7 @@ impl eframe::App for GopherEguiApp {
|
||||
let controller_text = if self.selected_controller[i] == -1 {
|
||||
"None".to_string()
|
||||
} else {
|
||||
self.controllers[self.selected_controller[i] as usize].clone()
|
||||
self.controller_names[self.selected_controller[i] as usize].clone()
|
||||
};
|
||||
egui::ComboBox::from_id_salt(format!("controller-combo-{}", i))
|
||||
.selected_text(controller_text)
|
||||
@@ -447,11 +460,11 @@ impl eframe::App for GopherEguiApp {
|
||||
-1,
|
||||
"None".to_string(),
|
||||
);
|
||||
for j in 0..self.controllers.len() {
|
||||
for j in 0..self.controller_names.len() {
|
||||
ui.selectable_value(
|
||||
&mut self.selected_controller[i],
|
||||
j as i32,
|
||||
self.controllers[j].clone(),
|
||||
self.controller_names[j].clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
+52
-44
@@ -193,25 +193,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let file = task.await;
|
||||
|
||||
if let Some(file) = file {
|
||||
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()
|
||||
.to_string();
|
||||
let _ = tx
|
||||
.send((hash, game_name, file.file_name(), rom_contents))
|
||||
.await;
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
parse_rom_file(file, tx).await
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
@@ -299,7 +281,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
} else {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let mut game_name = app.netplay.game_info.1.trim().to_string();
|
||||
let mut game_name = app.netplay.game_info.1.to_string();
|
||||
if game_name.is_empty() {
|
||||
// If the ROM doesn't report a name, use the filename
|
||||
game_name = app.netplay.create_rom_label.clone();
|
||||
@@ -349,7 +331,12 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
while socket.read().is_ok() {}
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
@@ -409,6 +396,30 @@ fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
let _ = tx
|
||||
.send((hash, game_name, file.file_name(), rom_contents))
|
||||
.await;
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.socket_waiting {
|
||||
let socket = app.netplay.socket.as_mut().unwrap();
|
||||
@@ -548,7 +559,12 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
while socket.read().is_ok() {}
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
@@ -580,26 +596,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let file = task.await;
|
||||
|
||||
if let Some(file) = file {
|
||||
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()
|
||||
.to_string();
|
||||
let _ = tx
|
||||
.send((hash, game_name, file.file_name(), rom_contents))
|
||||
.await;
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
parse_rom_file(file, tx).await
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
@@ -761,7 +758,13 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
app.netplay.player_number = player as u8;
|
||||
socket.close(None).unwrap();
|
||||
while socket.read().is_ok() {}
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
gui::open_rom(app, ctx);
|
||||
app.netplay = Default::default();
|
||||
return;
|
||||
@@ -851,7 +854,12 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if ui.button("Close").clicked() {
|
||||
if let Some(socket) = app.netplay.socket.as_mut() {
|
||||
socket.close(None).unwrap();
|
||||
while socket.read().is_ok() {}
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
}
|
||||
app.netplay = Default::default();
|
||||
};
|
||||
|
||||
+407
-291
@@ -30,8 +30,8 @@ const MAX_AXIS_VALUE: f64 = 85.0;
|
||||
|
||||
pub struct Controllers {
|
||||
pub rumble: bool,
|
||||
pub game_controller: Option<sdl2::controller::GameController>,
|
||||
pub joystick: Option<sdl2::joystick::Joystick>,
|
||||
pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
|
||||
pub joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
}
|
||||
|
||||
fn bound_axis(x: &mut f64, y: &mut f64) {
|
||||
@@ -50,30 +50,38 @@ fn bound_axis(x: &mut f64, y: &mut f64) {
|
||||
|
||||
fn set_axis_from_joystick(
|
||||
profile: &ui::config::InputProfile,
|
||||
joystick: &sdl2::joystick::Joystick,
|
||||
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.joystick_axis[AXIS_LEFT].0 {
|
||||
let axis_position = joystick.axis(profile.joystick_axis[AXIS_LEFT].1).unwrap();
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].1)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_RIGHT].0 {
|
||||
let axis_position = joystick.axis(profile.joystick_axis[AXIS_RIGHT].1).unwrap();
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].1)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_DOWN].0 {
|
||||
let axis_position = joystick.axis(profile.joystick_axis[AXIS_DOWN].1).unwrap();
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].1)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
if profile.joystick_axis[AXIS_UP].0 {
|
||||
let axis_position = joystick.axis(profile.joystick_axis[AXIS_UP].1).unwrap();
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].1)
|
||||
};
|
||||
if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
@@ -81,68 +89,85 @@ fn set_axis_from_joystick(
|
||||
(x, y)
|
||||
}
|
||||
|
||||
fn get_axis_from_i32(axis: i32) -> sdl2::controller::Axis {
|
||||
fn get_axis_from_i32(axis: i32) -> sdl3_sys::gamepad::SDL_GamepadAxis {
|
||||
match axis {
|
||||
0 => sdl2::controller::Axis::LeftX,
|
||||
1 => sdl2::controller::Axis::LeftY,
|
||||
2 => sdl2::controller::Axis::RightX,
|
||||
3 => sdl2::controller::Axis::RightY,
|
||||
4 => sdl2::controller::Axis::TriggerLeft,
|
||||
5 => sdl2::controller::Axis::TriggerRight,
|
||||
0 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTX,
|
||||
1 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTY,
|
||||
2 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTX,
|
||||
3 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTY,
|
||||
4 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFT_TRIGGER,
|
||||
5 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHT_TRIGGER,
|
||||
_ => panic!("Invalid axis"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_button_from_i32(button: i32) -> sdl2::controller::Button {
|
||||
fn get_button_from_i32(button: i32) -> sdl3_sys::gamepad::SDL_GamepadButton {
|
||||
match button {
|
||||
0 => sdl2::controller::Button::A,
|
||||
1 => sdl2::controller::Button::B,
|
||||
2 => sdl2::controller::Button::X,
|
||||
3 => sdl2::controller::Button::Y,
|
||||
4 => sdl2::controller::Button::Back,
|
||||
5 => sdl2::controller::Button::Guide,
|
||||
6 => sdl2::controller::Button::Start,
|
||||
7 => sdl2::controller::Button::LeftStick,
|
||||
8 => sdl2::controller::Button::RightStick,
|
||||
9 => sdl2::controller::Button::LeftShoulder,
|
||||
10 => sdl2::controller::Button::RightShoulder,
|
||||
11 => sdl2::controller::Button::DPadUp,
|
||||
12 => sdl2::controller::Button::DPadDown,
|
||||
13 => sdl2::controller::Button::DPadLeft,
|
||||
14 => sdl2::controller::Button::DPadRight,
|
||||
0 => sdl3_sys::gamepad::SDL_GamepadButton::SOUTH,
|
||||
1 => sdl3_sys::gamepad::SDL_GamepadButton::EAST,
|
||||
2 => sdl3_sys::gamepad::SDL_GamepadButton::WEST,
|
||||
3 => sdl3_sys::gamepad::SDL_GamepadButton::NORTH,
|
||||
4 => sdl3_sys::gamepad::SDL_GamepadButton::BACK,
|
||||
5 => sdl3_sys::gamepad::SDL_GamepadButton::GUIDE,
|
||||
6 => sdl3_sys::gamepad::SDL_GamepadButton::START,
|
||||
7 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_STICK,
|
||||
8 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_STICK,
|
||||
9 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_SHOULDER,
|
||||
10 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_SHOULDER,
|
||||
11 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_UP,
|
||||
12 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_DOWN,
|
||||
13 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_LEFT,
|
||||
14 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_RIGHT,
|
||||
_ => panic!("Invalid button"),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_axis_from_controller(
|
||||
profile: &ui::config::InputProfile,
|
||||
controller: &sdl2::controller::GameController,
|
||||
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.controller_axis[AXIS_LEFT].0 {
|
||||
let axis_position =
|
||||
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1));
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_RIGHT].0 {
|
||||
let axis_position =
|
||||
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1));
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 {
|
||||
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_DOWN].0 {
|
||||
let axis_position =
|
||||
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1));
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
}
|
||||
if profile.controller_axis[AXIS_UP].0 {
|
||||
let axis_position = controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_UP].1));
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile.controller_axis[AXIS_UP].1),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 {
|
||||
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
|
||||
}
|
||||
@@ -152,35 +177,27 @@ fn set_axis_from_controller(
|
||||
|
||||
fn set_axis_from_keys(
|
||||
profile: &ui::config::InputProfile,
|
||||
keyboard_state: &sdl2::keyboard::KeyboardState,
|
||||
keyboard_state: *const bool,
|
||||
) -> (f64, f64) {
|
||||
let mut x = 0.0;
|
||||
let mut y = 0.0;
|
||||
if profile.keys[AXIS_LEFT].0
|
||||
&& keyboard_state.is_scancode_pressed(
|
||||
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_LEFT].1).unwrap(),
|
||||
)
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].1 as isize) }
|
||||
{
|
||||
x = -MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_RIGHT].0
|
||||
&& keyboard_state.is_scancode_pressed(
|
||||
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_RIGHT].1).unwrap(),
|
||||
)
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].1 as isize) }
|
||||
{
|
||||
x = MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_DOWN].0
|
||||
&& keyboard_state.is_scancode_pressed(
|
||||
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_DOWN].1).unwrap(),
|
||||
)
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].1 as isize) }
|
||||
{
|
||||
y = -MAX_AXIS_VALUE
|
||||
}
|
||||
if profile.keys[AXIS_UP].0
|
||||
&& keyboard_state.is_scancode_pressed(
|
||||
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_UP].1).unwrap(),
|
||||
)
|
||||
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].1 as isize) }
|
||||
{
|
||||
y = MAX_AXIS_VALUE
|
||||
}
|
||||
@@ -190,25 +207,29 @@ fn set_axis_from_keys(
|
||||
fn set_buttons_from_joystick(
|
||||
profile: &ui::config::InputProfile,
|
||||
i: usize,
|
||||
joystick: &sdl2::joystick::Joystick,
|
||||
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
keys: &mut u32,
|
||||
) {
|
||||
let profile_joystick_button = profile.joystick_buttons[i];
|
||||
if profile_joystick_button.0 {
|
||||
*keys |= (joystick.button(profile_joystick_button.1).unwrap() as u32) << i;
|
||||
*keys |= (unsafe {
|
||||
sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.1)
|
||||
} as u32)
|
||||
<< i;
|
||||
}
|
||||
|
||||
let profile_joystick_hat = profile.joystick_hat[i];
|
||||
if profile_joystick_hat.0
|
||||
&& joystick.hat(profile_joystick_hat.1).unwrap()
|
||||
== sdl2::joystick::HatState::from_raw(profile_joystick_hat.2)
|
||||
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.1) }
|
||||
== profile_joystick_hat.2
|
||||
{
|
||||
*keys |= 1 << i;
|
||||
}
|
||||
|
||||
let profile_joystick_axis = profile.joystick_axis[i];
|
||||
if profile_joystick_axis.0 {
|
||||
let axis_position = joystick.axis(profile_joystick_axis.1).unwrap();
|
||||
let axis_position =
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.1) };
|
||||
if axis_position as isize * profile_joystick_axis.2 as isize > 0
|
||||
&& axis_position.saturating_abs() > i16::MAX / 2
|
||||
{
|
||||
@@ -220,17 +241,28 @@ fn set_buttons_from_joystick(
|
||||
fn set_buttons_from_controller(
|
||||
profile: &ui::config::InputProfile,
|
||||
i: usize,
|
||||
controller: &sdl2::controller::GameController,
|
||||
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
|
||||
keys: &mut u32,
|
||||
) {
|
||||
let profile_controller_button = profile.controller_buttons[i];
|
||||
if profile_controller_button.0 {
|
||||
*keys |= (controller.button(get_button_from_i32(profile_controller_button.1)) as u32) << i;
|
||||
*keys |= (unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadButton(
|
||||
controller,
|
||||
get_button_from_i32(profile_controller_button.1),
|
||||
)
|
||||
} as u32)
|
||||
<< i;
|
||||
}
|
||||
|
||||
let profile_controller_axis = profile.controller_axis[i];
|
||||
if profile_controller_axis.0 {
|
||||
let axis_position = controller.axis(get_axis_from_i32(profile_controller_axis.1));
|
||||
let axis_position = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadAxis(
|
||||
controller,
|
||||
get_axis_from_i32(profile_controller_axis.1),
|
||||
)
|
||||
};
|
||||
if axis_position as isize * profile_controller_axis.2 as isize > 0
|
||||
&& axis_position.saturating_abs() > i16::MAX / 2
|
||||
{
|
||||
@@ -243,36 +275,34 @@ pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) {
|
||||
if !ui.controllers[channel].rumble {
|
||||
return;
|
||||
}
|
||||
let controller = &mut ui.controllers[channel].game_controller;
|
||||
let joystick = &mut ui.controllers[channel].joystick;
|
||||
if controller.is_some() {
|
||||
controller
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_rumble(
|
||||
let controller = ui.controllers[channel].game_controller;
|
||||
let joystick = ui.controllers[channel].joystick;
|
||||
if !controller.is_null() {
|
||||
unsafe {
|
||||
sdl3_sys::gamepad::SDL_RumbleGamepad(
|
||||
controller,
|
||||
(rumble & 1) as u16 * u16::MAX,
|
||||
(rumble & 1) as u16 * u16::MAX,
|
||||
(rumble & 1) as u32 * 60000,
|
||||
)
|
||||
.unwrap();
|
||||
} else if joystick.is_some() {
|
||||
joystick
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.set_rumble(
|
||||
};
|
||||
} else if !joystick.is_null() {
|
||||
unsafe {
|
||||
sdl3_sys::joystick::SDL_RumbleJoystick(
|
||||
joystick,
|
||||
(rumble & 1) as u16 * u16::MAX,
|
||||
(rumble & 1) as u16 * u16::MAX,
|
||||
(rumble & 1) as u32 * 60000,
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn change_paks(
|
||||
profile: &ui::config::InputProfile,
|
||||
joystick: &Option<sdl2::joystick::Joystick>,
|
||||
controller: &Option<sdl2::controller::GameController>,
|
||||
keyboard_state: &sdl2::keyboard::KeyboardState,
|
||||
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
|
||||
keyboard_state: *const bool,
|
||||
) -> bool {
|
||||
let controller_button = profile.controller_buttons[CHANGE_PAK];
|
||||
let joystick_button = profile.joystick_buttons[CHANGE_PAK];
|
||||
@@ -280,51 +310,42 @@ fn change_paks(
|
||||
let key = profile.keys[CHANGE_PAK];
|
||||
|
||||
let mut pressed = false;
|
||||
if controller_button.0 && controller.is_some() {
|
||||
pressed = controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.button(get_button_from_i32(controller_button.1));
|
||||
} else if joystick_button.0 && joystick.is_some() {
|
||||
pressed = joystick
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.button(joystick_button.1)
|
||||
.unwrap();
|
||||
} else if joystick_hat.0 && joystick.is_some() {
|
||||
pressed = joystick.as_ref().unwrap().hat(joystick_hat.1).unwrap()
|
||||
== sdl2::joystick::HatState::from_raw(joystick_hat.2);
|
||||
if controller_button.0 && !controller.is_null() {
|
||||
pressed = unsafe {
|
||||
sdl3_sys::gamepad::SDL_GetGamepadButton(
|
||||
controller,
|
||||
get_button_from_i32(controller_button.1),
|
||||
)
|
||||
};
|
||||
} else if joystick_button.0 && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.1) };
|
||||
} else if joystick_hat.0 && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.1) }
|
||||
== joystick_hat.2;
|
||||
} else if key.0 {
|
||||
pressed =
|
||||
keyboard_state.is_scancode_pressed(sdl2::keyboard::Scancode::from_i32(key.1).unwrap());
|
||||
pressed = unsafe { *keyboard_state.offset(key.1 as isize) };
|
||||
}
|
||||
pressed
|
||||
}
|
||||
|
||||
pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
let events = ui.sdl_context.as_ref().unwrap().event_pump().unwrap();
|
||||
let keyboard_state = events.keyboard_state();
|
||||
|
||||
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;
|
||||
let controller = &ui.controllers[channel].game_controller;
|
||||
let joystick = &ui.controllers[channel].joystick;
|
||||
let controller = ui.controllers[channel].game_controller;
|
||||
let joystick = ui.controllers[channel].joystick;
|
||||
for i in 0..14 {
|
||||
if profile_name != "default" || channel == 0 {
|
||||
let profile_key = profile.keys[i];
|
||||
if profile_key.0 {
|
||||
keys |= (keyboard_state
|
||||
.is_scancode_pressed(sdl2::keyboard::Scancode::from_i32(profile_key.1).unwrap())
|
||||
as u32)
|
||||
<< i;
|
||||
keys |= (unsafe { *ui.keyboard_state.offset(profile_key.1 as isize) } as u32) << i;
|
||||
}
|
||||
}
|
||||
|
||||
if controller.is_some() {
|
||||
set_buttons_from_controller(profile, i, controller.as_ref().unwrap(), &mut keys);
|
||||
} else if joystick.is_some() {
|
||||
set_buttons_from_joystick(profile, i, joystick.as_ref().unwrap(), &mut keys);
|
||||
if !controller.is_null() {
|
||||
set_buttons_from_controller(profile, i, controller, &mut keys);
|
||||
} else if !joystick.is_null() {
|
||||
set_buttons_from_joystick(profile, i, joystick, &mut keys);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,13 +353,13 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
let mut y: f64 = 0.0;
|
||||
|
||||
if profile_name != "default" || channel == 0 {
|
||||
(x, y) = set_axis_from_keys(profile, &keyboard_state);
|
||||
(x, y) = set_axis_from_keys(profile, ui.keyboard_state);
|
||||
}
|
||||
|
||||
if controller.is_some() {
|
||||
(x, y) = set_axis_from_controller(profile, controller.as_ref().unwrap())
|
||||
} else if joystick.is_some() {
|
||||
(x, y) = set_axis_from_joystick(profile, joystick.as_ref().unwrap())
|
||||
if !controller.is_null() {
|
||||
(x, y) = set_axis_from_controller(profile, controller)
|
||||
} else if !joystick.is_null() {
|
||||
(x, y) = set_axis_from_joystick(profile, joystick)
|
||||
}
|
||||
bound_axis(&mut x, &mut y);
|
||||
|
||||
@@ -347,31 +368,20 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
|
||||
|
||||
(
|
||||
keys,
|
||||
change_paks(profile, joystick, controller, &keyboard_state),
|
||||
change_paks(profile, joystick, controller, ui.keyboard_state),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list_controllers(ui: &ui::Ui) {
|
||||
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
if num_joysticks == 0 {
|
||||
println!("No controllers connected")
|
||||
}
|
||||
for i in 0..num_joysticks {
|
||||
println!("{}: {}", i, joystick_subsystem.name_for_index(i).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assign_controller(ui: &mut ui::Ui, controller: u32, port: usize) {
|
||||
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
|
||||
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
|
||||
if controller < num_joysticks {
|
||||
ui.config.input.controller_assignment[port - 1] = Some(
|
||||
joystick_subsystem
|
||||
.device_guid(controller)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
|
||||
if controller < ui.num_joysticks {
|
||||
let path = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
|
||||
*(ui.joysticks.offset(controller as isize)),
|
||||
))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
ui.config.input.controller_assignment[port - 1] = Some(path);
|
||||
} else {
|
||||
println!("Invalid controller number")
|
||||
}
|
||||
@@ -392,7 +402,21 @@ pub fn clear_bindings(ui: &mut ui::Ui) {
|
||||
}
|
||||
}
|
||||
|
||||
fn close_controllers(
|
||||
open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick>,
|
||||
open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad>,
|
||||
) {
|
||||
for joystick in open_joysticks {
|
||||
unsafe { sdl3_sys::joystick::SDL_CloseJoystick(joystick) }
|
||||
}
|
||||
for controller in open_controllers {
|
||||
unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
|
||||
if profile == "default" {
|
||||
println!("Profile name cannot be default");
|
||||
return;
|
||||
@@ -401,30 +425,43 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
println!("Profile name cannot be empty");
|
||||
return;
|
||||
}
|
||||
let controller_subsystem = ui.controller_subsystem.as_ref().unwrap();
|
||||
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
|
||||
let mut controllers = vec![];
|
||||
let mut joysticks = vec![];
|
||||
for i in 0..joystick_subsystem.num_joysticks().unwrap() {
|
||||
let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new();
|
||||
let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new();
|
||||
|
||||
for offset in 0..ui.num_joysticks as isize {
|
||||
if !dinput {
|
||||
if let Ok(controller) = controller_subsystem.open(i) {
|
||||
controllers.push(controller);
|
||||
continue;
|
||||
let controller =
|
||||
unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*ui.joysticks.offset(offset)) };
|
||||
if !controller.is_null() {
|
||||
open_controllers.push(controller);
|
||||
}
|
||||
} else {
|
||||
let joystick =
|
||||
unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*ui.joysticks.offset(offset)) };
|
||||
if !joystick.is_null() {
|
||||
open_joysticks.push(joystick);
|
||||
}
|
||||
}
|
||||
if let Ok(joystick) = joystick_subsystem.open(i) {
|
||||
joysticks.push(joystick);
|
||||
}
|
||||
}
|
||||
|
||||
let mut builder =
|
||||
ui.video_subsystem
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.window("configure input profile", 640, 480);
|
||||
builder.position_centered();
|
||||
let window = builder.build().unwrap();
|
||||
let mut canvas = window.into_canvas().build().unwrap();
|
||||
let title = std::ffi::CString::new("configure input profile").unwrap();
|
||||
let mut window: *mut sdl3_sys::video::SDL_Window = std::ptr::null_mut();
|
||||
let mut renderer: *mut sdl3_sys::render::SDL_Renderer = std::ptr::null_mut();
|
||||
if !unsafe {
|
||||
sdl3_sys::render::SDL_CreateWindowAndRenderer(
|
||||
title.as_ptr(),
|
||||
640,
|
||||
480,
|
||||
0,
|
||||
&mut window,
|
||||
&mut renderer,
|
||||
)
|
||||
} {
|
||||
panic!("Could not create window and renderer")
|
||||
}
|
||||
if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } {
|
||||
panic!("Could not show window")
|
||||
}
|
||||
let font =
|
||||
rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
|
||||
|
||||
@@ -451,101 +488,89 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
];
|
||||
|
||||
let mut new_keys = [(false, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_buttons = [(false, 0u32); PROFILE_SIZE];
|
||||
let mut new_joystick_hat = [(false, 0u32, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_axis = [(false, 0u32, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_buttons = [(false, 0i32); PROFILE_SIZE];
|
||||
let mut new_joystick_hat = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
let mut new_joystick_axis = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
let mut new_controller_buttons = [(false, 0i32); PROFILE_SIZE];
|
||||
let mut new_controller_axis = [(false, 0i32, 0); PROFILE_SIZE];
|
||||
|
||||
let mut last_joystick_axis_result = (false, 0, 0);
|
||||
let mut last_controller_axis_result = (false, 0, 0);
|
||||
let mut events = ui.sdl_context.as_ref().unwrap().event_pump().unwrap();
|
||||
for (key, value) in key_labels.iter() {
|
||||
for _event in events.poll_iter() {} // clear events
|
||||
let mut event: sdl3_sys::events::SDL_Event = Default::default();
|
||||
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {} // clear events
|
||||
|
||||
ui::video::draw_text(
|
||||
format!("Select binding for: {key}").as_str(),
|
||||
&mut canvas,
|
||||
renderer,
|
||||
&font,
|
||||
);
|
||||
|
||||
let mut key_set = false;
|
||||
while !key_set {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
for event in events.poll_iter() {
|
||||
match event {
|
||||
sdl2::event::Event::Window {
|
||||
win_event: sdl2::event::WindowEvent::Close,
|
||||
..
|
||||
} => return,
|
||||
sdl2::event::Event::KeyDown {
|
||||
scancode: Some(scancode),
|
||||
..
|
||||
} => {
|
||||
new_keys[*value] = (true, scancode as i32);
|
||||
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {
|
||||
let event_type = unsafe { event.r#type };
|
||||
if event_type == u32::from(sdl3_sys::events::SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
|
||||
close_controllers(open_joysticks, open_controllers);
|
||||
return;
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_KEY_DOWN) {
|
||||
new_keys[*value] = (true, i32::from(unsafe { event.key.scancode }));
|
||||
key_set = true
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
|
||||
if !open_controllers.is_empty() {
|
||||
new_controller_buttons[*value] =
|
||||
(true, i32::from(unsafe { event.gbutton.button }));
|
||||
key_set = true
|
||||
}
|
||||
sdl2::event::Event::ControllerButtonDown { button, .. } => {
|
||||
if !controllers.is_empty() {
|
||||
new_controller_buttons[*value] = (true, button as i32);
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_AXIS_MOTION) {
|
||||
let axis_value = unsafe { event.gaxis.value };
|
||||
let axis = unsafe { event.gaxis.axis };
|
||||
if !open_controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
|
||||
if result != last_joystick_axis_result {
|
||||
new_controller_axis[*value] = result;
|
||||
last_joystick_axis_result = result;
|
||||
key_set = true
|
||||
}
|
||||
}
|
||||
sdl2::event::Event::ControllerAxisMotion {
|
||||
axis,
|
||||
value: axis_value,
|
||||
..
|
||||
} => {
|
||||
if !controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result =
|
||||
(true, axis as i32, axis_value / axis_value.saturating_abs());
|
||||
if result != last_controller_axis_result {
|
||||
new_controller_axis[*value] = result;
|
||||
last_controller_axis_result = result;
|
||||
key_set = true
|
||||
}
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_BUTTON_DOWN)
|
||||
{
|
||||
if !open_joysticks.is_empty() {
|
||||
new_joystick_buttons[*value] =
|
||||
(true, i32::from(unsafe { event.jbutton.button }));
|
||||
key_set = true
|
||||
}
|
||||
sdl2::event::Event::JoyButtonDown { button_idx, .. } => {
|
||||
if !joysticks.is_empty() {
|
||||
new_joystick_buttons[*value] = (true, button_idx as u32);
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_HAT_MOTION) {
|
||||
let state = unsafe { event.jhat.value };
|
||||
let hat = unsafe { event.jhat.hat };
|
||||
if !open_joysticks.is_empty() && state != sdl3_sys::joystick::SDL_HAT_CENTERED {
|
||||
new_joystick_hat[*value] = (true, hat as i32, state);
|
||||
key_set = true
|
||||
}
|
||||
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_AXIS_MOTION)
|
||||
{
|
||||
let axis_value = unsafe { event.jaxis.value };
|
||||
let axis = unsafe { event.jaxis.axis };
|
||||
if !open_joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
|
||||
if result != last_joystick_axis_result {
|
||||
new_joystick_axis[*value] = result;
|
||||
last_joystick_axis_result = result;
|
||||
key_set = true
|
||||
}
|
||||
}
|
||||
sdl2::event::Event::JoyHatMotion { hat_idx, state, .. } => {
|
||||
if !joysticks.is_empty() && state != sdl2::joystick::HatState::Centered {
|
||||
new_joystick_hat[*value] = (
|
||||
true,
|
||||
hat_idx as u32,
|
||||
sdl2::joystick::HatState::to_raw(state),
|
||||
);
|
||||
key_set = true
|
||||
}
|
||||
}
|
||||
sdl2::event::Event::JoyAxisMotion {
|
||||
axis_idx,
|
||||
value: axis_value,
|
||||
..
|
||||
} => {
|
||||
if !joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
|
||||
let result = (
|
||||
true,
|
||||
axis_idx as u32,
|
||||
axis_value / axis_value.saturating_abs(),
|
||||
);
|
||||
if result != last_joystick_axis_result {
|
||||
new_joystick_axis[*value] = result;
|
||||
last_joystick_axis_result = result;
|
||||
key_set = true
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close_controllers(open_joysticks, open_controllers);
|
||||
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_DestroyRenderer(renderer);
|
||||
sdl3_sys::video::SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
let new_profile = ui::config::InputProfile {
|
||||
keys: new_keys,
|
||||
controller_buttons: new_controller_buttons,
|
||||
@@ -562,45 +587,103 @@ pub fn get_default_profile() -> ui::config::InputProfile {
|
||||
let mut default_controller_buttons = [(false, 0); PROFILE_SIZE];
|
||||
let mut default_controller_axis = [(false, 0, 0); PROFILE_SIZE];
|
||||
let mut default_keys = [(false, 0); PROFILE_SIZE];
|
||||
default_keys[R_DPAD] = (true, sdl2::keyboard::Scancode::D as i32);
|
||||
default_keys[L_DPAD] = (true, sdl2::keyboard::Scancode::A as i32);
|
||||
default_keys[D_DPAD] = (true, sdl2::keyboard::Scancode::S as i32);
|
||||
default_keys[U_DPAD] = (true, sdl2::keyboard::Scancode::W as i32);
|
||||
default_keys[START_BUTTON] = (true, sdl2::keyboard::Scancode::Return as i32);
|
||||
default_keys[Z_TRIG] = (true, sdl2::keyboard::Scancode::Z as i32);
|
||||
default_keys[B_BUTTON] = (true, sdl2::keyboard::Scancode::LCtrl as i32);
|
||||
default_keys[A_BUTTON] = (true, sdl2::keyboard::Scancode::LShift as i32);
|
||||
default_keys[R_CBUTTON] = (true, sdl2::keyboard::Scancode::L as i32);
|
||||
default_keys[L_CBUTTON] = (true, sdl2::keyboard::Scancode::J as i32);
|
||||
default_keys[D_CBUTTON] = (true, sdl2::keyboard::Scancode::K as i32);
|
||||
default_keys[U_CBUTTON] = (true, sdl2::keyboard::Scancode::I as i32);
|
||||
default_keys[R_TRIG] = (true, sdl2::keyboard::Scancode::C as i32);
|
||||
default_keys[L_TRIG] = (true, sdl2::keyboard::Scancode::X as i32);
|
||||
default_keys[AXIS_LEFT] = (true, sdl2::keyboard::Scancode::Left as i32);
|
||||
default_keys[AXIS_RIGHT] = (true, sdl2::keyboard::Scancode::Right as i32);
|
||||
default_keys[AXIS_UP] = (true, sdl2::keyboard::Scancode::Up as i32);
|
||||
default_keys[AXIS_DOWN] = (true, sdl2::keyboard::Scancode::Down as i32);
|
||||
default_keys[CHANGE_PAK] = (true, sdl2::keyboard::Scancode::Comma as i32);
|
||||
default_keys[R_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_D));
|
||||
default_keys[L_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_A));
|
||||
default_keys[D_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_S));
|
||||
default_keys[U_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_W));
|
||||
default_keys[START_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN));
|
||||
default_keys[Z_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z));
|
||||
default_keys[B_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL));
|
||||
default_keys[A_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT));
|
||||
default_keys[R_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_L));
|
||||
default_keys[L_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_J));
|
||||
default_keys[D_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_K));
|
||||
default_keys[U_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_I));
|
||||
default_keys[R_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_C));
|
||||
default_keys[L_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_X));
|
||||
default_keys[AXIS_LEFT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT));
|
||||
default_keys[AXIS_RIGHT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT));
|
||||
default_keys[AXIS_UP] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP));
|
||||
default_keys[AXIS_DOWN] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN));
|
||||
default_keys[CHANGE_PAK] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA));
|
||||
|
||||
default_controller_buttons[R_DPAD] = (true, sdl2::controller::Button::DPadRight as i32);
|
||||
default_controller_buttons[L_DPAD] = (true, sdl2::controller::Button::DPadLeft as i32);
|
||||
default_controller_buttons[D_DPAD] = (true, sdl2::controller::Button::DPadDown as i32);
|
||||
default_controller_buttons[U_DPAD] = (true, sdl2::controller::Button::DPadUp as i32);
|
||||
default_controller_buttons[START_BUTTON] = (true, sdl2::controller::Button::Start as i32);
|
||||
default_controller_axis[Z_TRIG] = (true, sdl2::controller::Axis::TriggerLeft as i32, 1);
|
||||
default_controller_buttons[B_BUTTON] = (true, sdl2::controller::Button::X as i32);
|
||||
default_controller_buttons[A_BUTTON] = (true, sdl2::controller::Button::A as i32);
|
||||
default_controller_axis[R_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, 1);
|
||||
default_controller_axis[L_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, -1);
|
||||
default_controller_axis[D_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, 1);
|
||||
default_controller_axis[U_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, -1);
|
||||
default_controller_buttons[R_TRIG] = (true, sdl2::controller::Button::RightShoulder as i32);
|
||||
default_controller_buttons[L_TRIG] = (true, sdl2::controller::Button::LeftShoulder as i32);
|
||||
default_controller_axis[AXIS_LEFT] = (true, sdl2::controller::Axis::LeftX as i32, -1);
|
||||
default_controller_axis[AXIS_RIGHT] = (true, sdl2::controller::Axis::LeftX as i32, 1);
|
||||
default_controller_axis[AXIS_UP] = (true, sdl2::controller::Axis::LeftY as i32, -1);
|
||||
default_controller_axis[AXIS_DOWN] = (true, sdl2::controller::Axis::LeftY as i32, 1);
|
||||
default_controller_buttons[CHANGE_PAK] = (true, sdl2::controller::Button::Back as i32);
|
||||
default_controller_buttons[R_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
|
||||
);
|
||||
default_controller_buttons[L_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT),
|
||||
);
|
||||
default_controller_buttons[D_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN),
|
||||
);
|
||||
default_controller_buttons[U_DPAD] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP),
|
||||
);
|
||||
default_controller_buttons[START_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START));
|
||||
default_controller_axis[Z_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
1,
|
||||
);
|
||||
default_controller_buttons[B_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST));
|
||||
default_controller_buttons[A_BUTTON] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH));
|
||||
default_controller_axis[R_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[L_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[D_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[U_CBUTTON] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
-1,
|
||||
);
|
||||
default_controller_buttons[R_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER),
|
||||
);
|
||||
default_controller_buttons[L_TRIG] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER),
|
||||
);
|
||||
default_controller_axis[AXIS_LEFT] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[AXIS_RIGHT] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
|
||||
1,
|
||||
);
|
||||
default_controller_axis[AXIS_UP] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
-1,
|
||||
);
|
||||
default_controller_axis[AXIS_DOWN] = (
|
||||
true,
|
||||
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
|
||||
1,
|
||||
);
|
||||
default_controller_buttons[CHANGE_PAK] =
|
||||
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK));
|
||||
|
||||
ui::config::InputProfile {
|
||||
keys: default_keys,
|
||||
@@ -614,54 +697,74 @@ pub fn get_default_profile() -> ui::config::InputProfile {
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui) {
|
||||
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
|
||||
let controller_subsystem = ui.controller_subsystem.as_ref().unwrap();
|
||||
ui.keyboard_state = unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) };
|
||||
if ui.keyboard_state.is_null() {
|
||||
panic!("Could not get keyboard state");
|
||||
}
|
||||
|
||||
let mut taken = [false; 4];
|
||||
for i in 0..4 {
|
||||
let controller_assignment = &ui.config.input.controller_assignment[i];
|
||||
if controller_assignment.is_some() {
|
||||
let mut joystick_index = u32::MAX;
|
||||
let guid = controller_assignment.clone().unwrap();
|
||||
for i in 0..joystick_subsystem.num_joysticks().unwrap() {
|
||||
if joystick_subsystem.device_guid(i).unwrap().to_string() == guid
|
||||
&& !taken[i as usize]
|
||||
{
|
||||
joystick_index = i;
|
||||
taken[i as usize] = true;
|
||||
let mut joystick_id = 0;
|
||||
let assigned_path = controller_assignment.as_ref().unwrap();
|
||||
|
||||
for offset in 0..ui.num_joysticks as isize {
|
||||
let path = unsafe {
|
||||
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
|
||||
*(ui.joysticks.offset(offset)),
|
||||
))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
};
|
||||
if path == *assigned_path && !taken[offset as usize] {
|
||||
joystick_id = unsafe { *(ui.joysticks.offset(offset)) };
|
||||
taken[offset as usize] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if joystick_index < u32::MAX {
|
||||
|
||||
if joystick_id != 0 {
|
||||
let profile_name = ui.config.input.input_profile_binding[i].clone();
|
||||
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
|
||||
|
||||
if !profile.dinput {
|
||||
let controller_result = controller_subsystem.open(joystick_index);
|
||||
if controller_result.is_ok() {
|
||||
ui.controllers[i].game_controller = Some(controller_result.unwrap());
|
||||
if ui.controllers[i]
|
||||
.game_controller
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.has_rumble()
|
||||
{
|
||||
ui.controllers[i].rumble = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ui.controllers[i].game_controller.is_none() {
|
||||
let joystick_result = joystick_subsystem.open(joystick_index);
|
||||
if joystick_result.is_err() {
|
||||
println!(
|
||||
"could not connect joystick: {}",
|
||||
joystick_result.err().unwrap()
|
||||
)
|
||||
let gamepad = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) };
|
||||
if gamepad.is_null() {
|
||||
println!("could not connect gamepad: {}", joystick_id)
|
||||
} else {
|
||||
ui.controllers[i].joystick = Some(joystick_result.unwrap());
|
||||
if ui.controllers[i].joystick.as_ref().unwrap().has_rumble() {
|
||||
ui.controllers[i].rumble = true;
|
||||
ui.controllers[i].game_controller = gamepad;
|
||||
let properties =
|
||||
unsafe { sdl3_sys::gamepad::SDL_GetGamepadProperties(gamepad) };
|
||||
if properties == 0 {
|
||||
println!("could not get gamepad properties");
|
||||
}
|
||||
ui.controllers[i].rumble = unsafe {
|
||||
sdl3_sys::properties::SDL_GetBooleanProperty(
|
||||
properties,
|
||||
sdl3_sys::gamepad::SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN,
|
||||
false,
|
||||
)
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) };
|
||||
if joystick.is_null() {
|
||||
println!("could not connect joystick: {}", joystick_id)
|
||||
} else {
|
||||
ui.controllers[i].joystick = joystick;
|
||||
let properties =
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickProperties(joystick) };
|
||||
if properties == 0 {
|
||||
println!("could not get joystick properties");
|
||||
}
|
||||
ui.controllers[i].rumble = unsafe {
|
||||
sdl3_sys::properties::SDL_GetBooleanProperty(
|
||||
properties,
|
||||
sdl3_sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
|
||||
false,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -670,3 +773,16 @@ pub fn init(ui: &mut ui::Ui) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(ui: &mut ui::Ui) {
|
||||
for controller in ui.controllers.iter_mut() {
|
||||
if !controller.joystick.is_null() {
|
||||
unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) }
|
||||
controller.joystick = std::ptr::null_mut();
|
||||
}
|
||||
if !controller.game_controller.is_null() {
|
||||
unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller.game_controller) }
|
||||
controller.game_controller = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
-24
@@ -1,23 +1,29 @@
|
||||
#![allow(non_snake_case)]
|
||||
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
pub fn init(device: &mut device::Device, fullscreen: bool) {
|
||||
let mut builder = device
|
||||
.ui
|
||||
.video_subsystem
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.window("gopher64", 640, 480);
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
|
||||
builder.position_centered().vulkan();
|
||||
let title = std::ffi::CString::new("gopher64").unwrap();
|
||||
|
||||
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN;
|
||||
|
||||
if fullscreen {
|
||||
builder.fullscreen_desktop();
|
||||
flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN;
|
||||
} else {
|
||||
builder.resizable();
|
||||
flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
|
||||
device.ui.window =
|
||||
unsafe { sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), 640, 480, 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");
|
||||
}
|
||||
device.ui.window = Some(builder.build().unwrap());
|
||||
|
||||
let gfx_info = GFX_INFO {
|
||||
RDRAM: device.rdram.mem.as_mut_ptr(),
|
||||
@@ -36,7 +42,7 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
|
||||
|
||||
unsafe {
|
||||
rdp_init(
|
||||
device.ui.window.as_ref().unwrap().raw() as *mut std::ffi::c_void,
|
||||
device.ui.window as *mut std::ffi::c_void,
|
||||
gfx_info,
|
||||
device.ui.config.video.upscale,
|
||||
device.ui.config.video.integer_scaling,
|
||||
@@ -45,9 +51,10 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close() {
|
||||
pub fn close(ui: &ui::Ui) {
|
||||
unsafe {
|
||||
rdp_close();
|
||||
sdl3_sys::video::SDL_DestroyWindow(ui.window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,19 +73,23 @@ pub fn process_rdp_list() -> u64 {
|
||||
unsafe { rdp_process_commands() }
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
text: &str,
|
||||
canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
|
||||
font: &rusttype::Font,
|
||||
) {
|
||||
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);
|
||||
let v_metrics = font.v_metrics(scale);
|
||||
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
|
||||
|
||||
// Clear the canvas
|
||||
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0));
|
||||
canvas.clear();
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderClear(renderer);
|
||||
};
|
||||
|
||||
for glyph in font.layout(text, scale, offset) {
|
||||
if let Some(bb) = glyph.pixel_bounding_box() {
|
||||
@@ -86,15 +97,23 @@ pub fn draw_text(
|
||||
let x = x as i32 + bb.min.x;
|
||||
let y = y as i32 + bb.min.y + (240 - text_size);
|
||||
if v > 0.5 {
|
||||
canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255));
|
||||
canvas
|
||||
.draw_point(sdl2::rect::Point::new(x, y))
|
||||
.expect("Error drawing pixel");
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Present the canvas
|
||||
canvas.present();
|
||||
if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } {
|
||||
panic!("Could not present renderer");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user