mirror of
https://github.com/gopher64/gopher64.git
synced 2026-07-11 09:35:21 +02:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 581cc0b9aa | |||
| 6f6e21c718 | |||
| 181774a1de | |||
| 002fbbe8a4 | |||
| ab630cc8cb | |||
| 06f9c56a79 | |||
| c3f4b11519 | |||
| 921a0d5362 | |||
| 24fc5b9638 | |||
| 1559856483 | |||
| 1deb91fc1e | |||
| 726a58f854 | |||
| fe0297690f | |||
| 847df61526 | |||
| 6de8128f5b | |||
| 6c09176d12 | |||
| 2050098735 | |||
| d3ecc37da6 | |||
| a0237e9f7d | |||
| d6d608b674 | |||
| f254bec968 | |||
| bd4aff2b2c | |||
| 0bf70a77ea | |||
| e23e7f33c1 | |||
| 9d0516545e | |||
| e3023a902f | |||
| 431c7c2a8d | |||
| cf99539f85 | |||
| c2493b829e | |||
| 2f25de55d7 | |||
| 116ffe9845 | |||
| f3271cba63 | |||
| 9037680cda | |||
| fc41e26a6d | |||
| d1f28bb7a8 | |||
| 9db831fdef | |||
| 004b603e96 | |||
| 8661e2c9b1 | |||
| c712f2a288 | |||
| 366162e6fe | |||
| 2b93fb8aa1 | |||
| a191701610 | |||
| 0605967c28 | |||
| d6e6a16814 | |||
| e374825d5e | |||
| f6d6746207 | |||
| 100238f198 | |||
| daee53a626 | |||
| 018d8ca3e3 | |||
| 24d26d864e | |||
| 7b8c549e8e | |||
| f6c7e3d3c0 | |||
| 374b01dccf | |||
| 569b26a067 | |||
| 380d196ae7 | |||
| 4fa1a8ad39 | |||
| 8acf3b9de8 | |||
| b3297f82b2 | |||
| 2127705e7b | |||
| cccb079d63 | |||
| c52b680753 | |||
| 8799650245 | |||
| 90be2130c1 | |||
| 96af33c434 | |||
| 0a42152bcb | |||
| db335e24d2 | |||
| 4857b86897 | |||
| 26022fff72 | |||
| 658dede9e9 | |||
| 6ad1a31e34 | |||
| 4476d476de | |||
| 8037fb426e | |||
| f0381a2cd1 | |||
| c8e70e6125 | |||
| e9664b1087 | |||
| 3f72700da0 | |||
| 9fcd2835eb | |||
| c6767c94a6 | |||
| f1968e3a58 | |||
| 23fdfff1ac | |||
| fc512d9517 | |||
| 3c317460fd | |||
| b1fcaa5cf4 | |||
| 98c9cf142d | |||
| 1eb1503981 | |||
| 5aa9a65e7b | |||
| 71f1d98132 | |||
| 0037c0f344 |
+1
-1
@@ -1,4 +1,4 @@
|
||||
[target.'cfg(target_os = "linux")']
|
||||
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
|
||||
|
||||
[target.'cfg(target_os = "windows")']
|
||||
|
||||
@@ -3,7 +3,11 @@ updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "rust-toolchain"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# - package-ecosystem: "cargo"
|
||||
# directory: "/"
|
||||
# schedule:
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
arch: linux-aarch64
|
||||
llvm: ARM64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
|
||||
version: ${{ matrix.target }}-1.0
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
@@ -60,8 +60,10 @@ jobs:
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
env:
|
||||
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
@@ -81,7 +83,7 @@ jobs:
|
||||
# arch: windows-aarch64
|
||||
# llvm: woa64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
@@ -102,15 +104,19 @@ jobs:
|
||||
cargo build --release -v --target=${{ matrix.target }}
|
||||
mkdir output
|
||||
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
|
||||
env:
|
||||
CC: clang-cl
|
||||
CXX: clang-cl
|
||||
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
|
||||
- name: Upload unsigned artifact
|
||||
id: upload-unsigned-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}.exe
|
||||
- name: SignPath
|
||||
if: ${{ github.ref_type == 'tag' }}
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
uses: signpath/github-action-submit-signing-request@v2.0
|
||||
with:
|
||||
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
|
||||
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
|
||||
@@ -118,8 +124,10 @@ jobs:
|
||||
artifact-configuration-slug: "gopher64-${{ matrix.arch }}"
|
||||
signing-policy-slug: "release-signing"
|
||||
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
|
||||
wait-for-completion: false
|
||||
|
||||
build-macos:
|
||||
if: false
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -130,7 +138,7 @@ jobs:
|
||||
- target: aarch64-apple-darwin
|
||||
arch: mac-aarch64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
@@ -144,8 +152,9 @@ jobs:
|
||||
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
|
||||
env:
|
||||
AR: ar
|
||||
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
|
||||
- name: Upload file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: gopher64-${{ matrix.arch }}
|
||||
path: output/gopher64-${{ matrix.arch }}
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
lint-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
|
||||
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
|
||||
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
|
||||
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
|
||||
version: lint-1.0
|
||||
- name: Install LLVM and Clang
|
||||
uses: KyleMayes/install-llvm-action@v2
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
checks: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: rustsec/audit-check@v2
|
||||
|
||||
@@ -2,3 +2,6 @@
|
||||
path = parallel-rdp/parallel-rdp-standalone
|
||||
url = https://github.com/gopher64/parallel-rdp-standalone.git
|
||||
branch = gopher64
|
||||
[submodule "src/compat/sse2neon"]
|
||||
path = src/compat/sse2neon
|
||||
url = https://github.com/DLTcollab/sse2neon.git
|
||||
|
||||
Generated
+1342
-995
File diff suppressed because it is too large
Load Diff
+14
-7
@@ -1,32 +1,32 @@
|
||||
[package]
|
||||
name = "gopher64"
|
||||
version = "1.1.2"
|
||||
version = "1.1.10"
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
rust-version = "1.91"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dirs = "6.0"
|
||||
zip = "4.3"
|
||||
zip = "6.0"
|
||||
governor = "0.10"
|
||||
sevenz-rust2 = { version = "0.17", default-features = false }
|
||||
sevenz-rust2 = { version = "0.19", default-features = false }
|
||||
chrono = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-big-array = "0.5"
|
||||
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-femtovg"] }
|
||||
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia-vulkan"] }
|
||||
open = "5.3"
|
||||
sha2 = "0.10"
|
||||
ab_glyph = "0.2"
|
||||
sdl3-sys = { version = "0.5", features = ["build-from-source-static"] }
|
||||
sdl3-ttf-sys = { version = "0.2", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
|
||||
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
|
||||
tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] }
|
||||
spin_sleep = "1.3"
|
||||
encoding_rs = "0.8"
|
||||
mimalloc = "0.1"
|
||||
regex = "1.11"
|
||||
tokio-tungstenite = "0.27"
|
||||
tokio-tungstenite = "0.28"
|
||||
futures = "0.3"
|
||||
socket2 = { version = "0.6", features = ["all"] }
|
||||
rand_chacha = "0.9"
|
||||
@@ -45,3 +45,10 @@ cc = { version = "1.2", features = ["parallel"] }
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = "thin"
|
||||
strip = true
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
strip = false
|
||||
panic = "unwind"
|
||||
|
||||
@@ -54,6 +54,7 @@ fn main() {
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/timeline_trace_file.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/environment.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/thread_name.cpp")
|
||||
.file("parallel-rdp/parallel-rdp-standalone/util/slab_allocator.cpp")
|
||||
.file("parallel-rdp/interface.cpp")
|
||||
.file("parallel-rdp/wsi_platform.cpp")
|
||||
.include("parallel-rdp/parallel-rdp-standalone/parallel-rdp")
|
||||
@@ -64,11 +65,14 @@ fn main() {
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
|
||||
.join("include"),
|
||||
)
|
||||
.include(
|
||||
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_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();
|
||||
let profile = std::env::var("PROFILE").unwrap();
|
||||
let opt_flag = if arch == "x86_64" {
|
||||
"-march=x86-64-v3"
|
||||
} else if arch == "aarch64" {
|
||||
@@ -91,11 +95,10 @@ fn main() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if profile == "release" {
|
||||
volk_build.flag("-flto=thin");
|
||||
rdp_build.flag("-flto=thin");
|
||||
simd_build.flag("-flto=thin");
|
||||
}
|
||||
volk_build.flag("-flto=thin");
|
||||
rdp_build.flag("-flto=thin");
|
||||
simd_build.flag("-flto=thin");
|
||||
|
||||
volk_build.compile("volk");
|
||||
rdp_build.compile("parallel-rdp");
|
||||
|
||||
@@ -107,9 +110,12 @@ fn main() {
|
||||
.allowlist_function("rdp_close")
|
||||
.allowlist_function("rdp_set_vi_register")
|
||||
.allowlist_function("rdp_update_screen")
|
||||
.allowlist_function("rdp_render_frame")
|
||||
.allowlist_function("rdp_process_commands")
|
||||
.allowlist_function("rdp_onscreen_message")
|
||||
.allowlist_function("rdp_check_callback")
|
||||
.allowlist_function("rdp_new_processor")
|
||||
.allowlist_function("rdp_check_framebuffers")
|
||||
.allowlist_function("rdp_state_size")
|
||||
.allowlist_function("rdp_save_state")
|
||||
.allowlist_function("rdp_load_state")
|
||||
@@ -123,7 +129,7 @@ fn main() {
|
||||
|
||||
if arch == "aarch64" {
|
||||
let simd_bindings = bindgen::Builder::default()
|
||||
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
|
||||
.header("src/compat/sse2neon/sse2neon.h")
|
||||
.allowlist_function("_mm_setzero_si128")
|
||||
.allowlist_function("_mm_set_epi8")
|
||||
.allowlist_function("_mm_movemask_epi8")
|
||||
@@ -157,6 +163,7 @@ fn main() {
|
||||
.allowlist_function("_mm_cmplt_epi16")
|
||||
.allowlist_function("_mm_subs_epu16")
|
||||
.allowlist_function("_mm_set1_epi32")
|
||||
.allowlist_function("_mm_set1_epi16")
|
||||
.blocklist_type("__m128i")
|
||||
.blocklist_type("int64x2_t")
|
||||
.wrap_static_fns(true)
|
||||
@@ -186,5 +193,8 @@ fn main() {
|
||||
let git_hash = String::from_utf8(git_output.stdout).unwrap();
|
||||
println!("cargo:rustc-env=GIT_HASH={git_hash}");
|
||||
|
||||
let netplay_id = std::env::var("NETPLAY_ID").unwrap_or("gopher64".to_string());
|
||||
println!("cargo:rustc-env=NETPLAY_ID={netplay_id}");
|
||||
|
||||
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+249
-16
@@ -5,6 +5,7 @@
|
||||
#include "spirv.hpp"
|
||||
#include "spirv_crt.hpp"
|
||||
#include <SDL3/SDL_vulkan.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
using namespace Vulkan;
|
||||
|
||||
@@ -52,14 +53,27 @@ enum vi_registers
|
||||
VI_REGS_COUNT
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t depthbuffer_address;
|
||||
uint32_t framebuffer_address;
|
||||
uint32_t framebuffer_y_offset;
|
||||
uint32_t texture_address;
|
||||
uint32_t framebuffer_pixel_size;
|
||||
uint32_t framebuffer_width;
|
||||
uint32_t texture_pixel_size;
|
||||
uint32_t texture_width;
|
||||
uint32_t framebuffer_height;
|
||||
bool depth_buffer_enabled;
|
||||
} FrameBufferInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t cmd_data[0x00040000 >> 2];
|
||||
int cmd_cur;
|
||||
int cmd_ptr;
|
||||
uint32_t region;
|
||||
uint64_t vi_counter;
|
||||
uint64_t last_sync;
|
||||
FrameBufferInfo frame_buffer_info;
|
||||
} RDP_DEVICE;
|
||||
|
||||
static SDL_Window *window;
|
||||
@@ -74,6 +88,15 @@ static GFX_INFO gfx_info;
|
||||
static const uint32_t *fragment_spirv;
|
||||
static size_t fragment_size;
|
||||
|
||||
std::vector<bool> rdram_dirty;
|
||||
uint64_t sync_signal;
|
||||
|
||||
static TTF_Font *message_font;
|
||||
static std::queue<std::string> messages;
|
||||
static uint64_t message_timer;
|
||||
|
||||
#define MESSAGE_TIME 3000 // 3 seconds
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float SourceSize[4];
|
||||
@@ -200,6 +223,27 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
|
||||
case SDL_SCANCODE_RIGHTBRACKET:
|
||||
callback.raise_volume = true;
|
||||
break;
|
||||
case SDL_SCANCODE_SLASH:
|
||||
callback.frame_advance = true;
|
||||
break;
|
||||
case SDL_SCANCODE_0:
|
||||
case SDL_SCANCODE_1:
|
||||
case SDL_SCANCODE_2:
|
||||
case SDL_SCANCODE_3:
|
||||
case SDL_SCANCODE_4:
|
||||
case SDL_SCANCODE_5:
|
||||
case SDL_SCANCODE_6:
|
||||
case SDL_SCANCODE_7:
|
||||
case SDL_SCANCODE_8:
|
||||
case SDL_SCANCODE_9:
|
||||
if (event->key.mod & SDL_KMOD_ALT)
|
||||
{
|
||||
if (event->key.scancode == SDL_SCANCODE_0)
|
||||
callback.save_state_slot = 0;
|
||||
else
|
||||
callback.save_state_slot = event->key.scancode - SDL_SCANCODE_1 + 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -212,6 +256,9 @@ void rdp_new_processor(GFX_INFO _gfx_info)
|
||||
{
|
||||
gfx_info = _gfx_info;
|
||||
|
||||
sync_signal = 0;
|
||||
rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false);
|
||||
|
||||
if (processor)
|
||||
{
|
||||
delete processor;
|
||||
@@ -228,11 +275,32 @@ void rdp_new_processor(GFX_INFO _gfx_info)
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_4X_BIT;
|
||||
}
|
||||
else if (gfx_info.upscale == 8)
|
||||
{
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
|
||||
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_8X_BIT;
|
||||
}
|
||||
|
||||
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
|
||||
}
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info)
|
||||
static ImageHandle create_message_image(Vulkan::Device &device, int width, const char *message)
|
||||
{
|
||||
SDL_Color fg = {255, 255, 255, 255};
|
||||
SDL_Color bg = {0, 0, 0, 0};
|
||||
SDL_Surface *surface = TTF_RenderText_LCD_Wrapped(message_font, message, 0, fg, bg, width);
|
||||
ImageCreateInfo info = ImageCreateInfo::immutable_2d_image(surface->w, surface->h, VK_FORMAT_B8G8R8A8_UNORM, false);
|
||||
ImageInitialData initial_data = {};
|
||||
initial_data.data = surface->pixels;
|
||||
initial_data.row_length = surface->pitch / 4;
|
||||
initial_data.image_height = surface->h;
|
||||
|
||||
ImageHandle handle = device.create_image(info, &initial_data);
|
||||
SDL_DestroySurface(surface);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size)
|
||||
{
|
||||
memset(&rdp_device, 0, sizeof(RDP_DEVICE));
|
||||
|
||||
@@ -269,33 +337,51 @@ void rdp_init(void *_window, GFX_INFO _gfx_info)
|
||||
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
|
||||
{
|
||||
rdp_close();
|
||||
return;
|
||||
}
|
||||
if (!wsi->init_simple(1, handles))
|
||||
{
|
||||
rdp_close();
|
||||
return;
|
||||
}
|
||||
|
||||
rdp_new_processor(gfx_info);
|
||||
|
||||
if (!processor->device_is_supported())
|
||||
{
|
||||
delete processor;
|
||||
delete wsi;
|
||||
processor = nullptr;
|
||||
rdp_close();
|
||||
return;
|
||||
}
|
||||
|
||||
message_font = TTF_OpenFontIO(SDL_IOFromConstMem(font, font_size), true, 30.0);
|
||||
if (!message_font)
|
||||
{
|
||||
rdp_close();
|
||||
return;
|
||||
}
|
||||
|
||||
wsi->begin_frame();
|
||||
|
||||
callback.emu_running = true;
|
||||
callback.enable_speedlimiter = true;
|
||||
callback.paused = false;
|
||||
callback.save_state_slot = 0;
|
||||
crop_letterbox = false;
|
||||
|
||||
messages = std::queue<std::string>();
|
||||
message_timer = 0;
|
||||
}
|
||||
|
||||
void rdp_close()
|
||||
{
|
||||
wsi->end_frame();
|
||||
if (wsi)
|
||||
wsi->end_frame();
|
||||
|
||||
if (message_font)
|
||||
{
|
||||
TTF_CloseFont(message_font);
|
||||
message_font = nullptr;
|
||||
}
|
||||
if (processor)
|
||||
{
|
||||
delete processor;
|
||||
@@ -425,11 +511,30 @@ static void render_frame(Vulkan::Device &device)
|
||||
cmd->push_constants(&push, 0, sizeof(push));
|
||||
}
|
||||
|
||||
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::LinearClamp);
|
||||
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::NearestClamp);
|
||||
cmd->set_viewport(vp);
|
||||
// The vertices are constants in the shader.
|
||||
// Draws fullscreen quad using oversized triangle.
|
||||
cmd->draw(3);
|
||||
|
||||
if (!messages.empty())
|
||||
{
|
||||
Vulkan::ImageHandle message_image = create_message_image(device, vp.width, messages.front().c_str());
|
||||
cmd->set_texture(0, 0, message_image->get_view(), Vulkan::StockSampler::NearestClamp);
|
||||
vp.x = vp.x + (vp.width - message_image->get_width()) / 2;
|
||||
vp.y = vp.y + vp.height - message_image->get_height();
|
||||
vp.height = message_image->get_height();
|
||||
vp.width = message_image->get_width();
|
||||
cmd->set_viewport(vp);
|
||||
|
||||
cmd->draw(3);
|
||||
|
||||
if (SDL_GetTicks() > message_timer)
|
||||
{
|
||||
messages.pop();
|
||||
message_timer = SDL_GetTicks() + MESSAGE_TIME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd->end_render_pass();
|
||||
@@ -442,13 +547,16 @@ void rdp_set_vi_register(uint32_t reg, uint32_t value)
|
||||
processor->set_vi_register(RDP::VIRegister(reg), value);
|
||||
}
|
||||
|
||||
void rdp_update_screen()
|
||||
void rdp_render_frame()
|
||||
{
|
||||
auto &device = wsi->get_device();
|
||||
render_frame(device);
|
||||
}
|
||||
|
||||
void rdp_update_screen()
|
||||
{
|
||||
wsi->end_frame();
|
||||
wsi->begin_frame();
|
||||
rdp_device.vi_counter += 1;
|
||||
}
|
||||
|
||||
CALL_BACK rdp_check_callback()
|
||||
@@ -458,9 +566,32 @@ CALL_BACK rdp_check_callback()
|
||||
callback.load_state = false;
|
||||
callback.lower_volume = false;
|
||||
callback.raise_volume = false;
|
||||
callback.frame_advance = false;
|
||||
return return_value;
|
||||
}
|
||||
|
||||
void rdp_check_framebuffers(uint32_t address, uint32_t length)
|
||||
{
|
||||
if (sync_signal)
|
||||
{
|
||||
address >>= 3;
|
||||
length = (length + 7) >> 3;
|
||||
|
||||
if (address >= rdram_dirty.size())
|
||||
return;
|
||||
|
||||
uint32_t end_addr = std::min(address + length, static_cast<uint32_t>(rdram_dirty.size()));
|
||||
|
||||
auto it = std::find(rdram_dirty.begin() + address, rdram_dirty.begin() + end_addr, true);
|
||||
if (it != rdram_dirty.begin() + end_addr)
|
||||
{
|
||||
processor->wait_for_timeline(sync_signal);
|
||||
rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false);
|
||||
sync_signal = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t rdp_state_size()
|
||||
{
|
||||
return sizeof(RDP_DEVICE);
|
||||
@@ -468,6 +599,7 @@ size_t rdp_state_size()
|
||||
|
||||
void rdp_save_state(uint8_t *state)
|
||||
{
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
memcpy(state, &rdp_device, sizeof(RDP_DEVICE));
|
||||
}
|
||||
|
||||
@@ -476,6 +608,31 @@ void rdp_load_state(const uint8_t *state)
|
||||
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
|
||||
}
|
||||
|
||||
void rdp_onscreen_message(const char *_message)
|
||||
{
|
||||
if (messages.empty())
|
||||
message_timer = SDL_GetTicks() + MESSAGE_TIME;
|
||||
messages.push(_message);
|
||||
}
|
||||
|
||||
uint32_t pixel_size(uint32_t pixel_type, uint32_t area)
|
||||
{
|
||||
switch (pixel_type)
|
||||
{
|
||||
case 0:
|
||||
return area / 2;
|
||||
case 1:
|
||||
return area;
|
||||
case 2:
|
||||
return area * 2;
|
||||
case 3:
|
||||
return area * 4;
|
||||
default:
|
||||
printf("Invalid pixel size: %u\n", pixel_type);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t rdp_process_commands()
|
||||
{
|
||||
uint64_t interrupt_timer = 0;
|
||||
@@ -539,6 +696,75 @@ uint64_t rdp_process_commands()
|
||||
|
||||
switch (RDP::Op(command))
|
||||
{
|
||||
case RDP::Op::FillTriangle:
|
||||
case RDP::Op::FillZBufferTriangle:
|
||||
case RDP::Op::TextureTriangle:
|
||||
case RDP::Op::TextureZBufferTriangle:
|
||||
case RDP::Op::ShadeTriangle:
|
||||
case RDP::Op::ShadeZBufferTriangle:
|
||||
case RDP::Op::ShadeTextureTriangle:
|
||||
case RDP::Op::ShadeTextureZBufferTriangle:
|
||||
case RDP::Op::TextureRectangle:
|
||||
case RDP::Op::TextureRectangleFlip:
|
||||
case RDP::Op::FillRectangle:
|
||||
{
|
||||
uint32_t offset_address = (rdp_device.frame_buffer_info.framebuffer_address + pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
|
||||
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
|
||||
{
|
||||
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
|
||||
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
|
||||
}
|
||||
|
||||
if (rdp_device.frame_buffer_info.depth_buffer_enabled)
|
||||
{
|
||||
offset_address = (rdp_device.frame_buffer_info.depthbuffer_address + pixel_size(2, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
|
||||
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
|
||||
{
|
||||
uint32_t end_addr = std::min(offset_address + ((pixel_size(2, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
|
||||
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RDP::Op::LoadTLut:
|
||||
case RDP::Op::LoadTile:
|
||||
{
|
||||
uint32_t upper_left_t = (w1 & 0xFFF) >> 2;
|
||||
uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3;
|
||||
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
|
||||
{
|
||||
uint32_t lower_right_t = (w2 & 0xFFF) >> 2;
|
||||
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, (lower_right_t - upper_left_t) * rdp_device.frame_buffer_info.texture_width) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
|
||||
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RDP::Op::LoadBlock:
|
||||
{
|
||||
uint32_t upper_left_s = ((w1 >> 12) & 0xFFF);
|
||||
uint32_t upper_left_t = (w1 & 0xFFF);
|
||||
uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_s + upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3;
|
||||
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
|
||||
{
|
||||
uint32_t lower_right_s = ((w2 >> 12) & 0xFFF);
|
||||
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, lower_right_s - upper_left_s) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
|
||||
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RDP::Op::SetColorImage:
|
||||
rdp_device.frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF);
|
||||
rdp_device.frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
|
||||
rdp_device.frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
|
||||
break;
|
||||
case RDP::Op::SetMaskImage:
|
||||
rdp_device.frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF);
|
||||
break;
|
||||
case RDP::Op::SetTextureImage:
|
||||
rdp_device.frame_buffer_info.texture_address = (w2 & 0x00FFFFFF);
|
||||
rdp_device.frame_buffer_info.texture_pixel_size = (w1 >> 19) & 0x3;
|
||||
rdp_device.frame_buffer_info.texture_width = (w1 & 0x3FF) + 1;
|
||||
break;
|
||||
case RDP::Op::SetScissor:
|
||||
{
|
||||
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
|
||||
@@ -553,14 +779,21 @@ uint64_t rdp_process_commands()
|
||||
{
|
||||
rdp_device.region = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
rdp_device.frame_buffer_info.framebuffer_y_offset = upper_left_y;
|
||||
rdp_device.frame_buffer_info.framebuffer_height = lower_right_y - upper_left_y;
|
||||
}
|
||||
break;
|
||||
case RDP::Op::SetOtherModes:
|
||||
{
|
||||
uint8_t cycle_type = (w1 >> 20) & 3;
|
||||
uint8_t depth_read_write = (w2 >> 4) & 3;
|
||||
rdp_device.frame_buffer_info.depth_buffer_enabled = ((cycle_type & 2) == 0) && (depth_read_write != 0);
|
||||
}
|
||||
break;
|
||||
case RDP::Op::SyncFull:
|
||||
if (rdp_device.last_sync != rdp_device.vi_counter)
|
||||
{
|
||||
processor->wait_for_timeline(processor->signal_timeline());
|
||||
rdp_device.last_sync = rdp_device.vi_counter;
|
||||
}
|
||||
sync_signal = processor->signal_timeline();
|
||||
|
||||
interrupt_timer = rdp_device.region;
|
||||
if (interrupt_timer == 0)
|
||||
interrupt_timer = 5000;
|
||||
|
||||
@@ -34,15 +34,20 @@ extern "C"
|
||||
bool lower_volume;
|
||||
bool raise_volume;
|
||||
bool paused;
|
||||
bool frame_advance;
|
||||
uint32_t save_state_slot;
|
||||
} CALL_BACK;
|
||||
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info);
|
||||
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size);
|
||||
void rdp_close();
|
||||
void rdp_set_vi_register(uint32_t reg, uint32_t value);
|
||||
void rdp_update_screen();
|
||||
void rdp_render_frame();
|
||||
CALL_BACK rdp_check_callback();
|
||||
uint64_t rdp_process_commands();
|
||||
void rdp_onscreen_message(const char *_message);
|
||||
void rdp_new_processor(GFX_INFO _gfx_info);
|
||||
void rdp_check_framebuffers(uint32_t address, uint32_t length);
|
||||
size_t rdp_state_size();
|
||||
void rdp_save_state(uint8_t *state);
|
||||
void rdp_load_state(const uint8_t *state);
|
||||
|
||||
Submodule parallel-rdp/parallel-rdp-standalone updated: 3bff34d835...091c7fec1c
+1
-1
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.88.0"
|
||||
channel = "1.91.0"
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ pub fn init(
|
||||
|
||||
if !device.cheats.cheats.is_empty() {
|
||||
device.cheats.enabled = true;
|
||||
ui::audio::play_cheat_event(&device.ui);
|
||||
ui::video::onscreen_message(&device.ui, "Cheats enabled");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
|
||||
#include "src/compat/sse2neon/sse2neon.h"
|
||||
|
||||
// Static wrappers
|
||||
|
||||
|
||||
Submodule
+1
Submodule src/compat/sse2neon added at 3cf69760cc
File diff suppressed because it is too large
Load Diff
@@ -322,6 +322,7 @@ impl Device {
|
||||
pif: pif::Pif {
|
||||
rom: [0; 1984],
|
||||
ram: [0; 64],
|
||||
input_gathered: false,
|
||||
channels: [pif::PifChannel {
|
||||
tx: None,
|
||||
tx_buf: None,
|
||||
@@ -347,6 +348,7 @@ impl Device {
|
||||
sector: 0,
|
||||
buffer: vec![0; 8192],
|
||||
writeback_sector: vec![0; 256],
|
||||
usb_buffer: vec![],
|
||||
},
|
||||
flashram: cart::sram::Flashram {
|
||||
status: 0,
|
||||
|
||||
@@ -21,16 +21,6 @@ const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
|
||||
const EEPROM_BLOCK_SIZE: usize = 8;
|
||||
pub const EEPROM_MAX_SIZE: usize = 0x800;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub enum CicType {
|
||||
CicNus6101,
|
||||
CicNus6102,
|
||||
CicNus6103,
|
||||
CicNus6105,
|
||||
CicNus6106,
|
||||
CicNus5167,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct AfRtc {
|
||||
pub control: u16,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::device;
|
||||
use crate::netplay;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
const CART_MASK: usize = 0xFFFFFFF;
|
||||
pub const CART_MASK: usize = 0xFFFFFFF;
|
||||
|
||||
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
|
||||
let mut data: [u8; 4] = [0; 4];
|
||||
|
||||
+127
-2
@@ -28,6 +28,7 @@ pub struct Sc64 {
|
||||
pub cfg: [u32; SC64_CFG_COUNT as usize],
|
||||
pub sector: u32,
|
||||
pub writeback_sector: Vec<u32>,
|
||||
pub usb_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
fn format_sdcard(device: &mut device::Device) {
|
||||
@@ -209,8 +210,132 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
|
||||
}
|
||||
device.ui.storage.saves.sdcard.written = true;
|
||||
}
|
||||
'U' | 'u' => {} // USB status, ignored
|
||||
'M' | 'm' => {} // USB read/write, ignored
|
||||
'U' => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
}
|
||||
'u' => {
|
||||
// used to notify the game that there is data to read
|
||||
if let Some(cart_rx) = device.ui.usb.cart_rx.as_mut() {
|
||||
match cart_rx.try_recv() {
|
||||
Ok(data) => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] =
|
||||
data.data_type & 0xFF; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] =
|
||||
data.data_size & 0xFFFFFF; // length
|
||||
device.cart.sc64.usb_buffer = data.data; // store the data to be read
|
||||
}
|
||||
Err(err) => {
|
||||
match err {
|
||||
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
|
||||
panic!("cart_rx lagged: {err}");
|
||||
}
|
||||
_ => {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
|
||||
}
|
||||
}
|
||||
'M' => {
|
||||
// Send data from from flashcart to USB
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let length =
|
||||
device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize & 0xFFFFFF;
|
||||
|
||||
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
|
||||
let mut usb_buffer = vec![0; length];
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
if address < device.rdram.size as u64 {
|
||||
while i < length {
|
||||
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
|
||||
.rdram
|
||||
.mem
|
||||
.get((address as usize + i) ^ device.byte_swap)
|
||||
.unwrap_or(&0);
|
||||
i += 1;
|
||||
}
|
||||
} else if address >= device::memory::MM_CART_ROM as u64
|
||||
&& address < device::memory::MM_PIF_MEM as u64
|
||||
{
|
||||
while i < length {
|
||||
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
|
||||
.ui
|
||||
.storage
|
||||
.saves
|
||||
.romsave
|
||||
.data
|
||||
.get(
|
||||
&(((address as usize + i)
|
||||
& device::cart::rom::CART_MASK)
|
||||
as u32),
|
||||
)
|
||||
.unwrap_or(
|
||||
device
|
||||
.cart
|
||||
.rom
|
||||
.get(
|
||||
(address as usize + i)
|
||||
& device::cart::rom::CART_MASK,
|
||||
)
|
||||
.unwrap_or(&0),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown address {address:#x} for SC64 M command");
|
||||
}
|
||||
|
||||
ui::usb::send_to_usb(
|
||||
usb_tx,
|
||||
ui::usb::UsbData {
|
||||
data: usb_buffer,
|
||||
data_type: device.cart.sc64.regs[SC64_DATA1_REG as usize] >> 24,
|
||||
data_size: device.cart.sc64.regs[SC64_DATA1_REG as usize]
|
||||
& 0xFFFFFF,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
'm' => {
|
||||
// Receive data from USB to flashcart
|
||||
let address =
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
|
||||
let length = device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize;
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
if address < device.rdram.size as u64 {
|
||||
while i < length {
|
||||
*device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((address as usize + i) ^ device.byte_swap)
|
||||
.unwrap_or(&mut 0) =
|
||||
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0);
|
||||
i += 1;
|
||||
}
|
||||
} else if address >= device::memory::MM_CART_ROM as u64
|
||||
&& address < device::memory::MM_PIF_MEM as u64
|
||||
{
|
||||
while i < length {
|
||||
device.ui.storage.saves.romsave.data.insert(
|
||||
((address as usize + i) & device::cart::rom::CART_MASK) as u32,
|
||||
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0),
|
||||
);
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
panic!("Unknown address {address:#x} for SC64 m command");
|
||||
}
|
||||
}
|
||||
'w' => {
|
||||
// SD card writeback pending
|
||||
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
|
||||
|
||||
@@ -189,20 +189,22 @@ pub fn pak_switch_event(device: &mut device::Device) {
|
||||
write: device::controller::mempak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "MemPak selected");
|
||||
} else if new_pak_type == PakType::RumblePak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::rumble::read,
|
||||
write: device::controller::rumble::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "RumblePak selected");
|
||||
} else if new_pak_type == PakType::TransferPak {
|
||||
channel.pak_handler = Some(device::controller::PakHandler {
|
||||
read: device::controller::transferpak::read,
|
||||
write: device::controller::transferpak::write,
|
||||
pak_type: new_pak_type,
|
||||
});
|
||||
ui::video::onscreen_message(&device.ui, "TransferPak selected");
|
||||
}
|
||||
ui::audio::play_pak_switch(&device.ui, new_pak_type);
|
||||
channel.change_pak = PakType::None;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,4 +1,4 @@
|
||||
use crate::{device, savestates};
|
||||
use crate::{device, savestates, ui};
|
||||
|
||||
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum State {
|
||||
@@ -227,7 +227,10 @@ pub fn init(device: &mut device::Device) {
|
||||
device.cpu.clock_rate = if !device.cpu.overclock {
|
||||
93750000
|
||||
} else {
|
||||
println!("Overclocking enabled, setting clock rate to 125 MHz");
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
"Overclocking enabled, setting clock rate to 125 MHz",
|
||||
);
|
||||
125000000
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
|
||||
} else if device.cpu.cop0.pending_compare_interrupt {
|
||||
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
|
||||
device.cpu.cop0.pending_compare_interrupt = false;
|
||||
}
|
||||
|
||||
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
|
||||
|
||||
@@ -22,8 +22,8 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
let data = std::str::from_utf8(
|
||||
&device.cart.is_viewer_buffer[0x20_usize..(0x20 + length) as usize],
|
||||
);
|
||||
if data.is_ok() {
|
||||
print!("{}", data.unwrap());
|
||||
if let Ok(data) = data {
|
||||
print!("{}", data);
|
||||
}
|
||||
} else {
|
||||
let mut data = u32::from_be_bytes(
|
||||
|
||||
+9
-3
@@ -1,6 +1,6 @@
|
||||
use crate::device;
|
||||
|
||||
const MI_INIT_MODE_REG: u32 = 0;
|
||||
pub const MI_INIT_MODE_REG: u32 = 0;
|
||||
const MI_VERSION_REG: u32 = 1;
|
||||
pub const MI_INTR_REG: u32 = 2;
|
||||
pub const MI_INTR_MASK_REG: u32 = 3;
|
||||
@@ -29,7 +29,7 @@ const MI_CLR_DP: u32 = 1 << 10;
|
||||
const MI_SET_DP: u32 = 1 << 11;
|
||||
|
||||
/* mode read */
|
||||
const MI_INIT_MODE: u32 = 1 << 7;
|
||||
pub const MI_INIT_MODE: u32 = 1 << 7;
|
||||
const MI_EBUS_MODE: u32 = 1 << 8;
|
||||
const MI_RDRAM_MODE: u32 = 1 << 9;
|
||||
|
||||
@@ -42,7 +42,7 @@ const MI_CLR_DP_INTR: u32 = 1 << 11;
|
||||
const MI_CLR_RDRAM: u32 = 1 << 12;
|
||||
const MI_SET_RDRAM: u32 = 1 << 13;
|
||||
|
||||
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct Mi {
|
||||
@@ -78,9 +78,15 @@ fn update_init_mode(device: &mut device::Device, w: u32) {
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
|
||||
|
||||
if w & MI_CLR_INIT != 0 {
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_MODE
|
||||
}
|
||||
if w & MI_SET_INIT != 0 {
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem_repeat;
|
||||
}
|
||||
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_INIT_MODE
|
||||
}
|
||||
if w & MI_CLR_EBUS != 0 {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
|
||||
const PI_DRAM_ADDR_REG: u32 = 0;
|
||||
@@ -92,6 +93,9 @@ fn dma_write(device: &mut device::Device) {
|
||||
length -= dram_addr & 0x7;
|
||||
}
|
||||
|
||||
// check RDP before PI writes to RDRAM
|
||||
ui::video::check_framebuffers(dram_addr, length);
|
||||
|
||||
let cycles = (handler.write)(device, cart_addr, dram_addr, length);
|
||||
|
||||
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
|
||||
|
||||
@@ -8,6 +8,7 @@ pub struct Pif {
|
||||
#[serde(with = "serde_big_array::BigArray")]
|
||||
pub ram: [u8; 64],
|
||||
pub channels: [PifChannel; 5],
|
||||
pub input_gathered: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
@@ -95,6 +96,9 @@ fn process_channel(device: &mut device::Device, channel: usize) -> usize {
|
||||
}
|
||||
|
||||
pub fn update_pif_ram(device: &mut device::Device) -> u64 {
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
device.pif.input_gathered = true;
|
||||
|
||||
let mut active_channels = 0;
|
||||
for k in 0..PIF_CHANNELS_COUNT {
|
||||
active_channels += process_channel(device, k)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
use std::alloc::{Layout, alloc_zeroed};
|
||||
|
||||
//const RDRAM_CONFIG_REG: u32 = 0;
|
||||
@@ -50,6 +51,8 @@ pub fn read_mem(
|
||||
);
|
||||
let masked_address = address as usize & RDRAM_MASK;
|
||||
|
||||
ui::video::check_framebuffers(masked_address as u32, 4);
|
||||
|
||||
u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
@@ -62,6 +65,8 @@ pub fn read_mem(
|
||||
}
|
||||
|
||||
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
ui::video::check_framebuffers(address as u32, 4);
|
||||
|
||||
let mut data = u32::from_ne_bytes(
|
||||
device
|
||||
.rdram
|
||||
@@ -80,6 +85,39 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
|
||||
.copy_from_slice(&data.to_ne_bytes());
|
||||
}
|
||||
|
||||
pub fn write_mem_repeat(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
||||
if mask != 0xFFFFFFFF {
|
||||
panic!("RDRAM write_mem_repeat called with mask {:#x}", mask);
|
||||
}
|
||||
|
||||
let repeat_length = (device.mi.regs[device::mi::MI_INIT_MODE_REG as usize]
|
||||
& device::mi::MI_INIT_LENGTH_MASK)
|
||||
+ 1;
|
||||
|
||||
if !repeat_length.is_multiple_of(4) {
|
||||
panic!(
|
||||
"RDRAM write_mem_repeat called with non-word-aligned length {}",
|
||||
repeat_length
|
||||
);
|
||||
}
|
||||
|
||||
ui::video::check_framebuffers(address as u32, repeat_length);
|
||||
|
||||
for i in (0..repeat_length as u64).step_by(4) {
|
||||
device
|
||||
.rdram
|
||||
.mem
|
||||
.get_mut((address + i) as usize..(address + i) as usize + 4)
|
||||
.unwrap_or(&mut [0; 4])
|
||||
.copy_from_slice(&value.to_ne_bytes());
|
||||
}
|
||||
|
||||
device.mi.regs[device::mi::MI_INIT_MODE_REG as usize] &= !device::mi::MI_INIT_MODE;
|
||||
for i in 0..(0x3F00000 >> 16) {
|
||||
device.memory.memory_map_write[i] = device::rdram::write_mem;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_regs(
|
||||
device: &mut device::Device,
|
||||
address: u64,
|
||||
|
||||
+14
-10
@@ -1,4 +1,5 @@
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
const SP_MEM_ADDR_REG: u32 = 0;
|
||||
const SP_DRAM_ADDR_REG: u32 = 1;
|
||||
@@ -146,6 +147,7 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
|
||||
let mut dram_addr = dma.dramaddr & 0xfffff8;
|
||||
let offset = dma.memaddr & 0x1000;
|
||||
|
||||
ui::video::check_framebuffers(dram_addr, count * (length + skip) - skip);
|
||||
if dma.dir == DmaDir::Read {
|
||||
let mut j = 0;
|
||||
while j < count {
|
||||
@@ -219,6 +221,8 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) {
|
||||
panic!("RSP DMA already full")
|
||||
}
|
||||
|
||||
device.rsp.cpu.sync_point = true;
|
||||
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] != 0 {
|
||||
device.rsp.fifo[1].dir = dir;
|
||||
if dir == DmaDir::Read {
|
||||
@@ -276,21 +280,20 @@ pub fn read_regs(
|
||||
}
|
||||
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);
|
||||
& (SP_STATUS_SIG0
|
||||
| SP_STATUS_SIG1
|
||||
| SP_STATUS_SIG2
|
||||
| SP_STATUS_SIG3
|
||||
| SP_STATUS_SIG4
|
||||
| SP_STATUS_SIG5
|
||||
| SP_STATUS_SIG6
|
||||
| SP_STATUS_SIG7);
|
||||
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]
|
||||
}
|
||||
@@ -464,6 +467,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
|
||||
fn do_task(device: &mut device::Device) {
|
||||
device.rsp.cpu.sync_point = false;
|
||||
device.rsp.last_status_value = 0;
|
||||
device.rdp.last_status_value = 0;
|
||||
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
|
||||
device.rsp.run_after_dma = true
|
||||
} else {
|
||||
|
||||
@@ -304,25 +304,44 @@ pub fn lh(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 2];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFE {
|
||||
u16::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 2]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let w0 = device.rsp.mem[addr_masked];
|
||||
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
|
||||
((w0 as u16) << 8) | w1 as u16
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
(((w[0] as u16) << 8) | w[1] as u16) as i16 as i32 as u32
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value as i16 as i32 as u32;
|
||||
}
|
||||
|
||||
pub fn lw(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 4];
|
||||
for (i, item) in w.iter_mut().enumerate() {
|
||||
*item = device.rsp.mem[(address as usize + i) & 0xFFF];
|
||||
}
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
|
||||
}
|
||||
|
||||
pub fn lbu(device: &mut device::Device, opcode: u32) {
|
||||
@@ -336,24 +355,44 @@ pub fn lhu(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 2];
|
||||
w[0] = device.rsp.mem[address as usize & 0xFFF];
|
||||
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFE {
|
||||
u16::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 2]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let w0 = device.rsp.mem[addr_masked];
|
||||
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
|
||||
((w0 as u16) << 8) | w1 as u16
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = (((w[0] as u16) << 8) | w[1] as u16) as u32
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value as u32;
|
||||
}
|
||||
|
||||
pub fn lwu(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
let mut w = [0; 4];
|
||||
for (i, item) in w.iter_mut().enumerate() {
|
||||
*item = device.rsp.mem[(address as usize + i) & 0xFFF];
|
||||
}
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] =
|
||||
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
|
||||
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
|
||||
}
|
||||
|
||||
pub fn sb(device: &mut device::Device, opcode: u32) {
|
||||
@@ -367,18 +406,30 @@ pub fn sh(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
device.rsp.mem[address as usize & 0xFFF] = (device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
|
||||
device.rsp.mem[(address as usize + 1) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = device.rsp.cpu.gpr[rt(opcode) as usize] as u16;
|
||||
// Optimize for aligned access when possible
|
||||
if addr_masked <= 0xFFE {
|
||||
device.rsp.mem[addr_masked..addr_masked + 2].copy_from_slice(&value.to_be_bytes());
|
||||
} else {
|
||||
device.rsp.mem[addr_masked] = (value >> 8) as u8;
|
||||
device.rsp.mem[(addr_masked + 1) & 0xFFF] = value as u8;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sw(device: &mut device::Device, opcode: u32) {
|
||||
let address =
|
||||
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
|
||||
|
||||
for i in 0..4 {
|
||||
device.rsp.mem[(address as usize + i) & 0xFFF] =
|
||||
(device.rsp.cpu.gpr[rt(opcode) as usize] >> ((3 - i) * 8)) as u8;
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = device.rsp.cpu.gpr[rt(opcode) as usize];
|
||||
// Optimize for aligned access when possible
|
||||
if addr_masked <= 0xFFC {
|
||||
device.rsp.mem[addr_masked..addr_masked + 4].copy_from_slice(&value.to_be_bytes());
|
||||
} else {
|
||||
for i in 0..4 {
|
||||
device.rsp.mem[(addr_masked + i) & 0xFFF] = (value >> ((3 - i) * 8)) as u8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,10 +788,21 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element.is_multiple_of(4) {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
|
||||
}
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFFC {
|
||||
u32::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..4 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr32(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 4,
|
||||
@@ -767,10 +829,21 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element.is_multiple_of(8) {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
|
||||
}
|
||||
// Optimize for aligned access when possible
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF8 {
|
||||
u64::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 8]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..8 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u64) << (8 * (7 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr64(
|
||||
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
|
||||
element / 8,
|
||||
@@ -797,10 +870,21 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = velement(opcode);
|
||||
|
||||
if element == 0 && address.is_multiple_of(16) {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
// Optimize for aligned 16-byte access
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF0 {
|
||||
u128::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 16]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
|
||||
@@ -823,10 +907,21 @@ pub fn lrv(device: &mut device::Device, opcode: u32) {
|
||||
let mut element = 16u8.wrapping_sub(((address & 15) as u8).wrapping_sub(velement(opcode)));
|
||||
address &= !15;
|
||||
if element == 0 {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
|
||||
}
|
||||
// Optimize for aligned 16-byte access
|
||||
let addr_masked = address as usize & 0xFFF;
|
||||
let value = if addr_masked <= 0xFF0 {
|
||||
u128::from_be_bytes(
|
||||
device.rsp.mem[addr_masked..addr_masked + 16]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
} else {
|
||||
let mut value = 0;
|
||||
for i in 0..16 {
|
||||
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
|
||||
}
|
||||
value
|
||||
};
|
||||
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
|
||||
} else {
|
||||
while element < 16 {
|
||||
|
||||
+431
-447
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
use rand_chacha::rand_core::RngCore;
|
||||
|
||||
use crate::device;
|
||||
use crate::ui;
|
||||
|
||||
const SI_DRAM_ADDR_REG: u32 = 0;
|
||||
const SI_PIF_ADDR_RD64B_REG: u32 = 1;
|
||||
@@ -98,6 +99,8 @@ fn copy_pif_rdram(device: &mut device::Device) {
|
||||
i += 4;
|
||||
}
|
||||
} else if device.si.dma_dir == DmaDir::Read {
|
||||
// check RDP before SI writes to RDRAM
|
||||
ui::video::check_framebuffers(dram_addr as u32, device::pif::PIF_RAM_SIZE as u32);
|
||||
let mut i = 0;
|
||||
while i < device::pif::PIF_RAM_SIZE {
|
||||
let data = u32::from_be_bytes(device.pif.ram[i..i + 4].try_into().unwrap());
|
||||
|
||||
+14
-2
@@ -137,7 +137,10 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
if device.cheats.enabled {
|
||||
cheats::execute_cheats(device, device.cheats.cheats.clone());
|
||||
}
|
||||
let mut speed_limiter_toggled = ui::video::check_callback(device);
|
||||
|
||||
ui::video::render_frame();
|
||||
|
||||
let (mut speed_limiter_toggled, paused) = ui::video::check_callback(device);
|
||||
|
||||
if device.netplay.is_some() {
|
||||
netplay::send_sync_check(device);
|
||||
@@ -147,11 +150,20 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
|
||||
}
|
||||
}
|
||||
|
||||
device.vi.vi_counter += 1;
|
||||
if device.vi.vi_counter.is_multiple_of(device.vi.limit_freq) && device.vi.enable_speed_limiter {
|
||||
speed_limiter(device, speed_limiter_toggled);
|
||||
}
|
||||
|
||||
ui::video::update_screen();
|
||||
device.vi.vi_counter += 1;
|
||||
|
||||
if device.netplay.is_none() && paused {
|
||||
ui::video::pause_loop();
|
||||
} else if !device.pif.input_gathered {
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() }; // in case the game isn't prompting for input
|
||||
} else {
|
||||
device.pif.input_gathered = false;
|
||||
}
|
||||
|
||||
/*
|
||||
let vis = if device.cart.pal { 50 } else { 60 };
|
||||
|
||||
+10
-2
@@ -22,15 +22,22 @@ struct Args {
|
||||
short,
|
||||
long,
|
||||
value_name = "PROFILE_NAME",
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)."
|
||||
help = "Create a new input profile (keyboard/gamepad mappings)"
|
||||
)]
|
||||
configure_input_profile: Option<String>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Use DirectInput when configuring a new input profile."
|
||||
help = "Use DirectInput when configuring a new input profile"
|
||||
)]
|
||||
use_dinput: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_name = "DEADZONE_PERCENTAGE",
|
||||
help = "Used along with --configure-input-profile to set the deadzone for analog sticks"
|
||||
)]
|
||||
deadzone: Option<i32>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
@@ -112,6 +119,7 @@ async fn main() {
|
||||
&mut ui,
|
||||
args.configure_input_profile.unwrap(),
|
||||
args.use_dinput,
|
||||
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
+4
-14
@@ -46,14 +46,6 @@ struct InputEvent {
|
||||
plugin: u8,
|
||||
}
|
||||
|
||||
pub const NETPLAY_ERROR_DESYNC: u32 = 0;
|
||||
pub const NETPLAY_ERROR_LOST_CONNECTION: u32 = 1;
|
||||
pub const NETPLAY_ERROR_PLAYER_DISCONNECTED: u32 = 2;
|
||||
pub const NETPLAY_ERROR_PLAYER_1_DISCONNECTED: u32 = 3;
|
||||
pub const NETPLAY_ERROR_PLAYER_2_DISCONNECTED: u32 = 4;
|
||||
pub const NETPLAY_ERROR_PLAYER_3_DISCONNECTED: u32 = 5;
|
||||
pub const NETPLAY_ERROR_PLAYER_4_DISCONNECTED: u32 = 6;
|
||||
|
||||
pub fn send_rtc(netplay: &mut Netplay, rtc: i64) {
|
||||
let mut request: Vec<u8> = [TCP_SEND_RTC].to_vec();
|
||||
let size: u32 = 8;
|
||||
@@ -155,7 +147,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::Inpu
|
||||
.remove(&netplay.player_data[channel].count);
|
||||
|
||||
if std::time::Instant::now() > timeout {
|
||||
ui::audio::play_netplay_audio(&device.ui, NETPLAY_ERROR_LOST_CONNECTION);
|
||||
ui::video::onscreen_message(&device.ui, "Lost connection to netplay server");
|
||||
input = Some(InputEvent {
|
||||
input: 0,
|
||||
plugin: 0,
|
||||
@@ -200,15 +192,13 @@ fn process_incoming(netplay: &mut Netplay, ui: &ui::Ui) {
|
||||
}
|
||||
if current_status != netplay.status {
|
||||
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
|
||||
ui::audio::play_netplay_audio(ui, NETPLAY_ERROR_DESYNC);
|
||||
ui::video::onscreen_message(ui, "Netplay desync detected");
|
||||
ui::video::onscreen_message(ui, "Netplay desync detected"); // queue this message twice to ensure they see it
|
||||
}
|
||||
for dis in 1..5 {
|
||||
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
|
||||
{
|
||||
ui::audio::play_netplay_audio(
|
||||
ui,
|
||||
NETPLAY_ERROR_PLAYER_DISCONNECTED + dis,
|
||||
);
|
||||
ui::video::onscreen_message(ui, &format!("Player {dis} disconnected"));
|
||||
}
|
||||
}
|
||||
netplay.status = current_status;
|
||||
|
||||
+19
-7
@@ -87,9 +87,12 @@ pub fn create_savestate(device: &device::Device) {
|
||||
compressed_file,
|
||||
)
|
||||
.unwrap();
|
||||
println!(
|
||||
"Savestate created at {}",
|
||||
device.ui.storage.paths.savestate_file_path.display()
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
&format!(
|
||||
"Savestate created in slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,12 +198,21 @@ pub fn load_savestate(device: &mut device::Device) {
|
||||
ui::audio::close_game_audio(&mut device.ui);
|
||||
ui::audio::init_game_audio(&mut device.ui, device.ai.freq);
|
||||
ui::video::load_state(device, rdp_state.as_ptr());
|
||||
println!(
|
||||
"Savestate loaded from {}",
|
||||
device.ui.storage.paths.savestate_file_path.display()
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
&format!(
|
||||
"Savestate loaded from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
);
|
||||
} else {
|
||||
println!("Failed to load savestate");
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
&format!(
|
||||
"Failed to load savestate from slot {}",
|
||||
device.ui.storage.save_state_slot
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod gui;
|
||||
pub mod input;
|
||||
pub mod netplay;
|
||||
pub mod storage;
|
||||
pub mod usb;
|
||||
pub mod video;
|
||||
pub mod vru;
|
||||
|
||||
@@ -14,11 +15,7 @@ pub struct Dirs {
|
||||
}
|
||||
|
||||
pub struct Audio {
|
||||
pub audio_device_spec: sdl3_sys::audio::SDL_AudioSpec,
|
||||
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
|
||||
pub audio_device: u32,
|
||||
pub event_audio: audio::EventAudio,
|
||||
pub gain: f32,
|
||||
}
|
||||
|
||||
@@ -32,6 +29,7 @@ pub struct Storage {
|
||||
pub save_type: Vec<storage::SaveTypes>,
|
||||
pub paths: storage::Paths,
|
||||
pub saves: storage::Saves,
|
||||
pub save_state_slot: u32,
|
||||
}
|
||||
|
||||
pub struct Video {
|
||||
@@ -39,6 +37,11 @@ pub struct Video {
|
||||
pub fullscreen: bool,
|
||||
}
|
||||
|
||||
pub struct Usb {
|
||||
pub usb_tx: Option<tokio::sync::broadcast::Sender<usb::UsbData>>,
|
||||
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
pub dirs: Dirs,
|
||||
pub config: config::Config,
|
||||
@@ -49,12 +52,14 @@ pub struct Ui {
|
||||
pub input: Input,
|
||||
pub storage: Storage,
|
||||
pub video: Video,
|
||||
pub usb: Usb,
|
||||
}
|
||||
|
||||
impl Drop for Ui {
|
||||
fn drop(&mut self) {
|
||||
if self.with_sdl {
|
||||
unsafe {
|
||||
sdl3_ttf_sys::ttf::TTF_Quit();
|
||||
sdl3_sys::init::SDL_Quit();
|
||||
}
|
||||
}
|
||||
@@ -73,6 +78,14 @@ pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ttf_init() {
|
||||
unsafe {
|
||||
if !sdl3_ttf_sys::ttf::TTF_Init() {
|
||||
panic!("Could not initialize TTF");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dirs() -> Dirs {
|
||||
let exe_path = std::env::current_exe().unwrap();
|
||||
let portable_dir = exe_path.parent();
|
||||
@@ -125,6 +138,7 @@ impl Ui {
|
||||
joysticks,
|
||||
},
|
||||
storage: Storage {
|
||||
save_state_slot: 0,
|
||||
save_type: vec![],
|
||||
paths: storage::Paths {
|
||||
eep_file_path: std::path::PathBuf::new(),
|
||||
@@ -167,31 +181,17 @@ impl Ui {
|
||||
game_id: String::new(),
|
||||
game_hash: String::new(),
|
||||
audio: Audio {
|
||||
event_audio: audio::EventAudio {
|
||||
mempak: include_bytes!("../data/mempak.wav").to_vec(),
|
||||
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
|
||||
transferpak: include_bytes!("../data/transferpak.wav").to_vec(),
|
||||
netplay_desync: include_bytes!("../data/netplay_desync.wav").to_vec(),
|
||||
netplay_lost_connection: include_bytes!("../data/netplay_lost_connection.wav")
|
||||
.to_vec(),
|
||||
netplay_disconnected: [
|
||||
include_bytes!("../data/netplay_p1_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p2_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p3_disconnected.wav").to_vec(),
|
||||
include_bytes!("../data/netplay_p4_disconnected.wav").to_vec(),
|
||||
],
|
||||
cheats_enabled: include_bytes!("../data/cheats_enabled.wav").to_vec(),
|
||||
},
|
||||
audio_device_spec: Default::default(),
|
||||
audio_stream: std::ptr::null_mut(),
|
||||
event_audio_stream: std::ptr::null_mut(),
|
||||
audio_device: 0,
|
||||
gain: 1.0,
|
||||
},
|
||||
video: Video {
|
||||
window: std::ptr::null_mut(),
|
||||
fullscreen: false,
|
||||
},
|
||||
usb: Usb {
|
||||
usb_tx: None,
|
||||
cart_rx: None,
|
||||
},
|
||||
dirs,
|
||||
with_sdl,
|
||||
}
|
||||
|
||||
+9
-139
@@ -1,63 +1,8 @@
|
||||
use crate::device;
|
||||
use crate::netplay;
|
||||
use crate::ui;
|
||||
|
||||
pub struct EventAudio {
|
||||
pub mempak: Vec<u8>,
|
||||
pub rumblepak: Vec<u8>,
|
||||
pub transferpak: Vec<u8>,
|
||||
pub netplay_desync: Vec<u8>,
|
||||
pub netplay_lost_connection: Vec<u8>,
|
||||
pub netplay_disconnected: [Vec<u8>; 4],
|
||||
pub cheats_enabled: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn init(ui: &mut ui::Ui, frequency: u64) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
|
||||
|
||||
let device_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 48000,
|
||||
channels: 2,
|
||||
};
|
||||
ui.audio.audio_device = unsafe {
|
||||
sdl3_sys::audio::SDL_OpenAudioDevice(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&device_audio_spec,
|
||||
)
|
||||
};
|
||||
if ui.audio.audio_device == 0 {
|
||||
panic!("Could not open audio device");
|
||||
}
|
||||
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_GetAudioDeviceFormat(
|
||||
ui.audio.audio_device,
|
||||
&mut ui.audio.audio_device_spec,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
} {
|
||||
panic!("Could not get audio device format");
|
||||
}
|
||||
|
||||
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
|
||||
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
|
||||
freq: 24000,
|
||||
channels: 1,
|
||||
};
|
||||
|
||||
ui.audio.event_audio_stream = unsafe {
|
||||
sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &ui.audio.audio_device_spec)
|
||||
};
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain)
|
||||
&& sdl3_sys::audio::SDL_BindAudioStream(
|
||||
ui.audio.audio_device,
|
||||
ui.audio.event_audio_stream,
|
||||
)
|
||||
} {
|
||||
panic!("Could not bind audio stream");
|
||||
}
|
||||
init_game_audio(ui, frequency);
|
||||
}
|
||||
|
||||
@@ -69,29 +14,26 @@ pub fn init_game_audio(ui: &mut ui::Ui, frequency: u64) {
|
||||
};
|
||||
|
||||
ui.audio.audio_stream = unsafe {
|
||||
sdl3_sys::audio::SDL_CreateAudioStream(&game_audio_spec, &ui.audio.audio_device_spec)
|
||||
sdl3_sys::audio::SDL_OpenAudioDeviceStream(
|
||||
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
|
||||
&game_audio_spec,
|
||||
None,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if ui.audio.audio_stream.is_null() {
|
||||
return;
|
||||
panic!("Could not create audio stream");
|
||||
}
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain)
|
||||
&& sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.audio_stream)
|
||||
&& sdl3_sys::audio::SDL_ResumeAudioStreamDevice(ui.audio.audio_stream)
|
||||
} {
|
||||
panic!("Could not bind audio stream");
|
||||
panic!("Could not resume audio stream");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(ui: &mut ui::Ui) {
|
||||
close_game_audio(ui);
|
||||
unsafe {
|
||||
if !ui.audio.event_audio_stream.is_null() {
|
||||
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.event_audio_stream);
|
||||
ui.audio.event_audio_stream = std::ptr::null_mut();
|
||||
}
|
||||
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio.audio_device);
|
||||
ui.audio.audio_device = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_game_audio(ui: &mut ui::Ui) {
|
||||
@@ -110,7 +52,6 @@ pub fn lower_audio_volume(ui: &mut ui::Ui) {
|
||||
ui.audio.gain = 0.0;
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,77 +62,6 @@ pub fn raise_audio_volume(ui: &mut ui::Ui) {
|
||||
ui.audio.gain = 2.0;
|
||||
}
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
|
||||
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.event_audio_stream, ui.audio.gain);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_cheat_event(ui: &ui::Ui) {
|
||||
if ui.audio.event_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
let sound = &ui.audio.event_audio.cheats_enabled;
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.audio.event_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_netplay_audio(ui: &ui::Ui, error: u32) {
|
||||
if ui.audio.event_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
let sound = match error {
|
||||
netplay::NETPLAY_ERROR_DESYNC => &ui.audio.event_audio.netplay_desync,
|
||||
netplay::NETPLAY_ERROR_LOST_CONNECTION => &ui.audio.event_audio.netplay_lost_connection,
|
||||
netplay::NETPLAY_ERROR_PLAYER_1_DISCONNECTED => {
|
||||
&ui.audio.event_audio.netplay_disconnected[0]
|
||||
}
|
||||
netplay::NETPLAY_ERROR_PLAYER_2_DISCONNECTED => {
|
||||
&ui.audio.event_audio.netplay_disconnected[1]
|
||||
}
|
||||
netplay::NETPLAY_ERROR_PLAYER_3_DISCONNECTED => {
|
||||
&ui.audio.event_audio.netplay_disconnected[2]
|
||||
}
|
||||
netplay::NETPLAY_ERROR_PLAYER_4_DISCONNECTED => {
|
||||
&ui.audio.event_audio.netplay_disconnected[3]
|
||||
}
|
||||
_ => panic!("Invalid netplay error"),
|
||||
};
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.audio.event_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn play_pak_switch(ui: &ui::Ui, pak: device::controller::PakType) {
|
||||
if ui.audio.event_audio_stream.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let sound = match pak {
|
||||
device::controller::PakType::RumblePak => &ui.audio.event_audio.rumblepak,
|
||||
device::controller::PakType::MemPak => &ui.audio.event_audio.mempak,
|
||||
device::controller::PakType::TransferPak => &ui.audio.event_audio.transferpak,
|
||||
_ => panic!("Invalid pak type"),
|
||||
};
|
||||
if !unsafe {
|
||||
sdl3_sys::audio::SDL_PutAudioStreamData(
|
||||
ui.audio.event_audio_stream,
|
||||
sound.as_ptr() as *const std::ffi::c_void,
|
||||
sound.len() as i32,
|
||||
)
|
||||
} {
|
||||
panic!("Could not play audio");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ pub struct InputProfile {
|
||||
pub joystick_hat: [InputJoystickHat; ui::input::PROFILE_SIZE],
|
||||
pub joystick_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
|
||||
pub dinput: bool,
|
||||
pub deadzone: i32,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -54,6 +55,7 @@ pub struct Video {
|
||||
pub struct Emulation {
|
||||
pub overclock: bool,
|
||||
pub disable_expansion_pak: bool,
|
||||
pub usb: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
@@ -150,6 +152,7 @@ impl Config {
|
||||
emulation: Emulation {
|
||||
overclock: false,
|
||||
disable_expansion_pak: false,
|
||||
usb: false,
|
||||
},
|
||||
rom_dir: std::path::PathBuf::new(),
|
||||
}
|
||||
|
||||
+46
-9
@@ -5,7 +5,9 @@ use slint::Model;
|
||||
|
||||
slint::include_modules!();
|
||||
|
||||
pub const N64_EXTENSIONS: [&str; 5] = ["n64", "v64", "z64", "7z", "zip"];
|
||||
pub const N64_EXTENSIONS: [&str; 12] = [
|
||||
"n64", "v64", "z64", "7z", "zip", "bin", "N64", "V64", "Z64", "7Z", "ZIP", "BIN",
|
||||
];
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct GithubData {
|
||||
@@ -62,15 +64,23 @@ fn check_latest_version(weak: slint::Weak<AppWindow>) {
|
||||
});
|
||||
}
|
||||
|
||||
fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>]) {
|
||||
fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>], usb: ui::Usb) {
|
||||
let dirs = ui::get_dirs();
|
||||
let weak = app.as_weak();
|
||||
let controller_paths = controller_paths.to_owned();
|
||||
app.on_open_rom_button_clicked(move || {
|
||||
let controller_paths = controller_paths.clone();
|
||||
let mut usb_tx = None;
|
||||
if let Some(usb_tx_inner) = usb.usb_tx.as_ref() {
|
||||
usb_tx = Some(usb_tx_inner.clone());
|
||||
}
|
||||
let mut cart_rx = None;
|
||||
if let Some(cart_rx_inner) = usb.cart_rx.as_ref() {
|
||||
cart_rx = Some(cart_rx_inner.resubscribe());
|
||||
}
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
save_settings(&handle, &controller_paths);
|
||||
open_rom(&handle)
|
||||
open_rom(&handle, ui::Usb { usb_tx, cart_rx })
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
@@ -96,10 +106,12 @@ fn settings_window(app: &AppWindow, config: &ui::config::Config) {
|
||||
app.set_apply_crt_shader(config.video.crt);
|
||||
app.set_overclock_n64_cpu(config.emulation.overclock);
|
||||
app.set_disable_expansion_pak(config.emulation.disable_expansion_pak);
|
||||
app.set_emulate_usb(config.emulation.usb);
|
||||
let combobox_value = match config.video.upscale {
|
||||
1 => 0,
|
||||
2 => 1,
|
||||
4 => 2,
|
||||
8 => 3,
|
||||
_ => 0,
|
||||
};
|
||||
app.set_resolution(combobox_value);
|
||||
@@ -200,6 +212,7 @@ fn controller_window(
|
||||
let weak_app = app.as_weak();
|
||||
app.on_input_profile_button_clicked(move || {
|
||||
let dialog = InputProfileDialog::new().unwrap();
|
||||
dialog.set_deadzone(ui::input::DEADZONE_DEFAULT);
|
||||
let weak_dialog = dialog.as_weak();
|
||||
let weak_app = weak_app.clone();
|
||||
dialog.on_profile_creation_button_clicked(move || {
|
||||
@@ -209,10 +222,16 @@ fn controller_window(
|
||||
handle.hide().unwrap();
|
||||
let profile_name = handle.get_profile_name().into();
|
||||
let dinput = handle.get_dinput();
|
||||
let deadzone = handle.get_deadzone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut game_ui = ui::Ui::new();
|
||||
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
|
||||
ui::input::configure_input_profile(
|
||||
&mut game_ui,
|
||||
profile_name,
|
||||
dinput,
|
||||
deadzone,
|
||||
);
|
||||
update_input_profiles(&weak_app, &game_ui.config);
|
||||
});
|
||||
})
|
||||
@@ -230,7 +249,8 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
|
||||
config.video.crt = app.get_apply_crt_shader();
|
||||
config.emulation.overclock = app.get_overclock_n64_cpu();
|
||||
config.emulation.disable_expansion_pak = app.get_disable_expansion_pak();
|
||||
let upscale_values = [1, 2, 4];
|
||||
config.emulation.usb = app.get_emulate_usb();
|
||||
let upscale_values = [1, 2, 4, 8];
|
||||
config.video.upscale = upscale_values[app.get_resolution() as usize];
|
||||
|
||||
config.input.emulate_vru = app.get_emulate_vru();
|
||||
@@ -280,6 +300,11 @@ fn about_window(app: &AppWindow) {
|
||||
pub fn app_window() {
|
||||
let app = AppWindow::new().unwrap();
|
||||
about_window(&app);
|
||||
let mut usb = ui::Usb {
|
||||
usb_tx: None,
|
||||
cart_rx: None,
|
||||
};
|
||||
let mut shutdown_tx = None;
|
||||
let mut controller_paths;
|
||||
{
|
||||
let game_ui = ui::Ui::new();
|
||||
@@ -289,11 +314,18 @@ pub fn app_window() {
|
||||
controller_paths.insert(0, None);
|
||||
settings_window(&app, &game_ui.config);
|
||||
controller_window(&app, &game_ui.config, &controller_names, &controller_paths);
|
||||
|
||||
if game_ui.config.emulation.usb {
|
||||
(shutdown_tx, usb) = ui::usb::init(app.as_weak());
|
||||
}
|
||||
}
|
||||
local_game_window(&app, &controller_paths);
|
||||
local_game_window(&app, &controller_paths, usb);
|
||||
ui::netplay::netplay_window(&app, &controller_paths);
|
||||
ui::cheats::cheats_window(&app);
|
||||
app.run().unwrap();
|
||||
if let Some(shutdown_tx) = &shutdown_tx {
|
||||
ui::usb::close(shutdown_tx);
|
||||
}
|
||||
save_settings(&app, &controller_paths);
|
||||
}
|
||||
|
||||
@@ -347,6 +379,7 @@ pub fn run_rom(
|
||||
mut game_settings: GameSettings,
|
||||
vru_channel: VruChannel,
|
||||
netplay: Option<NetplayDevice>,
|
||||
usb: ui::Usb,
|
||||
weak: slint::Weak<AppWindow>,
|
||||
) {
|
||||
std::thread::Builder::new()
|
||||
@@ -369,6 +402,9 @@ pub fn run_rom(
|
||||
}
|
||||
}
|
||||
|
||||
device.ui.usb.usb_tx = usb.usb_tx;
|
||||
device.ui.usb.cart_rx = usb.cart_rx;
|
||||
|
||||
device.vru_window.window_notifier = vru_channel.vru_window_notifier;
|
||||
device.vru_window.word_receiver = vru_channel.vru_word_receiver;
|
||||
|
||||
@@ -409,7 +445,7 @@ pub fn run_rom(
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn open_rom(app: &AppWindow) {
|
||||
fn open_rom(app: &AppWindow, usb: ui::Usb) {
|
||||
let rom_dir = app.get_rom_dir();
|
||||
let select_rom = if !rom_dir.is_empty()
|
||||
&& let Ok(exists) = std::fs::exists(&rom_dir)
|
||||
@@ -430,13 +466,13 @@ fn open_rom(app: &AppWindow) {
|
||||
select_gb_rom[i] = Some(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title(format!("GB ROM P{}", i + 1))
|
||||
.add_filter("GB ROM files", &["gb", "gbc"])
|
||||
.add_filter("GB ROM files", &["gb", "gbc", "GB", "GBC"])
|
||||
.pick_file(),
|
||||
);
|
||||
select_gb_ram[i] = Some(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title(format!("GB RAM P{}", i + 1))
|
||||
.add_filter("GB RAM files", &["sav", "ram"])
|
||||
.add_filter("GB RAM files", &["sav", "ram", "srm", "SAV", "RAM", "SRM"])
|
||||
.pick_file(),
|
||||
);
|
||||
}
|
||||
@@ -501,6 +537,7 @@ fn open_rom(app: &AppWindow) {
|
||||
}
|
||||
},
|
||||
None,
|
||||
usb,
|
||||
weak,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export component AppWindow inherits Window {
|
||||
in-out property overclock_n64_cpu <=> SettingsData.overclock_n64_cpu;
|
||||
in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak;
|
||||
in-out property resolution <=> SettingsData.resolution;
|
||||
in-out property emulate_usb <=> SettingsData.emulate_usb;
|
||||
in-out property rom_dir <=> SettingsData.rom_dir;
|
||||
in-out property emulate_vru <=> ControllerData.emulate_vru;
|
||||
in-out property controller_enabled <=> ControllerData.controller_enabled;
|
||||
|
||||
@@ -89,10 +89,12 @@ export component ControllerConfig inherits Page {
|
||||
|
||||
if !ControllerData.blank_profiles: GroupBox {
|
||||
title: @tr("Profile");
|
||||
horizontal-stretch: 0.25;
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: ComboBox {
|
||||
model: ControllerData.input_profiles;
|
||||
min-width: self.preferred-width > 150px ? self.preferred-width : 150px;
|
||||
current-index: ControllerData.selected_profile_binding[player];
|
||||
selected => {
|
||||
ControllerData.selected_profile_binding[player] = self.current-index;
|
||||
@@ -103,11 +105,12 @@ export component ControllerConfig inherits Page {
|
||||
|
||||
GroupBox {
|
||||
title: @tr("Controller");
|
||||
horizontal-stretch: 0.75;
|
||||
VerticalLayout {
|
||||
alignment: space-between;
|
||||
for player in [0, 1, 2, 3]: ComboBox {
|
||||
model: ControllerData.controller_names;
|
||||
width: self.preferred-width;
|
||||
min-width: self.preferred-width > 300px ? self.preferred-width : 300px;
|
||||
current-index: ControllerData.selected_controller[player];
|
||||
selected => {
|
||||
ControllerData.selected_controller[player] = self.current-index;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox } from "std-widgets.slint";
|
||||
import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox, Slider } from "std-widgets.slint";
|
||||
|
||||
export global InputProfileData {
|
||||
in-out property <bool> dinput;
|
||||
in-out property <string> profile_name;
|
||||
in-out property <int> deadzone;
|
||||
}
|
||||
|
||||
export component InputProfileDialog inherits Window {
|
||||
@@ -10,6 +11,7 @@ export component InputProfileDialog inherits Window {
|
||||
callback profile_creation_button_clicked;
|
||||
in-out property dinput <=> InputProfileData.dinput;
|
||||
in-out property profile_name <=> InputProfileData.profile_name;
|
||||
in-out property deadzone <=> InputProfileData.deadzone;
|
||||
VerticalBox {
|
||||
HorizontalBox {
|
||||
LineEdit {
|
||||
@@ -21,6 +23,28 @@ export component InputProfileDialog inherits Window {
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
Text {
|
||||
text: @tr("Deadzone Percentage");
|
||||
vertical-alignment: center;
|
||||
}
|
||||
|
||||
Slider {
|
||||
min-width: 160px;
|
||||
maximum: 99;
|
||||
value: InputProfileData.deadzone;
|
||||
changed(value) => {
|
||||
InputProfileData.deadzone = value;
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: InputProfileData.deadzone + "%";
|
||||
vertical-alignment: center;
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: center;
|
||||
CheckBox {
|
||||
|
||||
@@ -9,6 +9,7 @@ export component NetplayJoin inherits Window {
|
||||
in-out property sessions <=> NetplayData.sessions;
|
||||
in-out property current_session <=> NetplayData.current_session;
|
||||
in-out property room_urls <=> NetplayData.room_urls;
|
||||
in-out property room_ports <=> NetplayData.room_ports;
|
||||
in-out property game_name <=> NetplayData.game_name;
|
||||
in-out property game_hash <=> NetplayData.game_hash;
|
||||
in-out property game_cheats <=> NetplayData.game_cheats;
|
||||
@@ -19,7 +20,7 @@ export component NetplayJoin inherits Window {
|
||||
in-out property rom_dir <=> NetplayData.rom_dir;
|
||||
callback refresh_session();
|
||||
callback select_rom(string);
|
||||
callback join_session(string, string, string, string, string);
|
||||
callback join_session(string, string, string, string, int);
|
||||
preferred-width: 700px;
|
||||
|
||||
VerticalBox {
|
||||
@@ -93,6 +94,7 @@ export component NetplayJoin inherits Window {
|
||||
NetplayData.current_session = -1;
|
||||
NetplayData.sessions = [];
|
||||
NetplayData.room_urls = [];
|
||||
NetplayData.room_ports = [];
|
||||
refresh_session();
|
||||
}
|
||||
}
|
||||
@@ -129,7 +131,7 @@ export component NetplayJoin inherits Window {
|
||||
NetplayData.game_hash,
|
||||
NetplayData.password,
|
||||
NetplayData.room_urls[NetplayData.current_session],
|
||||
NetplayData.sessions[NetplayData.current_session][1].text);
|
||||
NetplayData.room_ports[NetplayData.current_session]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export global NetplayData {
|
||||
in-out property <string> session_name;
|
||||
in-out property <[[StandardListViewItem]]> sessions;
|
||||
in-out property <[string]> room_urls;
|
||||
in-out property <[int]> room_ports;
|
||||
in-out property <int> current_session: -1;
|
||||
in-out property <string> rom_dir;
|
||||
in-out property <string> custom_server_url;
|
||||
|
||||
@@ -12,6 +12,7 @@ export global SettingsData {
|
||||
in-out property <bool> overclock_n64_cpu;
|
||||
in-out property <bool> disable_expansion_pak;
|
||||
in-out property <int> resolution;
|
||||
in-out property <bool> emulate_usb;
|
||||
in-out property <string> rom_dir;
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ export component Settings inherits Page {
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
model: ["1x", "2x", "4x"];
|
||||
model: ["1x", "2x", "4x", "8x"];
|
||||
current-index: SettingsData.resolution;
|
||||
selected => {
|
||||
SettingsData.resolution = self.current-index;
|
||||
@@ -105,5 +106,16 @@ export component Settings inherits Page {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalBox {
|
||||
alignment: start;
|
||||
CheckBox {
|
||||
text: @tr("Emulate UNFLoader (reload emulator after changing)");
|
||||
checked: SettingsData.emulate_usb;
|
||||
toggled => {
|
||||
SettingsData.emulate_usb = self.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+58
-13
@@ -28,6 +28,8 @@ const Y_AXIS_SHIFT: usize = 24;
|
||||
|
||||
const MAX_AXIS_VALUE: f64 = 85.0;
|
||||
|
||||
pub const DEADZONE_DEFAULT: i32 = 5;
|
||||
|
||||
pub struct Controllers {
|
||||
pub rumble: bool,
|
||||
pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
|
||||
@@ -40,7 +42,7 @@ pub struct InputData {
|
||||
}
|
||||
|
||||
fn bound_axis(x: &mut f64, y: &mut f64) {
|
||||
let radius = 95.0; // this is roughly the maxium diagonal distance of the controller
|
||||
let radius = f64::sqrt(70.0 * 70.0 + 70.0 * 70.0); // this is roughly the maxium diagonal distance of the controller
|
||||
|
||||
// Calculate the distance from the origin (0, 0)
|
||||
let distance = f64::sqrt((*x) * (*x) + (*y) * (*y));
|
||||
@@ -53,6 +55,24 @@ fn bound_axis(x: &mut f64, y: &mut f64) {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_deadzone(x: &mut f64, y: &mut f64, deadzone: i32) {
|
||||
let axis_deadzone = MAX_AXIS_VALUE * (deadzone as f64 / 100.0);
|
||||
|
||||
// Calculate the distance from the origin (0, 0)
|
||||
let distance = f64::sqrt((*x) * (*x) + (*y) * (*y));
|
||||
|
||||
if distance <= axis_deadzone {
|
||||
*x = 0.0;
|
||||
*y = 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
let new_distance =
|
||||
(distance - axis_deadzone) * MAX_AXIS_VALUE / (MAX_AXIS_VALUE - axis_deadzone);
|
||||
*x = *x / distance * new_distance;
|
||||
*y = *y / distance * new_distance;
|
||||
}
|
||||
|
||||
fn set_axis_from_joystick(
|
||||
profile: &ui::config::InputProfile,
|
||||
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
|
||||
@@ -225,8 +245,9 @@ fn set_buttons_from_joystick(
|
||||
|
||||
let profile_joystick_hat = profile.joystick_hat[i];
|
||||
if profile_joystick_hat.enabled
|
||||
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) }
|
||||
== profile_joystick_hat.direction
|
||||
&& (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.id) }
|
||||
& profile_joystick_hat.direction)
|
||||
!= 0
|
||||
{
|
||||
*keys |= 1 << i;
|
||||
}
|
||||
@@ -326,8 +347,9 @@ fn change_paks(
|
||||
pressed =
|
||||
unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.id) };
|
||||
} else if joystick_hat.enabled && !joystick.is_null() {
|
||||
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) }
|
||||
== joystick_hat.direction;
|
||||
pressed = (unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.id) }
|
||||
& joystick_hat.direction)
|
||||
!= 0;
|
||||
} else if key.enabled {
|
||||
pressed = unsafe { *keyboard_state.offset(key.id as isize) };
|
||||
}
|
||||
@@ -363,8 +385,6 @@ pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<Option<String>> {
|
||||
}
|
||||
|
||||
pub fn get(ui: &ui::Ui, channel: usize) -> InputData {
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
|
||||
let profile_name = ui.config.input.input_profile_binding[channel].clone();
|
||||
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
|
||||
let mut keys = 0;
|
||||
@@ -407,9 +427,11 @@ pub fn get(ui: &ui::Ui, channel: usize) -> InputData {
|
||||
}
|
||||
|
||||
if !controller.is_null() {
|
||||
(x, y) = set_axis_from_controller(profile, controller)
|
||||
(x, y) = set_axis_from_controller(profile, controller);
|
||||
apply_deadzone(&mut x, &mut y, profile.deadzone);
|
||||
} else if !joystick.is_null() {
|
||||
(x, y) = set_axis_from_joystick(profile, joystick)
|
||||
(x, y) = set_axis_from_joystick(profile, joystick);
|
||||
apply_deadzone(&mut x, &mut y, profile.deadzone);
|
||||
}
|
||||
bound_axis(&mut x, &mut y);
|
||||
|
||||
@@ -464,8 +486,9 @@ fn close_controllers(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, deadzone: i32) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
ui::ttf_init();
|
||||
|
||||
if profile == "default" {
|
||||
println!("Profile name cannot be default");
|
||||
@@ -567,8 +590,18 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
axis: 0,
|
||||
};
|
||||
|
||||
let font =
|
||||
ab_glyph::FontRef::try_from_slice(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
|
||||
let text_engine = unsafe { sdl3_ttf_sys::ttf::TTF_CreateRendererTextEngine(renderer) };
|
||||
let font = unsafe {
|
||||
let font_bytes = include_bytes!("../../data/Roboto-Regular.ttf");
|
||||
sdl3_ttf_sys::ttf::TTF_OpenFontIO(
|
||||
sdl3_sys::everything::SDL_IOFromConstMem(
|
||||
font_bytes.as_ptr() as *const std::ffi::c_void,
|
||||
font_bytes.len(),
|
||||
),
|
||||
true,
|
||||
35.0,
|
||||
)
|
||||
};
|
||||
|
||||
for (key, value) in key_labels.iter() {
|
||||
let mut event: sdl3_sys::events::SDL_Event = Default::default();
|
||||
@@ -577,7 +610,8 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
ui::video::draw_text(
|
||||
format!("Select binding for: {key}").as_str(),
|
||||
renderer,
|
||||
&font,
|
||||
text_engine,
|
||||
font,
|
||||
);
|
||||
|
||||
let mut key_set = false;
|
||||
@@ -666,11 +700,20 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
ui::video::draw_text(
|
||||
format!("Binding set for {key}").as_str(),
|
||||
renderer,
|
||||
text_engine,
|
||||
font,
|
||||
);
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
|
||||
close_controllers(open_joysticks, open_controllers);
|
||||
|
||||
unsafe {
|
||||
sdl3_ttf_sys::ttf::TTF_CloseFont(font);
|
||||
sdl3_ttf_sys::ttf::TTF_DestroyRendererTextEngine(text_engine);
|
||||
sdl3_sys::render::SDL_DestroyRenderer(renderer);
|
||||
sdl3_sys::video::SDL_DestroyWindow(window);
|
||||
}
|
||||
@@ -683,6 +726,7 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
|
||||
joystick_hat: new_joystick_hat,
|
||||
joystick_axis: new_joystick_axis,
|
||||
dinput,
|
||||
deadzone,
|
||||
};
|
||||
ui.config.input.input_profiles.insert(profile, new_profile);
|
||||
}
|
||||
@@ -883,6 +927,7 @@ pub fn get_default_profile() -> ui::config::InputProfile {
|
||||
axis: 0,
|
||||
}; PROFILE_SIZE],
|
||||
dinput: false,
|
||||
deadzone: DEADZONE_DEFAULT,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+67
-101
@@ -111,7 +111,7 @@ impl NetplayPages for NetplayJoin {
|
||||
fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slint::Weak<T>) {
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/getRegions")
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.header("netplay-id", env!("NETPLAY_ID"))
|
||||
.send();
|
||||
tokio::spawn(async move {
|
||||
let mut local_servers: Vec<(String, String)> = vec![];
|
||||
@@ -187,7 +187,7 @@ fn select_rom<T: ComponentHandle + NetplayPages + 'static>(
|
||||
if let Some(file) = select_rom.await {
|
||||
if let Some(rom_contents) = device::get_rom_contents(file.path()) {
|
||||
let hash = device::cart::rom::calculate_hash(&rom_contents);
|
||||
let game_name = ui::storage::get_game_name(&rom_contents);
|
||||
let mut game_name = ui::storage::get_game_name(&rom_contents);
|
||||
let game_crc = ui::storage::get_game_crc(&rom_contents);
|
||||
let cheats = ui::config::Cheats::new();
|
||||
let mut parsed_cheats = "".to_string();
|
||||
@@ -196,6 +196,14 @@ fn select_rom<T: ComponentHandle + NetplayPages + 'static>(
|
||||
{
|
||||
parsed_cheats = serde_json::to_string(game_cheats).unwrap();
|
||||
}
|
||||
if game_name.is_empty() {
|
||||
game_name = file
|
||||
.path()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
}
|
||||
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_game_name(game_name.into());
|
||||
@@ -288,7 +296,7 @@ pub fn setup_create_window(
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/createServer")
|
||||
.query(&[("region", server_url.strip_prefix("dispatcher:").unwrap())])
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.header("netplay-id", env!("NETPLAY_ID"))
|
||||
.send();
|
||||
let netplay_read_sender = netplay_read_sender.clone();
|
||||
let netplay_write_receiver = netplay_write_receiver.resubscribe();
|
||||
@@ -440,7 +448,7 @@ fn show_netplay_error(message: String) {
|
||||
fn update_sessions(weak: slint::Weak<NetplayJoin>) {
|
||||
let task = reqwest::Client::new()
|
||||
.get("https://dispatch.gopher64.com/getServers")
|
||||
.header("netplay-id", EMU_NAME)
|
||||
.header("netplay-id", env!("NETPLAY_ID"))
|
||||
.send();
|
||||
tokio::spawn(async move {
|
||||
let mut dispatcher_servers = std::collections::HashMap::new();
|
||||
@@ -483,6 +491,7 @@ fn update_sessions(weak: slint::Weak<NetplayJoin>) {
|
||||
tokio::spawn(async move {
|
||||
let mut sessions = vec![];
|
||||
let mut room_urls = vec![];
|
||||
let mut room_ports = vec![];
|
||||
for (server_name, server_url) in servers.iter() {
|
||||
if let Ok(Ok((socket, _response))) = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
@@ -515,50 +524,45 @@ fn update_sessions(weak: slint::Weak<NetplayJoin>) {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Some(Ok(response)) = read.next().await {
|
||||
if let Ok(message) =
|
||||
if let Some(Ok(response)) = read.next().await
|
||||
&& let Ok(message) =
|
||||
serde_json::from_slice::<NetplayMessage>(&response.into_data())
|
||||
{
|
||||
if message.message_type == "reply_get_rooms"
|
||||
&& message.accept.unwrap() == 0
|
||||
&& let Some(rooms) = message.rooms
|
||||
{
|
||||
for room in rooms {
|
||||
let mut session = vec![];
|
||||
room_urls.push(server_url.into());
|
||||
&& message.message_type == "reply_get_rooms"
|
||||
&& message.accept.unwrap() == 0
|
||||
&& let Some(rooms) = message.rooms
|
||||
{
|
||||
for room in rooms {
|
||||
let mut session = vec![];
|
||||
room_urls.push(server_url.into());
|
||||
room_ports.push(room.port.unwrap());
|
||||
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(server_name),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.room_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.game_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(if room.protected.unwrap() {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
}),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(
|
||||
if room
|
||||
.features
|
||||
.unwrap_or_default()
|
||||
.contains_key("cheats")
|
||||
{
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
),
|
||||
));
|
||||
sessions.push(session);
|
||||
}
|
||||
}
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(server_name),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.room_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(room.game_name.unwrap()),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(if room.protected.unwrap() {
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
}),
|
||||
));
|
||||
session.push(slint::StandardListViewItem::from(
|
||||
slint::SharedString::from(
|
||||
if room.features.unwrap_or_default().contains_key("cheats")
|
||||
{
|
||||
"True"
|
||||
} else {
|
||||
"False"
|
||||
},
|
||||
),
|
||||
));
|
||||
sessions.push(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -583,6 +587,11 @@ fn update_sessions(weak: slint::Weak<NetplayJoin>) {
|
||||
std::rc::Rc::new(room_urls_vec);
|
||||
handle.set_room_urls(slint::ModelRc::from(room_urls_model));
|
||||
|
||||
let room_ports_vec = slint::VecModel::from(room_ports.to_vec());
|
||||
let room_ports_model: std::rc::Rc<slint::VecModel<i32>> =
|
||||
std::rc::Rc::new(room_ports_vec);
|
||||
handle.set_room_ports(slint::ModelRc::from(room_ports_model));
|
||||
|
||||
handle.set_current_session(-1);
|
||||
handle.set_pending_refresh(false);
|
||||
})
|
||||
@@ -730,7 +739,7 @@ fn join_session(
|
||||
player_name: String,
|
||||
game_hash: String,
|
||||
password: String,
|
||||
room_name: String,
|
||||
room_port: i32,
|
||||
fullscreen: bool,
|
||||
weak_app: slint::Weak<AppWindow>,
|
||||
weak: slint::Weak<NetplayJoin>,
|
||||
@@ -738,73 +747,26 @@ fn join_session(
|
||||
tokio::spawn(async move {
|
||||
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
|
||||
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
|
||||
let request_rooms = NetplayMessage {
|
||||
message_type: "request_get_rooms".to_string(),
|
||||
player_name: None,
|
||||
client_sha: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
player_names: None,
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
rooms: None,
|
||||
message: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: None,
|
||||
};
|
||||
|
||||
netplay_write_sender.send(Some(request_rooms)).unwrap();
|
||||
|
||||
let mut port = 0;
|
||||
match tokio::time::timeout(
|
||||
std::time::Duration::from_secs(2),
|
||||
netplay_read_receiver.recv(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(message)) => {
|
||||
if message.message_type == "reply_get_rooms"
|
||||
&& message.accept.unwrap() == 0
|
||||
&& let Some(rooms) = message.rooms
|
||||
{
|
||||
for room in rooms {
|
||||
if room.room_name.unwrap() == room_name {
|
||||
port = room.port.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
panic!("netplay_read_receiver error: {err}");
|
||||
}
|
||||
Err(_) => {
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
handle.set_pending_session(false);
|
||||
show_netplay_error("Server did not respond".to_string());
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let join_room = NetplayMessage {
|
||||
message_type: "request_join_room".to_string(),
|
||||
player_name: Some(player_name),
|
||||
client_sha: Some(env!("GIT_HASH").to_string()),
|
||||
netplay_version: None,
|
||||
emulator: None,
|
||||
netplay_version: Some(NETPLAY_VERSION),
|
||||
emulator: Some(EMU_NAME.to_string()),
|
||||
accept: None,
|
||||
message: None,
|
||||
rooms: None,
|
||||
auth_time: None,
|
||||
player_names: None,
|
||||
auth: None,
|
||||
auth_time: Some(now_utc),
|
||||
auth: Some(format!("{:x}", hasher.finalize())),
|
||||
room: Some(NetplayRoom {
|
||||
room_name: None,
|
||||
password: Some(password),
|
||||
game_name: None,
|
||||
md5: Some(game_hash),
|
||||
protected: None,
|
||||
port: Some(port),
|
||||
port: Some(room_port),
|
||||
features: None,
|
||||
buffer_target: None,
|
||||
}),
|
||||
@@ -1127,6 +1089,10 @@ fn setup_wait_window(
|
||||
peer_addr: socket_addr,
|
||||
player_number: player_number as u8,
|
||||
}),
|
||||
ui::Usb {
|
||||
usb_tx: None,
|
||||
cart_rx: None,
|
||||
},
|
||||
weak_app,
|
||||
);
|
||||
})
|
||||
@@ -1194,7 +1160,7 @@ pub fn setup_join_window(
|
||||
});
|
||||
let weak = join_window.as_weak();
|
||||
join_window.on_join_session(
|
||||
move |player_name, game_hash, password, room_url, room_name| {
|
||||
move |player_name, game_hash, password, room_url, room_port| {
|
||||
let _ = netplay_write_sender.send(None); // close current websocket if any
|
||||
manage_websocket(
|
||||
room_url.to_string(),
|
||||
@@ -1209,7 +1175,7 @@ pub fn setup_join_window(
|
||||
player_name.to_string(),
|
||||
game_hash.to_string(),
|
||||
password.to_string(),
|
||||
room_name.to_string(),
|
||||
room_port,
|
||||
fullscreen,
|
||||
weak_app.clone(),
|
||||
weak.clone(),
|
||||
|
||||
+7
-4
@@ -199,10 +199,13 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
|
||||
.paths
|
||||
.savestate_file_path
|
||||
.clone_from(&states_path);
|
||||
ui.storage
|
||||
.paths
|
||||
.savestate_file_path
|
||||
.push(prefix.to_owned() + "-" + &ui.game_hash + ".state");
|
||||
ui.storage.paths.savestate_file_path.push(
|
||||
prefix.to_owned()
|
||||
+ "-"
|
||||
+ &ui.game_hash
|
||||
+ ".state"
|
||||
+ ui.storage.save_state_slot.to_string().as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
|
||||
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
use crate::ui;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
const DATATYPE_TCPTEST: u32 = 0x07;
|
||||
const DATATYPE_ROMUPLOAD: u32 = 0x08;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UsbData {
|
||||
pub data: Vec<u8>,
|
||||
pub data_type: u32,
|
||||
pub data_size: u32,
|
||||
}
|
||||
|
||||
fn respond_to_handshake(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, data: Vec<u8>) {
|
||||
if let Ok(data) = String::from_utf8(data)
|
||||
&& data == "N64"
|
||||
{
|
||||
ui::usb::send_to_usb(
|
||||
usb_tx,
|
||||
ui::usb::UsbData {
|
||||
data: [b'N', b'6', b'4'].to_vec(),
|
||||
data_type: DATATYPE_TCPTEST,
|
||||
data_size: 3,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn upload_rom(
|
||||
weak: slint::Weak<ui::gui::AppWindow>,
|
||||
rom: Vec<u8>,
|
||||
usb_tx: &tokio::sync::broadcast::Sender<UsbData>,
|
||||
cart_rx: &tokio::sync::broadcast::Receiver<UsbData>,
|
||||
) {
|
||||
let weak_clone = weak.clone();
|
||||
let usb_tx = usb_tx.clone();
|
||||
let cart_rx = cart_rx.resubscribe();
|
||||
|
||||
weak.upgrade_in_event_loop(move |handle| {
|
||||
let dir = std::env::temp_dir();
|
||||
let rom_path = dir.join("rom_upload.bin");
|
||||
std::fs::write(rom_path.clone(), rom).unwrap();
|
||||
|
||||
ui::gui::run_rom(
|
||||
ui::gui::GbPaths {
|
||||
rom: [None, None, None, None],
|
||||
ram: [None, None, None, None],
|
||||
},
|
||||
rom_path,
|
||||
ui::gui::GameSettings {
|
||||
fullscreen: handle.get_fullscreen(),
|
||||
overclock: handle.get_overclock_n64_cpu(),
|
||||
disable_expansion_pak: handle.get_disable_expansion_pak(),
|
||||
cheats: std::collections::HashMap::new(), // will be filled in later
|
||||
},
|
||||
ui::gui::VruChannel {
|
||||
vru_window_notifier: None,
|
||||
vru_word_receiver: None,
|
||||
},
|
||||
None,
|
||||
ui::Usb {
|
||||
usb_tx: Some(usb_tx),
|
||||
cart_rx: Some(cart_rx),
|
||||
},
|
||||
weak_clone,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
conn: tokio::net::TcpStream,
|
||||
mut shutdown_rx: tokio::sync::watch::Receiver<()>,
|
||||
mut usb_rx: tokio::sync::broadcast::Receiver<UsbData>,
|
||||
usb_tx: tokio::sync::broadcast::Sender<UsbData>,
|
||||
cart_tx: tokio::sync::broadcast::Sender<UsbData>,
|
||||
cart_rx: tokio::sync::broadcast::Receiver<UsbData>,
|
||||
weak: slint::Weak<ui::gui::AppWindow>,
|
||||
) {
|
||||
let (mut incoming, mut outgoing) = conn.into_split();
|
||||
|
||||
let mut shutdown_rx_clone = shutdown_rx.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
data = usb_rx.recv() => {
|
||||
match data {
|
||||
Ok(data) => {
|
||||
let mut output: Vec<u8> = vec![];
|
||||
output.extend_from_slice(&data.data_type.to_be_bytes());
|
||||
output.extend_from_slice(&data.data_size.to_be_bytes());
|
||||
output.extend_from_slice(&data.data);
|
||||
if outgoing.write_all(&output).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {
|
||||
panic!("usb_rx lagged");
|
||||
}
|
||||
Err(tokio::sync::broadcast::error::RecvError::Closed) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown_rx_clone.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut incoming_buffer = vec![0u8; 4096];
|
||||
let mut data_type: Option<u32> = None;
|
||||
let mut data_size: Option<u32> = None;
|
||||
let mut usb_buffer: Vec<u8> = vec![];
|
||||
loop {
|
||||
tokio::select! {
|
||||
result = incoming.read(&mut incoming_buffer) => {
|
||||
match result {
|
||||
Ok(0) => {
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
usb_buffer.extend_from_slice(&incoming_buffer[0..n]);
|
||||
if data_type.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_type = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if data_type.is_some() && data_size.is_none() {
|
||||
if usb_buffer.len() < 4 {
|
||||
continue;
|
||||
} else {
|
||||
data_size = Some(u32::from_be_bytes(usb_buffer[0..4].try_into().unwrap()));
|
||||
usb_buffer.drain(0..4);
|
||||
}
|
||||
}
|
||||
if let Some(d_type) = data_type && let Some(d_size) = data_size {
|
||||
let length = d_size as usize;
|
||||
if usb_buffer.len() >= length {
|
||||
let usb_data = UsbData {
|
||||
data: usb_buffer[0..length].to_vec(),
|
||||
data_type: d_type,
|
||||
data_size: d_size,
|
||||
};
|
||||
usb_buffer.drain(0..length);
|
||||
if usb_data.data_type == DATATYPE_TCPTEST {
|
||||
respond_to_handshake(&usb_tx,usb_data.data);
|
||||
} else if usb_data.data_type == DATATYPE_ROMUPLOAD {
|
||||
upload_rom(weak.clone(), usb_data.data,&usb_tx,&cart_rx);
|
||||
} else {
|
||||
cart_tx.send(usb_data).unwrap();
|
||||
}
|
||||
data_type = None;
|
||||
data_size = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown_rx.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
weak: slint::Weak<ui::gui::AppWindow>,
|
||||
) -> (Option<tokio::sync::watch::Sender<()>>, ui::Usb) {
|
||||
let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(());
|
||||
let (usb_tx, usb_rx): (
|
||||
tokio::sync::broadcast::Sender<UsbData>,
|
||||
tokio::sync::broadcast::Receiver<UsbData>,
|
||||
) = tokio::sync::broadcast::channel(1024);
|
||||
let (cart_tx, cart_rx): (
|
||||
tokio::sync::broadcast::Sender<UsbData>,
|
||||
tokio::sync::broadcast::Receiver<UsbData>,
|
||||
) = tokio::sync::broadcast::channel(1024);
|
||||
|
||||
let usb_tx_clone = usb_tx.clone();
|
||||
let cart_rx_clone = cart_rx.resubscribe();
|
||||
tokio::spawn(async move {
|
||||
let listener = tokio::net::TcpListener::bind("localhost:64000")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = listener.accept() => {
|
||||
if let Ok((c,_)) = res {
|
||||
handle_connection(c,shutdown_rx.clone(),usb_rx.resubscribe(),usb_tx.clone(),cart_tx.clone(),cart_rx.resubscribe(),weak.clone()).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ = shutdown_rx.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
(
|
||||
Some(shutdown_tx),
|
||||
ui::Usb {
|
||||
usb_tx: Some(usb_tx_clone),
|
||||
cart_rx: Some(cart_rx_clone),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn close(shutdown_tx: &tokio::sync::watch::Sender<()>) {
|
||||
let _ = shutdown_tx.send(());
|
||||
}
|
||||
|
||||
pub fn send_to_usb(usb_tx: &tokio::sync::broadcast::Sender<UsbData>, buffer: UsbData) {
|
||||
usb_tx.send(buffer).unwrap();
|
||||
}
|
||||
+63
-58
@@ -1,10 +1,10 @@
|
||||
#![allow(non_snake_case)]
|
||||
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
|
||||
use crate::{device, ui};
|
||||
use ab_glyph::{Font, ScaleFont};
|
||||
|
||||
pub fn init(device: &mut device::Device) {
|
||||
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
|
||||
ui::ttf_init();
|
||||
|
||||
let title = std::ffi::CString::new("gopher64").unwrap();
|
||||
|
||||
@@ -74,7 +74,15 @@ pub fn init(device: &mut device::Device) {
|
||||
crt: device.ui.config.video.crt,
|
||||
};
|
||||
|
||||
unsafe { rdp_init(device.ui.video.window as *mut std::ffi::c_void, gfx_info) }
|
||||
unsafe {
|
||||
let font_bytes = include_bytes!("../../data/Roboto-Regular.ttf");
|
||||
rdp_init(
|
||||
device.ui.video.window as *mut std::ffi::c_void,
|
||||
gfx_info,
|
||||
font_bytes.as_ptr() as *const std::ffi::c_void,
|
||||
font_bytes.len(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(ui: &ui::Ui) {
|
||||
@@ -85,10 +93,13 @@ pub fn close(ui: &ui::Ui) {
|
||||
}
|
||||
|
||||
pub fn update_screen() {
|
||||
// when the window is closed, running is set to false
|
||||
unsafe { rdp_update_screen() }
|
||||
}
|
||||
|
||||
pub fn render_frame() {
|
||||
unsafe { rdp_render_frame() }
|
||||
}
|
||||
|
||||
pub fn state_size() -> usize {
|
||||
unsafe { rdp_state_size() }
|
||||
}
|
||||
@@ -122,9 +133,21 @@ pub fn load_state(device: &mut device::Device, rdp_state: *const u8) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_callback(device: &mut device::Device) -> bool {
|
||||
pub fn pause_loop() {
|
||||
let mut paused = true;
|
||||
let mut frame_advance = false;
|
||||
while paused && !frame_advance {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
let callback = unsafe { rdp_check_callback() };
|
||||
paused = callback.paused;
|
||||
frame_advance = callback.frame_advance;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_callback(device: &mut device::Device) -> (bool, bool) {
|
||||
let mut speed_limiter_toggled = false;
|
||||
let mut callback = unsafe { rdp_check_callback() };
|
||||
let callback = unsafe { rdp_check_callback() };
|
||||
device.cpu.running = callback.emu_running;
|
||||
if device.netplay.is_none() {
|
||||
if callback.save_state {
|
||||
@@ -136,19 +159,27 @@ pub fn check_callback(device: &mut device::Device) -> bool {
|
||||
speed_limiter_toggled = true;
|
||||
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
|
||||
}
|
||||
while callback.paused {
|
||||
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
|
||||
unsafe { sdl3_sys::events::SDL_PumpEvents() };
|
||||
callback = unsafe { rdp_check_callback() };
|
||||
}
|
||||
}
|
||||
|
||||
if device.ui.storage.save_state_slot != callback.save_state_slot {
|
||||
ui::video::onscreen_message(
|
||||
&device.ui,
|
||||
&format!("Switching savestate slot to {}", callback.save_state_slot,),
|
||||
);
|
||||
device.ui.storage.save_state_slot = callback.save_state_slot;
|
||||
device
|
||||
.ui
|
||||
.storage
|
||||
.paths
|
||||
.savestate_file_path
|
||||
.set_extension("state".to_owned() + callback.save_state_slot.to_string().as_str());
|
||||
}
|
||||
if callback.lower_volume {
|
||||
ui::audio::lower_audio_volume(&mut device.ui);
|
||||
} else if callback.raise_volume {
|
||||
ui::audio::raise_audio_volume(&mut device.ui);
|
||||
}
|
||||
speed_limiter_toggled
|
||||
(speed_limiter_toggled, callback.paused)
|
||||
}
|
||||
|
||||
pub fn set_register(reg: u32, value: u32) {
|
||||
@@ -161,60 +192,34 @@ pub fn process_rdp_list() -> u64 {
|
||||
unsafe { rdp_process_commands() }
|
||||
}
|
||||
|
||||
pub fn check_framebuffers(address: u32, length: u32) {
|
||||
unsafe { rdp_check_framebuffers(address, length) }
|
||||
}
|
||||
|
||||
pub fn onscreen_message(_ui: &ui::Ui, message: &str) {
|
||||
unsafe { rdp_onscreen_message(std::ffi::CString::new(message).unwrap().as_ptr()) };
|
||||
}
|
||||
|
||||
pub fn draw_text(
|
||||
text: &str,
|
||||
renderer: *mut sdl3_sys::render::SDL_Renderer,
|
||||
font: &ab_glyph::FontRef,
|
||||
text_engine: *mut sdl3_ttf_sys::ttf::TTF_TextEngine,
|
||||
font: *mut sdl3_ttf_sys::ttf::TTF_Font,
|
||||
) {
|
||||
// Clear the canvas
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
let (mut w, mut h) = (0, 0);
|
||||
sdl3_sys::render::SDL_GetRenderOutputSize(renderer, &mut w, &mut h);
|
||||
|
||||
let ttf_text = sdl3_ttf_sys::ttf::TTF_CreateText(
|
||||
text_engine,
|
||||
font,
|
||||
std::ffi::CString::new(text).unwrap().as_ptr(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderClear(renderer);
|
||||
};
|
||||
|
||||
let text_size = 40.0;
|
||||
let (mut w, mut h) = (0, 0);
|
||||
unsafe { sdl3_sys::render::SDL_GetRenderOutputSize(renderer, &mut w, &mut h) };
|
||||
let x_start = 20.0;
|
||||
let y_start = (h / 2) as f32;
|
||||
|
||||
let mut x_offset = 0.0;
|
||||
for c in text.chars() {
|
||||
let q_glyph_id = font.glyph_id(c);
|
||||
let q_glyph = q_glyph_id.with_scale(text_size);
|
||||
|
||||
if let Some(q) = font.outline_glyph(q_glyph) {
|
||||
q.draw(|x, y, c| {
|
||||
if c > 0.5 {
|
||||
unsafe {
|
||||
sdl3_sys::render::SDL_SetRenderDrawColor(
|
||||
renderer,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
|
||||
);
|
||||
sdl3_sys::render::SDL_RenderPoint(
|
||||
renderer,
|
||||
x_start + x_offset + x as f32 - q.px_bounds().width()
|
||||
+ q.px_bounds().max.x,
|
||||
y_start + y as f32 - q.px_bounds().height() + q.px_bounds().max.y,
|
||||
);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
x_offset += font.as_scaled(text_size).h_advance(q_glyph_id);
|
||||
}
|
||||
|
||||
// Present the canvas
|
||||
if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } {
|
||||
panic!("Could not present renderer");
|
||||
sdl3_sys::everything::SDL_RenderClear(renderer);
|
||||
sdl3_ttf_sys::ttf::TTF_DrawRendererText(ttf_text, 20.0, h as f32 / 2.0);
|
||||
sdl3_sys::render::SDL_RenderPresent(renderer);
|
||||
sdl3_ttf_sys::ttf::TTF_DestroyText(ttf_text);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user