Compare commits

...

33 Commits

Author SHA1 Message Date
Logan McNaughton ca4a20f524 update cargo to v1.1.17 (#798) 2026-04-23 11:02:02 +02:00
Logan McNaughton 3b145909e8 show more detailed version in --version (#797) 2026-04-23 08:57:03 +02:00
Logan McNaughton 6eb3367f1b don't show hardcore switch if it is not enabled (#795) 2026-04-22 09:32:22 +02:00
Logan McNaughton 03f83fe917 fix git version with shallow clones (#794)
* fix git version with shallow clones

* more
2026-04-22 09:08:54 +02:00
Logan McNaughton c4771f91e0 print error to screen (#793)
* print error to screen

* mmore
2026-04-21 22:35:14 +02:00
Danny Kirkham bdd12352e1 Enable accessibility features (#791) 2026-04-21 18:23:10 +02:00
Logan McNaughton 224bd2df1d clean up RA variables (#789)
* clean up RA variables

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-04-21 12:57:41 +02:00
Logan McNaughton 412f928781 improve function order (#788)
* improve function order

* more
2026-04-20 22:53:42 +02:00
Logan McNaughton 779ef9fe77 update fullscreen note (#787) 2026-04-20 19:27:09 +02:00
Logan McNaughton 048d1d662f Track recent roms (#786)
* track recent roms

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-04-20 16:53:40 +02:00
Logan McNaughton cf3a27cecf Use switch instead of checkbox (#785) 2026-04-20 12:27:44 +02:00
Logan McNaughton b8c3556df6 cleanup loop (#784) 2026-04-20 10:34:27 +02:00
Logan McNaughton 9e337295ca don't launch game if already running (#783) 2026-04-20 10:13:15 +02:00
Logan McNaughton 8cb57811a6 make rfd optional (#782) 2026-04-20 09:27:29 +02:00
Logan McNaughton 82c02461f8 handle file dropping (#781)
* handle file dropping

* more
2026-04-20 09:11:09 +02:00
Logan McNaughton 26253b5ed7 just create config (#780) 2026-04-20 07:14:05 +02:00
Logan McNaughton f520aeab82 use tokio process (#779) 2026-04-20 07:00:39 +02:00
Logan McNaughton edb6960d4f Make Slint optional (#778)
* make slint optional

* more

* more
2026-04-19 23:15:56 +02:00
Logan McNaughton a488f0d91a fix some typos (#777) 2026-04-19 18:51:01 +02:00
Logan McNaughton 73d895a64a add vsync option (#776) 2026-04-19 18:25:36 +02:00
Logan McNaughton 805bcba249 run VRU dialog in main thread (#774)
* run VRU dialog in main thread

* more
2026-04-19 17:00:50 +02:00
Logan McNaughton 66abd14c3b add small note to netplay join (#773) 2026-04-19 14:09:27 +02:00
Logan McNaughton d856499f59 fix image positioning (#772)
* fix image positioning

* more
2026-04-19 07:57:57 +02:00
Logan McNaughton bc3dd4d39a Hotkey for onscreen FPS (#770)
* fps counter

* more
2026-04-18 21:55:37 +02:00
Logan McNaughton 5a91d09f9e improved transferpak setup (#769)
* improved transferpak setup

* more

* more

* more
2026-04-18 16:48:55 +02:00
Logan McNaughton b09f8bf891 update MACOSX_DEPLOYMENT_TARGET (#768) 2026-04-18 10:08:30 +02:00
Logan McNaughton 8ee10e40ad add provisionprofile (#767)
* add provisionprofile

* more
2026-04-18 08:02:30 +02:00
Logan McNaughton f8a7153fdd enable vsync if required (#765)
* enable vsync if required

* add note

* more

* more
2026-04-17 15:07:15 +02:00
Logan McNaughton a592d0eb84 remove uses of to_owned (#764)
* remove uses of to_owned

* more

* more
2026-04-17 09:59:32 +02:00
Logan McNaughton 28aa8ea280 use tokio::fs (#763) 2026-04-17 09:27:42 +02:00
Logan McNaughton cb87f79710 writeback GB ram files (#762) 2026-04-17 08:56:53 +02:00
Logan McNaughton be117214c3 Update README with notable features of Gopher64 (#761)
Added features list for Gopher64 emulator
2026-04-17 08:48:47 +02:00
Logan McNaughton bb056a77f4 bump version (#760) 2026-04-16 15:56:35 +02:00
41 changed files with 1271 additions and 677 deletions
+1
View File
@@ -8,6 +8,7 @@ updates:
directory: "/"
schedule:
interval: "weekly"
# - package-ecosystem: "cargo"
# directory: "/"
# schedule:
+30 -17
View File
@@ -4,11 +4,11 @@ permissions:
on:
push:
branches: ["main"]
branches: [ "main" ]
tags:
- "v*"
pull_request:
branches: ["main"]
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
@@ -40,26 +40,31 @@ jobs:
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
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 libthai-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
packages: build-essential git make pkg-config cmake ninja-build
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
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 libthai-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
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{ matrix.llvm }}.tar.xz
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{
matrix.llvm }}.tar.xz
- name: Build
run: |
cargo build --no-default-features -v --target=${{ matrix.target }}
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
@@ -104,9 +109,12 @@ jobs:
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm }}.exe
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm
}}.exe
- name: Build
run: |
cargo build --no-default-features -v --target=${{ matrix.target }}
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
@@ -163,16 +171,20 @@ jobs:
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-macOS-${{ matrix.llvm }}.tar.xz
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-macOS-${{
matrix.llvm }}.tar.xz
- name: Build
run: |
cargo install cargo-bundle
brew install molten-vk
cargo build --no-default-features -v --target=${{ matrix.target }}
cargo bundle --release --target=${{ matrix.target }} --format osx
install_name_tool -change /opt/homebrew/opt/freetype/lib/libfreetype.6.dylib \
@executable_path/../Frameworks/libfreetype.6.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64
@@ -180,11 +192,11 @@ jobs:
@executable_path/../Frameworks/libpng16.16.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/Frameworks/libfreetype.6.dylib
env:
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
MACOSX_DEPLOYMENT_TARGET: "15.0"
MACOSX_DEPLOYMENT_TARGET: "15.7"
- name: Sign Bundle Dev
if: ${{ github.ref_type != 'tag' }}
run: |
codesign -f --entitlements data/entitlements_dev.plist --deep --timestamp --options runtime -s - target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
codesign -f --entitlements data/macos/entitlements_dev.plist --deep --timestamp --options runtime -s - target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
- name: Import Codesign Certs
if: ${{ github.ref_type == 'tag' }}
uses: apple-actions/import-codesign-certs@v6
@@ -194,7 +206,8 @@ jobs:
- name: Sign Bundle Release
if: ${{ github.ref_type == 'tag' }}
run: |
codesign -f --entitlements data/entitlements_prod.plist --deep --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
cp data/macos/embedded.provisionprofile target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/embedded.provisionprofile
codesign -f --entitlements data/macos/entitlements_prod.plist --deep --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
zip -r gopher64-${{ matrix.arch }}.zip target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
xcrun notarytool submit --wait --apple-id "${{ secrets.APPLE_ID }}" --team-id "${{ secrets.APPLE_DEVELOPER_ID }}" --password "${{ secrets.APPLE_APP_PASSWORD }}" gopher64-${{ matrix.arch }}.zip
xcrun stapler staple target/${{ matrix.target }}/release/bundle/osx/Gopher64.app
+15 -11
View File
@@ -2,9 +2,9 @@ name: Rust-lint
on:
push:
branches: ["main"]
branches: [ "main" ]
pull_request:
branches: ["main"]
branches: [ "main" ]
permissions:
contents: read
@@ -23,26 +23,30 @@ jobs:
with:
components: clippy, rustfmt
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
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 libthai-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
packages: build-essential git make pkg-config cmake ninja-build
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
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 libthai-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
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
run: |
cargo clippy -- -Dwarnings
cargo clippy --no-default-features -- -Dwarnings
Generated
+358 -73
View File
@@ -18,6 +18,95 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]]
name = "accesskit"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eca13c82f9a5cd813120b2e9b6a5d10532c6e4cd140c295cebd1f770095c8a5"
[[package]]
name = "accesskit_atspi_common"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eb9cc46b7fb6987c4f891f0301b230b29d9e69b4854f060a0cf41fbc407ab77"
dependencies = [
"accesskit",
"accesskit_consumer",
"atspi-common",
"serde",
"zvariant",
]
[[package]]
name = "accesskit_consumer"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d880a613f29621c90e801feec40f5dd61d837d7e20bf9b67676d45e7364a36"
dependencies = [
"accesskit",
"hashbrown 0.16.1",
]
[[package]]
name = "accesskit_macos"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0ddfc3fe3d457d11cc1c4989105986a03583a1d54d0c25053118944b62e100"
dependencies = [
"accesskit",
"accesskit_consumer",
"hashbrown 0.16.1",
"objc2 0.5.2",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
]
[[package]]
name = "accesskit_unix"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d552169ef018149966ed139bb0311c6947b3343e9140d1b9f88d69da9528fd"
dependencies = [
"accesskit",
"accesskit_atspi_common",
"async-channel",
"async-executor",
"async-task",
"atspi",
"futures-lite",
"futures-util",
"serde",
"zbus",
]
[[package]]
name = "accesskit_windows"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d277279d0a3b0c0021dd110b55aa1fe326b09ee2cbc338df28f847c7daf94e25"
dependencies = [
"accesskit",
"accesskit_consumer",
"hashbrown 0.16.1",
"static_assertions",
"windows 0.61.3",
"windows-core 0.61.2",
]
[[package]]
name = "accesskit_winit"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db08dff285306264a1de127ea07bb9e7a1ed71bd8593c168d0731caa782516c9"
dependencies = [
"accesskit",
"accesskit_macos",
"accesskit_unix",
"accesskit_windows",
"raw-window-handle",
"winit",
]
[[package]]
name = "adler2"
version = "2.0.1"
@@ -372,6 +461,43 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atspi"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d"
dependencies = [
"atspi-common",
"atspi-proxies",
]
[[package]]
name = "atspi-common"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d"
dependencies = [
"enumflags2",
"serde",
"static_assertions",
"zbus",
"zbus-lockstep",
"zbus-lockstep-macros",
"zbus_names",
"zvariant",
]
[[package]]
name = "atspi-proxies"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
dependencies = [
"atspi-common",
"serde",
"zbus",
]
[[package]]
name = "auto_enums"
version = "0.8.8"
@@ -783,7 +909,7 @@ dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -1128,10 +1254,13 @@ dependencies = [
]
[[package]]
name = "ctor-lite"
version = "0.1.2"
name = "ctor"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300"
checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf"
dependencies = [
"dtor",
]
[[package]]
name = "cursor-icon"
@@ -1333,6 +1462,12 @@ dependencies = [
"linux-raw-sys 0.9.4",
]
[[package]]
name = "dtor"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b"
[[package]]
name = "dunce"
version = "1.0.5"
@@ -1627,8 +1762,8 @@ dependencies = [
"read-fonts",
"roxmltree",
"smallvec",
"windows",
"windows-core",
"windows 0.62.2",
"windows-core 0.62.2",
"yeslogic-fontconfig-sys",
]
@@ -1814,7 +1949,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix 1.1.4",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -1976,7 +2111,7 @@ dependencies = [
[[package]]
name = "gopher64"
version = "1.1.16"
version = "1.1.17"
dependencies = [
"bindgen",
"cc",
@@ -2013,9 +2148,9 @@ dependencies = [
[[package]]
name = "grid"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9e2d4c0a8296178d8802098410ca05d86b17a10bb5ab559b3fb404c1f948220"
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
[[package]]
name = "h2"
@@ -2303,6 +2438,8 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f7fddf48d12b366f352f8fb78d59665c490cf279786772f1d1592e143edd0d"
dependencies = [
"accesskit",
"accesskit_winit",
"block2 0.6.2",
"cfg-if",
"cfg_aliases",
@@ -2332,7 +2469,7 @@ dependencies = [
"wasm-bindgen",
"web-sys",
"webbrowser",
"windows",
"windows 0.62.2",
"winit",
"zbus",
]
@@ -2475,7 +2612,7 @@ dependencies = [
"softbuffer",
"unicode-segmentation",
"vtable",
"windows",
"windows 0.62.2",
"write-fonts",
]
@@ -2511,7 +2648,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
"windows-core 0.62.2",
]
[[package]]
@@ -2890,7 +3027,7 @@ dependencies = [
"simd_cesu8",
"thiserror 2.0.18",
"walkdir",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -3004,9 +3141,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libbz2-rs-sys"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f"
[[package]]
name = "libc"
@@ -3031,7 +3168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -3042,12 +3179,11 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "667f4fec20f29dfc6bc7357c582d91796c169ad7e2fce709468aefeb2c099870"
checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6"
dependencies = [
"cc",
"libc",
]
[[package]]
@@ -3233,9 +3369,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.48"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1ee66a4b64c74f4ef288bcbb9192ad9c3feaad75193129ac8509af543894fd8"
checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640"
dependencies = [
"libmimalloc-sys",
]
@@ -3862,9 +3998,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "open"
version = "5.3.3"
version = "5.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc"
checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd"
dependencies = [
"is-wsl",
"libc",
@@ -3885,9 +4021,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "orbclient"
version = "0.3.51"
version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6"
checksum = "12c6933ddbbd16539a7672e697bb8d41ac3a4e99ac43eeb40c07236bd7fcb2dd"
dependencies = [
"libc",
"libredox",
@@ -4220,9 +4356,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]]
name = "pxfm"
version = "0.1.28"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
[[package]]
name = "qoi"
@@ -4239,6 +4375,16 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "quick-xml"
version = "0.39.2"
@@ -4734,9 +4880,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.38"
version = "0.23.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e"
dependencies = [
"aws-lc-rs",
"once_cell",
@@ -4797,9 +4943,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.12"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
@@ -5173,7 +5319,7 @@ checksum = "6a71c01d325d40b1031dee67d251a5e0132e79e2a9ec272149a4f4a0d4b8b3be"
dependencies = [
"bitflags 2.11.1",
"skia-bindings",
"windows",
"windows 0.62.2",
]
[[package]]
@@ -5200,6 +5346,7 @@ checksum = "364a1e8fae7f1969224acc59ba729f06aeaf808123796389eae458c396119ef0"
dependencies = [
"const-field-offset",
"i-slint-backend-selector",
"i-slint-backend-winit",
"i-slint-common",
"i-slint-core",
"i-slint-core-macros",
@@ -5430,6 +5577,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strict-num"
version = "0.1.1"
@@ -5726,12 +5879,12 @@ dependencies = [
[[package]]
name = "tiny-xlib"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e"
checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4"
dependencies = [
"as-raw-xcb-connection",
"ctor-lite",
"ctor",
"libloading",
"pkg-config",
"tracing",
@@ -5765,14 +5918,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.52.0"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
@@ -5851,7 +6005,7 @@ dependencies = [
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.1",
"winnow 1.0.2",
]
[[package]]
@@ -5882,7 +6036,7 @@ dependencies = [
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow 1.0.1",
"winnow 1.0.2",
]
[[package]]
@@ -5891,7 +6045,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow 1.0.1",
"winnow 1.0.2",
]
[[package]]
@@ -6026,9 +6180,9 @@ checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
[[package]]
name = "typenum"
version = "1.19.0"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "udev"
@@ -6267,11 +6421,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.2+wasi-0.2.9"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen",
"wit-bindgen 0.57.1",
]
[[package]]
@@ -6280,7 +6434,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen",
"wit-bindgen 0.51.0",
]
[[package]]
@@ -6491,7 +6645,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
dependencies = [
"proc-macro2",
"quick-xml",
"quick-xml 0.39.2",
"quote",
]
@@ -6529,9 +6683,9 @@ dependencies = [
[[package]]
name = "webbrowser"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16"
checksum = "0fc95580916af1e68ff6a7be07446fc5db73ebf71cf092de939bbf5f7e189f72"
dependencies = [
"core-foundation 0.10.1",
"jni 0.22.4",
@@ -6567,16 +6721,38 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
"windows-collections 0.3.2",
"windows-core 0.62.2",
"windows-future 0.3.2",
"windows-numerics 0.3.1",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
]
[[package]]
@@ -6585,7 +6761,20 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core",
"windows-core 0.62.2",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
]
[[package]]
@@ -6596,9 +6785,20 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
]
[[package]]
@@ -6607,9 +6807,9 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading 0.2.1",
]
[[package]]
@@ -6634,20 +6834,36 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core",
"windows-link",
"windows-core 0.62.2",
"windows-link 0.2.1",
]
[[package]]
@@ -6656,9 +6872,18 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
@@ -6667,7 +6892,16 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
@@ -6676,7 +6910,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -6730,7 +6964,7 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -6785,7 +7019,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
@@ -6796,13 +7030,22 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -7048,9 +7291,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
dependencies = [
"memchr",
]
@@ -7074,6 +7317,12 @@ dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
@@ -7353,6 +7602,30 @@ dependencies = [
"zvariant",
]
[[package]]
name = "zbus-lockstep"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863"
dependencies = [
"zbus_xml",
"zvariant",
]
[[package]]
name = "zbus-lockstep-macros"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"zbus-lockstep",
"zbus_xml",
"zvariant",
]
[[package]]
name = "zbus_macros"
version = "5.14.0"
@@ -7379,6 +7652,18 @@ dependencies = [
"zvariant",
]
[[package]]
name = "zbus_xml"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac"
dependencies = [
"quick-xml 0.38.4",
"serde",
"zbus_names",
"zvariant",
]
[[package]]
name = "zeno"
version = "0.3.3"
+9 -7
View File
@@ -1,10 +1,12 @@
[package]
name = "gopher64"
version = "1.1.16"
version = "1.1.17"
edition = "2024"
rust-version = "1.95.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["gui"]
gui = ["dep:rfd", "dep:slint", "dep:slint-build"]
[dependencies]
dirs = "6.0"
@@ -14,13 +16,13 @@ chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-big-array = "0.5"
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia"] }
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-skia", "accessibility"], optional = true }
open = "5.3"
sha2 = "0.11"
sdl3-sys = { version = "0.6", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.6", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
rfd = "0.17"
tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs"] }
rfd = {version = "0.17", optional = true }
tokio = {version = "1.46", features = ["rt-multi-thread", "macros", "fs", "process"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
@@ -38,7 +40,7 @@ reqwest = { version = "0.13", features = ["json", "query"] }
[build-dependencies]
winresource = "0.1"
bindgen = "0.72"
slint-build = "1.12"
slint-build = { version = "1.12", optional = true }
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
@@ -58,4 +60,4 @@ identifier = "io.github.gopher64.gopher64"
category = "public.app-category.games"
icon = ["data/icon/gopher64_128x128.png","data/icon/gopher64_256x256.png","data/icon/gopher64_512x512.png"]
osx_frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib","/opt/homebrew/opt/freetype/lib/libfreetype.6.dylib","/opt/homebrew/opt/libpng/lib/libpng16.16.dylib"]
osx_minimum_system_version = "15.0"
osx_minimum_system_version = "15.7"
+10
View File
@@ -1,4 +1,14 @@
# gopher64
Gopher64 is a cross-platform N64 emulator. Some notable features:
* Netplay
* Homebrew support
* Upscaling
* CRT shader
* Emulate CPU overclocking
* Cheats
* Savestates
* RetroAchievements
## download
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
+40 -16
View File
@@ -3,8 +3,11 @@ fn main() {
println!("cargo::rerun-if-changed=retroachievements");
println!("cargo::rerun-if-changed=src/compat");
let slint_config = slint_build::CompilerConfiguration::new();
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
#[cfg(feature = "gui")]
{
let slint_config = slint_build::CompilerConfiguration::new();
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
}
let mut simd_build = cc::Build::new();
let mut volk_build = cc::Build::new();
@@ -62,11 +65,10 @@ fn main() {
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
.include("parallel-rdp/parallel-rdp-standalone/util")
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
.join("include"),
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").unwrap()).join("include"),
)
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").to_owned().unwrap())
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").unwrap())
.join("include"),
);
@@ -144,6 +146,9 @@ fn main() {
println!("cargo:rustc-link-search=native={}", runtime_dir);
println!("cargo:rustc-link-lib=static=clang_rt.osx");
}
if cfg!(not(feature = "gui")) && os == "linux" {
println!("cargo:rustc-link-lib=dylib=freetype");
}
volk_build.flag("-flto=thin");
rdp_build.flag("-flto=thin");
@@ -156,26 +161,32 @@ fn main() {
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let retroachievements_bindings = bindgen::Builder::default()
let mut retroachievements_builder = bindgen::Builder::default()
.header("retroachievements/retroachievements.h")
.allowlist_function("ra_init_client")
.allowlist_function("ra_welcome")
.allowlist_function("ra_shutdown_client")
.allowlist_function("ra_get_hardcore")
.allowlist_function("ra_load_game")
.allowlist_function("ra_unload_game")
.allowlist_function("ra_set_dmem")
.allowlist_function("ra_do_frame")
.allowlist_function("ra_do_idle")
.allowlist_function("ra_http_callback")
.allowlist_function("ra_logout_user")
.allowlist_function("ra_login_user")
.allowlist_function("ra_login_token_user")
.allowlist_function("ra_is_user_logged_in")
.allowlist_function("ra_get_username")
.allowlist_function("ra_get_token")
.allowlist_function("ra_state_size")
.allowlist_function("ra_save_state")
.allowlist_function("ra_load_state")
.allowlist_function("ra_load_state");
if cfg!(feature = "gui") {
retroachievements_builder = retroachievements_builder
.allowlist_function("ra_logout_user")
.allowlist_function("ra_get_username")
.allowlist_function("ra_get_token");
}
let retroachievements_bindings = retroachievements_builder
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
@@ -199,6 +210,7 @@ fn main() {
.allowlist_function("rdp_state_size")
.allowlist_function("rdp_save_state")
.allowlist_function("rdp_load_state")
.allowlist_function("rdp_set_fps")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
@@ -265,12 +277,24 @@ fn main() {
.compile("simd");
}
let git_output = std::process::Command::new("git")
.args(["describe", "--always", "--dirty"])
.output()
.unwrap();
let mut git_output = std::process::Command::new("git");
git_output.args(["describe", "--dirty"]);
let git_describe = String::from_utf8(git_output.stdout).unwrap();
let git_describe = if let Ok(git_output) = git_output.output()
&& git_output.status.success()
{
String::from_utf8(git_output.stdout).unwrap()
} else if let Ok(git_output) = git_output.args(["--always"]).output()
&& git_output.status.success()
{
format!(
"v{}-{}",
env!("CARGO_PKG_VERSION"),
String::from_utf8(git_output.stdout).unwrap()
)
} else {
panic!("Failed to get git describe");
};
println!("cargo:rustc-env=GIT_DESCRIBE={git_describe}");
println!("cargo:rerun-if-env-changed=NETPLAY_ID");
Binary file not shown.
@@ -12,7 +12,7 @@
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
+51 -5
View File
@@ -75,6 +75,11 @@ typedef struct {
FrameBufferInfo frame_buffer_info;
} RDP_DEVICE;
typedef struct {
uint32_t fps;
uint32_t vis;
} FPS_DATA;
typedef struct {
std::string message;
Vulkan::ImageHandle image;
@@ -106,6 +111,9 @@ static Vulkan::ImageHandle achievement_progress_indicator_image;
static std::map<uint32_t, std::string> leaderboard_trackers;
static bool display_challenge_indicator;
static bool display_fps;
static Vulkan::ImageHandle fps_image;
#define MESSAGE_TIME 3000 // 3 seconds
typedef struct {
@@ -147,6 +155,9 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) {
if (gfx_info.fullscreen)
callback.emu_running = false;
break;
case SDL_SCANCODE_F1:
display_fps = !display_fps;
break;
case SDL_SCANCODE_F4:
crop_letterbox = !crop_letterbox;
break;
@@ -275,13 +286,17 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font,
fragment_size = sizeof(plain_fragment_spirv);
}
bool window_vsync = 0;
wsi = new WSI;
wsi_platform = new SDL_WSIPlatform;
wsi_platform->set_window(window);
wsi->set_platform(wsi_platform);
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank
: PresentMode::UnlockedMaybeTear);
if (gfx_info.vsync) {
// VK_PRESENT_MODE_MAILBOX_KHR, fallback to VK_PRESENT_MODE_FIFO_KHR
wsi->set_present_mode(PresentMode::UnlockedNoTearing);
} else {
// VK_PRESENT_MODE_MAILBOX_KHR, fallback to VK_PRESENT_MODE_IMMEDIATE_KHR
wsi->set_present_mode(PresentMode::UnlockedMaybeTear);
}
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader(
@@ -326,12 +341,15 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font,
leaderboard_trackers.clear();
achievement_challenge_indicator_image = Vulkan::ImageHandle();
achievement_progress_indicator_image = Vulkan::ImageHandle();
fps_image = Vulkan::ImageHandle();
display_fps = false;
}
void rdp_close() {
messages = std::queue<Message>();
achievement_challenge_indicator_image = Vulkan::ImageHandle();
achievement_progress_indicator_image = Vulkan::ImageHandle();
fps_image = Vulkan::ImageHandle();
if (wsi)
wsi->end_frame();
@@ -403,8 +421,7 @@ static void calculate_viewport(float *x, float *y, float *width, float *height,
}
static void draw_indicator(CommandBufferHandle cmd,
Vulkan::ImageHandle indicator_image,
VkViewport &vp) {
Vulkan::ImageHandle indicator_image, VkViewport vp) {
cmd->set_texture(0, 0, indicator_image->get_view(),
Vulkan::StockSampler::NearestClamp);
vp.x = vp.x + vp.width - indicator_image->get_width();
@@ -416,6 +433,17 @@ static void draw_indicator(CommandBufferHandle cmd,
cmd->draw(3);
}
static void draw_fps(CommandBufferHandle cmd, VkViewport vp) {
cmd->set_texture(0, 0, fps_image->get_view(),
Vulkan::StockSampler::NearestClamp);
vp.y = vp.y + vp.height - fps_image->get_height();
vp.height = fps_image->get_height();
vp.width = fps_image->get_width();
cmd->set_viewport(vp);
cmd->draw(3);
}
static void render_frame(Vulkan::Device &device) {
RDP::ScanoutOptions options = {};
options.persist_frame_on_invalid_input = true;
@@ -512,6 +540,10 @@ static void render_frame(Vulkan::Device &device) {
display_challenge_indicator) {
draw_indicator(cmd, achievement_challenge_indicator_image, vp);
}
if (messages.empty() && display_fps && fps_image) {
draw_fps(cmd, vp);
}
}
cmd->end_render_pass();
@@ -817,6 +849,20 @@ uint64_t rdp_process_commands() {
return interrupt_timer;
}
void rdp_set_fps_callback(void *userdata) {
FPS_DATA *data = (FPS_DATA *)userdata;
fps_image = create_message_image(
wsi->get_device(), 0, achievement_challenge_indicator_font,
std::format("FPS: {} VI/S: {}", data->fps, data->vis).c_str());
}
void rdp_set_fps(uint32_t fps, uint32_t vis) {
if (display_fps) {
FPS_DATA data = {fps, vis};
SDL_RunOnMainThread(rdp_set_fps_callback, &data, true);
}
}
static void update_challenge_indicator() {
std::string message;
for (const auto &leaderboard_tracker : leaderboard_trackers) {
+2
View File
@@ -18,6 +18,7 @@ typedef struct {
bool PAL;
bool widescreen;
bool fullscreen;
bool vsync;
bool integer_scaling;
uint32_t upscale;
bool crt;
@@ -50,6 +51,7 @@ 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);
void rdp_set_fps(uint32_t fps, uint32_t vis);
void achievement_challenge_indicator_add(const char *achievement_title);
void achievement_challenge_indicator_remove(const char *achievement_title);
+41 -71
View File
@@ -18,13 +18,8 @@ void notify_load_game(void *userdata);
static rc_client_t *g_client = NULL;
static const uint8_t *g_dmem = NULL;
static size_t g_dmem_size = 0;
static bool g_game_loaded = false;
static bool g_user_logged_in = false;
static bool g_challenge = false;
static bool g_leaderboard = false;
static const char *g_username = NULL;
static const char *g_token = NULL;
static char load_game_error_message[512];
static rc_client_leaderboard_list_t *g_leaderboard_list = NULL;
static uint32_t read_memory(uint32_t address, uint8_t *buffer,
@@ -73,41 +68,33 @@ static void login_callback(int result, const char *error_message,
rc_client_t *client, void *userdata) {
// If not successful, just report the error and bail.
if (result != RC_OK) {
snprintf(load_game_error_message, sizeof(load_game_error_message),
"RA login failed: %s", error_message);
if (client && !rc_client_get_userdata(client))
rc_client_set_userdata(client, strdup(error_message));
store_retroachievements_credentials(NULL, NULL, userdata);
return;
} else {
memset(load_game_error_message, 0, sizeof(load_game_error_message));
}
// Login was successful. Capture the token for future logins so we don't have
// to store the password anywhere.
const rc_client_user_t *user = rc_client_get_user_info(client);
store_retroachievements_credentials(user->username, user->token, userdata);
g_username = user->username;
g_token = user->token;
g_user_logged_in = true;
}
bool ra_is_user_logged_in() { return g_user_logged_in; }
const char *ra_get_username() { return g_username; }
const char *ra_get_token() { return g_token; }
void ra_logout_user() {
g_user_logged_in = false;
g_username = NULL;
g_token = NULL;
rc_client_logout(g_client);
const char *ra_get_username() {
const rc_client_user_t *user = rc_client_get_user_info(g_client);
return user ? user->username : NULL;
}
const char *ra_get_token() {
const rc_client_user_t *user = rc_client_get_user_info(g_client);
return user ? user->token : NULL;
}
void ra_logout_user() { rc_client_logout(g_client); }
void ra_login_user(const char *username, const char *password, void *userdata) {
// This will generate an HTTP payload and call the server_call chain above.
// Eventually, login_callback will be called to let us know if the login was
// successful.
g_user_logged_in = false;
rc_client_begin_login_with_password(g_client, username, password,
login_callback, userdata);
}
@@ -117,7 +104,6 @@ void ra_login_token_user(const char *username, const char *token,
// This is exactly the same functionality as
// rc_client_begin_login_with_password, but uses the token captured from the
// first login instead of a password. Note that it uses the same callback.
g_user_logged_in = false;
rc_client_begin_login_with_token(g_client, username, token, login_callback,
userdata);
}
@@ -125,13 +111,11 @@ void ra_login_token_user(const char *username, const char *token,
static void load_game_callback(int result, const char *error_message,
rc_client_t *client, void *userdata) {
if (result != RC_OK) {
if (client && !rc_client_get_userdata(client))
rc_client_set_userdata(client, strdup(error_message));
rc_client_set_hardcore_enabled(client, false);
snprintf(load_game_error_message, sizeof(load_game_error_message),
"RA load failed: %s", error_message);
notify_load_game(userdata);
return;
} else {
memset(load_game_error_message, 0, sizeof(load_game_error_message));
}
if (!rc_client_is_processing_required(client)) {
@@ -141,20 +125,26 @@ static void load_game_callback(int result, const char *error_message,
g_leaderboard_list = rc_client_create_leaderboard_list(
client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
g_game_loaded = true;
notify_load_game(userdata);
}
void ra_welcome() {
if (strlen(load_game_error_message) > 0) {
rdp_onscreen_message(load_game_error_message, true);
}
if (!g_game_loaded)
if (!g_client)
return;
char buffer[512];
char *error_message = rc_client_get_userdata(g_client);
if (error_message) {
snprintf(buffer, sizeof(buffer), "RA Error: %s", error_message);
rdp_onscreen_message(buffer, true);
free(error_message);
rc_client_set_userdata(g_client, NULL);
return;
}
const rc_client_game_t *game = rc_client_get_game_info(g_client);
rc_client_user_game_summary_t summary;
rc_client_get_user_game_summary(g_client, &summary);
@@ -174,16 +164,19 @@ void ra_welcome() {
}
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata) {
if (!g_user_logged_in) {
notify_load_game(userdata);
return;
}
rc_client_begin_identify_and_load_game(g_client, RC_CONSOLE_NINTENDO_64, NULL,
rom, rom_size, load_game_callback,
userdata);
}
void ra_unload_game() {
if (g_leaderboard_list) {
rc_client_destroy_leaderboard_list(g_leaderboard_list);
g_leaderboard_list = NULL;
}
rc_client_unload_game(g_client);
}
void ra_set_dmem(const uint8_t *dmem, size_t dmem_size) {
g_dmem = dmem;
g_dmem_size = dmem_size;
@@ -323,8 +316,6 @@ void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
// Create the client instance (using a global variable simplifies this
// example)
g_client = rc_client_create(read_memory, server_call);
g_game_loaded = false;
g_user_logged_in = false;
g_dmem = NULL;
g_dmem_size = 0;
@@ -339,17 +330,9 @@ void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
g_leaderboard = leaderboard;
}
bool ra_get_hardcore() {
if (!g_user_logged_in)
return false;
return rc_client_get_hardcore_enabled(g_client);
}
bool ra_get_hardcore() { return rc_client_get_hardcore_enabled(g_client); }
void ra_shutdown_client() {
if (g_leaderboard_list) {
rc_client_destroy_leaderboard_list(g_leaderboard_list);
g_leaderboard_list = NULL;
}
if (g_client) {
// Release resources associated to the client instance
rc_client_destroy(g_client);
@@ -357,30 +340,16 @@ void ra_shutdown_client() {
}
}
void ra_do_frame() {
if (!g_game_loaded)
return;
void ra_do_frame() { rc_client_do_frame(g_client); }
rc_client_do_frame(g_client);
}
void ra_do_idle() { rc_client_idle(g_client); }
void ra_do_idle() {
if (!g_game_loaded)
return;
rc_client_idle(g_client);
}
size_t ra_state_size() {
if (!g_game_loaded)
return 0;
return rc_client_progress_size(g_client);
}
size_t ra_state_size() { return rc_client_progress_size(g_client); }
void ra_save_state(uint8_t *state, size_t state_size) {
if (!g_game_loaded)
if (!rc_client_is_game_loaded(g_client))
return;
if (rc_client_serialize_progress_sized(g_client, state, state_size) !=
RC_OK) {
printf("RetroAchievements: Failed to serialize progress\n");
@@ -388,8 +357,9 @@ void ra_save_state(uint8_t *state, size_t state_size) {
}
void ra_load_state(const uint8_t *state, size_t state_size) {
if (!g_game_loaded)
if (!rc_client_is_game_loaded(g_client))
return;
if (rc_client_deserialize_progress_sized(g_client, state, state_size) !=
RC_OK) {
printf("RetroAchievements: Failed to deserialize progress\n");
@@ -397,7 +367,7 @@ void ra_load_state(const uint8_t *state, size_t state_size) {
}
void ra_display_inprogress_achievements(void *userdata) {
if (!g_game_loaded)
if (!rc_client_is_game_loaded(g_client))
return;
rc_client_achievement_list_t *list = rc_client_create_achievement_list(
+1 -1
View File
@@ -7,6 +7,7 @@ void ra_shutdown_client();
void ra_welcome();
bool ra_get_hardcore();
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata);
void ra_unload_game();
void ra_set_dmem(const uint8_t *dmem, size_t dmem_size);
void ra_do_frame();
void ra_do_idle();
@@ -16,7 +17,6 @@ void ra_logout_user();
void ra_login_user(const char *username, const char *password, void *userdata);
void ra_login_token_user(const char *username, const char *token,
void *userdata);
bool ra_is_user_logged_in();
const char *ra_get_username();
const char *ra_get_token();
size_t ra_state_size();
+14 -6
View File
@@ -15,16 +15,24 @@ pub struct Cheats {
pub enabled: bool,
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CheatData {
pub note: String,
pub data: Vec<String>,
pub options: Option<std::collections::BTreeMap<String, String>>,
}
pub type Cheat = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
pub fn init(
device: &mut device::Device,
cheat_settings: std::collections::HashMap<String, Option<String>>,
) {
let cheats =
serde_json::from_slice::<ui::cheats::Cheats>(include_bytes!("../data/cheats.json"))
.unwrap()
.get(&ui::storage::get_game_crc(&device.cart.rom))
.cloned()
.unwrap_or_default();
let cheats = serde_json::from_slice::<Cheat>(include_bytes!("../data/cheats.json"))
.unwrap()
.get(&ui::storage::get_game_crc(&device.cart.rom))
.cloned()
.unwrap_or_default();
let re = regex::Regex::new(r"\?+").unwrap();
for cheat_setting in cheat_settings.iter() {
+15 -7
View File
@@ -44,7 +44,7 @@ pub mod tlb;
pub mod unmapped;
pub mod vi;
pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::gui::GameSettings) {
pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::GameSettings) {
device.cpu.overclock = game_settings.overclock;
if game_settings.disable_expansion_pak {
device.rdram.size = 0x400000;
@@ -89,8 +89,9 @@ pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::gui
cpu::run(device);
retroachievements::unload_game();
ui::storage::write_saves(device);
retroachievements::shutdown_client();
ui::input::close(&mut device.ui);
ui::audio::close(&mut device.ui);
ui::video::close(&device.ui);
@@ -140,7 +141,11 @@ fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
if file_path
.extension()
.unwrap_or_default()
.eq_ignore_ascii_case("zip")
{
let zip_file = fs::File::open(file_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
for i in 0..archive.len() {
@@ -157,7 +162,11 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
break;
}
}
} else if file_path.extension().unwrap().eq_ignore_ascii_case("7z") {
} else if file_path
.extension()
.unwrap_or_default()
.eq_ignore_ascii_case("7z")
{
let mut archive =
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
@@ -182,7 +191,8 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
)
.expect("ok");
} else {
contents = fs::read(file_path).expect("Should have been able to read the file");
contents = fs::read(file_path)
.unwrap_or_else(|_| panic!("Could not read ROM file: {}", file_path.display()));
}
if contents.is_empty() {
@@ -482,8 +492,6 @@ impl Device {
enable_speed_limiter: true,
next_pace_deadline: None,
vi_counter: 0,
last_origin: 0,
internal_frame_counter: 0,
min_wait_time: std::time::Duration::from_secs(1),
frame_time: 0.0,
limit_freq: 2,
+6 -1
View File
@@ -1,4 +1,6 @@
use crate::{device, ui};
use crate::device;
#[cfg(feature = "gui")]
use crate::ui;
use std::collections::HashMap;
const JCMD_VRU_READ: u8 = 0x09;
@@ -179,7 +181,10 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
}
JCMD_VRU_READ => {
#[cfg(feature = "gui")]
let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time);
#[cfg(not(feature = "gui"))]
let index = 0x7FFF;
let num_results = if index == 0x7FFF { 0 } else { 1 };
let data: HashMap<usize, u16> = HashMap::from([
(0, 0x8000),
+4 -13
View File
@@ -27,8 +27,6 @@ pub struct Vi {
pub count_per_scanline: u64,
pub enable_speed_limiter: bool,
pub vi_counter: u64,
pub last_origin: u32,
pub internal_frame_counter: u64,
pub min_wait_time: std::time::Duration,
pub frame_time: f64,
pub limit_freq: u64,
@@ -110,10 +108,10 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
}
VI_ORIGIN_REG => {
let current_origin = device.vi.regs[reg as usize];
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
if device.vi.regs[reg as usize] != device.vi.last_origin {
device.vi.last_origin = device.vi.regs[reg as usize];
device.vi.internal_frame_counter += 1;
if current_origin != device.vi.regs[reg as usize] {
let _ = device.ui.video.fps_tx.try_send(true);
}
}
_ => {
@@ -129,6 +127,7 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
}
ui::video::render_frame();
let _ = device.ui.video.vis_tx.try_send(true);
retroachievements::do_frame();
@@ -159,14 +158,6 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
unsafe { sdl3_sys::events::SDL_PumpEvents() };
/*
let vis = if device.cart.pal { 50 } else { 60 };
if device.vi.vi_counter % vis == 0 {
println!("FPS: {}", device.vi.internal_frame_counter);
device.vi.internal_frame_counter = 0;
}
*/
/* toggle vi field if in interlaced mode */
device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1;
+46 -29
View File
@@ -11,11 +11,10 @@ mod savestates;
mod ui;
use clap::Parser;
use std::io::Error;
use ui::gui;
/// N64 emulator
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })]
struct Args {
game: Option<String>,
#[arg(short, long)]
@@ -30,10 +29,6 @@ struct Args {
netplay_peer_addr: Option<String>,
#[arg(long, value_name = "NETPLAY_PLAYER_NUMBER", hide = true)]
netplay_player_number: Option<u8>,
#[arg(long, value_name = "GB_ROM_PATH", hide = true)]
gb_rom: Option<Vec<String>>,
#[arg(long, value_name = "GB_RAM_PATH", hide = true)]
gb_ram: Option<Vec<String>>,
#[arg(
long,
value_name = "PROFILE_NAME",
@@ -143,6 +138,11 @@ async fn main() -> std::io::Result<()> {
}
let mut device = device::Device::new();
device.ui.config.recent_roms.retain(|x| *x != game);
device.ui.config.recent_roms.insert(0, game);
device.ui.config.recent_roms.truncate(5);
if args.fullscreen {
device.ui.video.fullscreen = true;
} else {
@@ -168,15 +168,6 @@ async fn main() -> std::io::Result<()> {
.unwrap_or_default()
};
if cfg!(ra_hardcore_enabled) {
retroachievements::init_client(
args.ra_hardcore,
args.ra_challenge,
args.ra_leaderboard,
);
} else {
retroachievements::init_client(false, args.ra_challenge, args.ra_leaderboard);
}
let mut shutdown_tx = None;
if let Some(peer_addr) = args.netplay_peer_addr
@@ -184,16 +175,15 @@ async fn main() -> std::io::Result<()> {
{
device.netplay = Some(netplay::init(peer_addr.parse().unwrap(), player_number));
} else {
if let Some(gb_roms) = args.gb_rom
&& let Some(gb_rams) = args.gb_ram
{
for i in 0..4 {
if let Some(gb_rom) = gb_roms.get(i)
&& let Some(gb_ram) = gb_rams.get(i)
{
device.transferpaks[i].cart.rom = std::fs::read(gb_rom).unwrap();
device.transferpaks[i].cart.ram = std::fs::read(gb_ram).unwrap();
}
for i in 0..4 {
if device.ui.config.input.transfer_pak[i]
&& !device.ui.config.input.gb_rom_path[i].is_empty()
&& !device.ui.config.input.gb_ram_path[i].is_empty()
{
device.transferpaks[i].cart.rom =
std::fs::read(&device.ui.config.input.gb_rom_path[i]).unwrap();
device.transferpaks[i].cart.ram =
std::fs::read(&device.ui.config.input.gb_ram_path[i]).unwrap();
}
}
@@ -202,6 +192,16 @@ async fn main() -> std::io::Result<()> {
}
if let Some(username) = args.ra_username {
retroachievements::init_client(
if cfg!(ra_hardcore_enabled) {
args.ra_hardcore
} else {
false
},
args.ra_challenge,
args.ra_leaderboard,
);
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
if let Some(password) = args.ra_password {
retroachievements::login_user(username, password, tx);
@@ -219,16 +219,30 @@ async fn main() -> std::io::Result<()> {
device::run_game(
&mut device,
&rom_contents,
ui::gui::GameSettings {
ui::GameSettings {
overclock,
disable_expansion_pak,
cheats,
load_savestate_slot: args.load_state,
},
);
retroachievements::shutdown_client();
if device.netplay.is_some() {
netplay::close(&mut device);
} else {
for i in 0..4 {
if device.ui.config.input.transfer_pak[i]
&& !device.ui.config.input.gb_ram_path[i].is_empty()
{
tokio::fs::write(
&device.ui.config.input.gb_ram_path[i],
&device.transferpaks[i].cart.ram,
)
.await
.unwrap();
}
}
}
if let Some(shutdown_tx) = &shutdown_tx {
ui::usb::close(shutdown_tx);
@@ -274,9 +288,12 @@ async fn main() -> std::io::Result<()> {
ui::input::bind_input_profile(&mut ui, profile, port);
}
} else {
retroachievements::init_client(false, false, false);
gui::app_window();
retroachievements::shutdown_client();
#[cfg(feature = "gui")]
{
retroachievements::init_client(false, false, false);
ui::gui::app_window();
retroachievements::shutdown_client();
}
}
Ok(())
+1 -1
View File
@@ -85,7 +85,7 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
request.extend_from_slice(&(size as u32).to_be_bytes());
if size > 0 {
request.extend(save_data.to_owned());
request.extend_from_slice(save_data);
}
netplay.tcp_stream.write_all(&request).unwrap();
}
+27 -124
View File
@@ -1,7 +1,6 @@
include!(concat!(env!("OUT_DIR"), "/retroachievements_bindings.rs"));
use crate::ui;
use slint::ComponentHandle;
static WEB_CLIENT: std::sync::LazyLock<reqwest::Client> = std::sync::LazyLock::new(|| {
reqwest::Client::builder()
@@ -144,115 +143,6 @@ pub extern "C" fn rust_server_call(
});
}
fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver<bool>) {
app.set_ra_current_user_message("Logging in...".into());
let weak_app = app.as_weak();
tokio::spawn(async move {
rx.await.unwrap();
weak_app
.upgrade_in_event_loop(move |handle| {
if is_user_logged_in() {
handle.set_ra_current_user_message(
format!("Logged in as {}", handle.get_ra_username()).into(),
);
handle.set_ra_show_profile(true);
} else {
handle.set_ra_current_user_message("Login failed".into());
handle.set_ra_show_profile(false);
}
handle.set_ra_logging_in(false);
})
.unwrap();
});
}
pub fn ra_window(app: &ui::gui::AppWindow) {
let mut token = String::new();
if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json"))
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
{
app.set_ra_username(result.username.into());
app.set_ra_enabled(result.enabled);
app.set_ra_hardcore(result.hardcore);
app.set_ra_challenge(result.challenge);
app.set_ra_leaderboard(result.leaderboard);
token = result.token;
} else {
app.set_ra_hardcore(true);
}
if app.get_ra_enabled() && !app.get_ra_username().is_empty() {
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
login_token_user(app.get_ra_username().to_string(), token, tx);
set_current_user_message(app, rx);
} else {
app.set_ra_current_user_message("Not currently logged in".into());
}
let weak_app2 = app.as_weak();
app.on_ra_button_clicked(move |password| {
weak_app2
.upgrade_in_event_loop(move |handle| {
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
login_user(
handle.get_ra_username().to_string(),
password.to_string(),
tx,
);
set_current_user_message(&handle, rx);
})
.unwrap();
});
app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| {
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
&& let Ok(result) = serde_json::from_slice::<RAConfig>(ra_config.as_ref())
{
if !enabled {
unsafe { ra_logout_user() };
RAConfig {
username: "".into(),
token: "".into(),
enabled,
hardcore,
challenge,
leaderboard,
}
} else {
RAConfig {
username: result.username,
token: result.token,
enabled,
hardcore,
challenge,
leaderboard,
}
}
} else {
RAConfig {
username: "".into(),
token: "".into(),
enabled,
hardcore,
challenge,
leaderboard,
}
};
let f = std::fs::File::create(&file_path).unwrap();
serde_json::to_writer_pretty(f, &raconfig).unwrap();
});
app.on_ra_games_clicked(move || {
open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap();
});
app.on_ra_show_profile_clicked(move |username| {
open::that_detached(format!("https://retroachievements.org/user/{}", username)).unwrap();
});
}
pub async fn load_game(rom: &[u8], rom_size: usize) {
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
unsafe {
@@ -262,6 +152,10 @@ pub async fn load_game(rom: &[u8], rom_size: usize) {
rx.await.unwrap();
}
pub fn unload_game() {
unsafe { ra_unload_game() };
}
pub fn welcome() {
unsafe { ra_welcome() };
}
@@ -299,6 +193,11 @@ pub fn login_user(username: String, password: String, tx: tokio::sync::oneshot::
};
}
#[cfg(feature = "gui")]
pub fn logout_user() {
unsafe { ra_logout_user() };
}
pub fn login_token_user(username: String, token: String, tx: tokio::sync::oneshot::Sender<bool>) {
unsafe {
let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void;
@@ -308,29 +207,33 @@ pub fn login_token_user(username: String, token: String, tx: tokio::sync::onesho
};
}
pub fn is_user_logged_in() -> bool {
unsafe { ra_is_user_logged_in() }
}
pub fn get_username() -> &'static str {
#[cfg(feature = "gui")]
pub fn get_username() -> Option<String> {
let c_username = unsafe { ra_get_username() };
if c_username.is_null() {
"unknown"
None
} else {
unsafe { std::ffi::CStr::from_ptr(c_username) }
.to_str()
.unwrap()
Some(
unsafe { std::ffi::CStr::from_ptr(c_username) }
.to_str()
.unwrap()
.to_string(),
)
}
}
pub fn get_token() -> &'static str {
#[cfg(feature = "gui")]
pub fn get_token() -> Option<String> {
let c_token = unsafe { ra_get_token() };
if c_token.is_null() {
"unknown"
None
} else {
unsafe { std::ffi::CStr::from_ptr(c_token) }
.to_str()
.unwrap()
Some(
unsafe { std::ffi::CStr::from_ptr(c_token) }
.to_str()
.unwrap()
.to_string(),
)
}
}
+24
View File
@@ -1,12 +1,18 @@
pub mod audio;
#[cfg(feature = "gui")]
pub mod cheats;
pub mod config;
#[cfg(feature = "gui")]
pub mod gui;
pub mod input;
#[cfg(feature = "gui")]
pub mod netplay;
#[cfg(feature = "gui")]
pub mod retroachievements;
pub mod storage;
pub mod usb;
pub mod video;
#[cfg(feature = "gui")]
pub mod vru;
pub struct Dirs {
@@ -36,6 +42,10 @@ pub struct Storage {
pub struct Video {
pub window: *mut sdl3_sys::video::SDL_Window,
pub fullscreen: bool,
pub fps_tx: tokio::sync::mpsc::Sender<bool>,
pub fps_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
pub vis_tx: tokio::sync::mpsc::Sender<bool>,
pub vis_rx: Option<tokio::sync::mpsc::Receiver<bool>>,
}
pub struct Usb {
@@ -43,6 +53,14 @@ pub struct Usb {
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
}
#[derive(Clone)]
pub struct GameSettings {
pub overclock: bool,
pub disable_expansion_pak: bool,
pub cheats: std::collections::HashMap<String, Option<String>>,
pub load_savestate_slot: Option<u32>,
}
pub struct Ui {
pub dirs: Dirs,
pub config: config::Config,
@@ -113,6 +131,8 @@ impl Ui {
fn construct_ui(joysticks: Vec<sdl3_sys::everything::SDL_JoystickID>, with_sdl: bool) -> Ui {
let dirs = get_dirs();
let (fps_tx, fps_rx) = tokio::sync::mpsc::channel(1000);
let (vis_tx, vis_rx) = tokio::sync::mpsc::channel(1000);
Ui {
input: Input {
controllers: [
@@ -190,6 +210,10 @@ impl Ui {
video: Video {
window: std::ptr::null_mut(),
fullscreen: false,
fps_tx,
fps_rx: Some(fps_rx),
vis_tx,
vis_rx: Some(vis_rx),
},
usb: Usb {
usb_tx: None,
+7 -32
View File
@@ -1,18 +1,10 @@
use crate::cheats;
use crate::device;
use crate::ui;
use crate::ui::gui::AppWindow;
use crate::ui::gui::ErrorDialog;
use slint::ComponentHandle;
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CheatData {
note: String,
pub data: Vec<String>,
pub options: Option<std::collections::BTreeMap<String, String>>,
}
pub type Cheats = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
pub fn cheats_window(app: &AppWindow) {
let weak = app.as_weak();
app.on_cheats_select_rom_clicked(move |rom_dir| {
@@ -38,7 +30,7 @@ pub fn cheats_window(app: &AppWindow) {
handle.set_cheat_game_name(game_name.into());
})
.unwrap();
let cheats: Cheats =
let cheats: cheats::Cheat =
serde_json::from_slice(include_bytes!("../../data/cheats.json")).unwrap();
if let Some(cheat) = cheats.get(&game_crc) {
let cheat = cheat.clone();
@@ -77,16 +69,7 @@ pub fn cheats_window(app: &AppWindow) {
options,
));
}
#[allow(clippy::type_complexity)]
let cheats_model: std::rc::Rc<
slint::VecModel<(
slint::SharedString,
bool,
slint::SharedString,
slint::ModelRc<(bool, slint::SharedString)>,
)>,
> = std::rc::Rc::new(cheats_vec);
handle.set_cheats(slint::ModelRc::from(cheats_model));
handle.set_cheats(slint::ModelRc::from(std::rc::Rc::new(cheats_vec)));
})
.unwrap();
} else {
@@ -137,19 +120,11 @@ pub fn cheats_window(app: &AppWindow) {
fn clear_cheats(weak: &slint::Weak<AppWindow>, clear_name: bool) {
weak.upgrade_in_event_loop(move |handle| {
let cheats_vec = slint::VecModel::default();
#[allow(clippy::type_complexity)]
let cheats_model: std::rc::Rc<
slint::VecModel<(
slint::SharedString,
bool,
slint::SharedString,
slint::ModelRc<(bool, slint::SharedString)>,
)>,
> = std::rc::Rc::new(cheats_vec);
handle.set_cheats(slint::ModelRc::from(cheats_model));
handle.set_cheats(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::default(),
)));
if clear_name {
handle.set_cheat_game_name("".into());
handle.set_cheat_game_name(String::new().into());
}
})
.unwrap();
+8
View File
@@ -39,6 +39,8 @@ pub struct Input {
pub controller_assignment: [Option<String>; 4],
pub controller_enabled: [bool; 4],
pub transfer_pak: [bool; 4],
pub gb_rom_path: [String; 4],
pub gb_ram_path: [String; 4],
pub emulate_vru: bool,
}
@@ -48,6 +50,7 @@ pub struct Video {
pub integer_scaling: bool,
pub fullscreen: bool,
pub widescreen: bool,
pub vsync: bool,
pub crt: bool,
}
@@ -70,6 +73,7 @@ pub struct Config {
pub video: Video,
pub emulation: Emulation,
pub rom_dir: std::path::PathBuf,
pub recent_roms: Vec<String>,
}
impl Drop for Cheats {
@@ -140,6 +144,8 @@ impl Config {
input_profiles,
controller_enabled: [true, false, false, false],
transfer_pak: [false, false, false, false],
gb_rom_path: [String::new(), String::new(), String::new(), String::new()],
gb_ram_path: [String::new(), String::new(), String::new(), String::new()],
emulate_vru: false,
},
video: Video {
@@ -147,6 +153,7 @@ impl Config {
integer_scaling: false,
fullscreen: false,
widescreen: false,
vsync: true,
crt: false,
},
emulation: Emulation {
@@ -155,6 +162,7 @@ impl Config {
usb: false,
},
rom_dir: std::path::PathBuf::new(),
recent_roms: Vec::new(),
}
}
}
+236 -120
View File
@@ -1,6 +1,7 @@
use crate::retroachievements;
use crate::ui;
use slint::Model;
use slint::winit_030::WinitWindowAccessor;
slint::include_modules!();
@@ -18,19 +19,6 @@ pub struct NetplayDevice {
pub player_number: u8,
}
pub struct GbPaths {
pub rom: [Option<std::path::PathBuf>; 4],
pub ram: [Option<std::path::PathBuf>; 4],
}
#[derive(Clone)]
pub struct GameSettings {
pub overclock: bool,
pub disable_expansion_pak: bool,
pub cheats: std::collections::HashMap<String, Option<String>>,
pub load_savestate_slot: Option<u32>,
}
#[derive(Clone)]
pub struct RASettings {
pub enabled: bool,
@@ -66,12 +54,92 @@ fn check_latest_version(weak: slint::Weak<AppWindow>) {
});
}
fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>]) {
let dirs = ui::get_dirs();
fn run_with_path(
weak: slint::Weak<AppWindow>,
path: std::path::PathBuf,
controller_paths: &[Option<String>],
) {
let controller_paths = controller_paths.to_owned();
let weak2 = weak.clone();
weak.upgrade_in_event_loop(move |handle| {
if handle.get_game_running() {
return;
}
save_settings(&handle, &controller_paths);
run_rom(
path,
ui::GameSettings {
overclock: handle.get_overclock_n64_cpu(),
disable_expansion_pak: handle.get_disable_expansion_pak(),
cheats: std::collections::HashMap::new(), // will be filled in later
load_savestate_slot: None,
},
None,
RASettings {
enabled: handle.get_ra_enabled(),
hardcore: handle.get_ra_hardcore(),
challenge: handle.get_ra_challenge(),
leaderboard: handle.get_ra_leaderboard(),
},
weak2,
);
})
.unwrap();
}
fn file_dropped(app: &AppWindow, controller_paths: &[Option<String>]) {
let weak = app.as_weak();
let controller_paths = controller_paths.to_owned();
app.window()
.on_winit_window_event(move |_winit_window, event| {
if let slint::winit_030::winit::event::WindowEvent::DroppedFile(path) = event {
run_with_path(weak.clone(), path.to_path_buf(), &controller_paths);
}
slint::winit_030::EventResult::Propagate
});
}
fn local_game_window(
app: &AppWindow,
config: &ui::config::Config,
controller_paths: &[Option<String>],
) {
let dirs = ui::get_dirs();
app.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
config
.recent_roms
.iter()
.filter(|x| {
if let Ok(exists) = std::fs::exists(x)
&& exists
{
true
} else {
false
}
})
.map(|x| {
(
x.into(),
std::path::Path::new(x)
.file_name()
.unwrap()
.to_str()
.unwrap()
.into(),
)
})
.collect::<Vec<(slint::SharedString, slint::SharedString)>>(),
),
)));
let weak = app.as_weak();
let owned_controller_paths = controller_paths.to_owned();
app.on_open_rom_button_clicked(move || {
let controller_paths = controller_paths.clone();
let controller_paths = owned_controller_paths.clone();
weak.upgrade_in_event_loop(move |handle| {
save_settings(&handle, &controller_paths);
open_rom(&handle)
@@ -79,10 +147,25 @@ fn local_game_window(app: &AppWindow, controller_paths: &[Option<String>]) {
.unwrap();
});
let weak = app.as_weak();
let owned_controller_paths = controller_paths.to_owned();
app.on_recent_rom_button_clicked(move |rom| {
let controller_paths = owned_controller_paths.clone();
weak.upgrade_in_event_loop(move |handle| {
run_with_path(
handle.as_weak(),
std::path::PathBuf::from(rom.to_string()),
&controller_paths,
);
})
.unwrap();
});
let saves_path = dirs.data_dir.join("saves");
app.on_saves_folder_button_clicked(move || {
open::that_detached(saves_path.clone()).unwrap();
});
file_dropped(app, controller_paths);
}
fn input_profiles(config: &ui::config::Config) -> Vec<String> {
@@ -97,6 +180,7 @@ fn settings_window(app: &AppWindow, config: &ui::config::Config) {
app.set_integer_scaling(config.video.integer_scaling);
app.set_fullscreen(config.video.fullscreen);
app.set_widescreen(config.video.widescreen);
app.set_vsync(config.video.vsync);
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);
@@ -133,17 +217,17 @@ fn update_input_profiles(weak: &slint::Weak<AppWindow>, config: &ui::config::Con
profile_bindings.push(position.unwrap() as i32);
}
let input_profiles = slint::VecModel::default();
for profile in profiles {
input_profiles.push(profile.into());
}
let input_profiles_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(input_profiles);
handle.set_input_profiles(slint::ModelRc::from(input_profiles_model));
handle.set_input_profiles(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
profiles
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
let input_profile_binding_model: std::rc::Rc<slint::VecModel<i32>> =
std::rc::Rc::new(profile_bindings);
handle.set_selected_profile_binding(slint::ModelRc::from(input_profile_binding_model));
handle
.set_selected_profile_binding(slint::ModelRc::from(std::rc::Rc::new(profile_bindings)));
// this is a workaround to make the input profile combobox update
handle.set_blank_profiles(true);
@@ -161,53 +245,65 @@ fn update_input_profiles(weak: &slint::Weak<AppWindow>, config: &ui::config::Con
fn controller_window(
app: &AppWindow,
config: &ui::config::Config,
controller_names: &Vec<String>,
controller_names: &[String],
controller_paths: &[Option<String>],
) {
let controller_enabled_model: std::rc::Rc<slint::VecModel<bool>> = std::rc::Rc::new(
slint::VecModel::from(config.input.controller_enabled.to_vec()),
);
app.set_emulate_vru(config.input.emulate_vru);
app.set_controller_enabled(slint::ModelRc::from(controller_enabled_model));
app.set_controller_enabled(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(config.input.controller_enabled.to_vec()),
)));
let transferpak_enabled_model: std::rc::Rc<slint::VecModel<bool>> =
std::rc::Rc::new(slint::VecModel::from(config.input.transfer_pak.to_vec()));
app.set_transferpak(slint::ModelRc::from(transferpak_enabled_model));
app.set_transferpak(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(config.input.transfer_pak.to_vec()),
)));
app.set_gb_rom_paths(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
config
.input
.gb_rom_path
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
app.set_gb_ram_paths(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
config
.input
.gb_ram_path
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
update_input_profiles(&app.as_weak(), config);
let controllers = slint::VecModel::default();
for controller in controller_names {
controllers.push(controller.into());
}
let controller_names_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(controllers);
app.set_controller_names(slint::ModelRc::from(controller_names_model));
app.set_controller_names(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
controller_names
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
let controller_changed = slint::VecModel::default();
let selected_controllers = slint::VecModel::default();
for selected in config.input.controller_assignment.iter() {
let mut found = false;
for (i, path) in controller_paths.iter().enumerate() {
if selected == path {
selected_controllers.push(i as i32);
found = true;
continue;
}
}
if !found {
selected_controllers.push(0);
}
let selected_index = controller_paths
.iter()
.position(|path| selected == path)
.unwrap_or(0) as i32;
selected_controllers.push(selected_index);
controller_changed.push(false);
}
let selected_controllers_model: std::rc::Rc<slint::VecModel<i32>> =
std::rc::Rc::new(selected_controllers);
app.set_selected_controller(slint::ModelRc::from(selected_controllers_model));
app.set_selected_controller(slint::ModelRc::from(std::rc::Rc::new(selected_controllers)));
let controller_changed_model: std::rc::Rc<slint::VecModel<bool>> =
std::rc::Rc::new(controller_changed);
app.set_controller_changed(slint::ModelRc::from(controller_changed_model));
app.set_controller_changed(slint::ModelRc::from(std::rc::Rc::new(controller_changed)));
let weak_app = app.as_weak();
app.on_input_profile_button_clicked(move || {
@@ -226,7 +322,7 @@ fn controller_window(
tokio::spawn(async move {
let mut command =
std::process::Command::new(std::env::current_exe().unwrap());
tokio::process::Command::new(std::env::current_exe().unwrap());
command.args([
"--configure-input-profile",
&profile_name,
@@ -236,17 +332,62 @@ fn controller_window(
if dinput {
command.arg("--use-dinput");
}
if !command.status().unwrap().success() {
if !command.status().await.unwrap().success() {
eprintln!("Failed to configure input profile");
}
let game_ui = ui::Ui::new();
update_input_profiles(&weak_app, &game_ui.config);
let config = ui::config::Config::new();
update_input_profiles(&weak_app, &config);
});
})
.unwrap();
});
dialog.show().unwrap();
});
let weak_app2 = app.as_weak();
app.on_transferpak_toggled(move |player, enabled| {
if enabled {
let select_gb_rom = rfd::AsyncFileDialog::new()
.set_title(format!("GB ROM P{}", player + 1))
.add_filter("GB ROM files", &["gb", "gbc", "GB", "GBC"])
.pick_file();
let select_gb_ram = rfd::AsyncFileDialog::new()
.set_title(format!("GB RAM P{}", player + 1))
.add_filter("GB RAM files", &["sav", "ram", "srm", "SAV", "RAM", "SRM"])
.pick_file();
let weak_app3 = weak_app2.clone();
tokio::spawn(async move {
if let (Some(gb_rom), Some(gb_ram)) = (select_gb_rom.await, select_gb_ram.await) {
weak_app3
.upgrade_in_event_loop(move |handle| {
let rom_paths = handle.get_gb_rom_paths();
let ram_paths = handle.get_gb_ram_paths();
rom_paths.set_row_data(
player as usize,
gb_rom.path().to_str().unwrap().into(),
);
ram_paths.set_row_data(
player as usize,
gb_ram.path().to_str().unwrap().into(),
);
handle.set_gb_rom_paths(rom_paths);
handle.set_gb_ram_paths(ram_paths);
})
.unwrap();
} else {
weak_app3
.upgrade_in_event_loop(move |handle| {
let rom_paths = handle.get_gb_rom_paths();
let ram_paths = handle.get_gb_ram_paths();
rom_paths.set_row_data(player as usize, String::new().into());
ram_paths.set_row_data(player as usize, String::new().into());
handle.set_gb_rom_paths(rom_paths);
handle.set_gb_ram_paths(ram_paths);
})
.unwrap();
}
});
}
});
}
pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
@@ -254,6 +395,7 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
config.video.integer_scaling = app.get_integer_scaling();
config.video.fullscreen = app.get_fullscreen();
config.video.widescreen = app.get_widescreen();
config.video.vsync = app.get_vsync();
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();
@@ -267,6 +409,8 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
}
for (i, transferpak_enabled) in app.get_transferpak().iter().enumerate() {
config.input.transfer_pak[i] = transferpak_enabled;
config.input.gb_rom_path[i] = app.get_gb_rom_paths().row_data(i).unwrap().to_string();
config.input.gb_ram_path[i] = app.get_gb_ram_paths().row_data(i).unwrap().to_string();
}
for (i, input_profile_binding) in app.get_selected_profile_binding().iter().enumerate() {
config.input.input_profile_binding[i] = app
@@ -312,7 +456,7 @@ fn about_window(app: &AppWindow) {
pub fn app_window() {
let app = AppWindow::new().unwrap();
about_window(&app);
retroachievements::ra_window(&app);
ui::retroachievements::ra_window(&app);
let mut controller_paths;
{
let game_ui = ui::Ui::new();
@@ -322,8 +466,8 @@ 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);
local_game_window(&app, &game_ui.config, &controller_paths);
}
local_game_window(&app, &controller_paths);
ui::netplay::netplay_window(&app, &controller_paths);
ui::cheats::cheats_window(&app);
app.run().unwrap();
@@ -331,9 +475,8 @@ pub fn app_window() {
}
pub fn run_rom(
gb_paths: GbPaths,
file_path: std::path::PathBuf,
game_settings: GameSettings,
game_settings: ui::GameSettings,
netplay: Option<NetplayDevice>,
ra_settings: RASettings,
weak: slint::Weak<AppWindow>,
@@ -342,7 +485,7 @@ pub fn run_rom(
weak.upgrade_in_event_loop(move |handle| handle.set_game_running(true))
.unwrap();
let mut command = std::process::Command::new(std::env::current_exe().unwrap());
let mut command = tokio::process::Command::new(std::env::current_exe().unwrap());
command.args([
"--overclock",
&game_settings.overclock.to_string(),
@@ -366,9 +509,9 @@ pub fn run_rom(
if ra_settings.enabled {
command.args([
"--ra-username",
retroachievements::get_username(),
&retroachievements::get_username().unwrap_or("unknown".into()),
"--ra-token",
retroachievements::get_token(),
&retroachievements::get_token().unwrap_or("unknown".into()),
]);
if ra_settings.hardcore {
command.args(["--ra-hardcore"]);
@@ -381,23 +524,14 @@ pub fn run_rom(
}
}
for i in 0..4 {
if gb_paths.rom[i].is_some() && gb_paths.ram[i].is_some() {
command.args([
"--gb-rom",
gb_paths.rom[i].as_ref().unwrap().to_str().unwrap(),
"--gb-ram",
gb_paths.ram[i].as_ref().unwrap().to_str().unwrap(),
]);
}
}
if !command
let success = command
.arg(file_path.to_str().unwrap())
.status()
.await
.unwrap()
.success()
{
.success();
if !success {
eprintln!("Failed to run game");
}
@@ -407,6 +541,24 @@ pub fn run_rom(
if let Some(rom_dir) = file_path.parent().unwrap().to_str() {
handle.set_rom_dir(rom_dir.into());
}
if success {
let recent_roms = slint::VecModel::default();
recent_roms.push((
file_path.to_str().unwrap().into(),
file_path.file_name().unwrap().to_str().unwrap().into(),
));
for rom in handle.get_recent_roms().iter() {
if rom.0 != file_path.to_str().unwrap()
&& recent_roms.row_count() < 5
&& let Ok(exists) = std::fs::exists(&rom.0)
&& exists
{
recent_roms.push(rom);
}
}
handle.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new(recent_roms)));
}
handle.set_game_running(false);
})
.unwrap();
@@ -426,25 +578,6 @@ fn open_rom(app: &AppWindow) {
.set_title("Select ROM")
.add_filter("ROM files", &N64_EXTENSIONS)
.pick_file();
let mut select_gb_rom = [None, None, None, None];
let mut select_gb_ram = [None, None, None, None];
for (i, transfer_pak_enabled) in app.get_transferpak().iter().enumerate() {
if transfer_pak_enabled {
select_gb_rom[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB ROM P{}", i + 1))
.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", "srm", "SAV", "RAM", "SRM"])
.pick_file(),
);
}
}
let overclock = app.get_overclock_n64_cpu();
let disable_expansion_pak = app.get_disable_expansion_pak();
@@ -456,26 +589,9 @@ fn open_rom(app: &AppWindow) {
let weak = app.as_weak();
tokio::spawn(async move {
if let Some(file) = select_rom.await {
let mut gb_rom_path = [None, None, None, None];
let mut gb_ram_path = [None, None, None, None];
for i in 0..4 {
if let (Some(gb_rom), Some(gb_ram)) =
(select_gb_rom[i].as_mut(), select_gb_ram[i].as_mut())
&& let (Some(gb_rom), Some(gb_ram)) = (gb_rom.await, gb_ram.await)
{
gb_rom_path[i] = Some(gb_rom.path().to_path_buf());
gb_ram_path[i] = Some(gb_ram.path().to_path_buf());
}
}
run_rom(
GbPaths {
rom: gb_rom_path,
ram: gb_ram_path,
},
file.path().to_path_buf(),
GameSettings {
ui::GameSettings {
overclock,
disable_expansion_pak,
cheats: std::collections::HashMap::new(), // will be filled in later
+16 -3
View File
@@ -22,12 +22,14 @@ export component AppWindow inherits Window {
callback wiki_button_clicked;
callback discord_button_clicked;
callback newversion_button_clicked;
callback recent_rom_button_clicked(string);
callback patreon_button_clicked;
callback github_sponsors_button_clicked;
callback source_code_button_clicked;
callback open_rom_button_clicked;
callback saves_folder_button_clicked;
callback input_profile_button_clicked;
callback transferpak_toggled(int,bool);
callback create_session_button_clicked;
callback join_session_button_clicked;
callback netplay_discord_button_clicked;
@@ -37,7 +39,7 @@ export component AppWindow inherits Window {
callback cheat_toggled(string, string, string, bool);
callback ra_button_clicked(string);
callback ra_games_clicked;
callback ra_show_profile_clicked(string);
callback ra_show_profile_clicked;
callback ra_toggled(bool,bool,bool,bool);
in property version <=> AboutData.version;
in property has_update <=> AboutData.has_update;
@@ -50,9 +52,11 @@ export component AppWindow inherits Window {
in-out property ra_hardcore <=> RAData.hardcore;
in-out property ra_challenge <=> RAData.challenge;
in-out property ra_leaderboard <=> RAData.leaderboard;
in-out property ra_softcore_only <=> RAData.ra_softcore_only;
in-out property integer_scaling <=> SettingsData.integer_scaling;
in-out property fullscreen <=> SettingsData.fullscreen;
in-out property widescreen <=> SettingsData.widescreen;
in-out property vsync <=> SettingsData.vsync;
in-out property apply_crt_shader <=> SettingsData.apply_crt_shader;
in-out property overclock_n64_cpu <=> SettingsData.overclock_n64_cpu;
in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak;
@@ -63,11 +67,14 @@ export component AppWindow inherits Window {
in-out property controller_enabled <=> ControllerData.controller_enabled;
in-out property controller_changed <=> ControllerData.controller_changed;
in-out property transferpak <=> ControllerData.transferpak;
in-out property gb_rom_paths <=> ControllerData.gb_rom_paths;
in-out property gb_ram_paths <=> ControllerData.gb_ram_paths;
in-out property selected_profile_binding <=> ControllerData.selected_profile_binding;
in-out property input_profiles <=> ControllerData.input_profiles;
in-out property controller_names <=> ControllerData.controller_names;
in-out property selected_controller <=> ControllerData.selected_controller;
in-out property game_running <=> State.game_running;
in-out property recent_roms <=> State.recent_roms;
in-out property blank_profiles <=> ControllerData.blank_profiles;
in-out property cheat_game_name <=> CheatsData.cheat_game_name;
in-out property cheat_game_crc <=> CheatsData.cheat_game_crc;
@@ -101,11 +108,17 @@ export component AppWindow inherits Window {
newversion_button_clicked => {
newversion_button_clicked();
}
recent_rom_button_clicked(rom) => {
recent_rom_button_clicked(rom);
}
}
if(side-bar.current-item == 1): ControllerConfig {
input_profile_button_clicked => {
input_profile_button_clicked();
}
transferpak_toggled(player,enabled) => {
transferpak_toggled(player,enabled);
}
}
if(side-bar.current-item == 2): Netplay {
create_session_button_clicked => {
@@ -142,8 +155,8 @@ export component AppWindow inherits Window {
ra_games_clicked => {
ra_games_clicked();
}
ra_show_profile_clicked(username) => {
ra_show_profile_clicked(username);
ra_show_profile_clicked => {
ra_show_profile_clicked();
}
}
if(side-bar.current-item == 5): Settings { }
+3 -4
View File
@@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { ListView, HorizontalBox, VerticalBox, Button, GroupBox, CheckBox } from "std-widgets.slint";
import { ListView, HorizontalBox, VerticalBox, Button, GroupBox, Switch, CheckBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { SettingsData } from "settings_page.slint";
@@ -62,15 +62,14 @@ export component Cheats inherits Page {
wrap: word-wrap;
}
if data.options.length == 0: CheckBox {
text: @tr("Enabled");
if data.options.length == 0: Switch {
checked: data.enabled;
toggled => {
data.enabled = self.checked;
cheat_toggled(CheatsData.cheat_game_crc,data.cheat_name,"",self.checked);
}
}
for item[index] in data.options: CheckBox {
for item in data.options: CheckBox {
text: item.option;
enabled: !data.enabled || (data.enabled && item.enabled);
checked: item.enabled;
+8 -4
View File
@@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { CheckBox, ComboBox, Button, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { Switch, ComboBox, Button, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { State } from "localgame_page.slint";
@@ -9,6 +9,8 @@ export global ControllerData {
in-out property <bool> emulate_vru;
in-out property <[bool]> controller_enabled;
in-out property <[bool]> transferpak;
in-out property <[string]> gb_rom_paths;
in-out property <[string]> gb_ram_paths;
in-out property <[int]> selected_profile_binding: [-1, -1, -1, -1];
in-out property <[string]> input_profiles;
in-out property <[string]> controller_names;
@@ -19,6 +21,7 @@ export global ControllerData {
export component ControllerConfig inherits Page {
callback input_profile_button_clicked;
callback transferpak_toggled(int,bool);
title: @tr("Controller Configuration");
VerticalBox {
@@ -52,7 +55,7 @@ export component ControllerConfig inherits Page {
title: @tr("Enabled");
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: CheckBox {
for player in [0, 1, 2, 3]: Switch {
checked: ControllerData.controller_enabled[player];
toggled => {
ControllerData.controller_enabled[player] = self.checked;
@@ -65,7 +68,7 @@ export component ControllerConfig inherits Page {
title: @tr("Emulate VRU");
VerticalLayout {
alignment: space-between;
for valid in [false, false, false, true]: CheckBox {
for valid in [false, false, false, true]: Switch {
enabled: valid;
checked: valid && ControllerData.emulate_vru;
toggled => {
@@ -79,10 +82,11 @@ export component ControllerConfig inherits Page {
title: @tr("Transfer Pak");
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: CheckBox {
for player in [0, 1, 2, 3]: Switch {
checked: ControllerData.transferpak[player];
toggled => {
ControllerData.transferpak[player] = self.checked;
transferpak_toggled(player,self.checked);
}
}
}
+2 -2
View File
@@ -1,4 +1,4 @@
import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox, Slider } from "std-widgets.slint";
import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, Switch, Slider } from "std-widgets.slint";
export global InputProfileData {
in-out property <bool> dinput;
@@ -47,7 +47,7 @@ export component InputProfileDialog inherits Window {
HorizontalBox {
alignment: center;
CheckBox {
Switch {
text: @tr("Use DirectInput");
checked: InputProfileData.dinput;
toggled => {
+20 -1
View File
@@ -1,18 +1,20 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Button, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { AboutData, NewVersionButton } from "about_page.slint";
export global State {
in-out property <bool> game_running;
in-out property <[{a:string, b:string}]> recent_roms;
}
export component LocalGame inherits Page {
callback open_rom_button_clicked;
callback saves_folder_button_clicked;
callback newversion_button_clicked;
callback recent_rom_button_clicked(string);
title: @tr("Local Game");
VerticalBox {
@@ -44,5 +46,22 @@ export component LocalGame inherits Page {
}
}
Rectangle { }
if State.recent_roms.length>0:HorizontalBox {
alignment: center;
GroupBox {
title: @tr("Recent ROMs");
VerticalLayout {
alignment: space-between;
for rom in State.recent_roms: Button {
text: rom.b;
enabled: !State.game_running;
clicked => {
recent_rom_button_clicked(rom.a);
}
}
}
}
}
}
}
+3 -3
View File
@@ -25,7 +25,7 @@ export component NetplayCreate inherits Window {
}
LineEdit {
placeholder_text: @tr("Enter session name");
placeholder-text: @tr("Enter session name");
edited(text) => {
NetplayData.session_name = text;
}
@@ -38,7 +38,7 @@ export component NetplayCreate inherits Window {
}
LineEdit {
placeholder_text: @tr("Enter session password");
placeholder-text: @tr("Enter session password");
edited(text) => {
NetplayData.password = text;
}
@@ -52,7 +52,7 @@ export component NetplayCreate inherits Window {
}
LineEdit {
placeholder_text: @tr("Enter player name");
placeholder-text: @tr("Enter player name");
edited(text) => {
NetplayData.player_name = text;
}
+2 -2
View File
@@ -27,11 +27,11 @@ export component NetplayJoin inherits Window {
HorizontalBox {
VerticalBox {
Text {
text: @tr("Player Name:");
text: @tr("Player Name (required):");
}
LineEdit {
placeholder_text: @tr("Enter player name");
placeholder-text: @tr("Enter player name");
edited(text) => {
NetplayData.player_name = text;
}
+1 -1
View File
@@ -73,7 +73,7 @@ export component NetplayWait inherits Window {
HorizontalBox {
chat_message := LineEdit {
placeholder_text: @tr("Enter chat message");
placeholder-text: @tr("Enter chat message");
key-released(event) => {
if (event.text == Key.Return && chat_message.text != "") {
send_chat_message(chat_message.text);
+13 -12
View File
@@ -1,7 +1,7 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { LineEdit, CheckBox, Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { LineEdit, Switch, Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Page } from "page.slint";
export global RAData {
@@ -11,6 +11,7 @@ export global RAData {
in-out property <bool> leaderboard;
in-out property <bool> ra_logging_in;
in-out property <bool> ra_show_profile;
in-out property <bool> ra_softcore_only;
in-out property <string> ra_username;
in-out property <string> ra_password;
in-out property <string> current_user_message;
@@ -20,14 +21,14 @@ export component RetroAchievements inherits Page {
callback ra_button_clicked(string);
callback ra_toggled(bool,bool,bool,bool);
callback ra_games_clicked;
callback ra_show_profile_clicked(string);
callback ra_show_profile_clicked;
title: @tr("RetroAchievements");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: center;
CheckBox {
alignment: start;
Switch {
text: @tr("Enabled");
checked: RAData.enabled;
toggled => {
@@ -43,9 +44,9 @@ export component RetroAchievements inherits Page {
}
}
}
HorizontalBox {
alignment: center;
CheckBox {
if !RAData.ra_softcore_only:HorizontalBox {
alignment: start;
Switch {
text: @tr("Hardcore mode");
checked: RAData.hardcore;
enabled: RAData.enabled;
@@ -56,8 +57,8 @@ export component RetroAchievements inherits Page {
}
}
HorizontalBox {
alignment: center;
CheckBox {
alignment: start;
Switch {
text: @tr("Challenge Indicators");
checked: RAData.challenge;
enabled: RAData.enabled;
@@ -68,8 +69,8 @@ export component RetroAchievements inherits Page {
}
}
HorizontalBox {
alignment: center;
CheckBox {
alignment: start;
Switch {
text: @tr("Leaderboard Trackers");
checked: RAData.leaderboard;
enabled: RAData.enabled;
@@ -89,7 +90,7 @@ export component RetroAchievements inherits Page {
enabled: RAData.enabled;
text: @tr("View Profile");
clicked => {
ra_show_profile_clicked(RAData.ra_username);
ra_show_profile_clicked();
}
}
}
+22 -10
View File
@@ -1,13 +1,14 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { ComboBox, HorizontalBox, VerticalBox, CheckBox } from "std-widgets.slint";
import { ComboBox, HorizontalBox, VerticalBox, Switch } from "std-widgets.slint";
import { Page } from "page.slint";
export global SettingsData {
in-out property <bool> integer_scaling;
in-out property <bool> fullscreen;
in-out property <bool> widescreen;
in-out property <bool> vsync;
in-out property <bool> apply_crt_shader;
in-out property <bool> overclock_n64_cpu;
in-out property <bool> disable_expansion_pak;
@@ -39,7 +40,7 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
Switch {
text: @tr("Integer Scaling");
checked: SettingsData.integer_scaling;
toggled => {
@@ -50,8 +51,8 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Fullscreen (Esc closes game)");
Switch {
text: @tr("Fullscreen");
checked: SettingsData.fullscreen;
toggled => {
SettingsData.fullscreen = self.checked;
@@ -61,8 +62,8 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Widescreen (strech)");
Switch {
text: @tr("Widescreen (stretch)");
checked: SettingsData.widescreen;
toggled => {
SettingsData.widescreen = self.checked;
@@ -72,7 +73,18 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
Switch {
text: @tr("VSync");
checked: SettingsData.vsync;
toggled => {
SettingsData.vsync = self.checked;
}
}
}
HorizontalBox {
alignment: start;
Switch {
text: @tr("Apply CRT shader");
checked: SettingsData.apply_crt_shader;
toggled => {
@@ -87,7 +99,7 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
Switch {
text: @tr("Overclock N64 CPU (may cause bugs)");
checked: SettingsData.overclock_n64_cpu;
toggled => {
@@ -98,7 +110,7 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
Switch {
text: @tr("Disable Expansion Pak");
checked: SettingsData.disable_expansion_pak;
toggled => {
@@ -109,7 +121,7 @@ export component Settings inherits Page {
HorizontalBox {
alignment: start;
CheckBox {
Switch {
text: @tr("Emulate UNFLoader");
checked: SettingsData.emulate_usb;
toggled => {
+1
View File
@@ -384,6 +384,7 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
controllers
}
#[cfg(feature = "gui")]
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<Option<String>> {
let mut controller_paths: Vec<Option<String>> = vec![];
+34 -50
View File
@@ -1,8 +1,8 @@
use crate::device;
use crate::ui;
use crate::ui::gui::{
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, GbPaths,
NetplayCreate, NetplayDevice, NetplayJoin, NetplayWait, RASettings, run_rom, save_settings,
AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, NetplayCreate, NetplayDevice,
NetplayJoin, NetplayWait, RASettings, run_rom, save_settings,
};
use futures::{SinkExt, StreamExt};
use sha2::{Digest, Sha256};
@@ -159,12 +159,8 @@ fn populate_server_names<T: ComponentHandle + NetplayPages + 'static>(weak: slin
}
server_names.push("Custom".into());
handle.refresh_sessions();
let server_names_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_names);
let server_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(server_urls);
handle.set_server_names(slint::ModelRc::from(server_names_model));
handle.set_server_urls(slint::ModelRc::from(server_urls_model));
handle.set_server_names(slint::ModelRc::from(std::rc::Rc::new(server_names)));
handle.set_server_urls(slint::ModelRc::from(std::rc::Rc::new(server_urls)));
})
.unwrap();
});
@@ -192,7 +188,7 @@ fn select_rom<T: ComponentHandle + NetplayPages + 'static>(
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();
let mut parsed_cheats = String::new();
if let Some(game_cheats) = cheats.cheats.get(&game_crc)
&& !game_cheats.is_empty()
{
@@ -224,10 +220,10 @@ fn select_rom<T: ComponentHandle + NetplayPages + 'static>(
message_dialog.set_text("Could not read ROM".into());
message_dialog.show().unwrap();
handle.set_game_name("".into());
handle.set_game_hash("".into());
handle.set_game_cheats("".into());
handle.set_rom_path("".into());
handle.set_game_name(String::new().into());
handle.set_game_hash(String::new().into());
handle.set_game_cheats(String::new().into());
handle.set_rom_path(String::new().into());
})
.unwrap();
}
@@ -251,7 +247,7 @@ fn show_custom_url_dialog(weak: slint::Weak<NetplayCreate>, server_url: slint::S
pub fn setup_create_window(
create_window: &NetplayCreate,
game_settings: GameSettings,
game_settings: ui::GameSettings,
ra_settings: RASettings,
rom_dir: slint::SharedString,
weak_app: slint::Weak<AppWindow>,
@@ -583,26 +579,19 @@ fn update_sessions(weak: slint::Weak<NetplayJoin>) {
.upgrade_in_event_loop(move |handle| {
let sessions_vec = slint::VecModel::default();
for session in sessions.iter() {
let session_vec = slint::VecModel::from(session.to_vec());
let session_model: std::rc::Rc<
slint::VecModel<slint::StandardListViewItem>,
> = std::rc::Rc::new(session_vec);
sessions_vec.push(slint::ModelRc::from(session_model));
sessions_vec.push(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(session.to_vec()),
)));
}
let rooms_model: std::rc::Rc<
slint::VecModel<slint::ModelRc<slint::StandardListViewItem>>,
> = std::rc::Rc::new(sessions_vec);
handle.set_sessions(slint::ModelRc::from(rooms_model));
handle.set_sessions(slint::ModelRc::from(std::rc::Rc::new(sessions_vec)));
let room_urls_vec = slint::VecModel::from(room_urls.to_vec());
let room_urls_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(room_urls_vec);
handle.set_room_urls(slint::ModelRc::from(room_urls_model));
handle.set_room_urls(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(room_urls.to_vec()),
)));
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_room_ports(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(room_ports.to_vec()),
)));
handle.set_current_session(-1);
handle.set_pending_refresh(false);
@@ -624,7 +613,7 @@ fn create_session(
game_hash: String,
game_cheats: String,
password: String,
game_settings: GameSettings,
game_settings: ui::GameSettings,
ra_settings: RASettings,
weak_app: slint::Weak<AppWindow>,
weak: slint::Weak<NetplayCreate>,
@@ -714,7 +703,7 @@ fn create_session(
handle.get_rom_path(),
message.player_name.as_ref().unwrap().into(),
session.port.unwrap(),
GameSettings {
ui::GameSettings {
overclock: overclock.parse().unwrap(),
disable_expansion_pak: disable_expansion_pak.parse().unwrap(),
cheats: serde_json::from_str(cheats).unwrap(),
@@ -837,7 +826,7 @@ fn join_session(
handle.get_rom_path(),
message.player_name.as_ref().unwrap().into(),
session.port.unwrap(),
GameSettings {
ui::GameSettings {
overclock: overclock.parse().unwrap(),
disable_expansion_pak: disable_expansion_pak.parse().unwrap(),
cheats: serde_json::from_str(cheats).unwrap(),
@@ -883,7 +872,7 @@ fn setup_wait_window(
rom_path: slint::SharedString,
player_name: slint::SharedString,
port: i32,
game_settings: GameSettings,
game_settings: ui::GameSettings,
ra_settings: RASettings,
peer_addr: slint::SharedString,
weak_app: slint::Weak<AppWindow>,
@@ -1062,15 +1051,14 @@ fn setup_wait_window(
handle.set_can_start(false);
}
let players_vec: slint::VecModel<slint::SharedString> =
slint::VecModel::default();
for player in player_names {
players_vec.push(player.into());
}
let players_model: std::rc::Rc<
slint::VecModel<slint::SharedString>,
> = std::rc::Rc::new(players_vec);
handle.set_players(slint::ModelRc::from(players_model));
handle.set_players(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
player_names
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
}
})
.unwrap();
@@ -1101,12 +1089,8 @@ fn setup_wait_window(
}
run_rom(
GbPaths {
rom: [None, None, None, None],
ram: [None, None, None, None],
},
handle.get_rom_path().as_str().into(),
GameSettings {
ui::GameSettings {
overclock: game_settings.overclock,
disable_expansion_pak: game_settings.disable_expansion_pak,
cheats: game_settings.cheats,
@@ -1230,7 +1214,7 @@ pub fn netplay_window(app: &AppWindow, controller_paths: &[Option<String>]) {
save_settings(&handle, &controller_paths);
setup_create_window(
&create_window,
GameSettings {
ui::GameSettings {
overclock: handle.get_overclock_n64_cpu(),
disable_expansion_pak: handle.get_disable_expansion_pak(),
cheats: std::collections::HashMap::new(), // not used here
+120
View File
@@ -0,0 +1,120 @@
use crate::retroachievements;
use crate::ui;
use slint::ComponentHandle;
pub fn ra_window(app: &ui::gui::AppWindow) {
let mut token = String::new();
if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json"))
&& let Ok(result) =
serde_json::from_slice::<retroachievements::RAConfig>(ra_config.as_ref())
{
app.set_ra_username(result.username.into());
app.set_ra_enabled(result.enabled);
app.set_ra_hardcore(result.hardcore);
app.set_ra_challenge(result.challenge);
app.set_ra_leaderboard(result.leaderboard);
token = result.token;
} else {
app.set_ra_hardcore(true);
}
if !cfg!(ra_hardcore_enabled) {
app.set_ra_softcore_only(true);
}
if app.get_ra_enabled() && !app.get_ra_username().is_empty() {
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
retroachievements::login_token_user(app.get_ra_username().to_string(), token, tx);
set_current_user_message(app, rx);
} else {
app.set_ra_current_user_message("Not currently logged in".into());
}
let weak_app2 = app.as_weak();
app.on_ra_button_clicked(move |password| {
weak_app2
.upgrade_in_event_loop(move |handle| {
let (tx, rx) = tokio::sync::oneshot::channel::<bool>();
retroachievements::login_user(
handle.get_ra_username().to_string(),
password.to_string(),
tx,
);
set_current_user_message(&handle, rx);
})
.unwrap();
});
app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| {
let file_path = ui::get_dirs().config_dir.join("retroachievements.json");
let raconfig = if let Ok(ra_config) = std::fs::read(&file_path)
&& let Ok(result) =
serde_json::from_slice::<retroachievements::RAConfig>(ra_config.as_ref())
{
if !enabled {
retroachievements::logout_user();
retroachievements::RAConfig {
username: String::new(),
token: String::new(),
enabled,
hardcore,
challenge,
leaderboard,
}
} else {
retroachievements::RAConfig {
username: result.username,
token: result.token,
enabled,
hardcore,
challenge,
leaderboard,
}
}
} else {
retroachievements::RAConfig {
username: String::new(),
token: String::new(),
enabled,
hardcore,
challenge,
leaderboard,
}
};
let f = std::fs::File::create(&file_path).unwrap();
serde_json::to_writer_pretty(f, &raconfig).unwrap();
});
app.on_ra_games_clicked(move || {
open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap();
});
app.on_ra_show_profile_clicked(move || {
open::that_detached(format!(
"https://retroachievements.org/user/{}",
retroachievements::get_username().unwrap_or_default()
))
.unwrap();
});
}
fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver<bool>) {
app.set_ra_current_user_message("Logging in...".into());
let weak_app = app.as_weak();
tokio::spawn(async move {
rx.await.unwrap();
weak_app
.upgrade_in_event_loop(move |handle| {
if let Some(username) = retroachievements::get_username() {
handle.set_ra_current_user_message(format!("Logged in as {}", username).into());
handle.set_ra_show_profile(true);
} else {
handle.set_ra_current_user_message("Login failed".into());
handle.set_ra_show_profile(false);
}
handle.set_ra_logging_in(false);
})
.unwrap();
});
}
+13 -16
View File
@@ -135,13 +135,13 @@ pub fn get_game_crc(rom: &[u8]) -> String {
}
pub fn get_game_name(rom: &[u8]) -> String {
let mut game_name = "".to_owned();
let header_value = std::str::from_utf8(&rom[0x20..0x34]);
if let Ok(header_value) = header_value {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re.replace_all(header_value, "").trim().replace('\0', "");
re.replace_all(header_value, "").trim().replace('\0', "")
} else {
String::new()
}
game_name
}
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
@@ -163,49 +163,46 @@ pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.storage
.paths
.eep_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
.push(format!("{}-{}.eep", prefix, ui.game_hash));
ui.storage.paths.sra_file_path.clone_from(&saves_path);
ui.storage
.paths
.sra_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
.push(format!("{}-{}.sra", prefix, ui.game_hash));
ui.storage.paths.fla_file_path.clone_from(&saves_path);
ui.storage
.paths
.fla_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
.push(format!("{}-{}.fla", prefix, ui.game_hash));
ui.storage.paths.pak_file_path.clone_from(&saves_path);
ui.storage
.paths
.pak_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
.push(format!("{}-{}.mpk", prefix, ui.game_hash));
ui.storage.paths.sdcard_file_path.clone_from(&saves_path);
ui.storage
.paths
.sdcard_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".img");
.push(format!("{}-{}.img", prefix, ui.game_hash));
ui.storage.paths.romsave_file_path.clone_from(&saves_path);
ui.storage
.paths
.romsave_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave");
.push(format!("{}-{}.romsave", prefix, ui.game_hash));
ui.storage
.paths
.savestate_file_path
.clone_from(&states_path);
ui.storage.paths.savestate_file_path.push(
prefix.to_owned()
+ "-"
+ &ui.game_hash
+ ".state"
+ ui.storage.save_state_slot.to_string().as_str(),
);
ui.storage.paths.savestate_file_path.push(format!(
"{}-{}.state{}",
prefix, ui.game_hash, ui.storage.save_state_slot
));
}
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
+34
View File
@@ -21,6 +21,11 @@ fn build_gfx_info(device: &mut device::Device) -> GFX_INFO {
PAL: device.cart.pal,
widescreen: device.ui.config.video.widescreen,
fullscreen: device.ui.video.fullscreen,
vsync: if device.netplay.is_none() {
device.ui.config.video.vsync
} else {
false
},
integer_scaling: device.ui.config.video.integer_scaling,
upscale: device.ui.config.video.upscale,
crt: device.ui.config.video.crt,
@@ -101,6 +106,35 @@ pub fn init(device: &mut device::Device) {
device.ui.storage.save_state_slot,
)
}
fps_counter(&mut device.ui);
}
fn fps_counter(ui: &mut ui::Ui) {
let mut fps_rx = ui.video.fps_rx.take().unwrap();
let mut vis_rx = ui.video.vis_rx.take().unwrap();
tokio::spawn(async move {
loop {
let mut fps: u32 = 0;
let mut vis: u32 = 0;
loop {
match fps_rx.try_recv() {
Ok(_fps_update) => fps += 1,
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break,
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return,
}
}
loop {
match vis_rx.try_recv() {
Ok(_vis_update) => vis += 1,
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => break,
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return,
}
}
unsafe { rdp_set_fps(fps, vis) };
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
}
pub fn close(ui: &ui::Ui) {
+32 -34
View File
@@ -1,5 +1,5 @@
use crate::gui;
use crate::retroachievements;
use crate::ui::gui;
use slint::ComponentHandle;
pub fn prompt_for_match(words: &[String], frame_time: f64) -> u16 {
@@ -8,43 +8,41 @@ pub fn prompt_for_match(words: &[String], frame_time: f64) -> u16 {
dedup_words.dedup();
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
tokio::spawn(async move {
let vru_dialog = gui::VruDialog::new().unwrap();
let vru_dialog_weak = vru_dialog.as_weak();
let tx_clicked = tx.clone();
let vru_dialog = gui::VruDialog::new().unwrap();
let vru_dialog_weak = vru_dialog.as_weak();
vru_dialog.on_vru_button_clicked(move |chosen_word| {
tx_clicked.try_send(chosen_word.to_string()).unwrap();
vru_dialog_weak.unwrap().window().hide().unwrap();
});
vru_dialog.window().on_close_requested(move || {
tx.try_send("".to_string()).unwrap();
slint::CloseRequestResponse::HideWindow
});
let words_vec = slint::VecModel::default();
for word in dedup_words {
words_vec.push(word.into());
}
let words_model: std::rc::Rc<slint::VecModel<slint::SharedString>> =
std::rc::Rc::new(words_vec);
vru_dialog.set_words(slint::ModelRc::from(words_model));
vru_dialog.run().unwrap();
vru_dialog.on_vru_button_clicked(move |chosen_word| {
tx.try_send(chosen_word.to_string()).unwrap();
vru_dialog_weak.unwrap().window().hide().unwrap();
});
let mut result = rx.try_recv();
while result.is_err() {
result = rx.try_recv();
std::thread::sleep(std::time::Duration::from_secs_f64(frame_time));
unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the OS doesn't complain about the game window being frozen
retroachievements::do_idle();
}
for (i, v) in words.iter().enumerate() {
if *v == *result.as_ref().unwrap() {
return i as u16;
vru_dialog.set_words(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
dedup_words
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
let timer = slint::Timer::default();
timer.start(
slint::TimerMode::Repeated,
std::time::Duration::from_secs_f64(frame_time),
move || {
unsafe { sdl3_sys::events::SDL_PumpEvents() }; // so the OS doesn't complain about the game window being frozen
retroachievements::do_idle();
},
);
vru_dialog.run().unwrap();
if let Ok(result) = rx.try_recv() {
for (i, v) in words.iter().enumerate() {
if *v == result {
return i as u16;
}
}
}
0x7FFF