Compare commits

...

12 Commits

Author SHA1 Message Date
Logan McNaughton 6820da889c add icon for windows (#179) 2025-01-25 19:00:38 +01:00
Logan McNaughton 6292459f8b install proper SDL Dependencies (#178)
* install proper SDL Dependencies

* install proper SDL Dependencies
2025-01-25 15:41:58 +01:00
Logan McNaughton 416d43dee5 small input cleanup 2025-01-25 14:16:51 +01:00
Logan McNaughton b0e9c29a05 SDL code tweaks (#177)
* SDL code tweaks

* more

* more

* more

* more

* more
2025-01-25 12:43:37 +01:00
Logan McNaughton 02c8661b4e Use SDL3 (#175)
* Use SDL3

* more

* more

* more

* source

* more

* destory

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* fix

* fix

* more

* more

* more

* more

* more

* more

* more
2025-01-25 11:25:54 +01:00
Logan McNaughton fdf493bfc3 Bump to 0.1.22 (#174) 2025-01-23 20:11:06 +01:00
Logan McNaughton 8cbe1ca5b2 Update README.md 2025-01-23 16:52:50 +01:00
Logan McNaughton dae67b7e8b Netplay fix for buffer reporting (#173) 2025-01-23 15:14:16 +01:00
Logan McNaughton 5436b8c0fe Slightly better rdram init (#172)
* Slightly better rdram init

* var
2025-01-23 14:02:14 +01:00
Logan McNaughton 8366ab0cad Netplay code improvements (#171)
* better socket close code

* Set TOS value on packets

* better game name parsing
2025-01-23 13:00:34 +01:00
Logan McNaughton 7d805ac69b Update README.md 2025-01-23 09:47:00 +01:00
Logan McNaughton 2c0710a44f Bump to 0.1.21 (#170) 2025-01-22 22:12:39 +01:00
22 changed files with 842 additions and 541 deletions
+19 -5
View File
@@ -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:
+11
View File
@@ -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
View File
@@ -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
+5 -1
View File
@@ -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.
+4 -1
View File
@@ -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();
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+17 -21
View File
@@ -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);
+13 -8
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "wsi.hpp"
#include <SDL.h>
#include <SDL3/SDL.h>
class SDL_WSIPlatform : public Vulkan::WSIPlatform
{
+5 -1
View File
@@ -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],
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+48 -30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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");
}
}