mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 09:35:21 +02:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d63a694cc | |||
| 5c98a08fe4 | |||
| 39ffad3af3 | |||
| ca2a3d423b | |||
| fe5346235e | |||
| f84444a12e | |||
| 026e4c0e74 | |||
| 363ed0953a | |||
| cb9309d7e4 | |||
| 1b1313003c | |||
| 7219a4445c | |||
| 687badc142 | |||
| 3e2189463f | |||
| ed8b4e8e02 | |||
| 611121568e | |||
| 27922fbf9c | |||
| 71b7baf613 | |||
| f4714a7813 | |||
| 4730b7f293 | |||
| 6f33934180 | |||
| cbc9045ad4 | |||
| 2148b1c198 | |||
| 3dc28cc603 | |||
| 1f746c5b36 | |||
| a2ba5baaa8 | |||
| 333865e7de | |||
| b1bb96c7d2 | |||
| 9e88343c6b | |||
| 75e59ee051 | |||
| 952f951620 | |||
| 6820da889c | |||
| 6292459f8b | |||
| 416d43dee5 | |||
| b0e9c29a05 | |||
| 02c8661b4e | |||
| fdf493bfc3 |
+23
-72
@@ -11,11 +11,15 @@ env:
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu]
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: ubuntu-24.04-arm
|
||||
target: aarch64-unknown-linux-gnu
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -24,74 +28,18 @@ jobs:
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Install SDL2
|
||||
if: matrix.target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
cat <<EOF > deb822sources
|
||||
Types: deb
|
||||
URIs: http://archive.ubuntu.com/ubuntu/
|
||||
Suites: noble
|
||||
Components: main multiverse universe
|
||||
Architectures: amd64
|
||||
|
||||
Types: deb
|
||||
URIs: http://security.ubuntu.com/ubuntu/
|
||||
Suites: noble-security
|
||||
Components: main multiverse universe
|
||||
Architectures: amd64
|
||||
|
||||
Types: deb
|
||||
URIs: http://archive.ubuntu.com/ubuntu/
|
||||
Suites: noble-updates
|
||||
Components: main multiverse universe
|
||||
Architectures: amd64
|
||||
|
||||
Types: deb
|
||||
URIs: http://archive.ubuntu.com/ubuntu/
|
||||
Suites: noble-backports
|
||||
Components: main multiverse universe
|
||||
Architectures: amd64
|
||||
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/
|
||||
Suites: noble
|
||||
Components: main multiverse universe
|
||||
Architectures: arm64
|
||||
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/
|
||||
Suites: noble-security
|
||||
Components: main multiverse universe
|
||||
Architectures: arm64
|
||||
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/
|
||||
Suites: noble-updates
|
||||
Components: main multiverse universe
|
||||
Architectures: arm64
|
||||
|
||||
Types: deb
|
||||
URIs: http://ports.ubuntu.com/
|
||||
Suites: noble-backports
|
||||
Components: main multiverse universe
|
||||
Architectures: arm64
|
||||
EOF
|
||||
|
||||
sudo mv deb822sources /etc/apt/sources.list.d/ubuntu.sources
|
||||
sudo dpkg --add-architecture arm64
|
||||
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
|
||||
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
|
||||
- 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
|
||||
- name: Install SDL Dependencies
|
||||
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: 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
|
||||
version: ${{ matrix.target }}-1.0
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
@@ -102,7 +50,7 @@ jobs:
|
||||
path: target/${{ matrix.target }}/release/gopher64
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2025
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -125,7 +73,7 @@ jobs:
|
||||
path: target/${{ matrix.target }}/release/gopher64.exe
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -134,12 +82,15 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@master
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
rustflags: ""
|
||||
- name: Build
|
||||
run: |
|
||||
brew install molten-vk
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -19,6 +19,18 @@ 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
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: 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
|
||||
version: lint-1.0
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rust-lang/rustfmt@v1
|
||||
- name: Lint
|
||||
|
||||
+5
-14
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "0.1.21"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
rust-version = "1.82"
|
||||
|
||||
@@ -17,30 +17,21 @@ serde_json = "1.0"
|
||||
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"] }
|
||||
rfd = "0.15"
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tungstenite = "0.26"
|
||||
socket2 = "0.5"
|
||||
postcard = { version = "1.1", features = ["use-std"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.12"
|
||||
default-features = false
|
||||
features = ["http2","rustls-tls","charset","json"]
|
||||
|
||||
[dependencies.sdl2]
|
||||
version = "0.37"
|
||||
default-features = false
|
||||
features = ["static-link","bundled"]
|
||||
|
||||
[dependencies.rfd]
|
||||
version = "0.15"
|
||||
default-features = false
|
||||
features = ["xdg-portal","tokio"]
|
||||
features = ["http2","rustls-tls","charset","json","blocking"]
|
||||
|
||||
[build-dependencies]
|
||||
winres = "0.1"
|
||||
|
||||
@@ -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();
|
||||
@@ -106,7 +109,7 @@ fn main() {
|
||||
|
||||
if arch == "aarch64" {
|
||||
let simd_bindings = bindgen::Builder::default()
|
||||
.header("src/compat/sse2neon.h")
|
||||
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
|
||||
.allowlist_function("_mm_setzero_si128")
|
||||
.allowlist_function("_mm_set_epi8")
|
||||
.allowlist_function("_mm_movemask_epi8")
|
||||
@@ -155,4 +158,12 @@ fn main() {
|
||||
simd_build.include(".");
|
||||
simd_build.compile("simd");
|
||||
}
|
||||
|
||||
if os == "macos" {
|
||||
// Add Homebrew lib path for Apple Silicon Macs
|
||||
println!("cargo:rustc-link-search=native=/opt/homebrew/lib");
|
||||
// Add Homebrew lib path for Intel Macs
|
||||
println!("cargo:rustc-link-search=native=/usr/local/lib");
|
||||
println!("cargo:rustc-link-lib=static=MoltenVK");
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
+19
-27
@@ -3,6 +3,7 @@
|
||||
#include "rdp_device.hpp"
|
||||
#include "interface.hpp"
|
||||
#include "spirv.hpp"
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
|
||||
using namespace Vulkan;
|
||||
|
||||
@@ -129,28 +130,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 +158,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;
|
||||
@@ -179,7 +176,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
|
||||
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
|
||||
wsi->set_backbuffer_srgb(false);
|
||||
Context::SystemHandles handles = {};
|
||||
if (!::Vulkan::Context::init_loader(nullptr))
|
||||
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
|
||||
{
|
||||
rdp_close();
|
||||
}
|
||||
@@ -386,16 +383,14 @@ uint64_t rdp_process_commands()
|
||||
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
|
||||
return interrupt_timer;
|
||||
|
||||
*gfx_info.DPC_STATUS_REG |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
|
||||
|
||||
uint32_t offset = DP_CURRENT;
|
||||
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
|
||||
{
|
||||
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);
|
||||
@@ -449,8 +444,6 @@ uint64_t rdp_process_commands()
|
||||
height = 240;
|
||||
}
|
||||
interrupt_timer = width * height * 4;
|
||||
|
||||
*gfx_info.DPC_STATUS_REG &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
|
||||
}
|
||||
|
||||
cmd_cur += cmd_length;
|
||||
@@ -459,7 +452,6 @@ uint64_t rdp_process_commands()
|
||||
cmd_ptr = 0;
|
||||
cmd_cur = 0;
|
||||
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
|
||||
*gfx_info.DPC_STATUS_REG |= DP_STATUS_CBUF_READY;
|
||||
|
||||
return interrupt_timer;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "src/compat/sse2neon.h"
|
||||
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
|
||||
|
||||
// Static wrappers
|
||||
|
||||
|
||||
+10
-10
@@ -47,12 +47,7 @@ pub mod tlb;
|
||||
pub mod unmapped;
|
||||
pub mod vi;
|
||||
|
||||
pub fn run_game(
|
||||
rom_contents: Vec<u8>,
|
||||
data_dir: std::path::PathBuf,
|
||||
device: &mut Device,
|
||||
fullscreen: bool,
|
||||
) {
|
||||
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device, fullscreen: bool) {
|
||||
cart_rom::init(device, rom_contents); // cart needs to come before rdram
|
||||
|
||||
// rdram pointer is shared with parallel-rdp
|
||||
@@ -73,13 +68,15 @@ pub fn run_game(
|
||||
vi::init(device);
|
||||
cpu::init(device);
|
||||
|
||||
ui::storage::init(&mut device.ui, data_dir);
|
||||
ui::storage::init(&mut device.ui);
|
||||
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
|
||||
cart_rom::load_rom_save(device);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -182,7 +179,7 @@ pub struct Device {
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(config_dir: std::path::PathBuf) -> Device {
|
||||
pub fn new() -> Device {
|
||||
let mut byte_swap: usize = 0;
|
||||
let test: [u8; 4] = [1, 2, 3, 4];
|
||||
// if the host computer is little endian, that means the RDRAM will be stored as little endian
|
||||
@@ -192,13 +189,14 @@ impl Device {
|
||||
}
|
||||
Device {
|
||||
netplay: None,
|
||||
ui: ui::Ui::new(config_dir),
|
||||
ui: ui::Ui::new(),
|
||||
byte_swap,
|
||||
cpu: cpu::Cpu {
|
||||
cop0: cop0::Cop0 {
|
||||
regs: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
|
||||
reg_latch: 0,
|
||||
pending_compare_interrupt: false,
|
||||
instrs: [cop0::reserved; 32],
|
||||
instrs2: [cop0::reserved; 32],
|
||||
tlb_lut_w: vec![
|
||||
@@ -370,6 +368,8 @@ impl Device {
|
||||
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
|
||||
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
|
||||
mem: [0; 0x2000],
|
||||
last_status_value: 0,
|
||||
run_after_dma: false,
|
||||
fifo: [rsp_interface::RspDma {
|
||||
dir: rsp_interface::DmaDir::None,
|
||||
length: 0,
|
||||
|
||||
+2
-1
@@ -68,7 +68,7 @@ fn do_dma(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
|
||||
dma_event,
|
||||
);
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
|
||||
}
|
||||
|
||||
fn fifo_push(device: &mut device::Device) {
|
||||
@@ -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)
|
||||
|
||||
@@ -85,9 +85,8 @@ pub fn dma_read(
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
|
||||
|
||||
device.ui.saves.romsave.0.insert(
|
||||
(cart_addr + i).to_string(),
|
||||
serde_json::to_value(device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap])
|
||||
.unwrap(),
|
||||
cart_addr + i,
|
||||
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -139,8 +138,7 @@ pub fn load_rom_save(device: &mut device::Device) {
|
||||
return;
|
||||
}
|
||||
for (key, value) in device.ui.saves.romsave.0.iter() {
|
||||
device.cart.rom[key.parse::<usize>().unwrap()] =
|
||||
serde_json::from_value(value.clone()).unwrap()
|
||||
device.cart.rom[*key as usize] = *value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+101
-362
@@ -26,8 +26,8 @@ pub struct Vru {
|
||||
pub words: Vec<String>,
|
||||
pub talking: bool,
|
||||
pub word_mappings: HashMap<String, String>,
|
||||
pub window_notifier: Option<tokio::sync::mpsc::Sender<Vec<String>>>,
|
||||
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
|
||||
pub window_notifier: Option<std::sync::mpsc::Sender<Vec<String>>>,
|
||||
pub word_receiver: Option<std::sync::mpsc::Receiver<String>>,
|
||||
pub gui_ctx: Option<egui::Context>,
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
|
||||
ui::vru::prompt_for_match(
|
||||
&device.vru.words,
|
||||
device.vru.window_notifier.as_ref().unwrap(),
|
||||
device.vru.word_receiver.as_mut().unwrap(),
|
||||
device.vru.word_receiver.as_ref().unwrap(),
|
||||
device.vru.gui_ctx.as_ref().unwrap(),
|
||||
)
|
||||
} else {
|
||||
@@ -288,10 +288,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03A50024000303CF00A80003035D001200F900EA"),
|
||||
String::from("pikachu"),
|
||||
),
|
||||
(
|
||||
String::from("040801740024"),
|
||||
String::from("hey!"),
|
||||
),
|
||||
(String::from("040801740024"), String::from("hey!")),
|
||||
(
|
||||
String::from("03CF00C603360405000F0234"),
|
||||
String::from("come-here"),
|
||||
@@ -324,10 +321,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02E2006903FC000303B402E2018F"),
|
||||
String::from("lets-play"),
|
||||
),
|
||||
(
|
||||
String::from("040B00C002EB0213"),
|
||||
String::from("hello"),
|
||||
),
|
||||
(String::from("040B00C002EB0213"), String::from("hello")),
|
||||
(
|
||||
String::from("043B0213000303AB00A5034803FC006903FC00A503270024"),
|
||||
String::from("open-sesame"),
|
||||
@@ -356,14 +350,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("042F01AD033603FC012602F10024"),
|
||||
String::from("im-sorry"),
|
||||
),
|
||||
(
|
||||
String::from("03FC023A0024"),
|
||||
String::from("sorry"),
|
||||
),
|
||||
(
|
||||
String::from("03FC012602F10024"),
|
||||
String::from("sorry"),
|
||||
),
|
||||
(String::from("03FC023A0024"), String::from("sorry")),
|
||||
(String::from("03FC012602F10024"), String::from("sorry")),
|
||||
(
|
||||
String::from("042F01B00003036000ED03DB030C00EA"),
|
||||
String::from("i-choose-you"),
|
||||
@@ -392,10 +380,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03A50024000303D8000303CF00C9"),
|
||||
String::from("pikka"),
|
||||
),
|
||||
(
|
||||
String::from("03A50024000303CF00A8"),
|
||||
String::from("pika"),
|
||||
),
|
||||
(String::from("03A50024000303CF00A8"), String::from("pika")),
|
||||
(
|
||||
String::from("03A50024000303CF00A8000303A50024000303CF00A8"),
|
||||
String::from("pikapika"),
|
||||
@@ -440,10 +425,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03F600C60348000603870234"),
|
||||
String::from("thunder"),
|
||||
),
|
||||
(
|
||||
String::from("03F600C6033F0234"),
|
||||
String::from("thunder"),
|
||||
),
|
||||
(String::from("03F600C6033F0234"), String::from("thunder")),
|
||||
(
|
||||
String::from("0432009F02E20066000303D8000303C602F10045000303D802F40087000303C6"),
|
||||
String::from("electric-rat"),
|
||||
@@ -489,7 +471,9 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("boing-boing"),
|
||||
),
|
||||
(
|
||||
String::from("043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366"),
|
||||
String::from(
|
||||
"043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366",
|
||||
),
|
||||
String::from("on-to-the-next-stage!"),
|
||||
),
|
||||
(
|
||||
@@ -516,10 +500,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("039F020A02EE0006038700A5033C0087000303AB009F02EE"),
|
||||
String::from("golden-apple"),
|
||||
),
|
||||
(
|
||||
String::from("03A5002400030360"),
|
||||
String::from("peach"),
|
||||
),
|
||||
(String::from("03A5002400030360"), String::from("peach")),
|
||||
(
|
||||
String::from("03FC000303C602FD012C00060372029D0024"),
|
||||
String::from("strawberry"),
|
||||
@@ -556,10 +537,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("032101A702EE00060390040B02340006037E"),
|
||||
String::from("wild-herb"),
|
||||
),
|
||||
(
|
||||
String::from("03D502D60348"),
|
||||
String::from("corn"),
|
||||
),
|
||||
(String::from("03D502D60348"), String::from("corn")),
|
||||
(
|
||||
String::from("03B1014D000303B4000303D502D60348"),
|
||||
String::from("popcorn"),
|
||||
@@ -592,18 +570,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03CC008102F700A8000303C6"),
|
||||
String::from("carrot"),
|
||||
),
|
||||
(
|
||||
String::from("03CC02A300A8000303C6"),
|
||||
String::from("carrot"),
|
||||
),
|
||||
(String::from("03CC02A300A8000303C6"), String::from("carrot")),
|
||||
(
|
||||
String::from("043200C60339030900A50348"),
|
||||
String::from("onion"),
|
||||
),
|
||||
(
|
||||
String::from("043200A5033F00A50348"),
|
||||
String::from("onion"),
|
||||
),
|
||||
(String::from("043200A5033F00A50348"), String::from("onion")),
|
||||
(
|
||||
String::from("043501080339030900A50348"),
|
||||
String::from("onion"),
|
||||
@@ -621,7 +593,9 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("sweet-potato"),
|
||||
),
|
||||
(
|
||||
String::from("0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213"),
|
||||
String::from(
|
||||
"0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213",
|
||||
),
|
||||
String::from("hot-sweet-potato"),
|
||||
),
|
||||
(
|
||||
@@ -732,10 +706,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03CF00C9000303B4000303CC018F000303D8"),
|
||||
String::from("cupcake"),
|
||||
),
|
||||
(
|
||||
String::from("03840174002703DB0024"),
|
||||
String::from("daisy"),
|
||||
),
|
||||
(String::from("03840174002703DB0024"), String::from("daisy")),
|
||||
(
|
||||
String::from("03A50045000303C000E70339030900A8"),
|
||||
String::from("petunia"),
|
||||
@@ -768,10 +739,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03FC00C6034803F002EB01DD0234"),
|
||||
String::from("sunflower"),
|
||||
),
|
||||
(
|
||||
String::from("02DF003C02DF0024"),
|
||||
String::from("lily"),
|
||||
),
|
||||
(String::from("02DF003C02DF0024"), String::from("lily")),
|
||||
(
|
||||
String::from("02FD021603DE0006037500C900060390"),
|
||||
String::from("rose-bud"),
|
||||
@@ -852,10 +820,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03D5014D000303AB0234000303D501CE0348"),
|
||||
String::from("copper-coin"),
|
||||
),
|
||||
(
|
||||
String::from("02FA00EA0006036F0024"),
|
||||
String::from("ruby"),
|
||||
),
|
||||
(String::from("02FA00EA0006036F0024"), String::from("ruby")),
|
||||
(
|
||||
String::from("03FC008A03F001AA0300"),
|
||||
String::from("sapphire"),
|
||||
@@ -904,10 +869,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("037502340006039003F00069036C0234"),
|
||||
String::from("bird-feather"),
|
||||
),
|
||||
(
|
||||
String::from("037B020A02EE000303C6"),
|
||||
String::from("bolt"),
|
||||
),
|
||||
(String::from("037B020A02EE000303C6"), String::from("bolt")),
|
||||
(
|
||||
String::from("032A0087000603A203390045000303C6"),
|
||||
String::from("magnet"),
|
||||
@@ -920,10 +882,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0333025500060375009F02EE"),
|
||||
String::from("marble"),
|
||||
),
|
||||
(
|
||||
String::from("03D802F4018F000303C6"),
|
||||
String::from("crate"),
|
||||
),
|
||||
(String::from("03D802F4018F000303C6"), String::from("crate")),
|
||||
(
|
||||
String::from("03D50255000603900006037B02D9000603900006037B014D000303D803FC"),
|
||||
String::from("cardboard-box"),
|
||||
@@ -936,10 +895,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("043B021603EA022804050297"),
|
||||
String::from("over-here"),
|
||||
),
|
||||
(
|
||||
String::from("040B009F02EB0213"),
|
||||
String::from("hello"),
|
||||
),
|
||||
(String::from("040B009F02EB0213"), String::from("hello")),
|
||||
(
|
||||
String::from("039C010B00060390034501950024000303C6"),
|
||||
String::from("good-night"),
|
||||
@@ -972,18 +928,9 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("036C0087000303C603FC03F001AD0348"),
|
||||
String::from("thats-fine"),
|
||||
),
|
||||
(
|
||||
String::from("043B0213000303CC01740024"),
|
||||
String::from("ok"),
|
||||
),
|
||||
(
|
||||
String::from("04020276"),
|
||||
String::from("sure"),
|
||||
),
|
||||
(
|
||||
String::from("02FD01B0000303C6"),
|
||||
String::from("right"),
|
||||
),
|
||||
(String::from("043B0213000303CC01740024"), String::from("ok")),
|
||||
(String::from("04020276"), String::from("sure")),
|
||||
(String::from("02FD01B0000303C6"), String::from("right")),
|
||||
(
|
||||
String::from("02E2006903FC0006038A00ED036C0087000303C6"),
|
||||
String::from("lets-do-that"),
|
||||
@@ -1012,18 +959,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03CF00C9000303BD00A8000303C301F2000303C6"),
|
||||
String::from("cut-it-out"),
|
||||
),
|
||||
(
|
||||
String::from("03450213"),
|
||||
String::from("no"),
|
||||
),
|
||||
(String::from("03450213"), String::from("no")),
|
||||
(
|
||||
String::from("0318018F0006038700A80006039F0213"),
|
||||
String::from("way-to-go"),
|
||||
),
|
||||
(
|
||||
String::from("03F002EB01DD0234"),
|
||||
String::from("flower"),
|
||||
),
|
||||
(String::from("03F002EB01DD0234"), String::from("flower")),
|
||||
(
|
||||
String::from("038400840348000303C603FC"),
|
||||
String::from("dance"),
|
||||
@@ -1104,14 +1045,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03F90009030C00E102E2018F000303BD0234"),
|
||||
String::from("see-you-later"),
|
||||
),
|
||||
(
|
||||
String::from("03F90009030C00EA"),
|
||||
String::from("see-you"),
|
||||
),
|
||||
(
|
||||
String::from("037B01B0"),
|
||||
String::from("bye"),
|
||||
),
|
||||
(String::from("03F90009030C00EA"), String::from("see-you")),
|
||||
(String::from("037B01B0"), String::from("bye")),
|
||||
(
|
||||
String::from("0411031B00A8000303BD0219030900A80006038A00CF0042035A"),
|
||||
String::from("what-are-you-doing?"),
|
||||
@@ -1164,10 +1099,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("04290045000303C603FC021603EA0237036C02B8"),
|
||||
String::from("its-over-there"),
|
||||
),
|
||||
(
|
||||
String::from("02DF003C02DF0024"),
|
||||
String::from("lily"),
|
||||
),
|
||||
(String::from("02DF003C02DF0024"), String::from("lily")),
|
||||
(
|
||||
String::from("03FC00C6034803F002EB01DD0234"),
|
||||
String::from("sunflower"),
|
||||
@@ -1188,10 +1120,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0438012C0006038100480402"),
|
||||
String::from("oddish"),
|
||||
),
|
||||
(
|
||||
String::from("03A202E800E70336"),
|
||||
String::from("gloom"),
|
||||
),
|
||||
(String::from("03A202E800E70336"), String::from("gloom")),
|
||||
(
|
||||
String::from("03EA01A702EE000303B402E800E70336"),
|
||||
String::from("vileplume"),
|
||||
@@ -1200,14 +1129,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("042F01AD033603FC02BE0024"),
|
||||
String::from("im-sorry"),
|
||||
),
|
||||
(
|
||||
String::from("03FC02BE0024"),
|
||||
String::from("sorry"),
|
||||
),
|
||||
(
|
||||
String::from("03CC0084033F00A50348"),
|
||||
String::from("cannon"),
|
||||
),
|
||||
(String::from("03FC02BE0024"), String::from("sorry")),
|
||||
(String::from("03CC0084033F00A50348"), String::from("cannon")),
|
||||
(
|
||||
String::from("03810045000603930045000303C301F2000303C6"),
|
||||
String::from("dig-it-out"),
|
||||
@@ -1216,18 +1139,9 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03810045000603A2036C017A0234"),
|
||||
String::from("dig-there"),
|
||||
),
|
||||
(
|
||||
String::from("03810045000603A2"),
|
||||
String::from("dig"),
|
||||
),
|
||||
(
|
||||
String::from("03AE010202EE"),
|
||||
String::from("pull"),
|
||||
),
|
||||
(
|
||||
String::from("033F00C9000303C6"),
|
||||
String::from("nut"),
|
||||
),
|
||||
(String::from("03810045000603A2"), String::from("dig")),
|
||||
(String::from("03AE010202EE"), String::from("pull")),
|
||||
(String::from("033F00C9000303C6"), String::from("nut")),
|
||||
(
|
||||
String::from("03780108033C0084033F00C9"),
|
||||
String::from("banana"),
|
||||
@@ -1244,10 +1158,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0372029D002400060390000303C602F4006903E40234"),
|
||||
String::from("buried-treasure"),
|
||||
),
|
||||
(
|
||||
String::from("0408005D02EB0213"),
|
||||
String::from("hello"),
|
||||
),
|
||||
(String::from("0408005D02EB0213"), String::from("hello")),
|
||||
(
|
||||
String::from("042F01A702E500CC03E7030C00EA"),
|
||||
String::from("i-love-you"),
|
||||
@@ -1312,26 +1223,17 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("041101290348000303BD0234"),
|
||||
String::from("haunter"),
|
||||
),
|
||||
(
|
||||
String::from("02FD0129035A"),
|
||||
String::from("wrong"),
|
||||
),
|
||||
(String::from("02FD0129035A"), String::from("wrong")),
|
||||
(
|
||||
String::from("036C0087000303C603DE02FD0129035A"),
|
||||
String::from("thats-wrong"),
|
||||
),
|
||||
(
|
||||
String::from("03D503150045000303C6"),
|
||||
String::from("quit"),
|
||||
),
|
||||
(String::from("03D503150045000303C6"), String::from("quit")),
|
||||
(
|
||||
String::from("03CF00C9000303B700450006038D01F2000303C6"),
|
||||
String::from("cut-it-out"),
|
||||
),
|
||||
(
|
||||
String::from("03CF00A503270297"),
|
||||
String::from("c-mere"),
|
||||
),
|
||||
(String::from("03CF00A503270297"), String::from("c-mere")),
|
||||
(
|
||||
String::from("03CF00A5033604050297"),
|
||||
String::from("come-here"),
|
||||
@@ -1348,26 +1250,17 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0321012C0003036001F2000303C6"),
|
||||
String::from("watch-out"),
|
||||
),
|
||||
(
|
||||
String::from("036C02B8"),
|
||||
String::from("there"),
|
||||
),
|
||||
(String::from("036C02B8"), String::from("there")),
|
||||
(
|
||||
String::from("03FC03150042035A034501F2"),
|
||||
String::from("swing-now"),
|
||||
),
|
||||
(
|
||||
String::from("03FC03150042035A"),
|
||||
String::from("swing"),
|
||||
),
|
||||
(String::from("03FC03150042035A"), String::from("swing")),
|
||||
(
|
||||
String::from("039F020D02FD01B0000303C6"),
|
||||
String::from("go-right"),
|
||||
),
|
||||
(
|
||||
String::from("02FD01B0000303C6"),
|
||||
String::from("right"),
|
||||
),
|
||||
(String::from("02FD01B0000303C6"), String::from("right")),
|
||||
(
|
||||
String::from("03F00258036C022E02FD01B0000303C6"),
|
||||
String::from("farther-right"),
|
||||
@@ -1376,10 +1269,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("039F020A02E2006903F0000303C6"),
|
||||
String::from("go-left"),
|
||||
),
|
||||
(
|
||||
String::from("02E2006903F0000303C6"),
|
||||
String::from("left"),
|
||||
),
|
||||
(String::from("02E2006903F0000303C6"), String::from("left")),
|
||||
(
|
||||
String::from("03F00258036C022B02E2006903F0000303C6"),
|
||||
String::from("farther-left"),
|
||||
@@ -1472,10 +1362,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02DF004803F900420348000303BD00A503270024"),
|
||||
String::from("listen-to-me"),
|
||||
),
|
||||
(
|
||||
String::from("03F002FA00EA000303C6"),
|
||||
String::from("fruit"),
|
||||
),
|
||||
(String::from("03F002FA00EA000303C6"), String::from("fruit")),
|
||||
(
|
||||
String::from("03450213000303C6000303A8008700060390"),
|
||||
String::from("notepad"),
|
||||
@@ -1500,14 +1387,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0411014D000303C603FC03150024000303B4000303BA018F0006038D0213"),
|
||||
String::from("hot-sweet-potato"),
|
||||
),
|
||||
(
|
||||
String::from("03D200E102EE"),
|
||||
String::from("cool"),
|
||||
),
|
||||
(
|
||||
String::from("03F001AD0348"),
|
||||
String::from("fine"),
|
||||
),
|
||||
(String::from("03D200E102EE"), String::from("cool")),
|
||||
(String::from("03F001AD0348"), String::from("fine")),
|
||||
(
|
||||
String::from("0309023703FC0213000303C9030C00EA000303C6"),
|
||||
String::from("youre-so-cute"),
|
||||
@@ -1572,14 +1453,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("032A0087000603A2033F00A8000303C6"),
|
||||
String::from("magnet"),
|
||||
),
|
||||
(
|
||||
String::from("03D501CE0348"),
|
||||
String::from("coin"),
|
||||
),
|
||||
(
|
||||
String::from("036600D5009F02EE"),
|
||||
String::from("jewel"),
|
||||
),
|
||||
(String::from("03D501CE0348"), String::from("coin")),
|
||||
(String::from("036600D5009F02EE"), String::from("jewel")),
|
||||
(
|
||||
String::from("0333025500060375009F02EE"),
|
||||
String::from("marble"),
|
||||
@@ -1596,10 +1471,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03D50315004803DE000303C301AD0336"),
|
||||
String::from("quiz-time"),
|
||||
),
|
||||
(
|
||||
String::from("03B7002703E70024"),
|
||||
String::from("tv"),
|
||||
),
|
||||
(String::from("03B7002703E70024"), String::from("tv")),
|
||||
(
|
||||
String::from("042F01B0000303D503150045000303C6"),
|
||||
String::from("i-quit"),
|
||||
@@ -1616,10 +1488,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03150024000303CF00A50348000303B402E2018F"),
|
||||
String::from("we-can-play"),
|
||||
),
|
||||
(
|
||||
String::from("0372008700060390"),
|
||||
String::from("bad"),
|
||||
),
|
||||
(String::from("0372008700060390"), String::from("bad")),
|
||||
(
|
||||
String::from("0345014D000303C90045000303C3012F03F0"),
|
||||
String::from("knock-it-off"),
|
||||
@@ -1636,10 +1505,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("042F01AD03360345014D000303C6000303B402E201740042035A"),
|
||||
String::from("im-not-playing"),
|
||||
),
|
||||
(
|
||||
String::from("03D503150045000303C6"),
|
||||
String::from("quit"),
|
||||
),
|
||||
(String::from("03D503150045000303C6"), String::from("quit")),
|
||||
(
|
||||
String::from("03FC000303C3014D000303B4036C0087000303C6"),
|
||||
String::from("stop-that"),
|
||||
@@ -1660,10 +1526,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03D802DF00210348036C0087000303BD00C9000303B4"),
|
||||
String::from("clean-that-up"),
|
||||
),
|
||||
(
|
||||
String::from("03B70048040200EA"),
|
||||
String::from("tissue"),
|
||||
),
|
||||
(String::from("03B70048040200EA"), String::from("tissue")),
|
||||
(
|
||||
String::from("037500C002EE0006037500AB03FC02D9"),
|
||||
String::from("bulbasaur"),
|
||||
@@ -1768,10 +1631,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03FC000303A502880213"),
|
||||
String::from("spearow"),
|
||||
),
|
||||
(
|
||||
String::from("03ED02880213"),
|
||||
String::from("fearow"),
|
||||
),
|
||||
(String::from("03ED02880213"), String::from("fearow")),
|
||||
(
|
||||
String::from("04260024000303CF00A5034803DE"),
|
||||
String::from("ekans"),
|
||||
@@ -1788,10 +1648,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03A50024000303CF00A80003036000EA"),
|
||||
String::from("pikachu"),
|
||||
),
|
||||
(
|
||||
String::from("02FD01B00003036000EA"),
|
||||
String::from("raichu"),
|
||||
),
|
||||
(String::from("02FD01B00003036000EA"), String::from("raichu")),
|
||||
(
|
||||
String::from("03FC00840348040202FA00EA"),
|
||||
String::from("sandshrew"),
|
||||
@@ -1884,22 +1741,13 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0438012C0006038100480402"),
|
||||
String::from("oddish"),
|
||||
),
|
||||
(
|
||||
String::from("03A202E800E70336"),
|
||||
String::from("gloom"),
|
||||
),
|
||||
(String::from("03A202E800E70336"), String::from("gloom")),
|
||||
(
|
||||
String::from("03EA01A702EE000303B402E800E70336"),
|
||||
String::from("vileplume"),
|
||||
),
|
||||
(
|
||||
String::from("03B1024000AB03FC"),
|
||||
String::from("paras"),
|
||||
),
|
||||
(
|
||||
String::from("03A802A300AB03FC"),
|
||||
String::from("paras"),
|
||||
),
|
||||
(String::from("03B1024000AB03FC"), String::from("paras")),
|
||||
(String::from("03A802A300AB03FC"), String::from("paras")),
|
||||
(
|
||||
String::from("03B1024000AB03FC0066000303D8000303C6"),
|
||||
String::from("parasect"),
|
||||
@@ -1920,10 +1768,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03810045000603A202E500A8000303C6"),
|
||||
String::from("diglett"),
|
||||
),
|
||||
(
|
||||
String::from("0327001501F503F6"),
|
||||
String::from("meowth"),
|
||||
),
|
||||
(String::from("0327001501F503F6"), String::from("meowth")),
|
||||
(
|
||||
String::from("03AB023703E400A50348"),
|
||||
String::from("persian"),
|
||||
@@ -2024,14 +1869,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03A202F4008A03EA02E50234"),
|
||||
String::from("graveler"),
|
||||
),
|
||||
(
|
||||
String::from("039F020A02E500A50336"),
|
||||
String::from("golem"),
|
||||
),
|
||||
(
|
||||
String::from("039F014402EE0336"),
|
||||
String::from("golem"),
|
||||
),
|
||||
(String::from("039F020A02E500A50336"), String::from("golem")),
|
||||
(String::from("039F014402EE0336"), String::from("golem")),
|
||||
(
|
||||
String::from("03B1021003390024000303BD00A8"),
|
||||
String::from("ponyta"),
|
||||
@@ -2060,18 +1899,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("032A0087000603A2033F00A8000303C3014A0348"),
|
||||
String::from("magneton"),
|
||||
),
|
||||
(
|
||||
String::from("0008"),
|
||||
String::from("farfetch"),
|
||||
),
|
||||
(String::from("0008"), String::from("farfetch")),
|
||||
(
|
||||
String::from("038D02130006039002F100150213"),
|
||||
String::from("dodrio"),
|
||||
),
|
||||
(
|
||||
String::from("03F9001B02EE"),
|
||||
String::from("seel"),
|
||||
),
|
||||
(String::from("03F9001B02EE"), String::from("seel")),
|
||||
(
|
||||
String::from("038A00EA0006039F014A035A"),
|
||||
String::from("dewgong"),
|
||||
@@ -2080,14 +1913,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("038A00EA0006039F014A0348"),
|
||||
String::from("dewgong"),
|
||||
),
|
||||
(
|
||||
String::from("03A202FD01AD032D0234"),
|
||||
String::from("grimer"),
|
||||
),
|
||||
(
|
||||
String::from("032D00C9000303D8"),
|
||||
String::from("muk"),
|
||||
),
|
||||
(String::from("03A202FD01AD032D0234"), String::from("grimer")),
|
||||
(String::from("032D00C9000303D8"), String::from("muk")),
|
||||
(
|
||||
String::from("0402005D02EE000603870234"),
|
||||
String::from("shellder"),
|
||||
@@ -2100,10 +1927,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0396008A03FC000303C602DF00"),
|
||||
String::from("gastly"),
|
||||
),
|
||||
(
|
||||
String::from("0396008A03FC02DF0024"),
|
||||
String::from("gastly"),
|
||||
),
|
||||
(String::from("0396008A03FC02DF0024"), String::from("gastly")),
|
||||
(
|
||||
String::from("041101290348000303BD0234"),
|
||||
String::from("haunter"),
|
||||
@@ -2232,10 +2056,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03CC018C035A0006039900AB03FC000303D5014A0348"),
|
||||
String::from("kangaskhan"),
|
||||
),
|
||||
(
|
||||
String::from("041102DC03F90024"),
|
||||
String::from("horsea"),
|
||||
),
|
||||
(String::from("041102DC03F90024"), String::from("horsea")),
|
||||
(
|
||||
String::from("03F900240006039002F700A8"),
|
||||
String::from("seadra"),
|
||||
@@ -2260,10 +2081,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0327004803FC000303BD0231033301AD0336"),
|
||||
String::from("mr.mime"),
|
||||
),
|
||||
(
|
||||
String::from("03FC01B3036C0234"),
|
||||
String::from("scyther"),
|
||||
),
|
||||
(String::from("03FC01B3036C0234"), String::from("scyther")),
|
||||
(
|
||||
String::from("03630042035A000303D803FC"),
|
||||
String::from("jynx"),
|
||||
@@ -2280,18 +2098,9 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("032A0087000603A203330255"),
|
||||
String::from("magmar"),
|
||||
),
|
||||
(
|
||||
String::from("03A50042034803FC0234"),
|
||||
String::from("pinsir"),
|
||||
),
|
||||
(
|
||||
String::from("03C3020D02FD012F03FC"),
|
||||
String::from("tauros"),
|
||||
),
|
||||
(
|
||||
String::from("03C30246021603FC"),
|
||||
String::from("tauros"),
|
||||
),
|
||||
(String::from("03A50042034803FC0234"), String::from("pinsir")),
|
||||
(String::from("03C3020D02FD012F03FC"), String::from("tauros")),
|
||||
(String::from("03C30246021603FC"), String::from("tauros")),
|
||||
(
|
||||
String::from("032A0087000603630045000303D50255000303B4"),
|
||||
String::from("magikarp"),
|
||||
@@ -2312,14 +2121,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02E20087000303B402F700AB03FC"),
|
||||
String::from("lapras"),
|
||||
),
|
||||
(
|
||||
String::from("03810045000303C30213"),
|
||||
String::from("ditto"),
|
||||
),
|
||||
(
|
||||
String::from("0426002703E70024"),
|
||||
String::from("eevee"),
|
||||
),
|
||||
(String::from("03810045000303C30213"), String::from("ditto")),
|
||||
(String::from("0426002703E70024"), String::from("eevee")),
|
||||
(
|
||||
String::from("03EA018F000303B102BE0015014A0348"),
|
||||
String::from("vaporeon"),
|
||||
@@ -2416,10 +2219,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("0327030C00EA000303C000EA"),
|
||||
String::from("mewtwo"),
|
||||
),
|
||||
(
|
||||
String::from("0327030C00EA"),
|
||||
String::from("mew"),
|
||||
),
|
||||
(String::from("0327030C00EA"), String::from("mew")),
|
||||
(
|
||||
String::from("03C3014D000303AB00AB03EA000603630045000603A202DF0024000303AB00AB03F0"),
|
||||
String::from("top-of-jigglypuff"),
|
||||
@@ -2456,10 +2256,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("038700C9000603A2000303C602F100150213"),
|
||||
String::from("dugtrio"),
|
||||
),
|
||||
(
|
||||
String::from("03A50045000603630024"),
|
||||
String::from("pidgey"),
|
||||
),
|
||||
(String::from("03A50045000603630024"), String::from("pidgey")),
|
||||
(
|
||||
String::from("038D02130006038A00DB0213"),
|
||||
String::from("doduo"),
|
||||
@@ -2528,30 +2325,18 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02E2006903FC000303B402E2018F"),
|
||||
String::from("lets-play"),
|
||||
),
|
||||
(
|
||||
String::from("02FD0129035A"),
|
||||
String::from("wrong"),
|
||||
),
|
||||
(String::from("02FD0129035A"), String::from("wrong")),
|
||||
(
|
||||
String::from("0372008700060390000303A50024000303CF00A80003036000EA"),
|
||||
String::from("bad-pikachu"),
|
||||
),
|
||||
(
|
||||
String::from("0327004803FC"),
|
||||
String::from("miss"),
|
||||
),
|
||||
(String::from("0327004803FC"), String::from("miss")),
|
||||
(
|
||||
String::from("038D02100348000303C6000303D5012302DF0045000303C6"),
|
||||
String::from("dont-call-it"),
|
||||
),
|
||||
(
|
||||
String::from("041101F503FC"),
|
||||
String::from("house"),
|
||||
),
|
||||
(
|
||||
String::from("041102100336"),
|
||||
String::from("home"),
|
||||
),
|
||||
(String::from("041101F503FC"), String::from("house")),
|
||||
(String::from("041102100336"), String::from("home")),
|
||||
(
|
||||
String::from("03EA00A202F1004500060381000F00C60348"),
|
||||
String::from("viridian"),
|
||||
@@ -2560,10 +2345,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03EA00A202F1004500060381000F00C6034803F002C400AB03FC000303C6"),
|
||||
String::from("viridian-forest"),
|
||||
),
|
||||
(
|
||||
String::from("043B0213000303CF0234"),
|
||||
String::from("ochre"),
|
||||
),
|
||||
(String::from("043B0213000303CF0234"), String::from("ochre")),
|
||||
(
|
||||
String::from("043B0213000303CF0225031E010B0006039003DE"),
|
||||
String::from("ochre-woods"),
|
||||
@@ -2604,10 +2386,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03D502130006037E03ED004803FF0042035A0411020A02EE"),
|
||||
String::from("cobalt-fishing-hole"),
|
||||
),
|
||||
(
|
||||
String::from("03D5021603FC000303C6"),
|
||||
String::from("coast"),
|
||||
),
|
||||
(String::from("03D5021603FC000303C6"), String::from("coast")),
|
||||
(
|
||||
String::from("03D502130006037B012302EE000303C6000303D5021603FC000303C6"),
|
||||
String::from("cobalt-coast"),
|
||||
@@ -2620,16 +2399,15 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03BD0231033F00A8000303B4"),
|
||||
String::from("turnip"),
|
||||
),
|
||||
(
|
||||
String::from("03BD02310348000303B4"),
|
||||
String::from("turnip"),
|
||||
),
|
||||
(String::from("03BD02310348000303B4"), String::from("turnip")),
|
||||
(
|
||||
String::from("03FC03150024000303C6000303AB00A8000303BA018F0006038D0213"),
|
||||
String::from("sweet-potato"),
|
||||
),
|
||||
(
|
||||
String::from("0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213"),
|
||||
String::from(
|
||||
"0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213",
|
||||
),
|
||||
String::from("hot-sweet-potato"),
|
||||
),
|
||||
(
|
||||
@@ -2640,18 +2418,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("04080084034B00420348036C02B8"),
|
||||
String::from("hang-in-there"),
|
||||
),
|
||||
(
|
||||
String::from("03AE010202EE"),
|
||||
String::from("pull"),
|
||||
),
|
||||
(String::from("03AE010202EE"), String::from("pull")),
|
||||
(
|
||||
String::from("02F1001B02DF0045000303B700420348"),
|
||||
String::from("reel-it-in"),
|
||||
),
|
||||
(
|
||||
String::from("034501F2"),
|
||||
String::from("now"),
|
||||
),
|
||||
(String::from("034501F2"), String::from("now")),
|
||||
(
|
||||
String::from("03CC00870003035D0045000303C6"),
|
||||
String::from("catch-it"),
|
||||
@@ -2676,14 +2448,8 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02F1003C02DF002703F90045000303C6"),
|
||||
String::from("release-it"),
|
||||
),
|
||||
(
|
||||
String::from("02E500C6034800030360"),
|
||||
String::from("lunch"),
|
||||
),
|
||||
(
|
||||
String::from("03FC033C0087000303D8"),
|
||||
String::from("snack"),
|
||||
),
|
||||
(String::from("02E500C6034800030360"), String::from("lunch")),
|
||||
(String::from("03FC033C0087000303D8"), String::from("snack")),
|
||||
(
|
||||
String::from("02E2006903F90024000303C6"),
|
||||
String::from("lets-eat"),
|
||||
@@ -2700,10 +2466,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("042F01B0000603930045000303B70045000303C6"),
|
||||
String::from("i-get-it"),
|
||||
),
|
||||
(
|
||||
String::from("0411032101B0"),
|
||||
String::from("why?"),
|
||||
),
|
||||
(String::from("0411032101B0"), String::from("why?")),
|
||||
(
|
||||
String::from("041101F2000303CF00C60336"),
|
||||
String::from("how-come?"),
|
||||
@@ -2728,10 +2491,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03FC000303C3014D000303B4036C0087000303C6"),
|
||||
String::from("stop-that"),
|
||||
),
|
||||
(
|
||||
String::from("03D503150045000303C6"),
|
||||
String::from("quit"),
|
||||
),
|
||||
(String::from("03D503150045000303C6"), String::from("quit")),
|
||||
(
|
||||
String::from("03CF00C9000303B70045000303C301F2000303C6"),
|
||||
String::from("cut-it-out"),
|
||||
@@ -2744,10 +2504,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("02E20066000303C603DE0006039F0213"),
|
||||
String::from("lets-go"),
|
||||
),
|
||||
(
|
||||
String::from("031B0225012F03F0"),
|
||||
String::from("we-re-off"),
|
||||
),
|
||||
(String::from("031B0225012F03F0"), String::from("we-re-off")),
|
||||
(
|
||||
String::from("03BA008700060393030902190045000303C6"),
|
||||
String::from("tag-youre-it"),
|
||||
@@ -2804,18 +2561,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("039F0213000303BD00C6033302460213"),
|
||||
String::from("go-tomorrow"),
|
||||
),
|
||||
(
|
||||
String::from("02FD014D000303D8"),
|
||||
String::from("rock"),
|
||||
),
|
||||
(String::from("02FD014D000303D8"), String::from("rock")),
|
||||
(
|
||||
String::from("03F9004803DE023703FC"),
|
||||
String::from("scissors"),
|
||||
),
|
||||
(
|
||||
String::from("03A8018F000303AB0234"),
|
||||
String::from("paper"),
|
||||
),
|
||||
(String::from("03A8018F000303AB0234"), String::from("paper")),
|
||||
(
|
||||
String::from("0381002A000303B10255000303C6"),
|
||||
String::from("depart"),
|
||||
@@ -2872,10 +2623,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03450213000602E50042032A002A000303C6"),
|
||||
String::from("no-limit"),
|
||||
),
|
||||
(
|
||||
String::from("03B7005A0348"),
|
||||
String::from("ten"),
|
||||
),
|
||||
(String::from("03B7005A0348"), String::from("ten")),
|
||||
(
|
||||
String::from("03E7002A03F6000303B700090348"),
|
||||
String::from("fifteen"),
|
||||
@@ -2888,18 +2636,12 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03C30318004B0339038A002403EA019E03EA"),
|
||||
String::from("twenty-five"),
|
||||
),
|
||||
(
|
||||
String::from("036C02AC03BA0027"),
|
||||
String::from("thirty"),
|
||||
),
|
||||
(String::from("036C02AC03BA0027"), String::from("thirty")),
|
||||
(
|
||||
String::from("036C02AC03BA002703EA019E03EA"),
|
||||
String::from("thirty-five"),
|
||||
),
|
||||
(
|
||||
String::from("03EA02D6000603900048"),
|
||||
String::from("forty"),
|
||||
),
|
||||
(String::from("03EA02D6000603900048"), String::from("forty")),
|
||||
(
|
||||
String::from("03EA02D600060390004803EA019E03EA"),
|
||||
String::from("forty-five"),
|
||||
@@ -2928,10 +2670,7 @@ fn create_word_mappings(device: &mut device::Device) {
|
||||
String::from("03FC004B036C00630348000303B7000903EA019E03EA"),
|
||||
String::from("seventy-five"),
|
||||
),
|
||||
(
|
||||
String::from("042C018C03B70009"),
|
||||
String::from("eighty"),
|
||||
),
|
||||
(String::from("042C018C03B70009"), String::from("eighty")),
|
||||
(
|
||||
String::from("042C018C03B7000903EA019E03EA"),
|
||||
String::from("eighty-five"),
|
||||
|
||||
+5
-5
@@ -55,14 +55,14 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
|
||||
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
|
||||
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
|
||||
const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
|
||||
pub const COP0_CAUSE_BD: u64 = 1 << 31;
|
||||
|
||||
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
|
||||
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
|
||||
|
||||
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
|
||||
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
|
||||
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
|
||||
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
|
||||
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
|
||||
@@ -97,6 +97,7 @@ pub struct Cop0 {
|
||||
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
|
||||
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
|
||||
pub tlb_entries: [device::tlb::TlbEntry; 32],
|
||||
pub pending_compare_interrupt: bool,
|
||||
}
|
||||
|
||||
fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -214,6 +215,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
compare_event,
|
||||
);
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
}
|
||||
COP0_STATUS_REG => {
|
||||
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
|
||||
@@ -231,9 +233,7 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
|
||||
}
|
||||
|
||||
fn compare_event(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
|
||||
|
||||
device.cpu.cop0.pending_compare_interrupt = true;
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Compare,
|
||||
|
||||
@@ -8,7 +8,7 @@ pub enum EventType {
|
||||
PI,
|
||||
DP,
|
||||
SP,
|
||||
InterruptCheck,
|
||||
Interrupt,
|
||||
SPDma,
|
||||
Compare,
|
||||
Vru,
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
use crate::device;
|
||||
|
||||
pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& (device::cop0::COP0_STATUS_IE
|
||||
| device::cop0::COP0_STATUS_EXL
|
||||
@@ -20,6 +11,38 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if device.mi.regs[device::mi::MI_INTR_REG as usize]
|
||||
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
|
||||
!= 0
|
||||
{
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
|
||||
} else if device.cpu.cop0.pending_compare_interrupt {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
|
||||
& device::cop0::COP0_CAUSE_IP_MASK)
|
||||
== 0
|
||||
{
|
||||
// interrupt disabled, or no pending interrupts
|
||||
return;
|
||||
}
|
||||
|
||||
if device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] == device::cop0::COP0_CAUSE_IP7 {
|
||||
interrupt_exception(device);
|
||||
} else {
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::Interrupt,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
interrupt_exception,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn interrupt_exception(device: &mut device::Device) {
|
||||
exception_general(device, 0x180);
|
||||
}
|
||||
|
||||
|
||||
+3
-31
@@ -2,8 +2,8 @@ use crate::device;
|
||||
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
const MI_INTR_REG: u32 = 2;
|
||||
const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
pub const MI_REGS_COUNT: u32 = 4;
|
||||
|
||||
/* read */
|
||||
@@ -64,11 +64,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
|
||||
}
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
} else {
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
@@ -149,37 +145,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
device::exceptions::check_pending_interrupts(device)
|
||||
}
|
||||
|
||||
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
|
||||
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
|
||||
|
||||
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
|
||||
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
|
||||
}
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::InterruptCheck,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
|
||||
device::exceptions::check_pending_interrupts,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
|
||||
}
|
||||
|
||||
+7
-2
@@ -149,10 +149,15 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
device.pi.regs[PI_STATUS_REG as usize] = 0
|
||||
}
|
||||
}
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM1_PGS_REG | PI_BSD_DOM1_RLS_REG
|
||||
| PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG | PI_BSD_DOM2_PGS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xFF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_PGS_REG | PI_BSD_DOM2_PGS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xF, mask)
|
||||
}
|
||||
PI_BSD_DOM1_RLS_REG | PI_BSD_DOM2_RLS_REG => {
|
||||
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0x3, mask)
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -52,7 +52,17 @@ pub fn read_regs_dpc(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
|
||||
DPC_CURRENT_REG => {
|
||||
if device.rdp.wait_frozen {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rdp.regs_dpc[reg as usize]
|
||||
}
|
||||
_ => device.rdp.regs_dpc[reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
@@ -93,6 +103,7 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
|
||||
|
||||
fn run_rdp(device: &mut device::Device) {
|
||||
let timer = ui::video::process_rdp_list();
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY;
|
||||
|
||||
if timer != 0 {
|
||||
device::events::create_event(
|
||||
@@ -174,5 +185,7 @@ pub fn init(device: &mut device::Device) {
|
||||
}
|
||||
|
||||
fn rdp_interrupt_event(device: &mut device::Device) {
|
||||
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY);
|
||||
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
|
||||
}
|
||||
|
||||
+3
-2
@@ -73,16 +73,17 @@ pub fn read_regs(
|
||||
address: u64,
|
||||
_access_size: device::memory::AccessSize,
|
||||
) -> u32 {
|
||||
let chip_id = address >> 13 & 3;
|
||||
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,
|
||||
0x80 => 0x00000000, //Row, needed for libdragon
|
||||
_ => device.rdram.regs[chip_id as usize][reg as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
let chip_id = address >> 13 & 3;
|
||||
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],
|
||||
|
||||
+44
-14
@@ -81,6 +81,8 @@ pub struct Rsp {
|
||||
pub regs2: [u32; SP_REGS2_COUNT as usize],
|
||||
pub mem: [u8; 0x2000],
|
||||
pub fifo: [RspDma; 2],
|
||||
pub last_status_value: u32,
|
||||
pub run_after_dma: bool,
|
||||
}
|
||||
|
||||
pub fn read_mem_fast(
|
||||
@@ -249,7 +251,11 @@ fn fifo_pop(device: &mut device::Device) {
|
||||
do_dma(device, device.rsp.fifo[0])
|
||||
} else {
|
||||
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
|
||||
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
|
||||
if device.rsp.run_after_dma {
|
||||
device.rsp.run_after_dma = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,8 +266,30 @@ pub fn read_regs(
|
||||
) -> u32 {
|
||||
let reg = (address & 0xFFFF) >> 2;
|
||||
match reg as u32 {
|
||||
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
|
||||
if device.rsp.regs[reg as usize] != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
SP_STATUS_REG => {
|
||||
let value = device.rsp.regs[reg as usize]
|
||||
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
|
||||
if value == device.rsp.last_status_value && value != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
device.rsp.last_status_value = value;
|
||||
device.rsp.regs[reg as usize]
|
||||
}
|
||||
SP_SEMAPHORE_REG => {
|
||||
let value = device.rsp.regs[reg as usize];
|
||||
if value == 1 {
|
||||
device.rsp.cpu.sync_point = true;
|
||||
device.rsp.cpu.cycle_counter += 4; // needed for DK64
|
||||
}
|
||||
device.rsp.regs[reg as usize] = 1;
|
||||
value
|
||||
}
|
||||
@@ -284,9 +312,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
SP_SEMAPHORE_REG => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
|
||||
}
|
||||
_ => {
|
||||
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
|
||||
}
|
||||
_ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +361,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
}
|
||||
/* set SP interrupt */
|
||||
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
|
||||
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
|
||||
}
|
||||
|
||||
/* clear / set single step */
|
||||
@@ -421,20 +447,25 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
|
||||
device.rsp.cpu.broken = false;
|
||||
device.rsp.cpu.halted = false;
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device);
|
||||
}
|
||||
}
|
||||
|
||||
fn do_task(device: &mut device::Device) {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
device.rsp.cpu.sync_point = false;
|
||||
device.rsp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
|
||||
device.rsp.run_after_dma = true
|
||||
} else {
|
||||
let timer = device::rsp_cpu::run(device);
|
||||
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rsp_event,
|
||||
)
|
||||
device::events::create_event(
|
||||
device,
|
||||
device::events::EventType::SP,
|
||||
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
|
||||
rsp_event,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn rsp_event(device: &mut device::Device) {
|
||||
@@ -450,7 +481,6 @@ fn rsp_event(device: &mut device::Device) {
|
||||
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
|
||||
return;
|
||||
}
|
||||
device.rsp.cpu.sync_point = false;
|
||||
do_task(device)
|
||||
}
|
||||
|
||||
|
||||
@@ -565,8 +565,6 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
|
||||
device::memory::AccessSize::Word,
|
||||
)
|
||||
}
|
||||
device.rsp.cpu.cycle_counter += 4; // needed for DK64
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
@@ -592,7 +590,6 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
|
||||
!device::rsp_interface::SP_STATUS_HALT; // set halt when event happens
|
||||
device.rsp.cpu.halted = true // the RSP can halt itself by setting SP_SET_HALT
|
||||
}
|
||||
device.rsp.cpu.sync_point = true;
|
||||
}
|
||||
|
||||
pub fn mfc2(device: &mut device::Device, opcode: u32) {
|
||||
@@ -721,7 +718,7 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
|
||||
modify_vpr16(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 2,
|
||||
(device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8
|
||||
((device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8)
|
||||
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u16),
|
||||
);
|
||||
} else {
|
||||
|
||||
+31
-27
@@ -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,
|
||||
@@ -63,37 +64,22 @@ struct Args {
|
||||
clear_input_bindings: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let portable_dir = exe_path.parent();
|
||||
let portable = portable_dir.unwrap().join("portable.txt").exists();
|
||||
let config_dir;
|
||||
let cache_dir;
|
||||
let data_dir;
|
||||
if portable {
|
||||
config_dir = portable_dir.unwrap().join("portable_data").join("config");
|
||||
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
|
||||
data_dir = portable_dir.unwrap().join("portable_data").join("data");
|
||||
} else {
|
||||
config_dir = dirs::config_dir().unwrap().join("gopher64");
|
||||
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
|
||||
data_dir = dirs::data_dir().unwrap().join("gopher64");
|
||||
};
|
||||
fn main() {
|
||||
let dirs = ui::get_dirs();
|
||||
|
||||
let mut result = std::fs::create_dir_all(config_dir.clone());
|
||||
let mut result = std::fs::create_dir_all(dirs.config_dir);
|
||||
if result.is_err() {
|
||||
panic!("could not create config dir: {}", result.err().unwrap())
|
||||
}
|
||||
result = std::fs::create_dir_all(cache_dir.clone());
|
||||
result = std::fs::create_dir_all(dirs.cache_dir.clone());
|
||||
if result.is_err() {
|
||||
panic!("could not create cache dir: {}", result.err().unwrap())
|
||||
}
|
||||
result = std::fs::create_dir_all(data_dir.clone().join("saves"));
|
||||
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
|
||||
if result.is_err() {
|
||||
panic!("could not create data dir: {}", result.err().unwrap())
|
||||
}
|
||||
let running_file = cache_dir.join("game_running");
|
||||
let running_file = dirs.cache_dir.join("game_running");
|
||||
if running_file.exists() {
|
||||
result = std::fs::remove_file(running_file);
|
||||
if result.is_err() {
|
||||
@@ -105,7 +91,7 @@ async fn main() {
|
||||
let args_as_strings: Vec<String> = std::env::args().collect();
|
||||
let args_count = args_as_strings.len();
|
||||
if args_count > 1 {
|
||||
let mut device = device::Device::new(config_dir);
|
||||
let mut device = device::Device::new();
|
||||
|
||||
if args.clear_input_bindings {
|
||||
ui::input::clear_bindings(&mut device.ui);
|
||||
@@ -119,7 +105,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() {
|
||||
@@ -162,19 +151,34 @@ async fn main() {
|
||||
println!("Could not read rom file");
|
||||
return;
|
||||
}
|
||||
device::run_game(rom_contents, data_dir, &mut device, args.fullscreen);
|
||||
device::run_game(rom_contents, &mut device, args.fullscreen);
|
||||
}
|
||||
} 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()
|
||||
};
|
||||
|
||||
let controllers_paths;
|
||||
let controller_names;
|
||||
{
|
||||
let game_ui = ui::Ui::new();
|
||||
controllers_paths = gui::get_controller_paths(&game_ui);
|
||||
controller_names = gui::get_controller_names(&game_ui);
|
||||
}
|
||||
eframe::run_native(
|
||||
"gopher64",
|
||||
options,
|
||||
Box::new(|cc| {
|
||||
Ok(Box::new(ui::gui::GopherEguiApp::new(
|
||||
cc, config_dir, cache_dir, data_dir,
|
||||
cc,
|
||||
controllers_paths,
|
||||
controller_names,
|
||||
)))
|
||||
}),
|
||||
)
|
||||
|
||||
+27
-17
@@ -11,12 +11,13 @@ const UDP_SYNC_DATA: u8 = 4;
|
||||
|
||||
//TCP packet formats
|
||||
const TCP_SEND_SAVE: u8 = 1;
|
||||
const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_RECEIVE_SAVE: u8 = 2;
|
||||
//const TCP_SEND_SETTINGS: u8 = 3;
|
||||
//const TCP_RECEIVE_SETTINGS: u8 = 4;
|
||||
const TCP_REGISTER_PLAYER: u8 = 5;
|
||||
const TCP_GET_REGISTRATION: u8 = 6;
|
||||
const TCP_DISCONNECT_NOTICE: u8 = 7;
|
||||
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
|
||||
|
||||
const CS4: u32 = 32;
|
||||
|
||||
@@ -29,7 +30,7 @@ pub struct Netplay {
|
||||
status: u8,
|
||||
buffer_target: u8,
|
||||
pub fast_forward: bool,
|
||||
pub error_notifier: tokio::sync::mpsc::Sender<String>,
|
||||
pub error_notifier: std::sync::mpsc::Sender<String>,
|
||||
pub gui_ctx: egui::Context,
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ struct InputEvent {
|
||||
}
|
||||
|
||||
fn send_error(netplay: &mut Netplay, error: String) {
|
||||
netplay.error_notifier.try_send(error).unwrap();
|
||||
netplay.error_notifier.send(error).unwrap();
|
||||
|
||||
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
}
|
||||
@@ -57,19 +58,21 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
|
||||
request.push(0); // null terminate string
|
||||
request.extend_from_slice(&(size as u32).to_be_bytes());
|
||||
|
||||
let mut send_data = save_data.to_owned();
|
||||
send_data.resize(size, 0); // pad with zeros if needed
|
||||
request.extend(send_data);
|
||||
if size > 0 {
|
||||
request.extend(save_data.to_owned());
|
||||
}
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
}
|
||||
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>, size: usize) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE].to_vec();
|
||||
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
|
||||
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
|
||||
request.extend_from_slice(save_type.as_bytes());
|
||||
request.push(0); // null terminate string
|
||||
netplay.tcp_stream.write_all(&request).unwrap();
|
||||
|
||||
let mut response: Vec<u8> = vec![0; size];
|
||||
let mut size: [u8; 4] = [0; 4];
|
||||
netplay.tcp_stream.read_exact(&mut size).unwrap();
|
||||
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
|
||||
netplay.tcp_stream.read_exact(&mut response).unwrap();
|
||||
*save_data = response;
|
||||
}
|
||||
@@ -216,19 +219,26 @@ pub fn init(
|
||||
mut peer_addr: std::net::SocketAddr,
|
||||
session: ui::gui::gui_netplay::NetplayRoom,
|
||||
player_number: u8,
|
||||
error_notifier: tokio::sync::mpsc::Sender<String>,
|
||||
error_notifier: std::sync::mpsc::Sender<String>,
|
||||
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();
|
||||
};
|
||||
let socket_ref = socket2::SockRef::from(&udp_socket);
|
||||
socket_ref.set_tos(CS4 << 2).unwrap();
|
||||
|
||||
udp_socket.connect(peer_addr).unwrap();
|
||||
udp_socket.set_nonblocking(true).unwrap();
|
||||
|
||||
|
||||
@@ -6,80 +6,113 @@ pub mod storage;
|
||||
pub mod video;
|
||||
pub mod vru;
|
||||
|
||||
pub struct Dirs {
|
||||
pub config_dir: std::path::PathBuf,
|
||||
pub cache_dir: std::path::PathBuf,
|
||||
pub data_dir: std::path::PathBuf,
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
pub dirs: Dirs,
|
||||
pub keyboard_state: *const bool,
|
||||
pub controllers: [input::Controllers; 4],
|
||||
pub config_file_path: std::path::PathBuf,
|
||||
pub config: config::Config,
|
||||
pub save_type: Vec<storage::SaveTypes>,
|
||||
pub game_id: String,
|
||||
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) {
|
||||
write_config(self);
|
||||
unsafe {
|
||||
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
|
||||
sdl3_sys::init::SDL_Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(ui: &Ui) {
|
||||
let f = std::fs::File::create(ui.config_file_path.clone()).unwrap();
|
||||
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) {
|
||||
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
|
||||
.to_str()
|
||||
.unwrap();
|
||||
panic!("Could not initialize SDL subsystem: {}, {}", flag, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dirs() -> Dirs {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let portable_dir = exe_path.parent();
|
||||
let portable = portable_dir.unwrap().join("portable.txt").exists();
|
||||
let config_dir;
|
||||
let cache_dir;
|
||||
let data_dir;
|
||||
if portable {
|
||||
config_dir = portable_dir.unwrap().join("portable_data").join("config");
|
||||
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
|
||||
data_dir = portable_dir.unwrap().join("portable_data").join("data");
|
||||
} else {
|
||||
config_dir = dirs::config_dir().unwrap().join("gopher64");
|
||||
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
|
||||
data_dir = dirs::data_dir().unwrap().join("gopher64");
|
||||
};
|
||||
|
||||
Dirs {
|
||||
config_dir,
|
||||
cache_dir,
|
||||
data_dir,
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
let config_file_path = config_dir.join("config.json");
|
||||
let config_file = std::fs::read(config_file_path.clone());
|
||||
let mut config_map = config::Config::new();
|
||||
if config_file.is_ok() {
|
||||
let result = serde_json::from_slice(config_file.unwrap().as_ref());
|
||||
if result.is_ok() {
|
||||
config_map = result.unwrap();
|
||||
}
|
||||
pub fn new() -> Ui {
|
||||
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
|
||||
let mut num_joysticks = 0;
|
||||
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
|
||||
if joysticks.is_null() {
|
||||
panic!("Could not get joystick list");
|
||||
}
|
||||
|
||||
let dirs = get_dirs();
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
config_file_path,
|
||||
config: config_map,
|
||||
keyboard_state: std::ptr::null_mut(),
|
||||
config: config::Config::new(),
|
||||
save_type: vec![],
|
||||
game_id: String::new(),
|
||||
game_hash: String::new(),
|
||||
@@ -95,16 +128,20 @@ impl Ui {
|
||||
sram: (Vec::new(), false),
|
||||
flash: (Vec::new(), false),
|
||||
mempak: (Vec::new(), false),
|
||||
romsave: (serde_json::Map::new(), false),
|
||||
romsave: (std::collections::HashMap::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,
|
||||
dirs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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");
|
||||
}
|
||||
}
|
||||
|
||||
+25
-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,
|
||||
}
|
||||
|
||||
@@ -31,8 +31,30 @@ pub struct Config {
|
||||
pub video: Video,
|
||||
}
|
||||
|
||||
impl Drop for Config {
|
||||
fn drop(&mut self) {
|
||||
write_config(self);
|
||||
}
|
||||
}
|
||||
|
||||
fn write_config(config: &Config) {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("config.json");
|
||||
let f = std::fs::File::create(file_path).unwrap();
|
||||
serde_json::to_writer_pretty(f, &config).unwrap();
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Config {
|
||||
let dirs = ui::get_dirs();
|
||||
let file_path = dirs.config_dir.join("config.json");
|
||||
let config_file = std::fs::read(file_path);
|
||||
if config_file.is_ok() {
|
||||
let result = serde_json::from_slice(config_file.unwrap().as_ref());
|
||||
if let Ok(result) = result {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
let mut input_profiles = std::collections::HashMap::new();
|
||||
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
|
||||
Config {
|
||||
|
||||
+163
-117
@@ -2,28 +2,28 @@ use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
use eframe::egui;
|
||||
|
||||
pub mod gui_netplay;
|
||||
|
||||
pub struct GopherEguiApp {
|
||||
config_dir: std::path::PathBuf,
|
||||
cache_dir: std::path::PathBuf,
|
||||
data_dir: std::path::PathBuf,
|
||||
dirs: ui::Dirs,
|
||||
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>,
|
||||
controller_enabled: [bool; 4],
|
||||
controller_paths: Vec<String>,
|
||||
upscale: bool,
|
||||
integer_scaling: bool,
|
||||
fullscreen: bool,
|
||||
emulate_vru: bool,
|
||||
dinput: bool,
|
||||
show_vru_dialog: bool,
|
||||
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
|
||||
netplay_error_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
|
||||
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
|
||||
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
|
||||
netplay_error_receiver: Option<std::sync::mpsc::Receiver<String>>,
|
||||
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
|
||||
vru_word_list: Vec<String>,
|
||||
pub netplay: gui_netplay::GuiNetplay,
|
||||
}
|
||||
@@ -38,110 +38,116 @@ struct SaveConfig {
|
||||
emulate_vru: bool,
|
||||
}
|
||||
|
||||
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
|
||||
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
|
||||
let mut profiles = vec![];
|
||||
for key in game_ui.config.input.input_profiles.keys() {
|
||||
profiles.push((*key).clone())
|
||||
for key in config.input.input_profiles.keys() {
|
||||
profiles.push(key.clone())
|
||||
}
|
||||
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<'_>,
|
||||
config_dir: std::path::PathBuf,
|
||||
cache_dir: std::path::PathBuf,
|
||||
data_dir: std::path::PathBuf,
|
||||
controller_paths: Vec<String>,
|
||||
controller_names: Vec<String>,
|
||||
) -> 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 config = ui::config::Config::new();
|
||||
|
||||
let mut selected_controller = [-1, -1, -1, -1];
|
||||
for (pos, item) in game_ui
|
||||
.config
|
||||
.input
|
||||
.controller_assignment
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
for (pos, item) in config.input.controller_assignment.iter().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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GopherEguiApp {
|
||||
cache_dir: cache_dir.clone(),
|
||||
config_dir: config_dir.clone(),
|
||||
data_dir: data_dir.clone(),
|
||||
configure_profile: false,
|
||||
profile_name: "".to_string(),
|
||||
selected_profile: game_ui.config.input.input_profile_binding.clone(),
|
||||
selected_profile: config.input.input_profile_binding.clone(),
|
||||
selected_controller,
|
||||
controllers: get_controllers(&game_ui),
|
||||
input_profiles: get_input_profiles(&game_ui),
|
||||
controller_enabled: game_ui.config.input.controller_enabled,
|
||||
upscale: game_ui.config.video.upscale,
|
||||
integer_scaling: game_ui.config.video.integer_scaling,
|
||||
fullscreen: game_ui.config.video.fullscreen,
|
||||
emulate_vru: game_ui.config.input.emulate_vru,
|
||||
controller_names,
|
||||
input_profiles: get_input_profiles(&config),
|
||||
controller_enabled: config.input.controller_enabled,
|
||||
upscale: config.video.upscale,
|
||||
integer_scaling: config.video.integer_scaling,
|
||||
fullscreen: config.video.fullscreen,
|
||||
emulate_vru: config.input.emulate_vru,
|
||||
show_vru_dialog: false,
|
||||
dinput: false,
|
||||
controller_paths,
|
||||
netplay_error_receiver: None,
|
||||
vru_window_receiver: None,
|
||||
vru_word_notifier: None,
|
||||
vru_word_list: Vec::new(),
|
||||
netplay: Default::default(),
|
||||
dirs: ui::get_dirs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_config(game_ui: &mut ui::Ui, save_config_items: SaveConfig) {
|
||||
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
|
||||
fn save_config(
|
||||
config: &mut ui::config::Config,
|
||||
controller_paths: Vec<String>,
|
||||
save_config_items: SaveConfig,
|
||||
) {
|
||||
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(),
|
||||
);
|
||||
config.input.controller_assignment[pos] =
|
||||
Some(controller_paths[*item as usize].clone());
|
||||
} else {
|
||||
game_ui.config.input.controller_assignment[pos] = None
|
||||
config.input.controller_assignment[pos] = None
|
||||
}
|
||||
}
|
||||
|
||||
game_ui.config.input.input_profile_binding = save_config_items.selected_profile;
|
||||
game_ui.config.input.controller_enabled = save_config_items.controller_enabled;
|
||||
config.input.input_profile_binding = save_config_items.selected_profile;
|
||||
config.input.controller_enabled = save_config_items.controller_enabled;
|
||||
|
||||
game_ui.config.video.upscale = save_config_items.upscale;
|
||||
game_ui.config.video.integer_scaling = save_config_items.integer_scaling;
|
||||
game_ui.config.video.fullscreen = save_config_items.fullscreen;
|
||||
game_ui.config.input.emulate_vru = save_config_items.emulate_vru;
|
||||
config.video.upscale = save_config_items.upscale;
|
||||
config.video.integer_scaling = save_config_items.integer_scaling;
|
||||
config.video.fullscreen = save_config_items.fullscreen;
|
||||
config.input.emulate_vru = save_config_items.emulate_vru;
|
||||
}
|
||||
|
||||
impl Drop for GopherEguiApp {
|
||||
fn drop(&mut self) {
|
||||
let mut game_ui = ui::Ui::new(self.config_dir.clone());
|
||||
let save_config_items = SaveConfig {
|
||||
selected_controller: self.selected_controller,
|
||||
selected_profile: self.selected_profile.clone(),
|
||||
@@ -151,7 +157,12 @@ impl Drop for GopherEguiApp {
|
||||
fullscreen: self.fullscreen,
|
||||
emulate_vru: self.emulate_vru,
|
||||
};
|
||||
save_config(&mut game_ui, save_config_items);
|
||||
let mut config = ui::config::Config::new();
|
||||
save_config(
|
||||
&mut config,
|
||||
self.controller_paths.clone(),
|
||||
save_config_items,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,11 +179,25 @@ fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Configure Profile").clicked() {
|
||||
let profile_name = app.profile_name.clone();
|
||||
let config_dir = app.config_dir.clone();
|
||||
let dinput = app.dinput;
|
||||
tokio::spawn(async move {
|
||||
let mut game_ui = ui::Ui::new(config_dir);
|
||||
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
|
||||
std::thread::spawn(move || {
|
||||
if cfg!(target_os = "macos") {
|
||||
let mut command =
|
||||
std::process::Command::new(std::env::current_exe().unwrap());
|
||||
if dinput {
|
||||
command.arg("--use-dinput");
|
||||
}
|
||||
command.arg("--configure-input-profile");
|
||||
command.arg(profile_name);
|
||||
|
||||
let status = command.status().expect("failed to execute process");
|
||||
if !status.success() {
|
||||
panic!("process exited with: {}", status);
|
||||
}
|
||||
} else {
|
||||
let mut game_ui = ui::Ui::new();
|
||||
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
|
||||
}
|
||||
});
|
||||
app.configure_profile = false;
|
||||
if !app.profile_name.is_empty()
|
||||
@@ -211,7 +236,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.vru_word_notifier
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.try_send(v.clone())
|
||||
.send(v.clone())
|
||||
.unwrap();
|
||||
app.show_vru_dialog = false;
|
||||
}
|
||||
@@ -223,7 +248,7 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.vru_word_notifier
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.try_send(String::from(""))
|
||||
.send(String::from(""))
|
||||
.unwrap();
|
||||
app.show_vru_dialog = false;
|
||||
}
|
||||
@@ -232,7 +257,6 @@ fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let task;
|
||||
let netplay;
|
||||
|
||||
let selected_controller = app.selected_controller;
|
||||
@@ -242,21 +266,18 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
let integer_scaling = app.integer_scaling;
|
||||
let fullscreen = app.fullscreen;
|
||||
let emulate_vru = app.emulate_vru;
|
||||
let config_dir = app.config_dir.clone();
|
||||
let cache_dir = app.cache_dir.clone();
|
||||
let data_dir = app.data_dir.clone();
|
||||
let peer_addr;
|
||||
let session;
|
||||
let player_number;
|
||||
let cache_dir = app.dirs.cache_dir.clone();
|
||||
let controller_paths = app.controller_paths.clone();
|
||||
|
||||
if app.netplay.player_name.is_empty() {
|
||||
task = Some(rfd::AsyncFileDialog::new().pick_file());
|
||||
netplay = false;
|
||||
peer_addr = None;
|
||||
session = None;
|
||||
player_number = None;
|
||||
} else {
|
||||
task = None;
|
||||
netplay = true;
|
||||
peer_addr = app.netplay.peer_addr;
|
||||
session = app.netplay.waiting_session.clone();
|
||||
@@ -264,19 +285,19 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
|
||||
let (netplay_error_notifier, netplay_error_receiver): (
|
||||
tokio::sync::mpsc::Sender<String>,
|
||||
tokio::sync::mpsc::Receiver<String>,
|
||||
) = tokio::sync::mpsc::channel(8);
|
||||
std::sync::mpsc::Sender<String>,
|
||||
std::sync::mpsc::Receiver<String>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
let (vru_window_notifier, vru_window_receiver): (
|
||||
tokio::sync::mpsc::Sender<Vec<String>>,
|
||||
tokio::sync::mpsc::Receiver<Vec<String>>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
std::sync::mpsc::Sender<Vec<String>>,
|
||||
std::sync::mpsc::Receiver<Vec<String>>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
let (vru_word_notifier, vru_word_receiver): (
|
||||
tokio::sync::mpsc::Sender<String>,
|
||||
tokio::sync::mpsc::Receiver<String>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
std::sync::mpsc::Sender<String>,
|
||||
std::sync::mpsc::Receiver<String>,
|
||||
) = std::sync::mpsc::channel();
|
||||
|
||||
if netplay {
|
||||
app.netplay_error_receiver = Some(netplay_error_receiver);
|
||||
@@ -291,10 +312,41 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
|
||||
let rom_contents = app.netplay.rom_contents.clone();
|
||||
let gui_ctx = ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
let file = if !netplay { task.unwrap().await } else { None };
|
||||
std::thread::spawn(move || {
|
||||
let file = if !netplay {
|
||||
rfd::FileDialog::new().pick_file()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if file.is_some() || netplay {
|
||||
let save_config_items = SaveConfig {
|
||||
selected_controller,
|
||||
selected_profile,
|
||||
controller_enabled,
|
||||
upscale,
|
||||
integer_scaling,
|
||||
fullscreen,
|
||||
emulate_vru,
|
||||
};
|
||||
|
||||
if cfg!(target_os = "macos") && file.is_some() {
|
||||
// mac os requires the process to be started on the main thread
|
||||
// this means that netplay and VRU emulation will not work on mac os
|
||||
{
|
||||
let mut config = ui::config::Config::new();
|
||||
save_config(&mut config, controller_paths, save_config_items);
|
||||
}
|
||||
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
|
||||
if fullscreen {
|
||||
command.arg("--fullscreen");
|
||||
}
|
||||
command.arg(file.unwrap().as_path());
|
||||
|
||||
let status = command.status().expect("failed to execute process");
|
||||
if !status.success() {
|
||||
panic!("process exited with: {}", status);
|
||||
}
|
||||
} else if file.is_some() || netplay {
|
||||
let running_file = cache_dir.join("game_running");
|
||||
if running_file.exists() {
|
||||
println!("Game already running");
|
||||
@@ -304,18 +356,9 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if result.is_err() {
|
||||
panic!("could not create running file: {}", result.err().unwrap())
|
||||
}
|
||||
let mut device = device::Device::new(config_dir);
|
||||
|
||||
let save_config_items = SaveConfig {
|
||||
selected_controller,
|
||||
selected_profile,
|
||||
controller_enabled,
|
||||
upscale,
|
||||
integer_scaling,
|
||||
fullscreen,
|
||||
emulate_vru,
|
||||
};
|
||||
save_config(&mut device.ui, save_config_items);
|
||||
let mut device = device::Device::new();
|
||||
save_config(&mut device.ui.config, controller_paths, save_config_items);
|
||||
|
||||
if netplay {
|
||||
device.netplay = Some(netplay::init(
|
||||
@@ -325,7 +368,7 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
netplay_error_notifier,
|
||||
gui_ctx,
|
||||
));
|
||||
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
|
||||
device::run_game(rom_contents, &mut device, fullscreen);
|
||||
netplay::close(&mut device);
|
||||
} else {
|
||||
if emulate_vru {
|
||||
@@ -334,14 +377,14 @@ pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
device.vru.gui_ctx = Some(gui_ctx);
|
||||
}
|
||||
|
||||
let rom_contents = device::get_rom_contents(file.unwrap().path());
|
||||
let rom_contents = device::get_rom_contents(file.unwrap().as_path());
|
||||
if rom_contents.is_empty() {
|
||||
println!("Could not read rom file");
|
||||
} else {
|
||||
device::run_game(rom_contents, data_dir, &mut device, fullscreen);
|
||||
device::run_game(rom_contents, &mut device, fullscreen);
|
||||
}
|
||||
}
|
||||
let result = std::fs::remove_file(running_file.clone());
|
||||
let result = std::fs::remove_file(running_file);
|
||||
if result.is_err() {
|
||||
panic!("could not remove running file: {}", result.err().unwrap())
|
||||
}
|
||||
@@ -364,7 +407,7 @@ impl eframe::App for GopherEguiApp {
|
||||
}
|
||||
|
||||
if self.netplay_error_receiver.is_some() {
|
||||
let result = self.netplay_error_receiver.as_mut().unwrap().try_recv();
|
||||
let result = self.netplay_error_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
self.netplay.error = result.unwrap();
|
||||
}
|
||||
@@ -388,9 +431,9 @@ impl eframe::App for GopherEguiApp {
|
||||
if ui.button("Open ROM").clicked() {
|
||||
open_rom(self, ctx);
|
||||
}
|
||||
|
||||
if ui.button("Netplay: Create Session").clicked()
|
||||
&& !self.cache_dir.join("game_running").exists()
|
||||
if !cfg!(target_os = "macos")
|
||||
&& ui.button("Netplay: Create Session").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.netplay.create = true;
|
||||
}
|
||||
@@ -398,13 +441,14 @@ impl eframe::App for GopherEguiApp {
|
||||
ui.end_row();
|
||||
|
||||
if ui.button("Configure Input Profile").clicked()
|
||||
&& !self.cache_dir.join("game_running").exists()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.configure_profile = true;
|
||||
}
|
||||
|
||||
if ui.button("Netplay: Join Session").clicked()
|
||||
&& !self.cache_dir.join("game_running").exists()
|
||||
if !cfg!(target_os = "macos")
|
||||
&& ui.button("Netplay: Join Session").clicked()
|
||||
&& !self.dirs.cache_dir.join("game_running").exists()
|
||||
{
|
||||
self.netplay.join = true;
|
||||
}
|
||||
@@ -437,7 +481,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 +491,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(),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -463,16 +507,18 @@ impl eframe::App for GopherEguiApp {
|
||||
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
|
||||
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
|
||||
ui.add_space(32.0);
|
||||
ui.checkbox(
|
||||
&mut self.emulate_vru,
|
||||
"Emulate VRU (connects VRU to controller port 4)",
|
||||
);
|
||||
if !cfg!(target_os = "macos") {
|
||||
ui.checkbox(
|
||||
&mut self.emulate_vru,
|
||||
"Emulate VRU (connects VRU to controller port 4)",
|
||||
);
|
||||
}
|
||||
ui.add_space(32.0);
|
||||
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
|
||||
});
|
||||
|
||||
if self.emulate_vru && self.vru_window_receiver.is_some() {
|
||||
let result = self.vru_window_receiver.as_mut().unwrap().try_recv();
|
||||
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
self.show_vru_dialog = true;
|
||||
self.vru_word_list = result.unwrap();
|
||||
|
||||
+66
-65
@@ -41,8 +41,8 @@ pub struct GuiNetplay {
|
||||
pub socket:
|
||||
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
|
||||
pub server_receiver:
|
||||
Option<tokio::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
|
||||
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<GameInfo>>,
|
||||
Option<std::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
|
||||
pub game_info_receiver: Option<std::sync::mpsc::Receiver<GameInfo>>,
|
||||
pub broadcast_socket: Option<std::net::UdpSocket>,
|
||||
pub broadcast_timer: Option<std::time::Instant>,
|
||||
}
|
||||
@@ -99,20 +99,17 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_none() {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.server_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
std::thread::spawn(move || {
|
||||
if let Ok(response) =
|
||||
reqwest::get("https://m64p.s3.amazonaws.com/servers.json").await
|
||||
{
|
||||
if let Ok(servers) = response
|
||||
reqwest::blocking::get("https://m64p.s3.amazonaws.com/servers.json")
|
||||
.unwrap()
|
||||
.json::<std::collections::HashMap<String, String>>()
|
||||
.await
|
||||
{
|
||||
let _ = tx.send(servers).await;
|
||||
gui_ctx.request_repaint();
|
||||
}
|
||||
{
|
||||
tx.send(response).unwrap();
|
||||
gui_ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -146,7 +143,7 @@ fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.server_receiver.is_some() {
|
||||
let result = app.netplay.server_receiver.as_mut().unwrap().try_recv();
|
||||
let result = app.netplay.server_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.servers.extend(result.unwrap());
|
||||
app.netplay.server_receiver = None;
|
||||
@@ -184,25 +181,23 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
app.netplay.create_rom_label = "Open ROM".to_string();
|
||||
}
|
||||
if ui.button(&app.netplay.create_rom_label).clicked() {
|
||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.create_rom_label = "Inspecting ROM".to_string();
|
||||
tokio::spawn(async move {
|
||||
let file = task.await;
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx).await
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Open ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Open ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
@@ -222,7 +217,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
get_servers(app, ctx);
|
||||
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
@@ -347,7 +342,7 @@ pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
|
||||
fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
if app.netplay.have_sessions.is_some()
|
||||
&& app.netplay.server != app.netplay.have_sessions.as_ref().unwrap().clone()
|
||||
&& app.netplay.server != *app.netplay.have_sessions.as_ref().unwrap()
|
||||
{
|
||||
// User has changed the server
|
||||
app.netplay.have_sessions = None;
|
||||
@@ -396,8 +391,8 @@ 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());
|
||||
fn parse_rom_file(file: std::path::PathBuf, tx: std::sync::mpsc::Sender<GameInfo>) {
|
||||
let rom_contents = device::get_rom_contents(file.as_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])
|
||||
@@ -405,18 +400,21 @@ async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<Gam
|
||||
.trim()
|
||||
.replace('\0', "")
|
||||
.to_string();
|
||||
let _ = tx
|
||||
.send((hash, game_name, file.file_name(), rom_contents))
|
||||
.await;
|
||||
tx.send((
|
||||
hash,
|
||||
game_name,
|
||||
file.file_name().unwrap().to_string_lossy().to_string(),
|
||||
rom_contents,
|
||||
))
|
||||
.unwrap();
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"Invalid ROM".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +444,7 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
if app.netplay.game_info_receiver.is_some() {
|
||||
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
|
||||
let result = app.netplay.game_info_receiver.as_ref().unwrap().try_recv();
|
||||
if result.is_ok() {
|
||||
app.netplay.game_info_receiver = None;
|
||||
let data = result.unwrap();
|
||||
@@ -587,25 +585,23 @@ pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
{
|
||||
app.netplay.error = "Session requires a password".to_string();
|
||||
} else {
|
||||
let task = rfd::AsyncFileDialog::new().pick_file();
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
app.netplay.game_info_receiver = Some(rx);
|
||||
let gui_ctx = ctx.clone();
|
||||
app.netplay.join_rom_label = "Inspecting ROM".to_string();
|
||||
tokio::spawn(async move {
|
||||
let file = task.await;
|
||||
std::thread::spawn(move || {
|
||||
let file = rfd::FileDialog::new().pick_file();
|
||||
|
||||
if let Some(file) = file {
|
||||
parse_rom_file(file, tx).await
|
||||
parse_rom_file(file, tx);
|
||||
} else {
|
||||
let _ = tx
|
||||
.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"No ROM selected".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.await;
|
||||
tx.send((
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"No ROM selected".to_string(),
|
||||
vec![],
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
gui_ctx.request_repaint();
|
||||
});
|
||||
@@ -757,17 +753,22 @@ pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
app.netplay.player_number = player as u8;
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
gui::open_rom(app, ctx);
|
||||
app.netplay = Default::default();
|
||||
return;
|
||||
if message.accept.unwrap() == 0 {
|
||||
socket.close(None).unwrap();
|
||||
loop {
|
||||
match socket.read() {
|
||||
Err(tungstenite::Error::ConnectionClosed) => break,
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
gui::open_rom(app, ctx);
|
||||
app.netplay = Default::default();
|
||||
return;
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.netplay.error = message.message.unwrap();
|
||||
|
||||
+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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
-41
@@ -1,4 +1,3 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
@@ -27,7 +26,7 @@ pub struct Saves {
|
||||
pub sram: (Vec<u8>, bool),
|
||||
pub flash: (Vec<u8>, bool),
|
||||
pub mempak: (Vec<u8>, bool),
|
||||
pub romsave: (serde_json::Map<String, serde_json::Value>, bool),
|
||||
pub romsave: (std::collections::HashMap<u32, u8>, bool),
|
||||
}
|
||||
|
||||
fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
|
||||
@@ -88,11 +87,11 @@ fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, data_dir: std::path::PathBuf) {
|
||||
pub fn init(ui: &mut ui::Ui) {
|
||||
let id = ui.game_id.as_str();
|
||||
ui.save_type = get_save_type(id);
|
||||
|
||||
let base_path = data_dir.join("saves");
|
||||
let base_path = ui.dirs.data_dir.join("saves");
|
||||
|
||||
ui.paths.eep_file_path.clone_from(&base_path);
|
||||
ui.paths
|
||||
@@ -138,12 +137,9 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
if mempak.is_ok() {
|
||||
ui.saves.mempak.0 = mempak.unwrap();
|
||||
}
|
||||
if netplay.is_none() {
|
||||
// can't do romsaves with the current netplay implementation
|
||||
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
|
||||
if romsave.is_ok() {
|
||||
ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
|
||||
}
|
||||
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
|
||||
if romsave.is_ok() {
|
||||
ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,58 +149,53 @@ pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&ui.saves.eeprom.0,
|
||||
device::cart::EEPROM_MAX_SIZE,
|
||||
ui.saves.eeprom.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&ui.saves.sram.0,
|
||||
device::sram::SRAM_SIZE,
|
||||
ui.saves.sram.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&ui.saves.flash.0,
|
||||
device::sram::FLASHRAM_SIZE,
|
||||
ui.saves.flash.0.len(),
|
||||
);
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&ui.saves.mempak.0,
|
||||
device::controller::mempak::MEMPAK_SIZE * 4,
|
||||
ui.saves.mempak.0.len(),
|
||||
);
|
||||
let mut romsave_bytes: Vec<u8> = vec![];
|
||||
if !ui.saves.romsave.0.is_empty() {
|
||||
romsave_bytes = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
|
||||
}
|
||||
netplay::send_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"rom",
|
||||
&romsave_bytes,
|
||||
romsave_bytes.len(),
|
||||
);
|
||||
} else {
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"eep",
|
||||
&mut ui.saves.eeprom.0,
|
||||
device::cart::EEPROM_MAX_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"sra",
|
||||
&mut ui.saves.sram.0,
|
||||
device::sram::SRAM_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"fla",
|
||||
&mut ui.saves.flash.0,
|
||||
device::sram::FLASHRAM_SIZE,
|
||||
);
|
||||
netplay::receive_save(
|
||||
netplay.as_mut().unwrap(),
|
||||
"mpk",
|
||||
&mut ui.saves.mempak.0,
|
||||
device::controller::mempak::MEMPAK_SIZE * 4,
|
||||
);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0);
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0);
|
||||
let mut romsave_bytes: Vec<u8> = vec![];
|
||||
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut romsave_bytes);
|
||||
if !romsave_bytes.is_empty() {
|
||||
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rom_save(ui: &ui::Ui) {
|
||||
let f = std::fs::File::create(ui.paths.romsave_file_path.clone()).unwrap();
|
||||
serde_json::to_writer(f, &ui.saves.romsave.0).unwrap();
|
||||
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
|
||||
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
|
||||
@@ -221,7 +212,7 @@ pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
|
||||
if ui.saves.mempak.1 {
|
||||
write_save(ui, SaveTypes::Mempak)
|
||||
}
|
||||
if ui.saves.romsave.1 && netplay.is_none() {
|
||||
if ui.saves.romsave.1 {
|
||||
write_save(ui, SaveTypes::Romsave)
|
||||
}
|
||||
}
|
||||
|
||||
+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");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,14 +4,14 @@ use crate::ui;
|
||||
|
||||
pub fn prompt_for_match(
|
||||
words: &[String],
|
||||
window_notifier: &tokio::sync::mpsc::Sender<Vec<String>>,
|
||||
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
|
||||
window_notifier: &std::sync::mpsc::Sender<Vec<String>>,
|
||||
word_index_receiver: &std::sync::mpsc::Receiver<String>,
|
||||
gui_ctx: &egui::Context,
|
||||
) -> u16 {
|
||||
let mut dedup_words = words.to_owned();
|
||||
dedup_words.sort();
|
||||
dedup_words.dedup();
|
||||
window_notifier.try_send(dedup_words).unwrap();
|
||||
window_notifier.send(dedup_words).unwrap();
|
||||
gui_ctx.request_repaint(); // this is so the window pops up right away
|
||||
let mut result = word_index_receiver.try_recv();
|
||||
while result.is_err() {
|
||||
|
||||
Reference in New Issue
Block a user