Compare commits

..

127 Commits

Author SHA1 Message Date
Logan McNaughton 500b4bb505 only try to assign enabled controllers (#248) 2025-02-13 21:05:22 +01:00
Logan McNaughton b01bbaa82f update cargo packages 2025-02-13 21:04:15 +01:00
Logan McNaughton 2010b82c8e remove dead code 2025-02-13 11:57:16 +01:00
Logan McNaughton 74c65750a3 add some sdl window flags (#247) 2025-02-13 11:40:06 +01:00
Logan McNaughton 2d09425ca1 fix rsp reserved commands (#246) 2025-02-12 22:04:30 +01:00
Logan McNaughton d0965c03e1 dynamically calculate speed limit frequency (#245)
* dynamically calculate speed limit frequency

* dynamically calculate speed limit frequency

* dynamically calculate speed limit frequency

* more
2025-02-12 21:18:54 +01:00
Logan McNaughton 6ee55ce4ca add a way to show internal FPS (#244) 2025-02-12 17:53:17 +01:00
Logan McNaughton 63b2ed69e4 don't modify cart.rom (#243)
* don't modify cart.rom

* don't modify cart.rom

* don't modify cart.rom

* more

* more
2025-02-12 16:09:42 +01:00
Logan McNaughton 0faf33fcfa PAL aspect ratio adjustment (#242) 2025-02-12 10:14:06 +01:00
Logan McNaughton b67268ff62 fix PAL aspect ratio (#241) 2025-02-12 09:19:39 +01:00
dependabot[bot] bda50f8d9d Bump clap from 4.5.28 to 4.5.29 (#240)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.28 to 4.5.29.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.28...clap_complete-v4.5.29)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-12 06:13:45 +01:00
Logan McNaughton ea012b4587 bump to 1.0.6 (#239) 2025-02-11 15:48:46 +01:00
Logan McNaughton a3e67d7ad9 use cache-apt-pkgs-action@v1 (#238) 2025-02-11 15:32:02 +01:00
Logan McNaughton 8084a8cb80 update cargo packages (#237) 2025-02-11 15:26:25 +01:00
Logan McNaughton 2289971987 Savestate support (#236)
* add save state support

* bind to f5

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* arm

* arm

* arm

* arm

* arm

* more

* more

* more

* more

* more

* more
2025-02-11 15:22:35 +01:00
Logan McNaughton 6c1f8d0ab0 Remove fat LTO (#235) 2025-02-10 21:00:38 +01:00
Logan McNaughton 7f6de5119f use git hash for netplay sync (#234)
* use git hash for netplay sync

* more
2025-02-10 17:02:09 +01:00
Logan McNaughton 1c168c4bcd add some CPU read cycles (#233)
* add some CPU read cycles

* more

* more

* more

* use scissor region for rdp interrupt calc
2025-02-09 17:34:29 +01:00
Logan McNaughton 99511b7cc6 bump to 1.0.5 (#232) 2025-02-09 11:56:47 +01:00
Logan McNaughton 421d533479 update cargo packages (#231) 2025-02-09 11:45:36 +01:00
Logan McNaughton ab5ef57106 further function cleanup 2025-02-09 11:43:10 +01:00
Logan McNaughton 37b41c0f66 cleanup vi calc functions 2025-02-08 22:41:28 +01:00
Logan McNaughton 4c50203ad3 modify rdp sync code (#230) 2025-02-08 21:39:48 +01:00
Logan McNaughton 271e4be68c disable rdp sync optimization to be safe (#229) 2025-02-08 21:20:59 +01:00
Logan McNaughton a9afd6bb5c Improve RDP emulation performance (#228)
* Improve RDP emulation performance

* more

* more

* more

* more
2025-02-07 22:29:20 +01:00
Logan McNaughton cd2b3dd143 update cargo lock 2025-02-06 14:31:11 +01:00
Logan McNaughton ab31b44aac Bump to 1.0.4 (#227) 2025-02-05 22:19:47 +01:00
Logan McNaughton 58eaad1742 remove some dependencies (#226)
* remove some dependencies

* more

* more

* more

* more
2025-02-05 19:31:38 +01:00
Logan McNaughton a8e7c3c136 add Cargo.lock (#225)
* add Cargo.lock

* more

* more
2025-02-05 17:31:17 +01:00
Logan McNaughton f4099cfcce further sc64 cleanup (#224)
* further sc64 cleanup

* further sc64 cleanup
2025-02-05 13:39:24 +01:00
Logan McNaughton 5b3e2a25de Fix out-of-bounds ROM dma (#223)
* Fix out-of-bounds ROM dma

* Fix out-of-bounds ROM dma

* more
2025-02-05 13:18:44 +01:00
Logan McNaughton 55ab1cb60b Fix address masking in sc64 read/write (#222)
* fix address masking for sc64

* more

* more
2025-02-05 10:29:07 +01:00
dependabot[bot] 778a0a4015 Update eframe requirement from 0.30 to 0.31 (#221)
Updates the requirements on [eframe](https://github.com/emilk/egui) to permit the latest version.
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.30.0...0.31.0)

---
updated-dependencies:
- dependency-name: eframe
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-05 06:40:50 +01:00
Logan McNaughton 96d2a72850 compress sd and rom saves during netplay (#220)
* compress sd and rom saves during netplay

* compress sd and rom saves during netplay
2025-02-04 21:21:47 +01:00
Logan McNaughton 4c7361f963 No need to check for ROM bounds on write (#219)
* resize ROM when writing if needed

* more
2025-02-04 19:50:19 +01:00
Logan McNaughton 80fc5f8ea2 collapse variable 2025-02-04 15:42:29 +01:00
Logan McNaughton f0eb5764c8 emulate summercart64 ROM_WRITE_ENABLE flag (#218)
* emulate summercart64 ROM_WRITE_ENABLE flag

* clean up names
2025-02-04 15:37:55 +01:00
Logan McNaughton 480af8d36e Ignore summercart64 USB commands (#217) 2025-02-04 13:14:57 +01:00
Logan McNaughton ad4eddb408 Fix crash when game name cannot be parsed (#216) 2025-02-04 13:01:33 +01:00
Logan McNaughton 166b7daa9d Bump to 1.0.3 (#215) 2025-02-03 18:50:20 +01:00
Logan McNaughton 920a0323c0 cleanup 2025-02-03 16:49:42 +01:00
Logan McNaughton 14e45be300 SD card emulation (#214)
* SD card emulation

* more

* more

* more

* more

* more

* more

* more

* better save name

* better save name

* better save name

* more
2025-02-03 16:41:46 +01:00
Logan McNaughton fba6a4a6d4 support for Advanced Homebrew ROM Header (#213)
* support for Advanced Homebrew ROM Header

* more

* more
2025-02-03 10:22:21 +01:00
Logan McNaughton 1efdeafc31 bump to 1.0.2 (#212) 2025-02-02 12:54:48 +01:00
Logan McNaughton a7c2c26d78 minor tweak to interrupt system (#211) 2025-02-02 11:23:32 +01:00
Logan McNaughton 7d63a694cc fix compare interrupt (#210) 2025-02-01 20:47:26 +01:00
Logan McNaughton 5c98a08fe4 remove schedule_rcp_interrupt (#208)
* remove schedule_rcp_interrupt

* more

* more

* more
2025-02-01 19:16:38 +01:00
Logan McNaughton 39ffad3af3 Bump to 1.0.1 (#206) 2025-01-30 21:08:05 +01:00
Logan McNaughton ca2a3d423b Bump to 1.0.0 (#205) 2025-01-30 19:33:47 +01:00
Logan McNaughton fe5346235e fix freeze in JFG (#204)
* fix freeze in JFG

* more
2025-01-30 19:32:56 +01:00
Logan McNaughton f84444a12e remove rdp_full_sync (#203)
* remove rdp_full_sync

* swap

* swap
2025-01-30 14:42:04 +01:00
Logan McNaughton 026e4c0e74 Bump to 0.1.26 (#202) 2025-01-29 21:06:36 +01:00
Logan McNaughton 363ed0953a set specific version for apt cache 2025-01-29 20:49:21 +01:00
Logan McNaughton cb9309d7e4 RDP sync updates (#201)
* RDP sync updates

* rename

* rename
2025-01-29 20:21:49 +01:00
Logan McNaughton 1b1313003c dont run rsp task when dma is busy (#200) 2025-01-29 12:50:29 +01:00
Logan McNaughton 7219a4445c add another sync point for RSP (#199) 2025-01-29 11:14:50 +01:00
Logan McNaughton 687badc142 Modify RSP sync points (#198)
* modify RSP sync points

* more

* more

* more

* more

* more

* more

* more
2025-01-29 10:49:29 +01:00
Logan McNaughton 3e2189463f static link MoltenVK (#196)
* static link MoltenVK

* static link MoltenVK

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test

* test
2025-01-28 20:56:42 +01:00
Logan McNaughton ed8b4e8e02 Bump to 0.1.25 (#195) 2025-01-28 14:32:54 +01:00
Logan McNaughton 611121568e cache apt packages in github workflow (#194) 2025-01-28 14:21:28 +01:00
Logan McNaughton 27922fbf9c use TCP_RECEIVE_SAVE_WITH_SIZE for netplay (#193)
* use TCP_RECEIVE_SAVE_WITH_SIZE for netplay

* more

* more

* more
2025-01-28 13:30:59 +01:00
Logan McNaughton 71b7baf613 fix masking pi regs (#192) 2025-01-27 22:50:49 +01:00
Logan McNaughton f4714a7813 add rdram reg that libdragon seems to access (#191)
* add rdram reg that libdragon seems to access

* more
2025-01-27 19:42:36 +01:00
Logan McNaughton 4730b7f293 Bump to 0.1.24 (#190) 2025-01-27 18:12:03 +01:00
Logan McNaughton 6f33934180 on mac, run game in new process (#189)
* on mac, run game in new process

* more

* more

* more

* remove tokio

* clippy

* formatting

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-01-27 18:02:06 +01:00
Logan McNaughton cbc9045ad4 print error message when sdl fails 2025-01-27 12:02:42 +01:00
Logan McNaughton 2148b1c198 Use github arm runner (#188)
* Use github arm runner

* more
2025-01-27 08:52:29 +01:00
Logan McNaughton 3dc28cc603 Don't create UI in same thread as egui (#187) 2025-01-27 07:36:53 +01:00
Logan McNaughton 1f746c5b36 Use SDL_Vulkan_GetVkGetInstanceProcAddr (#186)
* static link MoltenVK

* static link MoltenVK

* test

* test2

* test2

* more

* more

* more
2025-01-26 22:17:50 +01:00
Logan McNaughton a2ba5baaa8 move sse2neon to versioned folder (#185)
* move sse2neon to versioned folder

* more
2025-01-26 19:13:19 +01:00
Logan McNaughton 333865e7de dont create new UI on close (#184)
* dont create new UI on close

* dont create new UI on close

* more

* more

* more

* more

* more
2025-01-26 17:47:17 +01:00
Logan McNaughton b1bb96c7d2 use binary format for rom saves (#183)
* use binary format for rom saves

* more
2025-01-26 15:00:49 +01:00
Logan McNaughton 9e88343c6b check accept value of netplay begin_game message (#182) 2025-01-26 12:26:24 +01:00
Logan McNaughton 75e59ee051 cleanup dir detection (#181)
* cleanup dir detection

* more

* more
2025-01-26 11:27:33 +01:00
Logan McNaughton 952f951620 Bump to 0.1.23 (#180) 2025-01-25 19:14:20 +01:00
Logan McNaughton 6820da889c add icon for windows (#179) 2025-01-25 19:00:38 +01:00
Logan McNaughton 6292459f8b install proper SDL Dependencies (#178)
* install proper SDL Dependencies

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

* more

* more

* more

* more

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

* more

* more

* more

* source

* more

* destory

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* fix

* fix

* more

* more

* more

* more

* more

* more

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

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

* Set TOS value on packets

* better game name parsing
2025-01-23 13:00:34 +01:00
Logan McNaughton 7d805ac69b Update README.md 2025-01-23 09:47:00 +01:00
Logan McNaughton 2c0710a44f Bump to 0.1.21 (#170) 2025-01-22 22:12:39 +01:00
Logan McNaughton 040ca493c8 Update README.md 2025-01-22 22:00:06 +01:00
Logan McNaughton bda2266d74 fix potential crash 2025-01-22 21:47:38 +01:00
Logan McNaughton 195412a48e Netplay input sync (#169)
* Netplay sync inputs

* use wrapping add

* non blocking

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* message

* more

* close

* fixes

* redirect rumble

* more

* fix

* more

* messages
2025-01-22 21:31:58 +01:00
Logan McNaughton 9257e8c66d clean up rom saving 2025-01-22 13:23:59 +01:00
Logan McNaughton 87ae2b940a optimize save loading 2025-01-22 13:21:19 +01:00
Logan McNaughton ba8f39f283 Netplay sync save files (#168) 2025-01-22 11:10:54 +01:00
Logan McNaughton 1a823399ab fix loading ROM in netplay 2025-01-22 09:48:09 +01:00
Logan McNaughton 26f0936dc7 disable VRU during netplay (#167) 2025-01-20 21:48:39 +01:00
Logan McNaughton ada4b7e3b4 Netplay registration (#166)
* Register player

* more

* more

* more

* more

* more
2025-01-20 21:29:46 +01:00
Logan McNaughton 004af133eb Netplay join room (#165)
* netplay Join room

* more

* more

* more

* more

* more

* more
2025-01-20 15:24:11 +01:00
Logan McNaughton 0af411d662 Netplay wait room (#164)
* netplay wait room

* more

* more

* more

* wait room

* more

* more
2025-01-20 11:58:15 +01:00
Logan McNaughton 549d71bebf repaint GUI while waiting for data 2025-01-20 07:56:11 +01:00
Logan McNaughton 9eacf8a916 Finish netplay create room logic (#163)
* Move files

* more

* auth

* more
2025-01-19 22:35:56 +01:00
Logan McNaughton dbd12516cf Netplay create room GUI (#162)
* netplay

* more

* more

* more

* more

* tokio

* more

* more

* more

* video

* functions

* more

* use rusttls

* fix broadcast
2025-01-19 14:17:09 +01:00
Logan McNaughton 4ead0218d9 remove pub keyword where it isn't needed (#161)
* remove pub keyword where it isn't needed

* more

* Update main.rs

* more
2025-01-18 19:58:23 +01:00
Logan McNaughton 14ea0eee3f cleanup loop detection (#160)
* cleanup loop detection

* more
2025-01-18 09:59:18 +01:00
Logan McNaughton b860be4143 Bump to 0.1.20 (#158) 2025-01-17 11:37:52 +01:00
Logan McNaughton b065f807a2 Change box for audio (#157)
* Remove box for audio

* more
2025-01-17 11:27:30 +01:00
Logan McNaughton ee95816385 Use Box for audio files (#156) 2025-01-17 11:07:39 +01:00
Logan McNaughton d021cb5d32 use mimalloc (#155) 2025-01-17 11:06:57 +01:00
Logan McNaughton 9819fad475 fix crash 2025-01-17 09:08:53 +01:00
Logan McNaughton dd767047fd make sure rumble is off when removing pak 2025-01-16 22:21:33 +01:00
Logan McNaughton f6480ea312 slight fix in audio code 2025-01-16 20:04:23 +01:00
Logan McNaughton e4174c7d4b cleanup some pak switching code 2025-01-16 19:49:20 +01:00
Logan McNaughton f3d8080a26 Rumble pak support (#154)
* Rumble pak support

* more

* more

* more

* more

* more

* more

* more

* more
2025-01-16 18:54:44 +01:00
Logan McNaughton b581bdcd40 Use GameController API in profile setup (#153)
* Use GameController API in profile setup

* more

* more

* more

* more

* more

* more

* more
2025-01-16 18:49:46 +01:00
Logan McNaughton 842c81988f Update README.md 2025-01-15 18:38:25 +01:00
Logan McNaughton 45d05cecf0 ignore HatState::Centered when configuring input (#151) 2025-01-15 07:33:31 +01:00
Logan McNaughton eae81c4623 Add integer scaling option (#150)
* Add integer scaling option

* more

* more

* more
2025-01-14 23:23:26 +01:00
Logan McNaughton b3f8688ba5 Bump to 0.1.19 (#149) 2025-01-14 21:53:20 +01:00
Logan McNaughton 0a10b2c0ba use SDL_AddEventWatch 2025-01-14 21:42:03 +01:00
Logan McNaughton be67cb264c Fix linux arm builds (#148) 2025-01-14 21:08:03 +01:00
Logan McNaughton c6c2187652 add fullscreen option (#146)
* add fullscreen option

* fix crash
2025-01-14 19:50:35 +01:00
Logan McNaughton b2c1d88ea4 cleanup translate_address func (#145) 2025-01-14 19:36:59 +01:00
Logan McNaughton 80176766a7 Fix arm linux builds (#144) 2025-01-14 15:34:59 +01:00
Logan McNaughton 7ac6f2adb1 Github actions fix (#142) 2025-01-14 14:15:01 +01:00
Logan McNaughton 643338e086 SDL code cleanup (#141)
* SDL code cleanup

* more
2025-01-14 13:59:47 +01:00
Logan McNaughton d9a08f112d cleanup rsp code (#140)
* cleanup rsp code

* more

* fix
2025-01-14 09:43:04 +01:00
Logan McNaughton 344f41cf47 Add minimum rust version (#138) 2025-01-13 22:56:33 +01:00
Logan McNaughton 9978e28cc3 Bump to 0.1.18 (#137) 2025-01-13 22:30:34 +01:00
62 changed files with 9880 additions and 2154 deletions
+23 -51
View File
@@ -11,10 +11,15 @@ env:
jobs:
build-linux:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
target: [x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu]
include:
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
with:
@@ -23,53 +28,18 @@ jobs:
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Install SDL2
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
cat <<EOF > deb822sources
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble
Components: main restricted universe
Architectures: amd64
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main restricted universe
Architectures: amd64
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble-updates
Components: main restricted universe
Architectures: amd64
Types: deb
URIs: http://azure.ports.ubuntu.com/ubuntu-ports/
Suites: noble
Components: main restricted multiverse universe
Architectures: arm64
Types: deb
URIs: http://azure.ports.ubuntu.com/ubuntu-ports/
Suites: noble-updates
Components: main restricted multiverse universe
Architectures: arm64
EOF
sudo mv deb822sources /etc/apt/sources.list.d/ubuntu.sources
sudo dpkg --add-architecture arm64
sudo apt update
sudo apt -y install libsdl2-dev:arm64 g++-aarch64-linux-gnu
- name: Install SDL2
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
sudo apt update
sudo apt -y install libsdl2-dev
- name: Set aarch64 linker
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: ${{ matrix.target }}-1.0
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
@@ -80,8 +50,9 @@ jobs:
path: target/${{ matrix.target }}/release/gopher64
build-windows:
runs-on: windows-latest
runs-on: windows-2025
strategy:
fail-fast: false
matrix:
target: [x86_64-pc-windows-msvc]
steps:
@@ -102,8 +73,9 @@ jobs:
path: target/${{ matrix.target }}/release/gopher64.exe
build-macos:
runs-on: macos-latest
runs-on: macos-14
strategy:
fail-fast: false
matrix:
target: [x86_64-apple-darwin, aarch64-apple-darwin]
steps:
+12
View File
@@ -19,6 +19,18 @@ jobs:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: lint-1.0
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
-4
View File
@@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
Generated
+5156
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -1,7 +1,8 @@
[package]
name = "gopher64"
version = "0.1.17"
version = "1.0.6"
edition = "2021"
rust-version = "1.82"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,20 +14,23 @@ sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.30"
rfd = "0.15"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "wgpu"] }
sha2 = "0.10"
rusttype = "0.9"
futures = "0.3"
sdl2-sys = "0.37"
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
clap = { version = "4.4", features = ["derive"] }
[dependencies.sdl2]
version = "0.37"
default-features = false
features = ["static-link","bundled"]
mimalloc = "0.1"
regex = "1.11"
tungstenite = "0.26"
socket2 = "0.5"
fatfs = "0.3"
postcard = { version = "1.1", features = ["use-std"] }
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.12", default-features = false, features = ["http2","rustls-tls","charset","json"] }
[build-dependencies]
winres = "0.1"
@@ -34,5 +38,4 @@ bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
lto = true
codegen-units = 1
+25 -12
View File
@@ -1,4 +1,5 @@
# gopher64
(gopher64 also has a GUI that can be launched by just running the executable)
```
Usage: gopher64 [OPTIONS] [GAME]
@@ -11,6 +12,8 @@ Options:
-c, --configure-input-profile <PROFILE_NAME>
Create a new input profile (keyboard/gamepad mappings).
-u, --use-dinput
Use DirectInput when configuring a new input profile.
-b, --bind-input-profile <PROFILE_NAME>
Must also specify --port. Used to bind a previously created profile to a port
-l, --list-controllers
@@ -19,11 +22,33 @@ Options:
Must also specify --port. Used to assign a controller listed in --list-controllers to a port
-p, --port <PORT>
Valid values: 1-4. To be used alongside --bind-input-profile and --assign-controller
-z, --clear-input-bindings
Clear all input profile bindings and controller assignments
-h, --help
Print help
-V, --version
Print version
```
## discord
https://discord.gg/9RGXq8W8JQ
## controls
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
## netplay
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
## portable mode
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## goals
1. Performance. I want to be able to use this emulator on my laptop.
@@ -38,18 +63,6 @@ Options:
4. `cargo build --release`
5. `./target/release/gopher64 /path/to/rom.z64`
## controls
Keys are mapped according to mupen64plus defaults: https://mupen64plus.org/wiki/index.php/KeyboardSetup#2._Default_Key_Mappings_for_SDL-Input_Plugin. Xbox-style controllers also have a default mapping applied.
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
## portable mode
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## contributing
I am very open to contributions! Please reach out to me via a GitHub issue, or via discord (loganmc10) before doing substantial work on a PR.
+17 -3
View File
@@ -52,7 +52,10 @@ fn main() {
.include("parallel-rdp/parallel-rdp-standalone/vulkan")
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
.include("parallel-rdp/parallel-rdp-standalone/util")
.includes(std::env::var("DEP_SDL2_INCLUDE"));
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
.join("include"),
);
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
@@ -96,7 +99,8 @@ fn main() {
.allowlist_function("rdp_set_vi_register")
.allowlist_function("rdp_update_screen")
.allowlist_function("rdp_process_commands")
.allowlist_function("rdp_full_sync")
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
@@ -107,7 +111,7 @@ fn main() {
if arch == "aarch64" {
let simd_bindings = bindgen::Builder::default()
.header("src/compat/sse2neon.h")
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
.allowlist_function("_mm_setzero_si128")
.allowlist_function("_mm_set_epi8")
.allowlist_function("_mm_movemask_epi8")
@@ -156,4 +160,14 @@ fn main() {
simd_build.include(".");
simd_build.compile("simd");
}
let git_output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.unwrap();
let git_hash = String::from_utf8(git_output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
View File
Binary file not shown.
Binary file not shown.
+106 -137
View File
@@ -3,6 +3,7 @@
#include "rdp_device.hpp"
#include "interface.hpp"
#include "spirv.hpp"
#include <SDL3/SDL_vulkan.h>
using namespace Vulkan;
@@ -51,6 +52,7 @@ enum vi_registers
};
static bool fullscreen;
static bool integer_scaling;
static SDL_Window *window;
static RDP::CommandProcessor *processor;
static SDL_WSIPlatform *wsi_platform;
@@ -58,12 +60,9 @@ static WSI *wsi;
static uint32_t cmd_data[0x00040000 >> 2];
static int cmd_cur;
static int cmd_ptr;
static bool emu_running;
static uint64_t rdp_sync_signal;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static uint64_t last_frame_counter;
static uint64_t frame_counter;
static uint32_t region;
static const unsigned cmd_len_lut[64] = {
1,
@@ -132,28 +131,29 @@ static const unsigned cmd_len_lut[64] = {
1,
};
int sdl_event_filter(void *userdata, SDL_Event *event)
bool sdl_event_filter(void *userdata, SDL_Event *event)
{
if (event->type == SDL_WINDOWEVENT)
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
{
switch (event->window.event)
{
case SDL_WINDOWEVENT_CLOSE:
emu_running = false;
break;
case SDL_WINDOWEVENT_RESIZED:
wsi_platform->do_resize();
break;
default:
break;
}
callback.emu_running = false;
}
else if (fullscreen && event->type == SDL_KEYDOWN)
else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running)
{
switch (event->key.keysym.scancode)
wsi_platform->do_resize();
}
else if (event->type == SDL_EVENT_KEY_DOWN)
{
switch (event->key.scancode)
{
case SDL_SCANCODE_ESCAPE:
emu_running = false;
if (fullscreen)
callback.emu_running = false;
break;
case SDL_SCANCODE_F5:
callback.save_state = true;
break;
case SDL_SCANCODE_F7:
callback.load_state = true;
break;
default:
break;
@@ -163,28 +163,12 @@ int sdl_event_filter(void *userdata, SDL_Event *event)
return 0;
}
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _fullscreen, bool _upscale)
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale)
{
window = (SDL_Window *)_window;
SDL_SetEventFilter(sdl_event_filter, nullptr);
gfx_info = _gfx_info;
fullscreen = _fullscreen;
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);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader(nullptr))
if (processor)
{
rdp_close();
}
if (!wsi->init_simple(1, handles))
{
rdp_close();
delete processor;
}
RDP::CommandProcessorFlags flags = 0;
if (_upscale)
@@ -193,6 +177,38 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _fullscreen, bool _upscale
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
}
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
}
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
{
window = (SDL_Window *)_window;
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
if (!result)
{
printf("Could not add event watch.\n");
return;
}
gfx_info = _gfx_info;
fullscreen = _fullscreen;
integer_scaling = _integer_scaling;
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);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
{
rdp_close();
}
if (!wsi->init_simple(1, handles))
{
rdp_close();
}
rdp_new_processor(gfx_info, _upscale);
if (!processor->device_is_supported())
{
@@ -203,9 +219,7 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _fullscreen, bool _upscale
}
wsi->begin_frame();
emu_running = true;
last_frame_counter = 0;
frame_counter = 0;
callback.emu_running = true;
}
void rdp_close()
@@ -231,34 +245,42 @@ void rdp_close()
static void calculate_viewport(float *x, float *y, float *width, float *height)
{
bool window_widescreen = false;
int32_t display_width = (window_widescreen ? 854 : 640);
int32_t display_height = 480;
const int32_t display_width = gfx_info.PAL ? 384 : 320;
const int32_t display_height = gfx_info.PAL ? 288 : 240;
int w, h;
SDL_GetWindowSize(window, &w, &h);
*width = w;
*height = h;
*x = 0;
*y = 0;
int32_t hw = display_height * *width;
int32_t wh = display_width * *height;
if (integer_scaling)
{
// Integer scaling path
int scale_x = w / display_width;
int scale_y = h / display_height;
int scale = (scale_x < scale_y) ? scale_x : scale_y;
if (scale < 1)
scale = 1;
// add letterboxes or pillarboxes if the window has a different aspect ratio
// than the current display mode
if (hw > wh)
{
int32_t w_max = wh / display_height;
*x += (*width - w_max) / 2;
*width = w_max;
// Calculate scaled dimensions
int scaled_width = display_width * scale;
int scaled_height = display_height * scale;
*width = scaled_width;
*height = scaled_height;
}
else if (hw < wh)
else
{
int32_t h_max = hw / display_width;
*y += (*height - h_max) / 2;
*height = h_max;
// Regular scaling path - maintain aspect ratio
float scale_x = w / (float)display_width;
float scale_y = h / (float)display_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
*width = display_width * scale;
*height = display_height * scale;
}
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
}
static void render_frame(Vulkan::Device &device)
@@ -317,53 +339,20 @@ void rdp_set_vi_register(uint32_t reg, uint32_t value)
processor->set_vi_register(RDP::VIRegister(reg), value);
}
bool rdp_update_screen()
void rdp_update_screen()
{
auto &device = wsi->get_device();
render_frame(device);
wsi->end_frame();
wsi->begin_frame();
frame_counter++;
return emu_running;
}
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale, uint32_t width)
CALL_BACK rdp_check_callback()
{
if (xscale == 0)
return 320;
uint32_t start = ((hstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t end = (hstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
uint32_t scale = (xscale & 0xFFF);
if (delta == 0)
{
delta = width;
}
return (delta * scale) / 0x400;
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
{
if (yscale == 0)
return 240;
uint32_t start = ((vstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t end = (vstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
uint32_t scale = (yscale & 0xFFF);
return (delta * scale) / 0x800;
CALL_BACK return_value = callback;
callback.save_state = false;
callback.load_state = false;
return return_value;
}
uint64_t rdp_process_commands()
@@ -380,16 +369,14 @@ uint64_t rdp_process_commands()
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
return interrupt_timer;
*gfx_info.DPC_STATUS_REG |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
uint32_t offset = DP_CURRENT;
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
{
do
{
offset &= 0xFF8;
cmd_data[2 * cmd_ptr + 0] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
cmd_data[2 * cmd_ptr + 1] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
offset += sizeof(uint64_t);
cmd_ptr++;
} while (--length > 0);
@@ -416,6 +403,7 @@ uint64_t rdp_process_commands()
while (cmd_cur - cmd_ptr < 0)
{
uint32_t w1 = cmd_data[2 * cmd_cur];
uint32_t w2 = cmd_data[2 * cmd_cur + 1];
uint32_t command = (w1 >> 24) & 63;
int cmd_length = cmd_len_lut[command];
@@ -428,31 +416,21 @@ uint64_t rdp_process_commands()
if (command >= 8)
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
if (RDP::Op(command) == RDP::Op::SetScissor)
{
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t upper_left_y = (w1 & 0xFFF) >> 2;
uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2;
uint32_t lower_right_y = (w2 & 0xFFF) >> 2;
region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
}
if (RDP::Op(command) == RDP::Op::SyncFull)
{
if (frame_counter != last_frame_counter) // Only sync once per frame
{
rdp_sync_signal = processor->signal_timeline();
last_frame_counter = frame_counter;
}
else
{
rdp_sync_signal = 0;
}
processor->wait_for_timeline(processor->signal_timeline());
uint32_t width = viCalculateHorizonalWidth(*gfx_info.VI_H_START_REG, *gfx_info.VI_X_SCALE_REG, *gfx_info.VI_WIDTH_REG);
if (width == 0)
{
width = 320;
}
uint32_t height = viCalculateVerticalHeight(*gfx_info.VI_V_START_REG, *gfx_info.VI_Y_SCALE_REG);
if (height == 0)
{
height = 240;
}
interrupt_timer = width * height * 4;
*gfx_info.DPC_STATUS_REG &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
interrupt_timer = region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
}
cmd_cur += cmd_length;
@@ -461,15 +439,6 @@ uint64_t rdp_process_commands()
cmd_ptr = 0;
cmd_cur = 0;
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
*gfx_info.DPC_STATUS_REG |= DP_STATUS_CBUF_READY;
return interrupt_timer;
}
void rdp_full_sync()
{
if (rdp_sync_signal)
{
processor->wait_for_timeline(rdp_sync_signal);
}
}
+12 -8
View File
@@ -16,19 +16,23 @@ extern "C"
uint32_t *DPC_START_REG;
uint32_t *DPC_END_REG;
uint32_t *DPC_STATUS_REG;
uint32_t *VI_H_START_REG;
uint32_t *VI_V_START_REG;
uint32_t *VI_X_SCALE_REG;
uint32_t *VI_Y_SCALE_REG;
uint32_t *VI_WIDTH_REG;
bool PAL;
} GFX_INFO;
void rdp_init(void *_window, GFX_INFO _gfx_info, bool fullscreen, bool _upscale);
typedef struct
{
bool emu_running;
bool save_state;
bool load_state;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
void rdp_close();
void rdp_set_vi_register(uint32_t reg, uint32_t value);
bool rdp_update_screen();
void rdp_update_screen();
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_full_sync();
void rdp_new_processor(GFX_INFO _gfx_info, bool _upscale);
#ifdef __cplusplus
}
+13 -9
View File
@@ -1,11 +1,11 @@
#include "wsi_platform.hpp"
#include <SDL_vulkan.h>
#include <SDL3/SDL_vulkan.h>
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
{
VkSurfaceKHR surface = nullptr;
SDL_bool result = SDL_Vulkan_CreateSurface(window, instance, &surface);
if (result != SDL_TRUE)
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
if (result != true)
{
printf("Error creating surface\n");
}
@@ -14,18 +14,23 @@ VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevi
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
{
SDL_Vulkan_DestroySurface(instance, surface, NULL);
}
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
{
unsigned int extensionCount = 0;
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
std::vector<const char *> extensionNames(extensionCount);
SDL_bool result = SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames.data());
if (result != SDL_TRUE)
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (extensions == NULL)
{
printf("Error creating surface\n");
printf("Error getting instance extensions\n");
}
std::vector<const char *> extensionNames;
for (int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}
return extensionNames;
}
@@ -56,7 +61,6 @@ void SDL_WSIPlatform::poll_input()
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
{
SDL_PumpEvents();
}
void SDL_WSIPlatform::set_window(SDL_Window *_window)
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "wsi.hpp"
#include <SDL.h>
#include <SDL3/SDL.h>
class SDL_WSIPlatform : public Vulkan::WSIPlatform
{
+1 -1
View File
@@ -1,4 +1,4 @@
#include "src/compat/sse2neon.h"
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
// Static wrappers
+9 -9
View File
@@ -1,46 +1,46 @@
unsafe extern "C" {
#[link_name = "_mm_srli_epi16__extern"]
pub fn _mm_srli_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> __m128i;
fn _mm_srli_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> __m128i;
#[link_name = "_mm_insert_epi8__extern"]
pub fn _mm_insert_epi8(
fn _mm_insert_epi8(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi8__extern"]
pub fn _mm_extract_epi8(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
fn _mm_extract_epi8(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi16__extern"]
pub fn _mm_insert_epi16(
fn _mm_insert_epi16(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi16__extern"]
pub fn _mm_extract_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
fn _mm_extract_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi32__extern"]
pub fn _mm_insert_epi32(
fn _mm_insert_epi32(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi32__extern"]
pub fn _mm_extract_epi32(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
fn _mm_extract_epi32(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi64__extern"]
pub fn _mm_insert_epi64(
fn _mm_insert_epi64(
__a: __m128i,
__i: ::std::os::raw::c_longlong,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi64__extern"]
pub fn _mm_extract_epi64(
fn _mm_extract_epi64(
__a: __m128i,
__imm8: ::std::os::raw::c_int,
) -> ::std::os::raw::c_longlong;
+81 -55
View File
@@ -10,15 +10,12 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use crate::ui;
use std::collections::HashMap;
use std::fs;
use std::io::Read;
use crate::{netplay, ui};
use std::{collections::HashMap, fs, io::Read};
pub mod ai;
pub mod cache;
pub mod cart;
pub mod cart_rom;
pub mod controller;
pub mod cop0;
pub mod cop1;
@@ -30,7 +27,6 @@ pub mod exceptions;
pub mod fpu_instructions;
pub mod is_viewer;
pub mod memory;
pub mod mempak;
pub mod mi;
pub mod pi;
pub mod pif;
@@ -42,48 +38,41 @@ pub mod rsp_interface;
pub mod rsp_su_instructions;
pub mod rsp_vu_instructions;
pub mod si;
pub mod sram;
pub mod tlb;
pub mod unmapped;
pub mod vi;
pub mod vru;
pub fn run_game(
file_path: &std::path::Path,
data_dir: std::path::PathBuf,
device: &mut Device,
fullscreen: bool,
) {
let rom_contents = get_rom_contents(file_path);
cart_rom::init(device, rom_contents); // cart needs to come before rdram
pub fn run_game(rom_contents: Vec<u8>, device: &mut Device) {
cart::rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
rdram::init(device);
ui::audio::init(&mut device.ui, 33600);
ui::video::init(device, fullscreen);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::init(device);
ui::input::init(&mut device.ui);
mi::init(device);
pif::init(device);
if device.ui.config.input.emulate_vru {
vru::init(device);
controller::vru::init(device);
}
memory::init(device);
cache::init(device);
rsp_interface::init(device);
rdp::init(device);
vi::init(device);
cpu::init(device);
ui::storage::init(&mut device.ui, data_dir);
ui::storage::load_saves(&mut device.ui);
cart_rom::load_rom_save(device);
ui::storage::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
cpu::run(device);
ui::video::close();
ui::storage::write_saves(&device.ui);
ui::input::close(&mut device.ui);
ui::video::close(&device.ui);
ui::audio::close(&mut device.ui);
ui::storage::write_saves(&device.ui, &device.netplay);
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
@@ -108,11 +97,12 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
}
return data;
} else {
panic!("unknown rom format")
let data: Vec<u8> = vec![];
data
}
}
fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
@@ -162,28 +152,38 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
swap_rom(contents)
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Device {
#[serde(skip)]
pub netplay: Option<netplay::Netplay>,
#[serde(skip, default = "ui::Ui::default")]
pub ui: ui::Ui,
byte_swap: usize,
cpu: cpu::Cpu,
pif: pif::Pif,
cart: cart_rom::Cart,
memory: memory::Memory,
pub save_state: bool,
pub load_state: bool,
pub cpu: cpu::Cpu,
pub pif: pif::Pif,
pub cart: cart::Cart,
pub memory: memory::Memory,
pub rsp: rsp_interface::Rsp,
pub rdp: rdp::Rdp,
pub rdram: rdram::Rdram,
mi: mi::Mi,
pi: pi::Pi,
pub mi: mi::Mi,
pub pi: pi::Pi,
pub vi: vi::Vi,
ai: ai::Ai,
si: si::Si,
ri: ri::Ri,
flashram: sram::Flashram,
pub vru: vru::Vru,
pub ai: ai::Ai,
pub si: si::Si,
pub ri: ri::Ri,
pub vru: controller::vru::Vru,
pub vru_window: controller::vru::VruWindow,
}
pub fn zero_m128i() -> __m128i {
unsafe { _mm_setzero_si128() }
}
impl Device {
pub fn new(config_dir: std::path::PathBuf) -> Device {
pub fn new() -> Device {
let mut byte_swap: usize = 0;
let test: [u8; 4] = [1, 2, 3, 4];
// if the host computer is little endian, that means the RDRAM will be stored as little endian
@@ -192,13 +192,18 @@ impl Device {
byte_swap = 3;
}
Device {
ui: ui::Ui::new(config_dir),
netplay: None,
ui: ui::Ui::new(),
byte_swap,
save_state: false,
load_state: false,
cpu: cpu::Cpu {
cop0: cop0::Cop0 {
regs: [0; cop0::COP0_REGS_COUNT as usize],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
tlb_lut_w: vec![
@@ -273,10 +278,9 @@ impl Device {
events: [events::Event {
enabled: false,
count: u64::MAX,
handler: events::dummy_event,
}; events::EventType::Count as usize],
}; events::EVENT_TYPE_COUNT],
next_event_count: u64::MAX,
next_event: 0,
next_event: events::EVENT_TYPE_NONE,
},
pif: pif::Pif {
rom: [0; 1984],
@@ -288,17 +292,32 @@ impl Device {
rx_buf: None,
process: None,
pak_handler: None,
change_pak: controller::PakType::None,
}; 5],
},
cart: cart_rom::Cart {
cart: cart::Cart {
rom: Vec::new(),
is_viewer_buffer: [0; 0xFFFF],
pal: false,
latch: 0,
cic_seed: 0,
cic_type: cart_rom::CicType::CicNus6102,
cic_type: cart::CicType::CicNus6102,
rdram_size_offset: 0,
rtc: cart::AfRtc { control: 0x0200 },
sc64: cart::sc64::Sc64 {
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
regs_locked: true,
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
sector: 0,
buffer: [0; 8192],
},
flashram: cart::sram::Flashram {
status: 0,
erase_page: 0,
page_buf: [0xff; 128],
silicon_id: [cart::sram::FLASHRAM_TYPE_ID, cart::sram::MX29L1100_ID],
mode: cart::sram::FlashramMode::ReadArray,
},
},
memory: memory::Memory {
fast_read: [unmapped::read_mem; 0x2000],
@@ -322,6 +341,7 @@ impl Device {
rdram: rdram::Rdram {
mem: vec![],
size: 0x800000,
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
},
rsp: rsp_interface::Rsp {
cpu: rsp_cpu::Cpu {
@@ -368,6 +388,8 @@ impl Device {
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
mem: [0; 0x2000],
last_status_value: 0,
run_after_dma: false,
fifo: [rsp_interface::RspDma {
dir: rsp_interface::DmaDir::None,
length: 0,
@@ -379,6 +401,7 @@ impl Device {
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
wait_frozen: false,
last_status_value: 0,
},
mi: mi::Mi {
regs: [0; mi::MI_REGS_COUNT as usize],
@@ -389,6 +412,7 @@ impl Device {
ai: ai::Ai {
regs: [0; ai::AI_REGS_COUNT as usize],
last_read: 0,
freq: 33600,
delayed_carry: false,
fifo: [ai::AiDma {
address: 0,
@@ -402,6 +426,7 @@ impl Device {
},
ri: ri::Ri {
regs: [0; ri::RI_REGS_COUNT as usize],
ram_init: false,
},
vi: vi::Vi {
regs: [0; vi::VI_REGS_COUNT as usize],
@@ -411,15 +436,19 @@ impl Device {
count_per_scanline: 0,
limiter: 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,
limit_freq_check: std::time::Instant::now(),
},
flashram: sram::Flashram {
status: 0,
erase_page: 0,
page_buf: [0xff; 128],
silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID],
mode: sram::FlashramMode::ReadArray,
vru_window: controller::vru::VruWindow {
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
vru: vru::Vru {
vru: controller::vru::Vru {
status: 0,
voice_state: 0,
load_offset: 0,
@@ -428,9 +457,6 @@ impl Device {
words: Vec::new(),
talking: false,
word_mappings: HashMap::new(),
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
}
}
+19 -16
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
const AI_DRAM_ADDR_REG: u32 = 0;
const AI_LEN_REG: u32 = 1;
@@ -12,26 +11,28 @@ pub const AI_REGS_COUNT: u32 = 6;
const AI_STATUS_BUSY: u32 = 0x40000000;
const AI_STATUS_FULL: u32 = 0x80000000;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ai {
pub regs: [u32; AI_REGS_COUNT as usize],
pub fifo: [AiDma; 2],
pub last_read: u64,
pub delayed_carry: bool,
pub freq: u64,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct AiDma {
pub address: u64,
pub length: u64,
pub duration: u64,
}
pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
if device.ai.fifo[0].duration == 0 {
return 0;
}
let next_ai_event = device::events::get_event(device, device::events::EventType::AI);
let next_ai_event = device::events::get_event(device, device::events::EVENT_TYPE_AI);
if next_ai_event.is_none() {
return 0;
}
@@ -43,7 +44,7 @@ pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
dma_length & !7
}
pub fn get_dma_duration(device: &device::Device) -> u64 {
fn get_dma_duration(device: &device::Device) -> u64 {
let samples_per_sec = device.vi.clock / (1 + device.ai.regs[AI_DACRATE_REG as usize]) as u64;
let bytes_per_sample = 4; /* XXX: assume 16bit stereo - should depends on bitrate instead */
let length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
@@ -51,7 +52,7 @@ pub fn get_dma_duration(device: &device::Device) -> u64 {
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
}
pub fn do_dma(device: &mut device::Device) {
fn do_dma(device: &mut device::Device) {
device.ai.last_read = device.ai.fifo[0].length;
if device.ai.delayed_carry {
@@ -64,14 +65,13 @@ pub fn do_dma(device: &mut device::Device) {
/* schedule end of dma event */
device::events::create_event(
device,
device::events::EventType::AI,
device::events::EVENT_TYPE_AI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
dma_event,
);
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
}
pub fn fifo_push(device: &mut device::Device) {
fn fifo_push(device: &mut device::Device) {
let duration = get_dma_duration(device);
if (device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_BUSY) != 0 {
@@ -91,7 +91,7 @@ pub fn fifo_push(device: &mut device::Device) {
}
}
pub fn fifo_pop(device: &mut device::Device) {
fn fifo_pop(device: &mut device::Device) {
if device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_FULL != 0 {
device.ai.fifo[0].address = device.ai.fifo[1].address;
device.ai.fifo[0].length = device.ai.fifo[1].length;
@@ -111,7 +111,7 @@ pub fn read_regs(
_access_size: device::memory::AccessSize,
) -> u32 {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
let return_value = match reg as u32 {
AI_LEN_REG => {
let value = get_remaining_dma_length(device);
if value < device.ai.last_read {
@@ -128,7 +128,9 @@ pub fn read_regs(
value as u32
}
_ => device.ai.regs[reg as usize],
}
};
device::cop0::add_cycles(device, 20);
return_value
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -143,8 +145,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
AI_STATUS_REG => device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_AI),
AI_DACRATE_REG => {
if device.ai.regs[reg as usize] != value & mask {
let frequency = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::init(&mut device.ui, frequency)
device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq)
}
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
}
+5 -4
View File
@@ -1,15 +1,16 @@
use crate::device;
use crate::{device, savestates};
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct ICache {
pub valid: bool,
pub tag: u32,
pub index: u16,
pub words: [u32; 8],
#[serde(skip, default = "savestates::default_instructions")]
pub instruction: [fn(&mut device::Device, u32); 8],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct DCache {
pub valid: bool,
pub dirty: bool,
@@ -205,7 +206,7 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
);
}
pub fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
device::cop0::add_cycles(device, 7);
device.memory.dcache[line_index].valid = true;
+40 -10
View File
@@ -1,8 +1,11 @@
use chrono::Datelike;
use chrono::Timelike;
use crate::device;
use crate::ui;
use crate::{device, ui};
pub mod rom;
pub mod sc64;
pub mod sram;
const JCMD_STATUS: u8 = 0x00;
const JCMD_EEPROM_READ: u8 = 0x04;
@@ -16,17 +19,44 @@ const JDT_AF_RTC: u16 = 0x1000; /* RTC */
const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
const EEPROM_BLOCK_SIZE: usize = 8;
const EEPROM_MAX_SIZE: usize = 0x800;
pub const EEPROM_MAX_SIZE: usize = 0x800;
#[derive(serde::Serialize, serde::Deserialize)]
pub enum CicType {
CicNus6101,
CicNus6102,
CicNus6103,
CicNus6105,
CicNus6106,
CicNus5167,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct AfRtc {
pub control: u16,
}
pub fn byte2bcd(mut n: u32) -> u8 {
fn byte2bcd(mut n: u32) -> u8 {
n %= 100;
(((n / 10) << 4) | (n % 10)) as u8
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
#[serde(with = "serde_big_array::BigArray")]
pub is_viewer_buffer: [u8; 0xFFFF],
pub pal: bool,
pub latch: u32,
pub cic_type: CicType,
pub cic_seed: u8,
pub rdram_size_offset: usize,
pub rtc: AfRtc,
pub sc64: sc64::Sc64,
pub flashram: sram::Flashram,
}
pub fn process(device: &mut device::Device, channel: usize) {
let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()];
@@ -95,13 +125,13 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
pub fn format_eeprom(device: &mut device::Device) {
fn format_eeprom(device: &mut device::Device) {
if device.ui.saves.eeprom.0.len() < EEPROM_MAX_SIZE {
device.ui.saves.eeprom.0.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
format_eeprom(device);
@@ -110,7 +140,7 @@ pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usiz
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
}
pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
format_eeprom(device);
@@ -123,7 +153,7 @@ pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usi
device.ui.saves.eeprom.1 = true
}
pub fn time2data(device: &mut device::Device, offset: usize) {
fn time2data(device: &mut device::Device, offset: usize) {
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
device.pif.ram[offset] = byte2bcd(now.second());
@@ -136,7 +166,7 @@ pub fn time2data(device: &mut device::Device, offset: usize) {
device.pif.ram[offset + 7] = byte2bcd((now.year() as u32 - 1900) / 100);
}
pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.pif.ram[offset] = device.cart.rtc.control as u8;
@@ -155,7 +185,7 @@ pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usiz
}
}
}
pub fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.cart.rtc.control =
@@ -1,25 +1,22 @@
use crate::device;
use sha2::{Digest, Sha256};
pub enum CicType {
CicNus6101,
CicNus6102,
CicNus6103,
CicNus6105,
CicNus6106,
CicNus5167,
}
const CART_MASK: usize = 0xFFFFFFF;
pub struct Cart {
pub rom: Vec<u8>,
pub is_viewer_buffer: [u8; 0xFFFF],
pub pal: bool,
pub latch: u32,
pub cic_type: CicType,
pub cic_seed: u8,
pub rdram_size_offset: usize,
pub rtc: device::cart::AfRtc,
fn read_cart_word(device: &mut device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = device.cart.rom[address..address + 4].try_into().unwrap();
for i in 0..4 {
if device
.ui
.saves
.romsave
.0
.contains_key(&(address as u32 + i))
{
data[i as usize] = device.ui.saves.romsave.0[&(address as u32 + i)];
}
}
u32::from_be_bytes(data)
}
pub fn read_mem_fast(
@@ -28,11 +25,7 @@ pub fn read_mem_fast(
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & CART_MASK;
u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
read_cart_word(device, masked_address)
}
pub fn read_mem(
@@ -48,15 +41,26 @@ pub fn read_mem(
device.cart.latch
} else {
let masked_address = address as usize & CART_MASK;
u32::from_be_bytes(
device.cart.rom[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
read_cart_word(device, masked_address)
}
}
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
let masked_address = address as usize & CART_MASK;
let mut data = read_cart_word(device, masked_address);
device::memory::masked_write_32(&mut data, value, mask);
for (i, item) in data.to_be_bytes().iter().enumerate() {
device
.ui
.saves
.romsave
.0
.insert((masked_address + i) as u32, *item);
}
device.ui.saves.romsave.1 = true;
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
@@ -64,9 +68,8 @@ pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u
let cycles = device::pi::calculate_cycles(device, 1, 4);
device::events::create_event(
device,
device::events::EventType::PI,
device::events::EVENT_TYPE_PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
device::pi::dma_event,
);
}
@@ -76,24 +79,22 @@ pub fn dma_read(
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
if device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] != 0 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= CART_MASK as u32;
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.cart.rom[(cart_addr + i) as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
for i in 0..length {
if cart_addr + i < device.cart.rom.len() as u32 {
device.ui.saves.romsave.0.insert(
cart_addr + i,
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap],
);
device.ui.saves.romsave.0.insert(
(cart_addr + i).to_string(),
serde_json::to_value(device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap])
.unwrap(),
);
device.ui.saves.romsave.1 = true;
}
}
}
device.ui.saves.romsave.1 = true;
device::pi::calculate_cycles(device, 1, length)
}
@@ -109,7 +110,11 @@ pub fn dma_write(
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && j < device.cart.rom.len() as u32 {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
if device.ui.saves.romsave.0.contains_key(&j) {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.romsave.0[&j];
} else {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.rom[j as usize];
}
i += 1;
j += 1;
}
@@ -134,17 +139,7 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
}
}
pub fn load_rom_save(device: &mut device::Device) {
if device.ui.saves.romsave.0.is_empty() {
return;
}
for (key, value) in device.ui.saves.romsave.0.iter() {
device.cart.rom[key.parse::<usize>().unwrap()] =
serde_json::from_value(value.clone()).unwrap()
}
}
pub fn set_system_region(device: &mut device::Device, country: u8) {
fn set_system_region(device: &mut device::Device, country: u8) {
let pal_codes: [u8; 8] = [b'D', b'F', b'I', b'P', b'S', b'U', b'X', b'Y'];
for i in pal_codes {
if country == i {
@@ -153,41 +148,42 @@ pub fn set_system_region(device: &mut device::Device, country: u8) {
}
}
pub fn set_cic(device: &mut device::Device) {
fn set_cic(device: &mut device::Device) {
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
match hash.as_str() {
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
device.cart.cic_type = CicType::CicNus6101;
device.cart.cic_type = device::cart::CicType::CicNus6101;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
}
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
device.cart.cic_type = CicType::CicNus6102;
device.cart.cic_type = device::cart::CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
}
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
device.cart.cic_type = CicType::CicNus6103;
device.cart.cic_type = device::cart::CicType::CicNus6103;
device.cart.cic_seed = 0x78;
device.cart.rdram_size_offset = 0x318;
}
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
device.cart.cic_type = CicType::CicNus6105;
device.cart.cic_type = device::cart::CicType::CicNus6105;
device.cart.cic_seed = 0x91;
device.cart.rdram_size_offset = 0x3F0;
}
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
device.cart.cic_type = CicType::CicNus6106;
device.cart.cic_type = device::cart::CicType::CicNus6106;
device.cart.cic_seed = 0x85;
device.cart.rdram_size_offset = 0x318;
}
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
device.cart.cic_type = CicType::CicNus5167;
device.cart.cic_type = device::cart::CicType::CicNus5167;
device.cart.cic_seed = 0xdd;
device.cart.rdram_size_offset = 0x318;
device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
}
_ => {
device.cart.cic_type = CicType::CicNus6102;
device.cart.cic_type = device::cart::CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
println!("unknown IPL3 {}", hash)
@@ -195,7 +191,7 @@ pub fn set_cic(device: &mut device::Device) {
}
}
fn calculate_hash(data: &[u8]) -> String {
pub fn calculate_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:X}", hasher.finalize())
+250
View File
@@ -0,0 +1,250 @@
use crate::device;
pub const SDCARD_SIZE: usize = 0x4000000;
const SC64_SCR_REG: u32 = 0;
const SC64_DATA0_REG: u32 = 1;
const SC64_DATA1_REG: u32 = 2;
const SC64_IDENTIFIER_REG: u32 = 3;
const SC64_KEY_REG: u32 = 4;
//const SC64_IRQ_REG: u32 = 5;
//const SC64_AUX_REG: u32 = 6;
pub const SC64_REGS_COUNT: u32 = 7;
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
pub const SC64_CFG_COUNT: u32 = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Sc64 {
#[serde(with = "serde_big_array::BigArray")]
pub buffer: [u8; 8192],
pub regs: [u32; SC64_REGS_COUNT as usize],
pub regs_locked: bool,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
}
fn format_sdcard(device: &mut device::Device) {
if device.ui.saves.sdcard.0.is_empty() {
device.ui.saves.sdcard.0.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.saves.sdcard.0);
fatfs::format_volume(buf, fatfs::FormatVolumeOptions::new()).unwrap();
}
}
pub fn read_regs(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
if device.cart.sc64.regs_locked {
return 0;
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.cart.sc64.regs[reg as usize],
SC64_IDENTIFIER_REG => 0x53437632,
_ => panic!("unknown read reg {} address {:#x}", reg, address),
}
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_KEY_REG => {
device::memory::masked_write_32(&mut device.cart.sc64.regs[reg as usize], value, mask);
if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F {
device.cart.sc64.regs_locked = false;
} else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
device.cart.sc64.regs_locked = true;
}
}
SC64_DATA0_REG | SC64_DATA1_REG => {
if !device.cart.sc64.regs_locked {
device::memory::masked_write_32(
&mut device.cart.sc64.regs[reg as usize],
value,
mask,
);
}
}
SC64_SCR_REG => {
if !device.cart.sc64.regs_locked {
match char::from_u32(value & mask).unwrap() {
'c' => {
// get config
device.cart.sc64.regs[SC64_DATA1_REG as usize] = device.cart.sc64.cfg
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize]
}
'C' => {
// set config
std::mem::swap(
&mut device.cart.sc64.cfg
[device.cart.sc64.regs[SC64_DATA0_REG as usize] as usize],
&mut device.cart.sc64.regs[SC64_DATA1_REG as usize],
);
}
'i' => {
// sd card operation
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
0 => { //Init SD card
}
1 => { //Deinit SD card
}
_ => {
panic!(
"unknown sc64 sd card operation: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
)
}
}
}
'I' => {
// set sd sector
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG as usize];
}
's' => {
format_sdcard(device);
// read sd card
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let offset = (device.cart.sc64.sector * 512) as usize;
let length =
(device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
let mut i = 0;
while i < length {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = u32::from_be_bytes(
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.try_into()
.unwrap(),
);
device::memory::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
} else {
panic!("sd card read out of bounds")
}
i += 4;
}
}
'S' => {
format_sdcard(device);
// write sd card
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let offset = (device.cart.sc64.sector * 512) as usize;
let length =
(device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
let mut i = 0;
while i < length {
if offset + i < device.ui.saves.sdcard.0.len() {
let data = device::memory::data_read(
device,
address + i as u64,
device::memory::AccessSize::Word,
false,
)
.to_be_bytes();
device.ui.saves.sdcard.0[(offset + i)..(offset + i + 4)]
.copy_from_slice(&data);
} else {
panic!("sd card write out of bounds")
}
i += 4;
}
device.ui.saves.sdcard.1 = true;
}
'U' => {} // USB_WRITE_STATUS, ignored
'M' => {} // USB_WRITE, ignored
_ => {
panic!(
"unknown sc64 command: {}",
char::from_u32(value & mask).unwrap()
)
}
}
}
}
_ => panic!(
"unknown write reg {} address {:#x} value {}",
reg,
address,
char::from_u32(value & mask).unwrap()
),
}
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & SC64_BUFFER_MASK;
u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
device.cart.sc64.buffer[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.cart.sc64.buffer[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
}
pub fn dma_read(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.cart.sc64.buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn dma_write(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SC64_BUFFER_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = device.cart.sc64.buffer[j as usize];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
+55 -57
View File
@@ -1,15 +1,14 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
const SRAM_MASK: usize = 0xFFFF;
const SRAM_SIZE: usize = 0x8000;
const FLASHRAM_SIZE: usize = 0x20000;
pub const SRAM_SIZE: usize = 0x8000;
pub const FLASHRAM_SIZE: usize = 0x20000;
pub const FLASHRAM_TYPE_ID: u32 = 0x11118001;
pub const MX29L1100_ID: u32 = 0x00c2001e;
const MX29L0000_ID: u32 = 0x00c20000;
const MX29L0001_ID: u32 = 0x00c20001;
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FlashramMode {
ReadArray,
ReadSiliconId,
@@ -19,27 +18,29 @@ pub enum FlashramMode {
PageProgram,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Flashram {
pub status: u32,
pub mode: FlashramMode,
pub erase_page: u16,
#[serde(with = "serde_big_array::BigArray")]
pub page_buf: [u8; 128],
pub silicon_id: [u32; 2],
}
pub fn format_sram(device: &mut device::Device) {
fn format_sram(device: &mut device::Device) {
if device.ui.saves.sram.0.len() < SRAM_SIZE {
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
}
}
pub fn format_flash(device: &mut device::Device) {
fn format_flash(device: &mut device::Device) {
if device.ui.saves.flash.0.len() < FLASHRAM_SIZE {
device.ui.saves.flash.0.resize(FLASHRAM_SIZE, 0xFF)
}
}
pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
let masked_address = address as usize & SRAM_MASK;
format_sram(device);
@@ -51,11 +52,12 @@ pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
)
}
pub fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* read Status register */
device.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.flashram.mode == FlashramMode::ReadArray {
device.cart.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.cart.flashram.mode == FlashramMode::ReadArray
{
/* flashram MMIO read are not supported except for the "dummy" read @0x0000 done before DMA.
* returns a "dummy" value. */
return 0;
@@ -80,7 +82,7 @@ pub fn read_mem(
}
}
pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SRAM_MASK;
format_sram(device);
@@ -96,10 +98,10 @@ pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mas
device.ui.saves.sram.1 = true
}
pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* clear/set Status register */
device.flashram.status = (value & mask) & 0xff;
device.cart.flashram.status = (value & mask) & 0xff;
} else if (address & 0x1ffff) == 0x10000 {
/* set command */
format_flash(device);
@@ -122,18 +124,12 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::events::create_event(
device,
device::events::EventType::PI,
device::events::EVENT_TYPE_PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
device::pi::dma_event,
);
}
pub fn dma_read_sram(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) {
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SRAM_MASK as u32;
let mut i = dram_addr;
@@ -150,16 +146,16 @@ pub fn dma_read_sram(
device.ui.saves.sram.1 = true
}
pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
format_flash(device);
if (cart_addr & 0x1ffff) == 0x00000
&& length == 128
&& device.flashram.mode == FlashramMode::PageProgram
&& device.cart.flashram.mode == FlashramMode::PageProgram
{
/* load page buf using DMA */
for i in 0..length {
device.flashram.page_buf[i as usize] =
device.cart.flashram.page_buf[i as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
}
} else {
@@ -178,7 +174,7 @@ pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, len
device::pi::calculate_cycles(device, 2, length)
}
pub fn dma_write_sram(
fn dma_write_sram(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
@@ -198,7 +194,7 @@ pub fn dma_write_sram(
}
}
pub fn dma_write_flash(
fn dma_write_flash(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
@@ -208,20 +204,22 @@ pub fn dma_write_flash(
if (cart_addr & 0x1ffff) == 0x00000
&& length == 8
&& device.flashram.mode == FlashramMode::ReadSiliconId
&& device.cart.flashram.mode == FlashramMode::ReadSiliconId
{
/* read Silicon ID using DMA */
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[0].to_ne_bytes());
.copy_from_slice(&device.cart.flashram.silicon_id[0].to_ne_bytes());
dram_addr += 4;
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000 && device.flashram.mode == FlashramMode::ReadArray {
.copy_from_slice(&device.cart.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000
&& device.cart.flashram.mode == FlashramMode::ReadArray
{
format_flash(device);
/* adjust flashram address before starting DMA. */
if device.flashram.silicon_id[1] == MX29L1100_ID
|| device.flashram.silicon_id[1] == MX29L0000_ID
|| device.flashram.silicon_id[1] == MX29L0001_ID
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
|| device.cart.flashram.silicon_id[1] == MX29L0001_ID
{
/* "old" flash needs special address adjusting */
cart_addr = (cart_addr & 0xffff) * 2;
@@ -251,31 +249,31 @@ pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, le
device::pi::calculate_cycles(device, 2, length)
}
pub fn flashram_command(device: &mut device::Device, command: u32) {
fn flashram_command(device: &mut device::Device, command: u32) {
match command & 0xff000000 {
0x3c000000 => {
/* set chip erase mode */
device.flashram.mode = FlashramMode::ChipErase;
device.cart.flashram.mode = FlashramMode::ChipErase;
}
0x4b000000 => {
/* set sector erase mode, set erase sector */
device.flashram.mode = FlashramMode::SectorErase;
device.flashram.erase_page = command as u16;
device.cart.flashram.mode = FlashramMode::SectorErase;
device.cart.flashram.erase_page = command as u16;
}
0x78000000 => {
/* set erase busy flag */
device.flashram.status |= 0x02;
device.cart.flashram.status |= 0x02;
/* do chip/sector erase */
if device.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128;
if device.cart.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128;
for i in 0..128 * 128 {
device.ui.saves.flash.0[offset + i] = 0xFF;
}
device.ui.saves.flash.1 = true
} else if device.flashram.mode == FlashramMode::ChipErase {
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.saves.flash.0[i] = 0xFF;
}
@@ -285,47 +283,47 @@ pub fn flashram_command(device: &mut device::Device, command: u32) {
}
/* clear erase busy flag, set erase success flag, transition to status mode */
device.flashram.status &= !0x02;
device.flashram.status |= 0x08;
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.status &= !0x02;
device.cart.flashram.status |= 0x08;
device.cart.flashram.mode = FlashramMode::Status;
}
0xa5000000 => {
/* set program busy flag */
device.flashram.status |= 0x01;
device.cart.flashram.status |= 0x01;
/* program selected page */
let offset: usize = (command & 0xffff) as usize * 128;
for i in 0..128 {
device.ui.saves.flash.0[offset + i] = device.flashram.page_buf[i];
device.ui.saves.flash.0[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.saves.flash.1 = true;
/* clear program busy flag, set program success flag, transition to status mode */
device.flashram.status &= !0x01;
device.flashram.status |= 0x04;
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.status &= !0x01;
device.cart.flashram.status |= 0x04;
device.cart.flashram.mode = FlashramMode::Status;
}
0xb4000000 => {
/* set page program mode */
device.flashram.mode = FlashramMode::PageProgram;
device.cart.flashram.mode = FlashramMode::PageProgram;
}
0xd2000000 => {
/* set status mode */
device.flashram.mode = FlashramMode::Status;
device.cart.flashram.mode = FlashramMode::Status;
}
0xe1000000 => {
/* set silicon_id mode */
device.flashram.mode = FlashramMode::ReadSiliconId;
device.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
device.cart.flashram.mode = FlashramMode::ReadSiliconId;
device.cart.flashram.status |= 0x01; /* Needed for Pokemon Puzzle League */
}
0xf0000000 => {
/* set read mode */
device.flashram.mode = FlashramMode::ReadArray;
device.cart.flashram.mode = FlashramMode::ReadArray;
}
_ => {
+77 -9
View File
@@ -1,5 +1,8 @@
use crate::device;
use crate::ui;
use crate::{device, netplay, savestates, ui};
pub mod mempak;
pub mod rumble;
pub mod vru;
pub const JCMD_STATUS: u8 = 0x00;
pub const JCMD_CONTROLLER_READ: u8 = 0x01;
@@ -12,14 +15,24 @@ const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates
//const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */
const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */
const PAK_CHUNK_SIZE: usize = 0x20;
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 0;
const CONT_STATUS_PAK_PRESENT: u8 = 1;
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2;
const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum PakType {
None = 0,
MemPak = 1,
RumblePak = 2,
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PakHandler {
#[serde(skip, default = "savestates::default_pak_handler")]
pub read: fn(&mut device::Device, usize, u16, usize, usize),
#[serde(skip, default = "savestates::default_pak_handler")]
pub write: fn(&mut device::Device, usize, u16, usize, usize),
pub pak_type: PakType,
}
pub fn process(device: &mut device::Device, channel: usize) {
@@ -40,8 +53,32 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = ui::input::get(&mut device.ui, channel);
device.pif.ram[offset..offset + 4].copy_from_slice(&input.to_ne_bytes());
let input = if device.netplay.is_none() {
ui::input::get(&mut device.ui, channel)
} else {
if device.netplay.as_ref().unwrap().player_number as usize == channel {
let local_input = ui::input::get(&mut device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
}
netplay::get_input(device, channel)
};
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
if input.1 {
// pak change button pressed
if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() {
device.pif.channels[channel].change_pak =
device.pif.channels[channel].pak_handler.unwrap().pak_type;
device.pif.channels[channel].pak_handler = None;
device::events::create_event(
device,
device::events::EVENT_TYPE_PAK,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ (device.cpu.clock_rate), // 1 second
)
}
}
}
JCMD_PAK_READ => pak_read_block(
device,
@@ -61,7 +98,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
pub fn pak_read_block(
fn pak_read_block(
device: &mut device::Device,
addr_acrc: usize,
data: usize,
@@ -80,7 +117,7 @@ pub fn pak_read_block(
}
}
pub fn pak_write_block(
fn pak_write_block(
device: &mut device::Device,
addr_acrc: usize,
data: usize,
@@ -99,7 +136,7 @@ pub fn pak_write_block(
}
}
pub fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
let mut i = 0;
let mut crc = 0;
@@ -118,3 +155,34 @@ pub fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8
}
crc
}
pub fn pak_switch_event(device: &mut device::Device) {
for (i, channel) in device.pif.channels.iter_mut().enumerate() {
if channel.change_pak != PakType::None {
if channel.change_pak == PakType::RumblePak {
if device.netplay.is_none() {
device::ui::input::set_rumble(&mut device.ui, i, 0);
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
device::ui::input::set_rumble(&mut device.ui, 0, 0);
}
let handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
channel.pak_handler = Some(handler);
ui::audio::play_pak_switch(&mut device.ui, PakType::MemPak);
} else if channel.change_pak == PakType::MemPak {
let handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
channel.pak_handler = Some(handler);
ui::audio::play_pak_switch(&mut device.ui, PakType::RumblePak);
}
channel.change_pak = PakType::None;
}
}
}
@@ -1,9 +1,9 @@
use crate::device;
const MEMPAK_SIZE: usize = 0x8000;
pub const MEMPAK_SIZE: usize = 0x8000;
const MPK_PAGE_SIZE: usize = 256;
pub fn format_mempak(device: &mut device::Device) {
fn format_mempak(device: &mut device::Device) {
if device.ui.saves.mempak.0.len() < MEMPAK_SIZE * 4 {
device.ui.saves.mempak.0.resize(MEMPAK_SIZE * 4, 0);
+24
View File
@@ -0,0 +1,24 @@
use crate::device;
pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: usize, size: usize) {
let value: u8 = if (0x8000..0x9000).contains(&address) {
0x80
} else {
0x00
};
for i in 0..size {
device.pif.ram[data + i] = value;
}
}
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if address == 0xc000 {
let rumble = device.pif.ram[data + size - 1];
if device.netplay.is_none() {
device::ui::input::set_rumble(&mut device.ui, channel, rumble);
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
device::ui::input::set_rumble(&mut device.ui, 0, rumble);
}
}
}
+118 -371
View File
@@ -17,17 +17,26 @@ const VOICE_STATUS_BUSY: u8 = 0x05;
const JDT_VRU: u16 = 0x0100; /* VRU */
const CONT_FLAVOR: u16 = JDT_VRU;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Vru {
pub status: u8,
pub voice_state: u8,
pub load_offset: u8,
pub voice_init: u8,
#[serde(with = "serde_big_array::BigArray")]
pub word_buffer: [u16; 40],
pub words: Vec<String>,
pub talking: bool,
pub word_mappings: HashMap<String, String>,
pub window_notifier: Option<std::sync::mpsc::Sender<Vec<String>>>,
pub word_receiver: Option<std::sync::mpsc::Receiver<String>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Vec<String>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
#[serde(skip)]
pub gui_ctx: Option<egui::Context>,
}
@@ -36,7 +45,7 @@ pub fn init(device: &mut device::Device) {
create_word_mappings(device);
}
pub fn reset_vru(device: &mut device::Device) {
fn reset_vru(device: &mut device::Device) {
device.vru.status = 0x00;
if device.cart.rom[0x3E] == /* Japan */ 0x4A || device.cart.rom[0x3E] == /* Demo */ 0x00 {
device.vru.voice_state = VOICE_STATUS_READY;
@@ -160,14 +169,13 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.vru.voice_init = 2;
device::events::create_event(
device,
device::events::EventType::Vru,
device::events::EVENT_TYPE_VRU,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ (device.cpu.clock_rate * 2), // 2 seconds
vru_talking_event,
)
} else if device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] == 0xEF {
device.vru.talking = false;
device::events::remove_event(device, device::events::EventType::Vru);
device::events::remove_event(device, device::events::EVENT_TYPE_VRU);
} else if device.pif.ram[device.pif.channels[channel].tx_buf.unwrap() + 3] == 0x2 {
device.vru.voice_init = 0;
device.vru.words.clear();
@@ -178,17 +186,17 @@ pub fn process(device: &mut device::Device, channel: usize) {
let offset = device.pif.channels[channel].tx_buf.unwrap() + 1;
if u16::from_ne_bytes(device.pif.ram[offset..offset + 2].try_into().unwrap()) == 0 {
device.vru.talking = false;
device::events::remove_event(device, device::events::EventType::Vru);
device::events::remove_event(device, device::events::EVENT_TYPE_VRU);
}
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
}
JCMD_VRU_READ => {
let index = if device.vru.window_notifier.is_some() {
let index = if device.vru_window.window_notifier.is_some() {
ui::vru::prompt_for_match(
&device.vru.words,
device.vru.window_notifier.as_ref().unwrap(),
device.vru.word_receiver.as_ref().unwrap(),
device.vru.gui_ctx.as_ref().unwrap(),
device.vru_window.window_notifier.as_ref().unwrap(),
device.vru_window.word_receiver.as_mut().unwrap(),
device.vru_window.gui_ctx.as_ref().unwrap(),
)
} else {
0x7FFF
@@ -254,7 +262,7 @@ pub fn vru_talking_event(device: &mut device::Device) {
device.vru.talking = false
}
pub fn create_word_mappings(device: &mut device::Device) {
fn create_word_mappings(device: &mut device::Device) {
device.vru.word_mappings = HashMap::from([
(
String::from("03A50024000303CF00A80003036000EA"),
@@ -288,10 +296,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003035D001200F900EA"),
String::from("pikachu"),
),
(
String::from("040801740024"),
String::from("hey!"),
),
(String::from("040801740024"), String::from("hey!")),
(
String::from("03CF00C603360405000F0234"),
String::from("come-here"),
@@ -324,10 +329,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("040B00C002EB0213"),
String::from("hello"),
),
(String::from("040B00C002EB0213"), String::from("hello")),
(
String::from("043B0213000303AB00A5034803FC006903FC00A503270024"),
String::from("open-sesame"),
@@ -356,14 +358,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC012602F10024"),
String::from("im-sorry"),
),
(
String::from("03FC023A0024"),
String::from("sorry"),
),
(
String::from("03FC012602F10024"),
String::from("sorry"),
),
(String::from("03FC023A0024"), String::from("sorry")),
(String::from("03FC012602F10024"), String::from("sorry")),
(
String::from("042F01B00003036000ED03DB030C00EA"),
String::from("i-choose-you"),
@@ -392,10 +388,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303D8000303CF00C9"),
String::from("pikka"),
),
(
String::from("03A50024000303CF00A8"),
String::from("pika"),
),
(String::from("03A50024000303CF00A8"), String::from("pika")),
(
String::from("03A50024000303CF00A8000303A50024000303CF00A8"),
String::from("pikapika"),
@@ -440,10 +433,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03F600C60348000603870234"),
String::from("thunder"),
),
(
String::from("03F600C6033F0234"),
String::from("thunder"),
),
(String::from("03F600C6033F0234"), String::from("thunder")),
(
String::from("0432009F02E20066000303D8000303C602F10045000303D802F40087000303C6"),
String::from("electric-rat"),
@@ -489,7 +479,9 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("boing-boing"),
),
(
String::from("043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366"),
String::from(
"043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366",
),
String::from("on-to-the-next-stage!"),
),
(
@@ -516,10 +508,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02EE0006038700A5033C0087000303AB009F02EE"),
String::from("golden-apple"),
),
(
String::from("03A5002400030360"),
String::from("peach"),
),
(String::from("03A5002400030360"), String::from("peach")),
(
String::from("03FC000303C602FD012C00060372029D0024"),
String::from("strawberry"),
@@ -556,10 +545,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("032101A702EE00060390040B02340006037E"),
String::from("wild-herb"),
),
(
String::from("03D502D60348"),
String::from("corn"),
),
(String::from("03D502D60348"), String::from("corn")),
(
String::from("03B1014D000303B4000303D502D60348"),
String::from("popcorn"),
@@ -592,18 +578,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03CC008102F700A8000303C6"),
String::from("carrot"),
),
(
String::from("03CC02A300A8000303C6"),
String::from("carrot"),
),
(String::from("03CC02A300A8000303C6"), String::from("carrot")),
(
String::from("043200C60339030900A50348"),
String::from("onion"),
),
(
String::from("043200A5033F00A50348"),
String::from("onion"),
),
(String::from("043200A5033F00A50348"), String::from("onion")),
(
String::from("043501080339030900A50348"),
String::from("onion"),
@@ -621,7 +601,9 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -732,10 +714,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303B4000303CC018F000303D8"),
String::from("cupcake"),
),
(
String::from("03840174002703DB0024"),
String::from("daisy"),
),
(String::from("03840174002703DB0024"), String::from("daisy")),
(
String::from("03A50045000303C000E70339030900A8"),
String::from("petunia"),
@@ -768,10 +747,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("02FD021603DE0006037500C900060390"),
String::from("rose-bud"),
@@ -852,10 +828,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03D5014D000303AB0234000303D501CE0348"),
String::from("copper-coin"),
),
(
String::from("02FA00EA0006036F0024"),
String::from("ruby"),
),
(String::from("02FA00EA0006036F0024"), String::from("ruby")),
(
String::from("03FC008A03F001AA0300"),
String::from("sapphire"),
@@ -904,10 +877,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("037502340006039003F00069036C0234"),
String::from("bird-feather"),
),
(
String::from("037B020A02EE000303C6"),
String::from("bolt"),
),
(String::from("037B020A02EE000303C6"), String::from("bolt")),
(
String::from("032A0087000603A203390045000303C6"),
String::from("magnet"),
@@ -920,10 +890,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0333025500060375009F02EE"),
String::from("marble"),
),
(
String::from("03D802F4018F000303C6"),
String::from("crate"),
),
(String::from("03D802F4018F000303C6"), String::from("crate")),
(
String::from("03D50255000603900006037B02D9000603900006037B014D000303D803FC"),
String::from("cardboard-box"),
@@ -936,10 +903,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("043B021603EA022804050297"),
String::from("over-here"),
),
(
String::from("040B009F02EB0213"),
String::from("hello"),
),
(String::from("040B009F02EB0213"), String::from("hello")),
(
String::from("039C010B00060390034501950024000303C6"),
String::from("good-night"),
@@ -972,18 +936,9 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("036C0087000303C603FC03F001AD0348"),
String::from("thats-fine"),
),
(
String::from("043B0213000303CC01740024"),
String::from("ok"),
),
(
String::from("04020276"),
String::from("sure"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("043B0213000303CC01740024"), String::from("ok")),
(String::from("04020276"), String::from("sure")),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("02E2006903FC0006038A00ED036C0087000303C6"),
String::from("lets-do-that"),
@@ -1012,18 +967,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303BD00A8000303C301F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03450213"),
String::from("no"),
),
(String::from("03450213"), String::from("no")),
(
String::from("0318018F0006038700A80006039F0213"),
String::from("way-to-go"),
),
(
String::from("03F002EB01DD0234"),
String::from("flower"),
),
(String::from("03F002EB01DD0234"), String::from("flower")),
(
String::from("038400840348000303C603FC"),
String::from("dance"),
@@ -1104,14 +1053,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03F90009030C00E102E2018F000303BD0234"),
String::from("see-you-later"),
),
(
String::from("03F90009030C00EA"),
String::from("see-you"),
),
(
String::from("037B01B0"),
String::from("bye"),
),
(String::from("03F90009030C00EA"), String::from("see-you")),
(String::from("037B01B0"), String::from("bye")),
(
String::from("0411031B00A8000303BD0219030900A80006038A00CF0042035A"),
String::from("what-are-you-doing?"),
@@ -1164,10 +1107,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("04290045000303C603FC021603EA0237036C02B8"),
String::from("its-over-there"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
@@ -1188,10 +1128,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
@@ -1200,14 +1137,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC02BE0024"),
String::from("im-sorry"),
),
(
String::from("03FC02BE0024"),
String::from("sorry"),
),
(
String::from("03CC0084033F00A50348"),
String::from("cannon"),
),
(String::from("03FC02BE0024"), String::from("sorry")),
(String::from("03CC0084033F00A50348"), String::from("cannon")),
(
String::from("03810045000603930045000303C301F2000303C6"),
String::from("dig-it-out"),
@@ -1216,18 +1147,9 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A2036C017A0234"),
String::from("dig-there"),
),
(
String::from("03810045000603A2"),
String::from("dig"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(
String::from("033F00C9000303C6"),
String::from("nut"),
),
(String::from("03810045000603A2"), String::from("dig")),
(String::from("03AE010202EE"), String::from("pull")),
(String::from("033F00C9000303C6"), String::from("nut")),
(
String::from("03780108033C0084033F00C9"),
String::from("banana"),
@@ -1244,10 +1166,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0372029D002400060390000303C602F4006903E40234"),
String::from("buried-treasure"),
),
(
String::from("0408005D02EB0213"),
String::from("hello"),
),
(String::from("0408005D02EB0213"), String::from("hello")),
(
String::from("042F01A702E500CC03E7030C00EA"),
String::from("i-love-you"),
@@ -1312,26 +1231,17 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("041101290348000303BD0234"),
String::from("haunter"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("036C0087000303C603DE02FD0129035A"),
String::from("thats-wrong"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B700450006038D01F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03CF00A503270297"),
String::from("c-mere"),
),
(String::from("03CF00A503270297"), String::from("c-mere")),
(
String::from("03CF00A5033604050297"),
String::from("come-here"),
@@ -1348,26 +1258,17 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0321012C0003036001F2000303C6"),
String::from("watch-out"),
),
(
String::from("036C02B8"),
String::from("there"),
),
(String::from("036C02B8"), String::from("there")),
(
String::from("03FC03150042035A034501F2"),
String::from("swing-now"),
),
(
String::from("03FC03150042035A"),
String::from("swing"),
),
(String::from("03FC03150042035A"), String::from("swing")),
(
String::from("039F020D02FD01B0000303C6"),
String::from("go-right"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("03F00258036C022E02FD01B0000303C6"),
String::from("farther-right"),
@@ -1376,10 +1277,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02E2006903F0000303C6"),
String::from("go-left"),
),
(
String::from("02E2006903F0000303C6"),
String::from("left"),
),
(String::from("02E2006903F0000303C6"), String::from("left")),
(
String::from("03F00258036C022B02E2006903F0000303C6"),
String::from("farther-left"),
@@ -1472,10 +1370,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02DF004803F900420348000303BD00A503270024"),
String::from("listen-to-me"),
),
(
String::from("03F002FA00EA000303C6"),
String::from("fruit"),
),
(String::from("03F002FA00EA000303C6"), String::from("fruit")),
(
String::from("03450213000303C6000303A8008700060390"),
String::from("notepad"),
@@ -1500,14 +1395,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0411014D000303C603FC03150024000303B4000303BA018F0006038D0213"),
String::from("hot-sweet-potato"),
),
(
String::from("03D200E102EE"),
String::from("cool"),
),
(
String::from("03F001AD0348"),
String::from("fine"),
),
(String::from("03D200E102EE"), String::from("cool")),
(String::from("03F001AD0348"), String::from("fine")),
(
String::from("0309023703FC0213000303C9030C00EA000303C6"),
String::from("youre-so-cute"),
@@ -1572,14 +1461,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C6"),
String::from("magnet"),
),
(
String::from("03D501CE0348"),
String::from("coin"),
),
(
String::from("036600D5009F02EE"),
String::from("jewel"),
),
(String::from("03D501CE0348"), String::from("coin")),
(String::from("036600D5009F02EE"), String::from("jewel")),
(
String::from("0333025500060375009F02EE"),
String::from("marble"),
@@ -1596,10 +1479,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03D50315004803DE000303C301AD0336"),
String::from("quiz-time"),
),
(
String::from("03B7002703E70024"),
String::from("tv"),
),
(String::from("03B7002703E70024"), String::from("tv")),
(
String::from("042F01B0000303D503150045000303C6"),
String::from("i-quit"),
@@ -1616,10 +1496,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03150024000303CF00A50348000303B402E2018F"),
String::from("we-can-play"),
),
(
String::from("0372008700060390"),
String::from("bad"),
),
(String::from("0372008700060390"), String::from("bad")),
(
String::from("0345014D000303C90045000303C3012F03F0"),
String::from("knock-it-off"),
@@ -1636,10 +1513,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD03360345014D000303C6000303B402E201740042035A"),
String::from("im-not-playing"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
@@ -1660,10 +1534,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03D802DF00210348036C0087000303BD00C9000303B4"),
String::from("clean-that-up"),
),
(
String::from("03B70048040200EA"),
String::from("tissue"),
),
(String::from("03B70048040200EA"), String::from("tissue")),
(
String::from("037500C002EE0006037500AB03FC02D9"),
String::from("bulbasaur"),
@@ -1768,10 +1639,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303A502880213"),
String::from("spearow"),
),
(
String::from("03ED02880213"),
String::from("fearow"),
),
(String::from("03ED02880213"), String::from("fearow")),
(
String::from("04260024000303CF00A5034803DE"),
String::from("ekans"),
@@ -1788,10 +1656,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003036000EA"),
String::from("pikachu"),
),
(
String::from("02FD01B00003036000EA"),
String::from("raichu"),
),
(String::from("02FD01B00003036000EA"), String::from("raichu")),
(
String::from("03FC00840348040202FA00EA"),
String::from("sandshrew"),
@@ -1884,22 +1749,13 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
),
(
String::from("03B1024000AB03FC"),
String::from("paras"),
),
(
String::from("03A802A300AB03FC"),
String::from("paras"),
),
(String::from("03B1024000AB03FC"), String::from("paras")),
(String::from("03A802A300AB03FC"), String::from("paras")),
(
String::from("03B1024000AB03FC0066000303D8000303C6"),
String::from("parasect"),
@@ -1920,10 +1776,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A202E500A8000303C6"),
String::from("diglett"),
),
(
String::from("0327001501F503F6"),
String::from("meowth"),
),
(String::from("0327001501F503F6"), String::from("meowth")),
(
String::from("03AB023703E400A50348"),
String::from("persian"),
@@ -2024,14 +1877,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03A202F4008A03EA02E50234"),
String::from("graveler"),
),
(
String::from("039F020A02E500A50336"),
String::from("golem"),
),
(
String::from("039F014402EE0336"),
String::from("golem"),
),
(String::from("039F020A02E500A50336"), String::from("golem")),
(String::from("039F014402EE0336"), String::from("golem")),
(
String::from("03B1021003390024000303BD00A8"),
String::from("ponyta"),
@@ -2060,18 +1907,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C3014A0348"),
String::from("magneton"),
),
(
String::from("0008"),
String::from("farfetch"),
),
(String::from("0008"), String::from("farfetch")),
(
String::from("038D02130006039002F100150213"),
String::from("dodrio"),
),
(
String::from("03F9001B02EE"),
String::from("seel"),
),
(String::from("03F9001B02EE"), String::from("seel")),
(
String::from("038A00EA0006039F014A035A"),
String::from("dewgong"),
@@ -2080,14 +1921,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("038A00EA0006039F014A0348"),
String::from("dewgong"),
),
(
String::from("03A202FD01AD032D0234"),
String::from("grimer"),
),
(
String::from("032D00C9000303D8"),
String::from("muk"),
),
(String::from("03A202FD01AD032D0234"), String::from("grimer")),
(String::from("032D00C9000303D8"), String::from("muk")),
(
String::from("0402005D02EE000603870234"),
String::from("shellder"),
@@ -2100,10 +1935,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0396008A03FC000303C602DF00"),
String::from("gastly"),
),
(
String::from("0396008A03FC02DF0024"),
String::from("gastly"),
),
(String::from("0396008A03FC02DF0024"), String::from("gastly")),
(
String::from("041101290348000303BD0234"),
String::from("haunter"),
@@ -2232,10 +2064,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03CC018C035A0006039900AB03FC000303D5014A0348"),
String::from("kangaskhan"),
),
(
String::from("041102DC03F90024"),
String::from("horsea"),
),
(String::from("041102DC03F90024"), String::from("horsea")),
(
String::from("03F900240006039002F700A8"),
String::from("seadra"),
@@ -2260,10 +2089,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0327004803FC000303BD0231033301AD0336"),
String::from("mr.mime"),
),
(
String::from("03FC01B3036C0234"),
String::from("scyther"),
),
(String::from("03FC01B3036C0234"), String::from("scyther")),
(
String::from("03630042035A000303D803FC"),
String::from("jynx"),
@@ -2280,18 +2106,9 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A203330255"),
String::from("magmar"),
),
(
String::from("03A50042034803FC0234"),
String::from("pinsir"),
),
(
String::from("03C3020D02FD012F03FC"),
String::from("tauros"),
),
(
String::from("03C30246021603FC"),
String::from("tauros"),
),
(String::from("03A50042034803FC0234"), String::from("pinsir")),
(String::from("03C3020D02FD012F03FC"), String::from("tauros")),
(String::from("03C30246021603FC"), String::from("tauros")),
(
String::from("032A0087000603630045000303D50255000303B4"),
String::from("magikarp"),
@@ -2312,14 +2129,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02E20087000303B402F700AB03FC"),
String::from("lapras"),
),
(
String::from("03810045000303C30213"),
String::from("ditto"),
),
(
String::from("0426002703E70024"),
String::from("eevee"),
),
(String::from("03810045000303C30213"), String::from("ditto")),
(String::from("0426002703E70024"), String::from("eevee")),
(
String::from("03EA018F000303B102BE0015014A0348"),
String::from("vaporeon"),
@@ -2416,10 +2227,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("0327030C00EA000303C000EA"),
String::from("mewtwo"),
),
(
String::from("0327030C00EA"),
String::from("mew"),
),
(String::from("0327030C00EA"), String::from("mew")),
(
String::from("03C3014D000303AB00AB03EA000603630045000603A202DF0024000303AB00AB03F0"),
String::from("top-of-jigglypuff"),
@@ -2456,10 +2264,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("038700C9000603A2000303C602F100150213"),
String::from("dugtrio"),
),
(
String::from("03A50045000603630024"),
String::from("pidgey"),
),
(String::from("03A50045000603630024"), String::from("pidgey")),
(
String::from("038D02130006038A00DB0213"),
String::from("doduo"),
@@ -2528,30 +2333,18 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("0372008700060390000303A50024000303CF00A80003036000EA"),
String::from("bad-pikachu"),
),
(
String::from("0327004803FC"),
String::from("miss"),
),
(String::from("0327004803FC"), String::from("miss")),
(
String::from("038D02100348000303C6000303D5012302DF0045000303C6"),
String::from("dont-call-it"),
),
(
String::from("041101F503FC"),
String::from("house"),
),
(
String::from("041102100336"),
String::from("home"),
),
(String::from("041101F503FC"), String::from("house")),
(String::from("041102100336"), String::from("home")),
(
String::from("03EA00A202F1004500060381000F00C60348"),
String::from("viridian"),
@@ -2560,10 +2353,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03EA00A202F1004500060381000F00C6034803F002C400AB03FC000303C6"),
String::from("viridian-forest"),
),
(
String::from("043B0213000303CF0234"),
String::from("ochre"),
),
(String::from("043B0213000303CF0234"), String::from("ochre")),
(
String::from("043B0213000303CF0225031E010B0006039003DE"),
String::from("ochre-woods"),
@@ -2604,10 +2394,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03D502130006037E03ED004803FF0042035A0411020A02EE"),
String::from("cobalt-fishing-hole"),
),
(
String::from("03D5021603FC000303C6"),
String::from("coast"),
),
(String::from("03D5021603FC000303C6"), String::from("coast")),
(
String::from("03D502130006037B012302EE000303C6000303D5021603FC000303C6"),
String::from("cobalt-coast"),
@@ -2620,16 +2407,15 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03BD0231033F00A8000303B4"),
String::from("turnip"),
),
(
String::from("03BD02310348000303B4"),
String::from("turnip"),
),
(String::from("03BD02310348000303B4"), String::from("turnip")),
(
String::from("03FC03150024000303C6000303AB00A8000303BA018F0006038D0213"),
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -2640,18 +2426,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("04080084034B00420348036C02B8"),
String::from("hang-in-there"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(String::from("03AE010202EE"), String::from("pull")),
(
String::from("02F1001B02DF0045000303B700420348"),
String::from("reel-it-in"),
),
(
String::from("034501F2"),
String::from("now"),
),
(String::from("034501F2"), String::from("now")),
(
String::from("03CC00870003035D0045000303C6"),
String::from("catch-it"),
@@ -2676,14 +2456,8 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02F1003C02DF002703F90045000303C6"),
String::from("release-it"),
),
(
String::from("02E500C6034800030360"),
String::from("lunch"),
),
(
String::from("03FC033C0087000303D8"),
String::from("snack"),
),
(String::from("02E500C6034800030360"), String::from("lunch")),
(String::from("03FC033C0087000303D8"), String::from("snack")),
(
String::from("02E2006903F90024000303C6"),
String::from("lets-eat"),
@@ -2700,10 +2474,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("042F01B0000603930045000303B70045000303C6"),
String::from("i-get-it"),
),
(
String::from("0411032101B0"),
String::from("why?"),
),
(String::from("0411032101B0"), String::from("why?")),
(
String::from("041101F2000303CF00C60336"),
String::from("how-come?"),
@@ -2728,10 +2499,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B70045000303C301F2000303C6"),
String::from("cut-it-out"),
@@ -2744,10 +2512,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("02E20066000303C603DE0006039F0213"),
String::from("lets-go"),
),
(
String::from("031B0225012F03F0"),
String::from("we-re-off"),
),
(String::from("031B0225012F03F0"), String::from("we-re-off")),
(
String::from("03BA008700060393030902190045000303C6"),
String::from("tag-youre-it"),
@@ -2804,18 +2569,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("039F0213000303BD00C6033302460213"),
String::from("go-tomorrow"),
),
(
String::from("02FD014D000303D8"),
String::from("rock"),
),
(String::from("02FD014D000303D8"), String::from("rock")),
(
String::from("03F9004803DE023703FC"),
String::from("scissors"),
),
(
String::from("03A8018F000303AB0234"),
String::from("paper"),
),
(String::from("03A8018F000303AB0234"), String::from("paper")),
(
String::from("0381002A000303B10255000303C6"),
String::from("depart"),
@@ -2872,10 +2631,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03450213000602E50042032A002A000303C6"),
String::from("no-limit"),
),
(
String::from("03B7005A0348"),
String::from("ten"),
),
(String::from("03B7005A0348"), String::from("ten")),
(
String::from("03E7002A03F6000303B700090348"),
String::from("fifteen"),
@@ -2888,18 +2644,12 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03C30318004B0339038A002403EA019E03EA"),
String::from("twenty-five"),
),
(
String::from("036C02AC03BA0027"),
String::from("thirty"),
),
(String::from("036C02AC03BA0027"), String::from("thirty")),
(
String::from("036C02AC03BA002703EA019E03EA"),
String::from("thirty-five"),
),
(
String::from("03EA02D6000603900048"),
String::from("forty"),
),
(String::from("03EA02D6000603900048"), String::from("forty")),
(
String::from("03EA02D600060390004803EA019E03EA"),
String::from("forty-five"),
@@ -2928,10 +2678,7 @@ pub fn create_word_mappings(device: &mut device::Device) {
String::from("03FC004B036C00630348000303B7000903EA019E03EA"),
String::from("seventy-five"),
),
(
String::from("042C018C03B70009"),
String::from("eighty"),
),
(String::from("042C018C03B70009"), String::from("eighty")),
(
String::from("042C018C03B7000903EA019E03EA"),
String::from("eighty-five"),
+67 -65
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
pub const COP0_INDEX_REG: u32 = 0;
const COP0_RANDOM_REG: u32 = 1;
@@ -55,14 +55,14 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_BD: u64 = 1 << 31;
pub const COP0_CAUSE_CE1: u64 = 1 << 28;
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
@@ -88,29 +88,34 @@ const COP0_XCONTEXT_REG_MASK: u64 =
const COP0_PARITYERR_REG_MASK: u64 = 0b00000000000000000000000011111111;
const COP0_TAGLO_REG_MASK: u64 = 0b00001111111111111111111111000000;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop0 {
pub reg_latch: u64,
pub regs: [u64; COP0_REGS_COUNT as usize],
pub reg_write_masks: [u64; COP0_REGS_COUNT as usize],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs2: [fn(&mut device::Device, u32); 32],
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
pub fn mfc0(device: &mut device::Device, opcode: u32) {
fn mfc0(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device::cpu_instructions::se32(
(get_control_registers(device, device::cpu_instructions::rd(opcode))) as u32 as i32,
)
}
pub fn dmfc0(device: &mut device::Device, opcode: u32) {
fn dmfc0(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
get_control_registers(device, device::cpu_instructions::rd(opcode))
}
pub fn mtc0(device: &mut device::Device, opcode: u32) {
fn mtc0(device: &mut device::Device, opcode: u32) {
device::cop0::set_control_registers(
device,
device::cpu_instructions::rd(opcode),
@@ -120,7 +125,7 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
)
}
pub fn dmtc0(device: &mut device::Device, opcode: u32) {
fn dmtc0(device: &mut device::Device, opcode: u32) {
device::cop0::set_control_registers(
device,
device::cpu_instructions::rd(opcode),
@@ -128,24 +133,24 @@ pub fn dmtc0(device: &mut device::Device, opcode: u32) {
)
}
pub fn tlbr(device: &mut device::Device, _opcode: u32) {
fn tlbr(device: &mut device::Device, _opcode: u32) {
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
}
pub fn tlbwi(device: &mut device::Device, _opcode: u32) {
fn tlbwi(device: &mut device::Device, _opcode: u32) {
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
}
pub fn tlbwr(device: &mut device::Device, _opcode: u32) {
fn tlbwr(device: &mut device::Device, _opcode: u32) {
let random = set_random_register(device);
device::tlb::write(device, random)
}
pub fn tlbp(device: &mut device::Device, _opcode: u32) {
fn tlbp(device: &mut device::Device, _opcode: u32) {
device::tlb::probe(device)
}
pub fn eret(device: &mut device::Device, _opcode: u32) {
fn eret(device: &mut device::Device, _opcode: u32) {
if device.cpu.cop0.regs[COP0_STATUS_REG as usize] & COP0_STATUS_ERL != 0 {
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG as usize];
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_ERL
@@ -158,7 +163,7 @@ pub fn eret(device: &mut device::Device, _opcode: u32) {
device::exceptions::check_pending_interrupts(device)
}
pub fn execute_cp0(device: &mut device::Device, opcode: u32) {
fn execute_cp0(device: &mut device::Device, opcode: u32) {
device.cpu.cop0.instrs2[(opcode & 0x3F) as usize](device, opcode)
}
@@ -174,7 +179,7 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
device::exceptions::reserved_exception(device, 0);
}
pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
match index {
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
COP0_RANDOM_REG => set_random_register(device),
@@ -183,7 +188,7 @@ pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
}
}
pub fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
device.cpu.cop0.reg_latch = data;
match index {
COP0_COUNT_REG => {
@@ -209,11 +214,11 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
device::events::create_event(
device,
device::events::EventType::Compare,
device::events::EVENT_TYPE_COMPARE,
device.cpu.cop0.regs[COP0_COUNT_REG as usize] + ((compare_event_diff as u64) << 1),
compare_event,
);
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
COP0_STATUS_REG => {
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
@@ -231,19 +236,16 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
}
pub fn compare_event(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = true;
device::events::create_event(
device,
device::events::EventType::Compare,
device::events::EVENT_TYPE_COMPARE,
device.cpu.next_event_count + (u32::MAX as u64),
compare_event,
);
device::exceptions::check_pending_interrupts(device);
}
pub fn set_random_register(device: &device::Device) -> u64 {
fn set_random_register(device: &device::Device) -> u64 {
if device.cpu.cop0.regs[COP0_WIRED_REG as usize] > 31 {
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize]) & 0x3F
} else {
@@ -257,42 +259,7 @@ pub fn add_cycles(device: &mut device::Device, cycles: u64) {
device.cpu.cop0.regs[COP0_COUNT_REG as usize] += cycles // COUNT_REG is shifted right 1 bit when read by MFC0
}
pub fn init(device: &mut device::Device) {
device.cpu.cop0.reg_write_masks = [
COP0_INDEX_REG_MASK,
0, // Random, read only
COP0_ENTRYLO_REG_MASK,
COP0_ENTRYLO_REG_MASK,
COP0_CONTEXT_REG_MASK,
COP0_PAGEMASK_REG_MASK,
COP0_WIRED_REG_MASK,
0, // 7
0, // BadVAddr, read only
u32::MAX as u64, // count
COP0_ENTRYHI_REG_MASK,
u32::MAX as u64, // compare
COP0_STATUS_REG_MASK,
COP0_CAUSE_REG_MASK,
u64::MAX, // EPC
0, // previd, read only
COP0_CONFIG_REG_MASK,
COP0_LLADDR_REG_MASK,
COP0_WATCHLO_REG_MASK,
COP0_WATCHHI_REG_MASK,
COP0_XCONTEXT_REG_MASK,
0, // 21
0, // 22
0, // 23
0, // 24
0, // 25
COP0_PARITYERR_REG_MASK,
0, // cache error
COP0_TAGLO_REG_MASK,
0, // taghi
u64::MAX, // ErrorPC
0, // 31
];
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop0.instrs = [
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
@@ -362,6 +329,46 @@ pub fn init(device: &mut device::Device) {
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
}
pub fn init(device: &mut device::Device) {
device.cpu.cop0.reg_write_masks = [
COP0_INDEX_REG_MASK,
0, // Random, read only
COP0_ENTRYLO_REG_MASK,
COP0_ENTRYLO_REG_MASK,
COP0_CONTEXT_REG_MASK,
COP0_PAGEMASK_REG_MASK,
COP0_WIRED_REG_MASK,
0, // 7
0, // BadVAddr, read only
u32::MAX as u64, // count
COP0_ENTRYHI_REG_MASK,
u32::MAX as u64, // compare
COP0_STATUS_REG_MASK,
COP0_CAUSE_REG_MASK,
u64::MAX, // EPC
0, // previd, read only
COP0_CONFIG_REG_MASK,
COP0_LLADDR_REG_MASK,
COP0_WATCHLO_REG_MASK,
COP0_WATCHHI_REG_MASK,
COP0_XCONTEXT_REG_MASK,
0, // 21
0, // 22
0, // 23
0, // 24
0, // 25
COP0_PARITYERR_REG_MASK,
0, // cache error
COP0_TAGLO_REG_MASK,
0, // taghi
u64::MAX, // ErrorPC
0, // 31
];
map_instructions(device);
// taken from VR4300 manual
device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 0b00000000000000000000000000011111;
device.cpu.cop0.regs[COP0_CONFIG_REG as usize] = 0b01110000000001101110010001100000;
@@ -372,10 +379,5 @@ pub fn init(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
device::events::create_event(
device,
device::events::EventType::Compare,
u32::MAX as u64,
compare_event,
)
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
}
+32 -21
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
//const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
//const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
@@ -24,6 +24,7 @@ const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop1 {
pub fcr0: u32,
pub fcr31: u32,
@@ -31,11 +32,17 @@ pub struct Cop1 {
//pub flush_mode: u32,
pub fgr32: [[u8; 4]; 32],
pub fgr64: [[u8; 8]; 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub b_instrs: [fn(&mut device::Device, u32); 4],
#[serde(skip, default = "savestates::default_instructions")]
pub s_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub d_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub w_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub l_instrs: [fn(&mut device::Device, u32); 64],
}
@@ -173,7 +180,7 @@ pub fn sdc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn mfc1(device: &mut device::Device, opcode: u32) {
fn mfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -185,7 +192,7 @@ pub fn mfc1(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32(u32::from_ne_bytes(value.to_ne_bytes()) as i32);
}
pub fn dmfc1(device: &mut device::Device, opcode: u32) {
fn dmfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -197,7 +204,7 @@ pub fn dmfc1(device: &mut device::Device, opcode: u32) {
u64::from_ne_bytes(value.to_ne_bytes());
}
pub fn cfc1(device: &mut device::Device, opcode: u32) {
fn cfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -209,7 +216,7 @@ pub fn cfc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dcfc1(device: &mut device::Device, opcode: u32) {
fn dcfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -221,7 +228,7 @@ pub fn dcfc1(device: &mut device::Device, opcode: u32) {
device::exceptions::floating_point_exception(device)
}
pub fn mtc1(device: &mut device::Device, opcode: u32) {
fn mtc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -239,7 +246,7 @@ pub fn mtc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dmtc1(device: &mut device::Device, opcode: u32) {
fn dmtc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -252,7 +259,7 @@ pub fn dmtc1(device: &mut device::Device, opcode: u32) {
set_fpr_double(device, device::fpu_instructions::fs(opcode) as usize, value)
}
pub fn ctc1(device: &mut device::Device, opcode: u32) {
fn ctc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -266,7 +273,7 @@ pub fn ctc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dctc1(device: &mut device::Device, opcode: u32) {
fn dctc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -278,7 +285,7 @@ pub fn dctc1(device: &mut device::Device, opcode: u32) {
device::exceptions::floating_point_exception(device)
}
pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -288,7 +295,7 @@ pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.b_instrs[((opcode >> 16) & 0x3) as usize](device, opcode)
}
pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -298,7 +305,7 @@ pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.s_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -308,7 +315,7 @@ pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.d_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -318,7 +325,7 @@ pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.l_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -328,7 +335,7 @@ pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.w_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn unusable(device: &mut device::Device, _opcode: u32) {
fn unusable(device: &mut device::Device, _opcode: u32) {
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE1)
}
@@ -342,7 +349,7 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE1);
}
pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
match index {
0 => device.cpu.cop1.fcr0,
31 => device.cpu.cop1.fcr31,
@@ -352,7 +359,7 @@ pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
}
}
pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
match index {
0 => { // read only
}
@@ -448,10 +455,7 @@ pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 {
}
}
pub fn init(device: &mut device::Device) {
set_fgr_registers(device, 0);
device.cpu.cop1.fcr0 = 0b101000000000;
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop1.b_instrs = [
device::fpu_instructions::bc1f, // 0
device::fpu_instructions::bc1t, // 1
@@ -763,6 +767,13 @@ pub fn init(device: &mut device::Device) {
]
}
pub fn init(device: &mut device::Device) {
set_fgr_registers(device, 0);
device.cpu.cop1.fcr0 = 0b101000000000;
map_instructions(device);
}
pub fn set_fgr_registers(device: &mut device::Device, status_reg: u64) {
// this method doesn't account for undocumented behaviour (accessing odd numbered registers in half mode)
if (status_reg & device::cop0::COP0_STATUS_FR) == 0 {
+15 -9
View File
@@ -1,11 +1,13 @@
use crate::device;
use crate::{device, savestates};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop2 {
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
pub reg_latch: u64,
}
pub fn mfc2(device: &mut device::Device, opcode: u32) {
fn mfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -15,7 +17,7 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
}
pub fn dmfc2(device: &mut device::Device, opcode: u32) {
fn dmfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -24,7 +26,7 @@ pub fn dmfc2(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device.cpu.cop2.reg_latch
}
pub fn cfc2(device: &mut device::Device, opcode: u32) {
fn cfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -34,7 +36,7 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
}
pub fn mtc2(device: &mut device::Device, opcode: u32) {
fn mtc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -43,7 +45,7 @@ pub fn mtc2(device: &mut device::Device, opcode: u32) {
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
pub fn dmtc2(device: &mut device::Device, opcode: u32) {
fn dmtc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -52,7 +54,7 @@ pub fn dmtc2(device: &mut device::Device, opcode: u32) {
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
pub fn ctc2(device: &mut device::Device, opcode: u32) {
fn ctc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -72,11 +74,11 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE2);
}
pub fn unusable(device: &mut device::Device, _opcode: u32) {
fn unusable(device: &mut device::Device, _opcode: u32) {
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2)
}
pub fn init(device: &mut device::Device) {
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop2.instrs = [
device::cop2::mfc2, // 0
device::cop2::dmfc2, // 1
@@ -112,3 +114,7 @@ pub fn init(device: &mut device::Device) {
device::cop2::reserved, // 31
]
}
pub fn init(device: &mut device::Device) {
map_instructions(device);
}
+17 -6
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::{device, savestates};
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum State {
Step,
Take,
@@ -11,10 +11,13 @@ pub enum State {
Exception,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: State,
pub pc: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cpu {
pub cop0: device::cop0::Cop0,
pub cop1: device::cop1::Cop1,
@@ -28,10 +31,13 @@ pub struct Cpu {
pub running: bool,
pub llbit: bool,
pub clock_rate: u64,
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub special_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
pub events: [device::events::Event; device::events::EventType::Count as usize],
pub events: [device::events::Event; device::events::EVENT_TYPE_COUNT],
pub next_event_count: u64,
pub next_event: usize,
}
@@ -47,9 +53,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
}
}
pub fn init(device: &mut device::Device) {
device.cpu.clock_rate = 93750000;
pub fn map_instructions(device: &mut device::Device) {
device.cpu.instrs = [
device::cop0::reserved, // SPECIAL
device::cop0::reserved, // REGIMM
@@ -216,6 +220,13 @@ pub fn init(device: &mut device::Device) {
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
}
pub fn init(device: &mut device::Device) {
device.cpu.clock_rate = 93750000;
map_instructions(device);
device::cop0::init(device);
device::cop1::init(device);
device::cop2::init(device);
+14 -16
View File
@@ -12,7 +12,7 @@ pub fn rt(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn sa(opcode: u32) -> u32 {
fn sa(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
@@ -28,23 +28,23 @@ pub fn se16(value: i16) -> u64 {
value as i64 as u64
}
pub fn se8(value: i8) -> u64 {
fn se8(value: i8) -> u64 {
value as i64 as u64
}
pub fn bshift<T: Into<u64>>(address: T) -> u64 {
fn bshift<T: Into<u64>>(address: T) -> u64 {
((address.into() & 3) ^ 3) << 3
}
pub fn hshift<T: Into<u64>>(address: T) -> u64 {
fn hshift<T: Into<u64>>(address: T) -> u64 {
((address.into() & 2) ^ 2) << 3
}
pub fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
(1 << x.into()) - 1
}
pub fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
!bits_below_mask(x)
}
@@ -60,8 +60,8 @@ pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
}
}
pub fn check_absolute_idle_loop(device: &mut device::Device, opcode: u32) {
if (opcode & 0x3FFFFFF) as u64 == (device.cpu.pc_phys & 0x0FFFFFFF) >> 2
fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
if device.cpu.pc == target
&& device.memory.fast_read[(device.cpu.pc_phys >> 16) as usize](
device,
device.cpu.pc_phys + 4,
@@ -76,10 +76,10 @@ pub fn j(device: &mut device::Device, opcode: u32) {
if device::cpu::in_delay_slot_taken(device) {
return;
}
check_absolute_idle_loop(device, opcode);
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
check_absolute_idle_loop(device, target);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc =
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
device.cpu.branch_state.pc = target
}
pub fn jal(device: &mut device::Device, opcode: u32) {
@@ -89,10 +89,10 @@ pub fn jal(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[31] = device.cpu.pc + 8
}
if !device::cpu::in_delay_slot_taken(device) {
check_absolute_idle_loop(device, opcode);
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
check_absolute_idle_loop(device, target);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc =
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
device.cpu.branch_state.pc = target
} else if !device::cpu::in_delay_slot(device) {
device.cpu.branch_state.state = device::cpu::State::NotTaken;
}
@@ -966,7 +966,6 @@ pub fn srav(device: &mut device::Device, opcode: u32) {
pub fn jr(device: &mut device::Device, opcode: u32) {
if !device::cpu::in_delay_slot_taken(device) {
check_relative_idle_loop(device, opcode);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
} else if !device::cpu::in_delay_slot(device) {
@@ -978,7 +977,6 @@ pub fn jalr(device: &mut device::Device, opcode: u32) {
let in_delay_slot_taken = device::cpu::in_delay_slot_taken(device);
if !in_delay_slot_taken {
check_relative_idle_loop(device, opcode);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
} else if !device::cpu::in_delay_slot(device) {
+42 -31
View File
@@ -1,62 +1,73 @@
use crate::device;
#[derive(PartialEq, Copy, Clone)]
pub enum EventType {
AI,
SI,
VI,
PI,
DP,
SP,
InterruptCheck,
SPDma,
Compare,
Vru,
Count,
}
pub const EVENT_TYPE_NONE: usize = 0;
pub const EVENT_TYPE_AI: usize = 1;
pub const EVENT_TYPE_SI: usize = 2;
pub const EVENT_TYPE_VI: usize = 3;
pub const EVENT_TYPE_PI: usize = 4;
pub const EVENT_TYPE_DP: usize = 5;
pub const EVENT_TYPE_SP: usize = 6;
pub const EVENT_TYPE_INT: usize = 7;
pub const EVENT_TYPE_SPDMA: usize = 8;
pub const EVENT_TYPE_COMPARE: usize = 9;
pub const EVENT_TYPE_VRU: usize = 10;
pub const EVENT_TYPE_PAK: usize = 11;
pub const EVENT_TYPE_COUNT: usize = 12;
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Event {
pub enabled: bool,
pub count: u64,
pub handler: fn(&mut device::Device),
}
pub fn create_event(
device: &mut device::Device,
name: EventType,
when: u64,
handler: fn(&mut device::Device),
) {
device.cpu.events[name as usize] = Event {
pub fn create_event(device: &mut device::Device, name: usize, when: u64) {
device.cpu.events[name] = Event {
enabled: true,
count: when,
handler,
};
set_next_event(device);
}
pub fn get_event(device: &mut device::Device, name: EventType) -> Option<&mut Event> {
if device.cpu.events[name as usize].enabled {
return Some(&mut device.cpu.events[name as usize]);
pub fn get_event(device: &mut device::Device, name: usize) -> Option<&mut Event> {
if device.cpu.events[name].enabled {
return Some(&mut device.cpu.events[name]);
}
None
}
pub fn remove_event(device: &mut device::Device, name: EventType) {
device.cpu.events[name as usize].enabled = false;
pub fn remove_event(device: &mut device::Device, name: usize) {
device.cpu.events[name].enabled = false;
}
fn get_event_handler(name: usize) -> fn(&mut device::Device) {
match name {
EVENT_TYPE_AI => device::ai::dma_event,
EVENT_TYPE_SI => device::si::dma_event,
EVENT_TYPE_VI => device::vi::vertical_interrupt_event,
EVENT_TYPE_PI => device::pi::dma_event,
EVENT_TYPE_DP => device::rdp::rdp_interrupt_event,
EVENT_TYPE_SP => device::rsp_interface::rsp_event,
EVENT_TYPE_INT => device::exceptions::interrupt_exception,
EVENT_TYPE_SPDMA => device::rsp_interface::fifo_pop,
EVENT_TYPE_COMPARE => device::cop0::compare_event,
EVENT_TYPE_VRU => device::controller::vru::vru_talking_event,
EVENT_TYPE_PAK => device::controller::pak_switch_event,
_ => dummy_event,
}
}
pub fn trigger_event(device: &mut device::Device) {
let next_event_name = device.cpu.next_event;
device.cpu.events[next_event_name].enabled = false;
let handler = device.cpu.events[next_event_name].handler;
let handler = get_event_handler(next_event_name);
device.cpu.cop0.is_event = true;
handler(device);
device.cpu.cop0.is_event = false;
set_next_event(device);
}
pub fn set_next_event(device: &mut device::Device) {
fn set_next_event(device: &mut device::Device) {
device.cpu.next_event_count = u64::MAX;
for (pos, i) in device.cpu.events.iter().enumerate() {
if i.enabled && i.count < device.cpu.next_event_count {
+32 -10
View File
@@ -1,15 +1,6 @@
use crate::device;
pub fn check_pending_interrupts(device: &mut device::Device) {
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& (device::cop0::COP0_STATUS_IE
| device::cop0::COP0_STATUS_EXL
@@ -20,6 +11,37 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.mi.regs[device::mi::MI_INTR_REG as usize]
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
!= 0
{
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
} else if device.cpu.cop0.pending_compare_interrupt {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if device.cpu.cop0.is_event {
interrupt_exception(device);
} else {
device::events::create_event(
device,
device::events::EVENT_TYPE_INT,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
);
}
}
pub fn interrupt_exception(device: &mut device::Device) {
exception_general(device, 0x180);
}
@@ -133,7 +155,7 @@ pub fn tlb_miss_exception(
exception_general(device, vector_offset)
}
pub fn exception_general(device: &mut device::Device, vector_offset: u32) {
fn exception_general(device: &mut device::Device, vector_offset: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
== 0
{
+1 -1
View File
@@ -8,7 +8,7 @@ pub fn ft(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn fd(opcode: u32) -> u32 {
fn fd(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
+25 -14
View File
@@ -1,4 +1,4 @@
use crate::device;
use crate::{device, savestates};
const MM_RDRAM_DRAM: usize = 0x00000000;
const MM_RDRAM_REGS: usize = 0x03f00000;
@@ -16,9 +16,11 @@ const MM_SI_REGS: usize = 0x04800000;
//const MM_DOM2_ADDR1: usize = 0x05000000;
pub const MM_DOM2_ADDR2: usize = 0x08000000;
pub const MM_CART_ROM: usize = 0x10000000;
const MM_PIF_MEM: usize = 0x1fc00000;
pub const MM_PIF_MEM: usize = 0x1fc00000;
//const MM_DOM1_ADDR3: usize = 0x1fd00000;
const MM_IS_VIEWER: usize = 0x13ff0000;
pub const MM_SC64_BUFFER: usize = 0x1ffe0000;
const MM_SC64_REGS: usize = 0x1fff0000;
#[derive(PartialEq)]
pub enum AccessType {
@@ -26,20 +28,27 @@ pub enum AccessType {
Read,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub enum AccessSize {
// None = 0,
#[default]
None = 0,
Word = 4,
Dword = 8,
Dcache = 16,
Icache = 32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Memory {
#[serde(skip, default = "savestates::default_memory_read")]
pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read")]
pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000],
#[serde(skip, default = "savestates::default_memory_write")]
pub memory_map_write: [fn(&mut device::Device, u64, u32, u32); 0x2000],
#[serde(with = "serde_big_array::BigArray")]
pub icache: [device::cache::ICache; 512],
#[serde(with = "serde_big_array::BigArray")]
pub dcache: [device::cache::DCache; 512],
}
@@ -56,13 +65,10 @@ pub fn translate_address(
address: u64,
access_type: AccessType,
) -> (u64, bool, bool) {
let mut cached = false;
if (address & 0xc0000000) != 0x80000000 {
return device::tlb::get_physical_address(device, address, access_type);
} else if address & 0x20000000 == 0 {
cached = true;
}
(address & 0x1FFFFFFF, cached, false)
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
}
pub fn data_read(
@@ -145,19 +151,24 @@ pub fn init(device: &mut device::Device) {
device.memory.memory_map_read[i] = device::si::read_regs;
device.memory.memory_map_write[i] = device::si::write_regs;
} else if (MM_DOM2_ADDR2 >> 16..=(MM_DOM2_ADDR2 + 0x1FFFF) >> 16).contains(&i) {
device.memory.memory_map_read[i] = device::sram::read_mem;
device.memory.memory_map_write[i] = device::sram::write_mem;
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
} else if i >= MM_CART_ROM >> 16 && i <= (MM_CART_ROM + device.cart.rom.len() - 1) >> 16 {
device.memory.fast_read[i] = device::cart_rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart_rom::read_mem;
device.memory.memory_map_write[i] = device::cart_rom::write_mem;
device.memory.fast_read[i] = device::cart::rom::read_mem_fast;
device.memory.memory_map_read[i] = device::cart::rom::read_mem;
device.memory.memory_map_write[i] = device::cart::rom::write_mem;
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::is_viewer::read_mem;
device.memory.memory_map_write[i] = device::is_viewer::write_mem;
} else if i >= MM_PIF_MEM >> 16 && i <= (MM_PIF_MEM + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::pif::read_mem;
device.memory.memory_map_write[i] = device::pif::write_mem;
} else if i >= MM_SC64_BUFFER >> 16 && i <= (MM_SC64_BUFFER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_mem;
device.memory.memory_map_write[i] = device::cart::sc64::write_mem;
} else if i >= MM_SC64_REGS >> 16 && i <= (MM_SC64_REGS + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sc64::read_regs;
device.memory.memory_map_write[i] = device::cart::sc64::write_regs;
}
}
device::cache::init(device)
}
+7 -33
View File
@@ -2,8 +2,8 @@ use crate::device;
const MI_INIT_MODE_REG: u32 = 0;
const MI_VERSION_REG: u32 = 1;
const MI_INTR_REG: u32 = 2;
const MI_INTR_MASK_REG: u32 = 3;
pub const MI_INTR_REG: u32 = 2;
pub const MI_INTR_MASK_REG: u32 = 3;
pub const MI_REGS_COUNT: u32 = 4;
/* read */
@@ -44,6 +44,7 @@ const MI_SET_RDRAM: u32 = 1 << 13;
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Mi {
pub regs: [u32; MI_REGS_COUNT as usize],
}
@@ -53,6 +54,7 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.mi.regs[((address & 0xFFFF) >> 2) as usize]
}
@@ -64,18 +66,14 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
}
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
} else {
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn update_init_mode(device: &mut device::Device, w: u32) {
fn update_init_mode(device: &mut device::Device, w: u32) {
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_LENGTH_MASK;
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
@@ -103,7 +101,7 @@ pub fn update_init_mode(device: &mut device::Device, w: u32) {
}
}
pub fn update_intr_mask(device: &mut device::Device, w: u32) {
fn update_intr_mask(device: &mut device::Device, w: u32) {
if w & MI_CLR_SP != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SP
}
@@ -149,37 +147,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::events::create_event(
device,
device::events::EventType::InterruptCheck,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
device::exceptions::check_pending_interrupts,
)
}
pub fn init(device: &mut device::Device) {
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
}
+57 -19
View File
@@ -26,11 +26,12 @@ const PI_STATUS_INTERRUPT: u32 = 1 << 3;
const PI_STATUS_RESET: u32 = 1 << 0;
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pi {
pub regs: [u32; PI_REGS_COUNT as usize],
}
pub struct PiHandler {
struct PiHandler {
read: fn(&mut device::Device, u32, u32, u32) -> u64,
write: fn(&mut device::Device, u32, u32, u32) -> u64,
}
@@ -40,6 +41,7 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
PI_WR_LEN_REG | PI_RD_LEN_REG => 0x7F,
@@ -49,7 +51,7 @@ pub fn read_regs(
}
}
pub fn dma_read(device: &mut device::Device) {
fn dma_read(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
@@ -65,9 +67,8 @@ pub fn dma_read(device: &mut device::Device) {
device::events::create_event(
device,
device::events::EventType::PI,
device::events::EVENT_TYPE_PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
dma_event,
);
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
@@ -79,7 +80,7 @@ pub fn dma_read(device: &mut device::Device) {
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
}
pub fn dma_write(device: &mut device::Device) {
fn dma_write(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
@@ -98,9 +99,8 @@ pub fn dma_write(device: &mut device::Device) {
device::events::create_event(
device,
device::events::EventType::PI,
device::events::EVENT_TYPE_PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
dma_event,
);
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
@@ -112,19 +112,29 @@ pub fn dma_write(device: &mut device::Device) {
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
}
pub fn get_handler(address: u32) -> PiHandler {
fn get_handler(address: u32) -> PiHandler {
let mut handler = PiHandler {
read: device::cart_rom::dma_read,
write: device::cart_rom::dma_write,
read: device::cart::rom::dma_read,
write: device::cart::rom::dma_write,
};
if address >= device::memory::MM_CART_ROM as u32 {
handler.read = device::cart_rom::dma_read;
handler.write = device::cart_rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
handler.read = device::sram::dma_read;
handler.write = device::sram::dma_write;
if address >= device::memory::MM_SC64_BUFFER as u32
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) as u32
{
handler.read = device::cart::sc64::dma_read;
handler.write = device::cart::sc64::dma_write;
} else if address >= device::memory::MM_CART_ROM as u32
&& address < device::memory::MM_PIF_MEM as u32
{
handler.read = device::cart::rom::dma_read;
handler.write = device::cart::rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32
&& address < device::memory::MM_CART_ROM as u32
{
handler.read = device::cart::sram::dma_read;
handler.write = device::cart::sram::dma_write;
} else {
panic!("unknown pi handler")
handler.read = unknown_dma_read;
handler.write = unknown_dma_write;
}
handler
}
@@ -149,10 +159,15 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
device.pi.regs[PI_STATUS_REG as usize] = 0
}
}
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM1_PGS_REG | PI_BSD_DOM1_RLS_REG
| PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG | PI_BSD_DOM2_PGS_REG | PI_BSD_DOM2_RLS_REG => {
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xFF, mask)
}
PI_BSD_DOM1_PGS_REG | PI_BSD_DOM2_PGS_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0xF, mask)
}
PI_BSD_DOM1_RLS_REG | PI_BSD_DOM2_RLS_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value & 0x3, mask)
}
_ => device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask),
}
}
@@ -189,3 +204,26 @@ pub fn dma_event(device: &mut device::Device) {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
fn unknown_dma_read(
device: &mut device::Device,
mut _cart_addr: u32,
mut _dram_addr: u32,
length: u32,
) -> u64 {
device::pi::calculate_cycles(device, 1, length)
}
fn unknown_dma_write(
device: &mut device::Device,
mut _cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+40 -15
View File
@@ -1,20 +1,25 @@
mod rom;
use crate::device;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pif {
#[serde(with = "serde_big_array::BigArray")]
pub rom: [u8; 1984],
#[serde(with = "serde_big_array::BigArray")]
pub ram: [u8; 64],
pub channels: [PifChannel; 5],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PifChannel {
pub tx: Option<usize>,
pub tx_buf: Option<usize>,
pub rx: Option<usize>,
pub rx_buf: Option<usize>,
#[serde(skip)]
pub process: Option<fn(&mut device::Device, usize)>,
pub pak_handler: Option<device::controller::PakHandler>,
pub change_pak: device::controller::PakType,
}
pub const PIF_RAM_SIZE: usize = 64;
@@ -64,15 +69,14 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device.si.dma_dir = device::si::DmaDir::Write;
device::events::create_event(
device,
device::events::EventType::SI,
device::events::EVENT_TYPE_SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 3200,
device::si::dma_event,
); //based on https://github.com/rasky/n64-systembench
device.si.regs[device::si::SI_STATUS_REG as usize] |=
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
}
pub fn process_channel(device: &mut device::Device, channel: usize) -> usize {
fn process_channel(device: &mut device::Device, channel: usize) -> usize {
/* don't process channel if it has been disabled */
if device.pif.channels[channel].tx.is_none() {
return 0;
@@ -99,17 +103,18 @@ pub fn update_pif_ram(device: &mut device::Device) -> u64 {
for k in 0..PIF_CHANNELS_COUNT {
active_channels += process_channel(device, k)
}
(24000 + (active_channels * 30000)) as u64
}
pub fn disable_pif_channel(channel: &mut PifChannel) {
fn disable_pif_channel(channel: &mut PifChannel) {
channel.tx = None;
channel.rx = None;
channel.tx_buf = None;
channel.rx_buf = None;
}
pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
let tx = device.pif.ram[buf] & 0x3f;
let rx = device.pif.ram[buf + 1] & 0x3f;
@@ -123,7 +128,7 @@ pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize
(2 + tx + rx) as usize
}
pub fn setup_channels_format(device: &mut device::Device) {
fn setup_channels_format(device: &mut device::Device) {
let mut i: usize = 0;
let mut k: usize = 0;
while i < PIF_RAM_SIZE && k < PIF_CHANNELS_COUNT {
@@ -215,6 +220,22 @@ pub fn process_ram(device: &mut device::Device) {
device.pif.ram[0x3F] &= !clrmask
}
pub fn connect_pif_channels(device: &mut device::Device) {
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@@ -225,24 +246,28 @@ pub fn init(device: &mut device::Device) {
device.pif.ram[0x27] = device.cart.cic_seed;
let mempak_handler = device::controller::PakHandler {
read: device::mempak::read,
write: device::mempak::write,
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
connect_pif_channels(device);
for i in 0..4 {
if device.ui.config.input.controller_enabled[i] {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].pak_handler = Some(mempak_handler);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(mempak_handler);
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].pak_handler = None;
device.pif.channels[3].process = Some(device::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
pub fn process_cic_challenge(device: &mut device::Device) {
fn process_cic_challenge(device: &mut device::Device) {
let mut challenge: [u8; 30] = [0; 30];
let mut response: [u8; 30] = [0; 30];
@@ -263,7 +288,7 @@ pub fn process_cic_challenge(device: &mut device::Device) {
}
}
pub fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
let lut0: [u8; 0x10] = [
0x4, 0x7, 0xA, 0x7, 0xE, 0x5, 0xE, 0x1, 0xC, 0xF, 0x8, 0xF, 0x6, 0x3, 0x6, 0x9,
];
+41 -8
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, savestates, ui};
pub const DPC_START_REG: u32 = 0;
pub const DPC_END_REG: u32 = 1;
@@ -41,10 +40,12 @@ const DPC_CLR_PIPE_CTR: u32 = 1 << 7;
const DPC_CLR_CMD_CTR: u32 = 1 << 8;
const DPC_CLR_CLOCK_CTR: u32 = 1 << 9;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rdp {
pub regs_dpc: [u32; DPC_REGS_COUNT as usize],
pub regs_dps: [u32; DPS_REGS_COUNT as usize],
pub wait_frozen: bool,
pub last_status_value: u32,
}
pub fn read_regs_dpc(
@@ -52,7 +53,27 @@ pub fn read_regs_dpc(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
device::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
DPC_STATUS_REG => {
let value =
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
if value == device.rdp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
device.rdp.last_status_value = value;
device.rdp.regs_dpc[reg as usize]
}
DPC_CLOCK_REG => 0xFFFFFF, // needed for JFG
DPC_CURRENT_REG => {
if device.rdp.wait_frozen {
device.rsp.cpu.sync_point = true;
}
device.rdp.regs_dpc[reg as usize]
}
_ => device.rdp.regs_dpc[reg as usize],
}
}
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -91,15 +112,25 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
}
}
pub fn run_rdp(device: &mut device::Device) {
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
if timer != 0 {
if device.save_state {
// Right after full sync, good time for save state
device.save_state = false;
savestates::create_savestate(device);
} else if device.load_state {
// Right after full sync, good time for save state
device.load_state = false;
savestates::load_savestate(device);
}
device::events::create_event(
device,
device::events::EventType::DP,
device::events::EVENT_TYPE_DP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rdp_interrupt_event,
)
}
}
@@ -109,6 +140,7 @@ pub fn read_regs_dps(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.rdp.regs_dps[((address & 0xFFFF) >> 2) as usize]
}
@@ -120,7 +152,7 @@ pub fn write_regs_dps(device: &mut device::Device, address: u64, value: u32, mas
)
}
pub fn update_dpc_status(device: &mut device::Device, w: u32) {
fn update_dpc_status(device: &mut device::Device, w: u32) {
/* clear / set xbus_dmem_dma */
if w & DPC_CLR_XBUS_DMEM_DMA != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_XBUS_DMEM_DMA
@@ -174,7 +206,8 @@ pub fn init(device: &mut device::Device) {
}
pub fn rdp_interrupt_event(device: &mut device::Device) {
ui::video::full_sync();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+36 -5
View File
@@ -1,11 +1,25 @@
use crate::device;
use std::alloc::{alloc_zeroed, Layout};
//const RDRAM_CONFIG_REG: u32 = 0;
//const RDRAM_DEVICE_ID_REG: u32 = 1;
//const RDRAM_DELAY_REG: u32 = 2;
const RDRAM_MODE_REG: u32 = 3;
//const RDRAM_REF_INTERVAL_REG: u32 = 4;
//const RDRAM_REF_ROW_REG: u32 = 5;
//const RDRAM_RAS_INTERVAL_REG: u32 = 6;
//const RDRAM_MIN_INTERVAL_REG: u32 = 7;
//const RDRAM_ADDR_SELECT_REG: u32 = 8;
//const RDRAM_DEVICE_MANUF_REG: u32 = 9;
pub const RDRAM_REGS_COUNT: u32 = 10;
pub const RDRAM_MASK: usize = 0xFFFFFF;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rdram {
pub mem: Vec<u8>,
pub size: u32,
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
}
pub fn read_mem_fast(
@@ -56,16 +70,28 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
}
pub fn read_regs(
_device: &mut device::Device,
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
println!("Warning: read from RDRAM reg {:#x}", address);
0
device::cop0::add_cycles(device, 20);
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
match reg as u32 {
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
0x80 => 0x00000000, //Row, needed for libdragon
_ => device.rdram.regs[chip_id as usize][reg as usize],
}
}
pub fn write_regs(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {
panic!("rdram write reg");
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
device::memory::masked_write_32(
&mut device.rdram.regs[chip_id as usize][reg as usize],
value,
mask,
)
}
pub fn init(device: &mut device::Device) {
@@ -79,6 +105,11 @@ pub fn init(device: &mut device::Device) {
// hack, skip RDRAM initialization
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
.copy_from_slice(&device.rdram.size.to_ne_bytes());
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
device.ri.regs[device::ri::RI_SELECT_REG as usize] = 0x14;
device.ri.regs[device::ri::RI_REFRESH_REG as usize] = 0x00063634;
}
pub fn rdram_calculate_cycles(length: u64) -> u64 {
+10 -6
View File
@@ -1,17 +1,19 @@
use crate::device;
//const RI_MODE_REG: u32 = 0;
//const RI_CONFIG_REG: u32 = 1;
pub const RI_MODE_REG: u32 = 0;
pub const RI_CONFIG_REG: u32 = 1;
//const RI_CURRENT_LOAD_REG: u32 = 2;
const RI_SELECT_REG: u32 = 3;
//const RI_REFRESH_REG: u32 = 4;
pub const RI_SELECT_REG: u32 = 3;
pub const RI_REFRESH_REG: u32 = 4;
//const RI_LATENCY_REG: u32 = 5;
//const RI_ERROR_REG: u32 = 6;
//const RI_WERROR_REG: u32 = 7;
pub const RI_REGS_COUNT: u32 = 8;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ri {
pub regs: [u32; RI_REGS_COUNT as usize],
pub ram_init: bool,
}
pub fn read_regs(
@@ -19,8 +21,10 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG {
return 0x14; // hack, skip RDRAM initialization
device::cop0::add_cycles(device, 20);
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG && !device.ri.ram_init {
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
device.ri.ram_init = true;
}
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
}
+182 -123
View File
@@ -2,28 +2,32 @@
use device::__m128i;
#[cfg(target_arch = "aarch64")]
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
use crate::device;
use crate::{device, savestates};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: device::cpu::State,
pub pc: u32,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Instructions {
#[serde(skip, default = "savestates::default_instruction")]
pub func: fn(&mut device::Device, u32),
pub opcode: u32,
}
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum InstructionType {
Su,
Vu,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cpu {
#[serde(with = "serde_big_array::BigArray")]
pub instructions: [Instructions; 0x1000 / 4],
pub last_instruction_type: InstructionType,
pub instruction_type: InstructionType,
@@ -33,29 +37,79 @@ pub struct Cpu {
pub halted: bool,
pub sync_point: bool,
pub cycle_counter: u64,
#[serde(
serialize_with = "savestates::serialize_m128i_array",
deserialize_with = "savestates::deserialize_m128i_array"
)]
pub shuffle: [__m128i; 16],
pub gpr: [u32; 32],
#[serde(
serialize_with = "savestates::serialize_m128i_array",
deserialize_with = "savestates::deserialize_m128i_array"
)]
pub vpr: [__m128i; 32],
#[serde(with = "serde_big_array::BigArray")]
pub reciprocals: [u16; 512],
#[serde(with = "serde_big_array::BigArray")]
pub inverse_square_roots: [u16; 512],
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcol: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcoh: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vccl: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vcch: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub vce: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub accl: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub accm: __m128i,
#[serde(
serialize_with = "savestates::serialize_m128i",
deserialize_with = "savestates::deserialize_m128i"
)]
pub acch: __m128i,
pub divdp: bool,
pub divin: i16,
pub divout: i16,
#[serde(skip, default = "savestates::default_instructions")]
pub special_instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub regimm_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub cop0_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub cop2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub lwc2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub swc2_instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 64],
#[serde(skip, default = "savestates::default_instructions")]
pub vec_instrs: [fn(&mut device::Device, u32); 64],
}
@@ -165,62 +219,7 @@ pub fn decode_opcode(device: &device::Device, opcode: u32) -> fn(&mut device::De
}
}
pub fn init(device: &mut device::Device) {
device.rsp.cpu.reciprocals[0] = u16::MAX;
let mut index = 1;
while index < 512 {
let a = (index + 512) as u64;
let b = (1_u64 << 34) / a;
device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16;
index += 1;
}
index = 0;
while index < 512 {
let mut shift = 0;
if index % 2 == 1 {
shift = 1
}
let a = ((index + 512) >> shift) as u64;
let mut b = (1 << 17) as u64;
//find the largest b where b < 1.0 / sqrt(a)
while a * (b + 1) * (b + 1) < (1_u64 << 44) {
b += 1;
}
device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16;
index += 1;
}
device.rsp.cpu.shuffle = unsafe {
[
//vector
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
//scalar quarter
_mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466
_mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577
//scalar half
_mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444
_mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555
_mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777
//scalar whole
_mm_set_epi8(
15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14,
), //00000000
_mm_set_epi8(
13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12,
), //11111111
_mm_set_epi8(
11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10,
), //22222222
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333
_mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444
_mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555
_mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666
_mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777
]
};
pub fn map_instructions(device: &mut device::Device) {
device.rsp.cpu.instrs = [
device::rsp_su_instructions::reserved, // SPECIAL
device::rsp_su_instructions::reserved, // REGIMM
@@ -289,70 +288,70 @@ pub fn init(device: &mut device::Device) {
];
device.rsp.cpu.special_instrs = [
device::rsp_su_instructions::sll, // 0
device::rsp_su_instructions::reserved, // 1
device::rsp_su_instructions::srl, // 2
device::rsp_su_instructions::sra, // 3
device::rsp_su_instructions::sllv, // 4
device::rsp_su_instructions::reserved, // 5
device::rsp_su_instructions::srlv, // 6
device::rsp_su_instructions::srav, // 7
device::rsp_su_instructions::jr, // 8
device::rsp_su_instructions::jalr, // 9
device::rsp_su_instructions::reserved, // 10
device::rsp_su_instructions::reserved, // 11
device::rsp_su_instructions::reserved, // 12
device::rsp_su_instructions::break_, // 13
device::rsp_su_instructions::reserved, // 14
device::rsp_su_instructions::reserved, // 15
device::rsp_su_instructions::reserved, // 16
device::rsp_su_instructions::reserved, // 17
device::rsp_su_instructions::reserved, // 18
device::rsp_su_instructions::reserved, // 19
device::rsp_su_instructions::reserved, // 20
device::rsp_su_instructions::reserved, // 21
device::rsp_su_instructions::reserved, // 22
device::rsp_su_instructions::reserved, // 23
device::rsp_su_instructions::reserved, // 24
device::rsp_su_instructions::reserved, // 25
device::rsp_su_instructions::reserved, // 26
device::rsp_su_instructions::reserved, // 27
device::rsp_su_instructions::reserved, // 28
device::rsp_su_instructions::reserved, // 29
device::rsp_su_instructions::reserved, // 30
device::rsp_su_instructions::reserved, // 31
device::rsp_su_instructions::add, // 32
device::rsp_su_instructions::addu, // 33
device::rsp_su_instructions::sub, // 34
device::rsp_su_instructions::subu, // 35
device::rsp_su_instructions::and, // 36
device::rsp_su_instructions::or, // 37
device::rsp_su_instructions::xor, // 38
device::rsp_su_instructions::nor, // 39
device::rsp_su_instructions::reserved, // 40
device::rsp_su_instructions::reserved, // 41
device::rsp_su_instructions::slt, // 42
device::rsp_su_instructions::sltu, // 43
device::rsp_su_instructions::reserved, // 44
device::rsp_su_instructions::reserved, // 45
device::rsp_su_instructions::reserved, // 46
device::rsp_su_instructions::reserved, // 47
device::rsp_su_instructions::reserved, // 48
device::rsp_su_instructions::reserved, // 49
device::rsp_su_instructions::reserved, // 50
device::rsp_su_instructions::reserved, // 51
device::rsp_su_instructions::reserved, // 52
device::rsp_su_instructions::reserved, // 53
device::rsp_su_instructions::reserved, // 54
device::rsp_su_instructions::reserved, // 55
device::rsp_su_instructions::reserved, // 56
device::rsp_su_instructions::reserved, // 57
device::rsp_su_instructions::reserved, // 58
device::rsp_su_instructions::reserved, // 59
device::rsp_su_instructions::reserved, // 60
device::rsp_su_instructions::reserved, // 61
device::rsp_su_instructions::reserved, // 62
device::rsp_su_instructions::reserved, // 63
device::rsp_su_instructions::sll, // 0
device::rsp_su_instructions::special_reserved, // 1
device::rsp_su_instructions::srl, // 2
device::rsp_su_instructions::sra, // 3
device::rsp_su_instructions::sllv, // 4
device::rsp_su_instructions::special_reserved, // 5
device::rsp_su_instructions::srlv, // 6
device::rsp_su_instructions::srav, // 7
device::rsp_su_instructions::jr, // 8
device::rsp_su_instructions::jalr, // 9
device::rsp_su_instructions::special_reserved, // 10
device::rsp_su_instructions::special_reserved, // 11
device::rsp_su_instructions::special_reserved, // 12
device::rsp_su_instructions::break_, // 13
device::rsp_su_instructions::special_reserved, // 14
device::rsp_su_instructions::special_reserved, // 15
device::rsp_su_instructions::special_reserved, // 16
device::rsp_su_instructions::special_reserved, // 17
device::rsp_su_instructions::special_reserved, // 18
device::rsp_su_instructions::special_reserved, // 19
device::rsp_su_instructions::special_reserved, // 20
device::rsp_su_instructions::special_reserved, // 21
device::rsp_su_instructions::special_reserved, // 22
device::rsp_su_instructions::special_reserved, // 23
device::rsp_su_instructions::special_reserved, // 24
device::rsp_su_instructions::special_reserved, // 25
device::rsp_su_instructions::special_reserved, // 26
device::rsp_su_instructions::special_reserved, // 27
device::rsp_su_instructions::special_reserved, // 28
device::rsp_su_instructions::special_reserved, // 29
device::rsp_su_instructions::special_reserved, // 30
device::rsp_su_instructions::special_reserved, // 31
device::rsp_su_instructions::add, // 32
device::rsp_su_instructions::addu, // 33
device::rsp_su_instructions::sub, // 34
device::rsp_su_instructions::subu, // 35
device::rsp_su_instructions::and, // 36
device::rsp_su_instructions::or, // 37
device::rsp_su_instructions::xor, // 38
device::rsp_su_instructions::nor, // 39
device::rsp_su_instructions::special_reserved, // 40
device::rsp_su_instructions::special_reserved, // 41
device::rsp_su_instructions::slt, // 42
device::rsp_su_instructions::sltu, // 43
device::rsp_su_instructions::special_reserved, // 44
device::rsp_su_instructions::special_reserved, // 45
device::rsp_su_instructions::special_reserved, // 46
device::rsp_su_instructions::special_reserved, // 47
device::rsp_su_instructions::special_reserved, // 48
device::rsp_su_instructions::special_reserved, // 49
device::rsp_su_instructions::special_reserved, // 50
device::rsp_su_instructions::special_reserved, // 51
device::rsp_su_instructions::special_reserved, // 52
device::rsp_su_instructions::special_reserved, // 53
device::rsp_su_instructions::special_reserved, // 54
device::rsp_su_instructions::special_reserved, // 55
device::rsp_su_instructions::special_reserved, // 56
device::rsp_su_instructions::special_reserved, // 57
device::rsp_su_instructions::special_reserved, // 58
device::rsp_su_instructions::special_reserved, // 59
device::rsp_su_instructions::special_reserved, // 60
device::rsp_su_instructions::special_reserved, // 61
device::rsp_su_instructions::special_reserved, // 62
device::rsp_su_instructions::special_reserved, // 63
];
device.rsp.cpu.regimm_instrs = [
@@ -597,3 +596,63 @@ pub fn init(device: &mut device::Device) {
device::rsp_vu_instructions::vnop, // 63
]
}
pub fn init(device: &mut device::Device) {
device.rsp.cpu.reciprocals[0] = u16::MAX;
let mut index = 1;
while index < 512 {
let a = (index + 512) as u64;
let b = (1_u64 << 34) / a;
device.rsp.cpu.reciprocals[index] = ((b + 1) >> 8) as u16;
index += 1;
}
index = 0;
while index < 512 {
let mut shift = 0;
if index % 2 == 1 {
shift = 1
}
let a = ((index + 512) >> shift) as u64;
let mut b = (1 << 17) as u64;
//find the largest b where b < 1.0 / sqrt(a)
while a * (b + 1) * (b + 1) < (1_u64 << 44) {
b += 1;
}
device.rsp.cpu.inverse_square_roots[index] = (b >> 1) as u16;
index += 1;
}
device.rsp.cpu.shuffle = unsafe {
[
//vector
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
_mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567
//scalar quarter
_mm_set_epi8(15, 14, 15, 14, 11, 10, 11, 10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466
_mm_set_epi8(13, 12, 13, 12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577
//scalar half
_mm_set_epi8(15, 14, 15, 14, 15, 14, 15, 14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444
_mm_set_epi8(13, 12, 13, 12, 13, 12, 13, 12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555
_mm_set_epi8(11, 10, 11, 10, 11, 10, 11, 10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777
//scalar whole
_mm_set_epi8(
15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14, 15, 14,
), //00000000
_mm_set_epi8(
13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12, 13, 12,
), //11111111
_mm_set_epi8(
11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10, 11, 10,
), //22222222
_mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333
_mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444
_mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555
_mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666
_mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777
]
};
map_instructions(device);
}
+58 -24
View File
@@ -60,14 +60,14 @@ const SP_SET_SIG7: u32 = 1 << 24;
const RSP_MEM_MASK: usize = 0x1FFF;
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct RspDma {
pub dir: DmaDir,
pub length: u32,
@@ -75,12 +75,16 @@ pub struct RspDma {
pub dramaddr: u32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rsp {
pub cpu: device::rsp_cpu::Cpu,
pub regs: [u32; SP_REGS_COUNT as usize],
pub regs2: [u32; SP_REGS2_COUNT as usize],
#[serde(with = "serde_big_array::BigArray")]
pub mem: [u8; 0x2000],
pub fifo: [RspDma; 2],
pub last_status_value: u32,
pub run_after_dma: bool,
}
pub fn read_mem_fast(
@@ -99,8 +103,10 @@ pub fn read_mem_fast(
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, access_size as u64 / 4);
let masked_address = address as usize & RSP_MEM_MASK;
u32::from_be_bytes(
device.rsp.mem[masked_address..masked_address + 4]
@@ -129,7 +135,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, _mask: u
// SH/SB are broken: They overwrite the whole 32 bit, filling everything that isn't written with zeroes
}
pub fn do_dma(device: &mut device::Device, dma: RspDma) {
fn do_dma(device: &mut device::Device, dma: RspDma) {
let l = dma.length;
let length = ((l & 0xfff) | 7) + 1;
@@ -197,15 +203,14 @@ pub fn do_dma(device: &mut device::Device, dma: RspDma) {
device::events::create_event(
device,
device::events::EventType::SPDma,
device::events::EVENT_TYPE_SPDMA,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ device::rdram::rdram_calculate_cycles((count * length) as u64)
+ 9,
device::rsp_interface::fifo_pop,
);
}
pub fn fifo_push(device: &mut device::Device, dir: DmaDir) {
fn fifo_push(device: &mut device::Device, dir: DmaDir) {
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
panic!("RSP DMA already full")
}
@@ -249,7 +254,11 @@ pub fn fifo_pop(device: &mut device::Device) {
do_dma(device, device.rsp.fifo[0])
} else {
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
if device.rsp.run_after_dma {
device.rsp.run_after_dma = false;
do_task(device);
}
}
}
@@ -258,10 +267,33 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SP_DMA_BUSY_REG | SP_DMA_FULL_REG => {
if device.rsp.regs[reg as usize] != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.regs[reg as usize]
}
SP_STATUS_REG => {
let value = device.rsp.regs[reg as usize]
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
if value == device.rsp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.last_status_value = value;
device.rsp.regs[reg as usize]
}
SP_SEMAPHORE_REG => {
let value = device.rsp.regs[reg as usize];
if value == 1 {
device.rsp.cpu.sync_point = true;
device.rsp.cpu.cycle_counter += 4; // needed for DK64
}
device.rsp.regs[reg as usize] = 1;
value
}
@@ -284,9 +316,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
SP_SEMAPHORE_REG => {
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], 0, mask)
}
_ => {
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
}
_ => device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask),
}
}
@@ -295,6 +325,7 @@ pub fn read_regs2(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.rsp.regs2[((address & 0xFFFF) >> 2) as usize]
}
@@ -312,7 +343,7 @@ pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask:
}
}
pub fn update_sp_status(device: &mut device::Device, w: u32) {
fn update_sp_status(device: &mut device::Device, w: u32) {
let was_halted = device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT != 0;
/* clear / set halt */
@@ -320,7 +351,7 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT
}
if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
device::events::remove_event(device, device::events::EventType::SP);
device::events::remove_event(device, device::events::EVENT_TYPE_SP);
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT
}
@@ -335,7 +366,7 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
}
/* set SP interrupt */
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
}
/* clear / set single step */
@@ -421,20 +452,24 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
device.rsp.cpu.broken = false;
device.rsp.cpu.halted = false;
device.rsp.cpu.sync_point = false;
do_task(device);
}
}
pub fn do_task(device: &mut device::Device) {
let timer = device::rsp_cpu::run(device);
fn do_task(device: &mut device::Device) {
device.rsp.cpu.sync_point = false;
device.rsp.last_status_value = 0;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
device.rsp.run_after_dma = true
} else {
let timer = device::rsp_cpu::run(device);
device::events::create_event(
device,
device::events::EventType::SP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rsp_event,
)
device::events::create_event(
device,
device::events::EVENT_TYPE_SP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
)
}
}
pub fn rsp_event(device: &mut device::Device) {
@@ -450,7 +485,6 @@ pub fn rsp_event(device: &mut device::Device) {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
return;
}
device.rsp.cpu.sync_point = false;
do_task(device)
}
+83 -171
View File
@@ -8,45 +8,45 @@ use crate::device;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
pub fn rd(opcode: u32) -> u32 {
fn rd(opcode: u32) -> u32 {
(opcode >> 11) & 0x1F
}
pub fn rs(opcode: u32) -> u32 {
fn rs(opcode: u32) -> u32 {
(opcode >> 21) & 0x1F
}
pub fn rt(opcode: u32) -> u32 {
fn rt(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn sa(opcode: u32) -> u32 {
fn sa(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
pub fn imm(opcode: u32) -> u16 {
fn imm(opcode: u32) -> u16 {
opcode as u16
}
pub fn se16(value: i16) -> u32 {
fn se16(value: i16) -> u32 {
value as i32 as u32
}
pub fn voffset(opcode: u32) -> u8 {
fn voffset(opcode: u32) -> u8 {
(opcode & 0x7F) as u8
}
pub fn velement(opcode: u32) -> u8 {
fn velement(opcode: u32) -> u8 {
((opcode >> 7) & 0xF) as u8
}
pub fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
let soffset = (((offset << 1) & 0x80) | offset) as i8;
(((soffset) as i32) as u32) << shift_amount
}
pub fn modify_vpr8(vpr: &mut __m128i, element: u8, value: u8) {
fn modify_vpr8(vpr: &mut __m128i, element: u8, value: u8) {
unsafe {
*vpr = match element & 15 {
0 => _mm_insert_epi8(*vpr, value as i32, 15),
@@ -70,7 +70,7 @@ pub fn modify_vpr8(vpr: &mut __m128i, element: u8, value: u8) {
}
}
pub fn get_vpr8(vpr: __m128i, element: u8) -> u8 {
fn get_vpr8(vpr: __m128i, element: u8) -> u8 {
unsafe {
match element & 15 {
0 => _mm_extract_epi8(vpr, 15) as u8,
@@ -126,7 +126,7 @@ pub fn get_vpr16(vpr: __m128i, element: u8) -> u16 {
}
}
pub fn modify_vpr32(vpr: &mut __m128i, element: u8, value: u32) {
fn modify_vpr32(vpr: &mut __m128i, element: u8, value: u32) {
unsafe {
*vpr = match element & 3 {
0 => _mm_insert_epi32(*vpr, value as i32, 3),
@@ -138,7 +138,7 @@ pub fn modify_vpr32(vpr: &mut __m128i, element: u8, value: u32) {
}
}
pub fn get_vpr32(vpr: __m128i, element: u8) -> u32 {
fn get_vpr32(vpr: __m128i, element: u8) -> u32 {
unsafe {
match element & 3 {
0 => _mm_extract_epi32(vpr, 3) as u32,
@@ -150,7 +150,7 @@ pub fn get_vpr32(vpr: __m128i, element: u8) -> u32 {
}
}
pub fn modify_vpr64(vpr: &mut __m128i, element: u8, value: u64) {
fn modify_vpr64(vpr: &mut __m128i, element: u8, value: u64) {
unsafe {
*vpr = match element & 1 {
0 => _mm_insert_epi64(*vpr, value as i64, 1),
@@ -160,7 +160,7 @@ pub fn modify_vpr64(vpr: &mut __m128i, element: u8, value: u64) {
}
}
pub fn get_vpr64(vpr: __m128i, element: u8) -> u64 {
fn get_vpr64(vpr: __m128i, element: u8) -> u64 {
unsafe {
match element & 1 {
0 => _mm_extract_epi64(vpr, 1) as u64,
@@ -170,11 +170,11 @@ pub fn get_vpr64(vpr: __m128i, element: u8) -> u64 {
}
}
pub fn modify_vpr128(vpr: &mut __m128i, value: u128) {
fn modify_vpr128(vpr: &mut __m128i, value: u128) {
unsafe { *vpr = std::mem::transmute::<u128, __m128i>(value) }
}
pub fn get_vpr128(vpr: __m128i) -> u128 {
fn get_vpr128(vpr: __m128i) -> u128 {
unsafe { std::mem::transmute::<__m128i, u128>(vpr) }
}
@@ -565,8 +565,6 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
device::memory::AccessSize::Word,
)
}
device.rsp.cpu.cycle_counter += 4; // needed for DK64
device.rsp.cpu.sync_point = true;
}
pub fn mtc0(device: &mut device::Device, opcode: u32) {
@@ -592,7 +590,6 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
!device::rsp_interface::SP_STATUS_HALT; // set halt when event happens
device.rsp.cpu.halted = true // the RSP can halt itself by setting SP_SET_HALT
}
device.rsp.cpu.sync_point = true;
}
pub fn mfc2(device: &mut device::Device, opcode: u32) {
@@ -721,7 +718,7 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
modify_vpr16(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 2,
(device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8
((device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u16),
);
} else {
@@ -745,13 +742,14 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 4 == 0 {
let mut value = 0;
for i in 0..4 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
}
modify_vpr32(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 4,
(device.rsp.mem[(address & 0xFFF) as usize] as u32) << (8 * 3)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u32) << (8 * 2)
| (device.rsp.mem[((address + 2) & 0xFFF) as usize] as u32) << 8
| (device.rsp.mem[((address + 3) & 0xFFF) as usize] as u32),
value,
);
} else {
let end = std::cmp::min(element + 4, 16);
@@ -774,17 +772,14 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 8 == 0 {
let mut value = 0;
for i in 0..8 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
}
modify_vpr64(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 8,
(device.rsp.mem[(address & 0xFFF) as usize] as u64) << (8 * 7)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u64) << (8 * 6)
| (device.rsp.mem[((address + 2) & 0xFFF) as usize] as u64) << (8 * 5)
| (device.rsp.mem[((address + 3) & 0xFFF) as usize] as u64) << (8 * 4)
| (device.rsp.mem[((address + 4) & 0xFFF) as usize] as u64) << (8 * 3)
| (device.rsp.mem[((address + 5) & 0xFFF) as usize] as u64) << (8 * 2)
| (device.rsp.mem[((address + 6) & 0xFFF) as usize] as u64) << 8
| (device.rsp.mem[((address + 7) & 0xFFF) as usize] as u64),
value,
);
} else {
let end = std::cmp::min(element + 8, 16);
@@ -807,25 +802,11 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element == 0 && address % 16 == 0 {
modify_vpr128(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
(device.rsp.mem[(address & 0xFFF) as usize] as u128) << (8 * 15)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u128) << (8 * 14)
| (device.rsp.mem[((address + 2) & 0xFFF) as usize] as u128) << (8 * 13)
| (device.rsp.mem[((address + 3) & 0xFFF) as usize] as u128) << (8 * 12)
| (device.rsp.mem[((address + 4) & 0xFFF) as usize] as u128) << (8 * 11)
| (device.rsp.mem[((address + 5) & 0xFFF) as usize] as u128) << (8 * 10)
| (device.rsp.mem[((address + 6) & 0xFFF) as usize] as u128) << (8 * 9)
| (device.rsp.mem[((address + 7) & 0xFFF) as usize] as u128) << (8 * 8)
| (device.rsp.mem[((address + 8) & 0xFFF) as usize] as u128) << (8 * 7)
| (device.rsp.mem[((address + 9) & 0xFFF) as usize] as u128) << (8 * 6)
| (device.rsp.mem[((address + 10) & 0xFFF) as usize] as u128) << (8 * 5)
| (device.rsp.mem[((address + 11) & 0xFFF) as usize] as u128) << (8 * 4)
| (device.rsp.mem[((address + 12) & 0xFFF) as usize] as u128) << (8 * 3)
| (device.rsp.mem[((address + 13) & 0xFFF) as usize] as u128) << (8 * 2)
| (device.rsp.mem[((address + 14) & 0xFFF) as usize] as u128) << 8
| (device.rsp.mem[((address + 15) & 0xFFF) as usize] as u128),
);
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
while element < end {
@@ -847,25 +828,11 @@ pub fn lrv(device: &mut device::Device, opcode: u32) {
let mut element = 16u8.wrapping_sub(((address & 15) as u8).wrapping_sub(velement(opcode)));
address &= !15;
if element == 0 {
modify_vpr128(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
(device.rsp.mem[(address & 0xFFF) as usize] as u128) << (8 * 15)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u128) << (8 * 14)
| (device.rsp.mem[((address + 2) & 0xFFF) as usize] as u128) << (8 * 13)
| (device.rsp.mem[((address + 3) & 0xFFF) as usize] as u128) << (8 * 12)
| (device.rsp.mem[((address + 4) & 0xFFF) as usize] as u128) << (8 * 11)
| (device.rsp.mem[((address + 5) & 0xFFF) as usize] as u128) << (8 * 10)
| (device.rsp.mem[((address + 6) & 0xFFF) as usize] as u128) << (8 * 9)
| (device.rsp.mem[((address + 7) & 0xFFF) as usize] as u128) << (8 * 8)
| (device.rsp.mem[((address + 8) & 0xFFF) as usize] as u128) << (8 * 7)
| (device.rsp.mem[((address + 9) & 0xFFF) as usize] as u128) << (8 * 6)
| (device.rsp.mem[((address + 10) & 0xFFF) as usize] as u128) << (8 * 5)
| (device.rsp.mem[((address + 11) & 0xFFF) as usize] as u128) << (8 * 4)
| (device.rsp.mem[((address + 12) & 0xFFF) as usize] as u128) << (8 * 3)
| (device.rsp.mem[((address + 13) & 0xFFF) as usize] as u128) << (8 * 2)
| (device.rsp.mem[((address + 14) & 0xFFF) as usize] as u128) << 8
| (device.rsp.mem[((address + 15) & 0xFFF) as usize] as u128),
);
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
while element < 16 {
modify_vpr8(
@@ -885,35 +852,15 @@ pub fn lpv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
unsafe {
device.rsp.cpu.vpr[rt(opcode) as usize] =
_mm_set_epi16(
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(0)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(1)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(2)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(3)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(4)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(5)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(6)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(7)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 8,
);
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 8) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn luv(device: &mut device::Device, opcode: u32) {
@@ -922,35 +869,15 @@ pub fn luv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
unsafe {
device.rsp.cpu.vpr[rt(opcode) as usize] =
_mm_set_epi16(
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(0)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(1)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(2)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(3)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(4)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(5)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(6)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(7)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
);
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 7) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn lhv(device: &mut device::Device, opcode: u32) {
@@ -959,35 +886,15 @@ pub fn lhv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
unsafe {
device.rsp.cpu.vpr[rt(opcode) as usize] =
_mm_set_epi16(
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(0)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(2)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(4)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(6)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(8)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(10)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(12)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(14)) & 15) as u32))
& 0xFFF) as usize] as i16)
<< 7,
);
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i * 2)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 7) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn lfv(device: &mut device::Device, opcode: u32) {
@@ -1074,10 +981,10 @@ pub fn ssv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 2 == 0 {
device.rsp.mem[(address & 0xFFF) as usize..((address + 2) & 0xFFF) as usize]
.copy_from_slice(
&get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element / 2).to_be_bytes(),
);
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 2].copy_from_slice(
&get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element / 2).to_be_bytes(),
);
} else {
let end = element + 2;
while element < end {
@@ -1096,10 +1003,10 @@ pub fn slv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 4 == 0 {
device.rsp.mem[(address & 0xFFF) as usize..((address + 4) & 0xFFF) as usize]
.copy_from_slice(
&get_vpr32(device.rsp.cpu.vpr[rt(opcode) as usize], element / 4).to_be_bytes(),
);
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 4].copy_from_slice(
&get_vpr32(device.rsp.cpu.vpr[rt(opcode) as usize], element / 4).to_be_bytes(),
);
} else {
let end = element + 4;
while element < end {
@@ -1118,10 +1025,10 @@ pub fn sdv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 8 == 0 {
device.rsp.mem[(address & 0xFFF) as usize..((address + 8) & 0xFFF) as usize]
.copy_from_slice(
&get_vpr64(device.rsp.cpu.vpr[rt(opcode) as usize], element / 8).to_be_bytes(),
);
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 8].copy_from_slice(
&get_vpr64(device.rsp.cpu.vpr[rt(opcode) as usize], element / 8).to_be_bytes(),
);
} else {
let end = element + 8;
while element < end {
@@ -1140,7 +1047,8 @@ pub fn sqv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element == 0 && address % 16 == 0 {
device.rsp.mem[(address & 0xFFF) as usize..((address + 16) & 0xFFF) as usize]
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 16]
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
} else {
let end = element + (16 - (address & 15)) as u8;
@@ -1163,7 +1071,8 @@ pub fn srv(device: &mut device::Device, opcode: u32) {
address &= !15;
if element == 0 && base == 0 {
device.rsp.mem[(address & 0xFFF) as usize..((address + 16) & 0xFFF) as usize]
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 16]
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
} else {
while element < end {
@@ -1295,6 +1204,9 @@ pub fn stv(device: &mut device::Device, opcode: u32) {
}
}
pub fn reserved(_device: &mut device::Device, _opcode: u32) {
panic!("rsp su reserved")
pub fn special_reserved(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rd(opcode) as usize] =
device.rsp.cpu.gpr[rs(opcode) as usize] >> (device.rsp.cpu.gpr[rs(opcode) as usize] & 31)
}
pub fn reserved(_device: &mut device::Device, _opcode: u32) {}
+9 -9
View File
@@ -9,41 +9,41 @@ use crate::device::rsp_su_instructions::{get_vpr16, modify_vpr16};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
pub fn vt(opcode: u32) -> u32 {
fn vt(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn ve(opcode: u32) -> u32 {
fn ve(opcode: u32) -> u32 {
(opcode >> 21) & 0xF
}
pub fn vs(opcode: u32) -> u32 {
fn vs(opcode: u32) -> u32 {
(opcode >> 11) & 0x1F
}
pub fn vd(opcode: u32) -> u32 {
fn vd(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
pub fn de(opcode: u32) -> u32 {
fn de(opcode: u32) -> u32 {
(opcode >> 11) & 0x7
}
pub fn clamp_signed_32(value: i32) -> i16 {
fn clamp_signed_32(value: i32) -> i16 {
value.clamp(-32768, 32767) as i16
}
pub fn clamp_signed_64(value: i64) -> i16 {
fn clamp_signed_64(value: i64) -> i16 {
value.clamp(-32768, 32767) as i16
}
pub fn s_clip(x: i64, bits: u32) -> i64 {
fn s_clip(x: i64, bits: u32) -> i64 {
let b = 1_u64 << (bits - 1);
let m = b * 2 - 1;
((((x as u64) & m) ^ b).wrapping_sub(b)) as i64
}
pub fn vte(device: &device::Device, vt: u32, index: usize) -> __m128i {
fn vte(device: &device::Device, vt: u32, index: usize) -> __m128i {
unsafe {
_mm_shuffle_epi8(
device.rsp.cpu.vpr[vt as usize],
+8 -8
View File
@@ -14,13 +14,14 @@ pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Si {
pub regs: [u32; SI_REGS_COUNT as usize],
pub dma_dir: DmaDir,
@@ -31,10 +32,11 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.si.regs[((address & 0xFFFF) >> 2) as usize]
}
pub fn dma_read(device: &mut device::Device) {
fn dma_read(device: &mut device::Device) {
device.si.dma_dir = DmaDir::Read;
let duration = device::pif::update_pif_ram(device);
@@ -43,13 +45,12 @@ pub fn dma_read(device: &mut device::Device) {
device::events::create_event(
device,
device::events::EventType::SI,
device::events::EVENT_TYPE_SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + duration,
dma_event,
)
}
pub fn dma_write(device: &mut device::Device) {
fn dma_write(device: &mut device::Device) {
device.si.dma_dir = DmaDir::Write;
copy_pif_rdram(device);
@@ -58,9 +59,8 @@ pub fn dma_write(device: &mut device::Device) {
device::events::create_event(
device,
device::events::EventType::SI,
device::events::EVENT_TYPE_SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench
dma_event,
)
}
@@ -78,7 +78,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
//rdram is in native endian format, and pif memory is in big endian format
pub fn copy_pif_rdram(device: &mut device::Device) {
fn copy_pif_rdram(device: &mut device::Device) {
let dram_addr = device.si.regs[SI_DRAM_ADDR_REG as usize] as usize & device::rdram::RDRAM_MASK;
if device.si.dma_dir == DmaDir::Write {
let mut i = 0;
+4 -4
View File
@@ -1,12 +1,12 @@
use crate::device;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct TlbLut {
pub address: u64,
pub cached: bool,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct TlbEntry {
pub mask: u64,
pub vpn2: u64,
@@ -145,7 +145,7 @@ pub fn probe(device: &mut device::Device) {
}
}
pub fn tlb_unmap(device: &mut device::Device, index: u64) {
fn tlb_unmap(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 {
@@ -183,7 +183,7 @@ pub fn tlb_unmap(device: &mut device::Device, index: u64) {
}
}
pub fn tlb_map(device: &mut device::Device, index: u64) {
fn tlb_map(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0
+86 -44
View File
@@ -1,67 +1,80 @@
use crate::device;
use crate::ui;
use crate::{device, netplay, ui};
use governor::clock::Clock;
const VI_STATUS_REG: u32 = 0;
//const VI_ORIGIN_REG: u32 = 1;
pub const VI_WIDTH_REG: u32 = 2;
const VI_ORIGIN_REG: u32 = 1;
//const VI_WIDTH_REG: u32 = 2;
//const VI_V_INTR_REG: u32 = 3;
const VI_CURRENT_REG: u32 = 4;
//const VI_BURST_REG: u32 = 5;
const VI_V_SYNC_REG: u32 = 6;
const VI_H_SYNC_REG: u32 = 7;
//const VI_LEAP_REG: u32 = 8;
pub const VI_H_START_REG: u32 = 9;
pub const VI_V_START_REG: u32 = 10;
//const VI_H_START_REG: u32 = 9;
//const VI_V_START_REG: u32 = 10;
//const VI_V_BURST_REG: u32 = 11;
pub const VI_X_SCALE_REG: u32 = 12;
pub const VI_Y_SCALE_REG: u32 = 13;
//const VI_X_SCALE_REG: u32 = 12;
//const VI_Y_SCALE_REG: u32 = 13;
pub const VI_REGS_COUNT: u32 = 14;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Vi {
pub regs: [u32; VI_REGS_COUNT as usize],
pub clock: u64,
pub delay: u64,
pub field: u32,
#[serde(skip)]
pub limiter: Option<governor::DefaultDirectRateLimiter>,
pub count_per_scanline: u64,
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,
#[serde(skip)]
#[serde(default = "std::time::Instant::now")]
pub limit_freq_check: std::time::Instant,
}
//static mut FRAME_COUNTER: u64 = 0;
const LIMIT_BUFFER: u64 = 3;
const MAX_LIMIT_FREQ: u64 = 3;
pub fn set_expected_refresh_rate(device: &mut device::Device) {
let expected_refresh_rate = device.vi.clock as f64
/ (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as f64
/ ((device.vi.regs[VI_H_SYNC_REG as usize] & 0xFFF) + 1) as f64
* 2.0;
device.vi.frame_time = 1.0 / expected_refresh_rate;
device.vi.delay = (device.cpu.clock_rate as f64 / expected_refresh_rate) as u64;
device.vi.count_per_scanline =
device.vi.delay / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as u64;
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
(1.0 / expected_refresh_rate) * LIMIT_BUFFER as f64,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
create_limiter(device);
}
pub fn set_vertical_interrupt(device: &mut device::Device) {
if device::events::get_event(device, device::events::EventType::VI).is_none() {
fn create_limiter(device: &mut device::Device) {
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
device.vi.frame_time * device.vi.limit_freq as f64,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota));
let _ = device.vi.limiter.as_ref().unwrap().check();
//println!("new limit freq: {}", device.vi.limit_freq);
}
fn set_vertical_interrupt(device: &mut device::Device) {
if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() {
device::events::create_event(
device,
device::events::EventType::VI,
device::events::EVENT_TYPE_VI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.vi.delay,
vertical_interrupt_event,
)
}
}
pub fn set_current_line(device: &mut device::Device) {
fn set_current_line(device: &mut device::Device) {
let delay = device.vi.delay;
let next_vi = device::events::get_event(device, device::events::EventType::VI);
let next_vi = device::events::get_event(device, device::events::EVENT_TYPE_VI);
if next_vi.is_some() {
device.vi.regs[VI_CURRENT_REG as usize] = ((delay
- (next_vi.unwrap().count
@@ -88,6 +101,7 @@ pub fn read_regs(
if reg as u32 == VI_CURRENT_REG {
set_current_line(device)
}
device::cop0::add_cycles(device, 20);
device.vi.regs[reg as usize]
}
@@ -108,6 +122,13 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
set_expected_refresh_rate(device);
}
}
VI_ORIGIN_REG => {
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;
}
}
_ => {
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
}
@@ -116,19 +137,28 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
pub fn vertical_interrupt_event(device: &mut device::Device) {
device.cpu.running = ui::video::update_screen();
ui::video::update_screen();
ui::video::check_callback(device);
/*
unsafe {
FRAME_COUNTER += 1;
}
*/
let mut enable_speed_limiter = true;
if device.netplay.is_some() {
netplay::send_sync_check(device);
enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
}
device.vi.vi_counter += 1;
if device.vi.vi_counter % LIMIT_BUFFER == 0 {
if device.vi.vi_counter % device.vi.limit_freq == 0 && enable_speed_limiter {
speed_limiter(device);
}
/*
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;
@@ -136,9 +166,8 @@ pub fn vertical_interrupt_event(device: &mut device::Device) {
device::events::create_event(
device,
device::events::EventType::VI,
device::events::EVENT_TYPE_VI,
device.cpu.next_event_count + device.vi.delay,
vertical_interrupt_event,
)
}
@@ -148,27 +177,40 @@ pub fn init(device: &mut device::Device) {
} else {
device.vi.clock = 48681812
}
/*
std::thread::spawn(move || {
let mut last_count = 0;
loop {
unsafe {
println!("{:?}", FRAME_COUNTER - last_count);
last_count = FRAME_COUNTER;
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
*/
}
pub fn speed_limiter(device: &device::Device) {
fn speed_limiter(device: &mut device::Device) {
let result = device.vi.limiter.as_ref().unwrap().check();
if result.is_err() {
let outcome = result.unwrap_err();
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
spin_sleep::sleep(dur);
if dur < device.vi.min_wait_time {
device.vi.min_wait_time = dur;
}
let _ = device.vi.limiter.as_ref().unwrap().check();
} else {
device.vi.min_wait_time = std::time::Duration::from_secs(0);
}
if std::time::Instant::now()
.duration_since(device.vi.limit_freq_check)
.as_secs_f64()
> 1.0
{
if device.vi.min_wait_time.as_secs_f64() == 0.0 && device.vi.limit_freq < MAX_LIMIT_FREQ {
device.vi.limit_freq += 1;
create_limiter(device);
} else if device.vi.min_wait_time.as_secs_f64() > device.vi.frame_time
&& device.vi.limit_freq > 1
{
device.vi.limit_freq -= 1;
create_limiter(device);
}
//println!("limit freq: {}", device.vi.limit_freq);
device.vi.min_wait_time = std::time::Duration::from_secs(1);
device.vi.limit_freq_check = std::time::Instant::now();
}
}
+70 -35
View File
@@ -1,8 +1,14 @@
#![deny(warnings)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod device;
mod netplay;
mod savestates;
mod ui;
use clap::Parser;
use ui::gui;
/// N64 emulator
#[derive(Parser, Debug)]
@@ -18,6 +24,12 @@ struct Args {
help = "Create a new input profile (keyboard/gamepad mappings)."
)]
configure_input_profile: Option<String>,
#[arg(
short,
long,
help = "Use DirectInput when configuring a new input profile."
)]
use_dinput: bool,
#[arg(
short,
long,
@@ -37,7 +49,7 @@ struct Args {
value_name = "CONTROLLER_NUMBER",
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
)]
assign_controller: Option<u32>,
assign_controller: Option<i32>,
#[arg(
short,
long,
@@ -53,36 +65,27 @@ struct Args {
clear_input_bindings: bool,
}
fn main() {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
#[tokio::main]
async fn main() {
let dirs = ui::get_dirs();
let mut result = std::fs::create_dir_all(config_dir.clone());
let mut result = std::fs::create_dir_all(dirs.config_dir);
if result.is_err() {
panic!("could not create config dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(cache_dir.clone());
result = std::fs::create_dir_all(dirs.cache_dir.clone());
if result.is_err() {
panic!("could not create cache dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(data_dir.clone().join("saves"));
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
if result.is_err() {
panic!("could not create data dir: {}", result.err().unwrap())
panic!("could not create save dir: {}", result.err().unwrap())
}
let running_file = cache_dir.join("game_running");
result = std::fs::create_dir_all(dirs.data_dir.join("states"));
if result.is_err() {
panic!("could not create state dir: {}", result.err().unwrap())
}
let running_file = dirs.cache_dir.join("game_running");
if running_file.exists() {
result = std::fs::remove_file(running_file);
if result.is_err() {
@@ -93,11 +96,11 @@ fn main() {
let args = Args::parse();
let args_as_strings: Vec<String> = std::env::args().collect();
let args_count = args_as_strings.len();
if args_count > 1 {
let mut device = device::Device::new(config_dir);
if args_count > 1 && args.game.is_none() {
let mut ui = ui::Ui::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
ui::input::clear_bindings(&mut ui);
return;
}
if args.port.is_some() {
@@ -108,7 +111,10 @@ fn main() {
}
}
if args.list_controllers {
ui::input::list_controllers(&device.ui);
let controllers = gui::get_controller_names(&ui);
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {}: {}", i, controller);
}
return;
}
if args.assign_controller.is_some() {
@@ -117,7 +123,7 @@ fn main() {
return;
}
ui::input::assign_controller(
&mut device.ui,
&mut ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
@@ -129,7 +135,7 @@ fn main() {
return;
}
ui::input::bind_input_profile(
&mut device.ui,
&mut ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
@@ -137,27 +143,56 @@ fn main() {
}
if args.configure_input_profile.is_some() {
ui::input::configure_input_profile(
&mut device.ui,
&mut ui,
args.configure_input_profile.unwrap(),
args.use_dinput,
);
return;
}
if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
device::run_game(file_path, data_dir, &mut device, args.fullscreen);
} else if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
let rom_contents = device::get_rom_contents(file_path);
if rom_contents.is_empty() {
println!("Could not read rom file");
return;
}
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
device.ui.fullscreen = args.fullscreen;
device::run_game(rom_contents, &mut device);
})
.unwrap();
handle.join().unwrap();
} else {
let options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]),
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([640.0, 480.0])
.with_icon(
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
.unwrap(),
),
..Default::default()
};
let controllers_paths;
let controller_names;
{
let game_ui = ui::Ui::new();
controllers_paths = gui::get_controller_paths(&game_ui);
controller_names = gui::get_controller_names(&game_ui);
}
eframe::run_native(
"gopher64",
options,
Box::new(|cc| {
Ok(Box::new(ui::gui::GopherEguiApp::new(
cc, config_dir, cache_dir, data_dir,
cc,
controllers_paths,
controller_names,
)))
}),
)
+329
View File
@@ -0,0 +1,329 @@
use crate::{device, ui};
use eframe::egui;
use std::io::{Read, Write};
//UDP packet formats
const UDP_SEND_KEY_INFO: u8 = 0;
const UDP_RECEIVE_KEY_INFO: u8 = 1;
const UDP_REQUEST_KEY_INFO: u8 = 2;
const UDP_RECEIVE_KEY_INFO_GRATUITOUS: u8 = 3;
const UDP_SYNC_DATA: u8 = 4;
//TCP packet formats
const TCP_SEND_SAVE: u8 = 1;
//const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_SEND_SETTINGS: u8 = 3;
//const TCP_RECEIVE_SETTINGS: u8 = 4;
const TCP_REGISTER_PLAYER: u8 = 5;
const TCP_GET_REGISTRATION: u8 = 6;
const TCP_DISCONNECT_NOTICE: u8 = 7;
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
const CS4: u32 = 32;
pub struct Netplay {
udp_socket: std::net::UdpSocket,
tcp_stream: std::net::TcpStream,
pub player_number: u8,
pub player_data: [PlayerData; 4],
vi_counter: u32,
status: u8,
buffer_target: u8,
pub fast_forward: bool,
pub error_notifier: tokio::sync::mpsc::Sender<String>,
pub gui_ctx: egui::Context,
}
pub struct PlayerData {
lag: u8,
count: u32,
pub reg_id: u32,
input_events: std::collections::HashMap<u32, InputEvent>,
}
struct InputEvent {
input: u32,
plugin: u8,
}
fn send_error(netplay: &mut Netplay, error: String) {
netplay.error_notifier.try_send(error).unwrap();
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
}
pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) {
let mut request: Vec<u8> = [TCP_SEND_SAVE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
request.extend_from_slice(&(size as u32).to_be_bytes());
if size > 0 {
request.extend(save_data.to_owned());
}
netplay.tcp_stream.write_all(&request).unwrap();
}
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
netplay.tcp_stream.write_all(&request).unwrap();
let mut size: [u8; 4] = [0; 4];
netplay.tcp_stream.read_exact(&mut size).unwrap();
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
netplay.tcp_stream.read_exact(&mut response).unwrap();
*save_data = response;
}
pub fn send_sync_check(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
if netplay.vi_counter % 600 == 0 {
let mut request: Vec<u8> = [UDP_SYNC_DATA].to_vec();
request.extend_from_slice(&(netplay.vi_counter).to_be_bytes());
for i in 0..device::cop0::COP0_REGS_COUNT as usize {
request.extend_from_slice(&(device.cpu.cop0.regs[i] as u32).to_be_bytes());
}
netplay.udp_socket.send(&request).unwrap();
}
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
}
pub fn send_input(netplay: &Netplay, input: (u32, bool)) {
let mut request: Vec<u8> = [UDP_SEND_KEY_INFO].to_vec();
request.push(netplay.player_number);
request.extend_from_slice(
&(netplay.player_data[netplay.player_number as usize].count).to_be_bytes(),
);
request.extend_from_slice(&(input.0).to_be_bytes());
request.push(input.1 as u8);
netplay.udp_socket.send(&request).unwrap();
}
pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
let netplay = device.netplay.as_mut().unwrap();
let mut input = None;
let timeout = std::time::Instant::now() + std::time::Duration::from_secs(10);
let mut request_timer = std::time::Instant::now() - std::time::Duration::from_millis(5);
while input.is_none() {
process_incoming(netplay); // we execute process_incoming before request_input so that we send an accurate buffer count
if std::time::Instant::now() > request_timer {
// sends a request packet every 5ms
request_input(netplay, channel);
request_timer = std::time::Instant::now() + std::time::Duration::from_millis(5);
}
input = netplay.player_data[channel]
.input_events
.remove(&netplay.player_data[channel].count);
if std::time::Instant::now() > timeout {
send_error(
netplay,
"Timed out waiting for input. Lost connection to server".to_string(),
);
input = Some(InputEvent {
input: 0,
plugin: 0,
});
device.cpu.running = false;
}
}
netplay.fast_forward = netplay.player_data[channel].lag > 0
&& netplay.player_data[channel].input_events.len() as u8 > netplay.buffer_target;
netplay.player_data[channel].count = netplay.player_data[channel].count.wrapping_add(1);
(
input.as_ref().unwrap().input,
input.as_ref().unwrap().plugin != 0,
)
}
fn request_input(netplay: &Netplay, channel: usize) {
let mut request: Vec<u8> = [UDP_REQUEST_KEY_INFO].to_vec();
request.push(channel as u8); //The player we need input for
request.extend_from_slice(
&(netplay.player_data[netplay.player_number as usize].reg_id).to_be_bytes(),
);
request.extend_from_slice(&(netplay.player_data[channel].count).to_be_bytes());
request.push(0); //spectator mode
request.push(netplay.player_data[channel].input_events.len() as u8);
netplay.udp_socket.send(&request).unwrap();
}
fn process_incoming(netplay: &mut Netplay) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
match buf[0] {
UDP_RECEIVE_KEY_INFO | UDP_RECEIVE_KEY_INFO_GRATUITOUS => {
let player = buf[1] as usize;
//current_status is a status update from the server
//it will let us know if another player has disconnected, or the games have desynced
let current_status = buf[2];
if buf[0] == UDP_RECEIVE_KEY_INFO {
netplay.player_data[player].lag = buf[3];
}
if current_status != netplay.status {
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
send_error(
netplay,
format!("Players have desynced at VI {}", netplay.vi_counter),
);
}
for dis in 1..5 {
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
{
send_error(netplay, format!("Player {} has disconnected", dis));
}
}
netplay.status = current_status;
}
let mut buffer_offset = 5;
for _i in 0..buf[4] {
let count = u32::from_be_bytes(
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
);
buffer_offset += 4;
if (count.wrapping_sub(netplay.player_data[player].count)) > (u32::MAX / 2) {
//event doesn't need to be recorded
buffer_offset += 5;
continue;
}
let input = u32::from_be_bytes(
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
);
buffer_offset += 4;
let plugin = buf[buffer_offset];
buffer_offset += 1;
let input_event = InputEvent { input, plugin };
netplay.player_data[player]
.input_events
.insert(count, input_event);
}
}
_ => {
panic! {"unknown UDP packet"}
}
}
}
}
pub fn init(
mut peer_addr: std::net::SocketAddr,
session: ui::gui::gui_netplay::NetplayRoom,
player_number: u8,
error_notifier: tokio::sync::mpsc::Sender<String>,
gui_ctx: egui::Context,
) -> Netplay {
peer_addr.set_port(session.port.unwrap() as u16);
let udp_socket;
if peer_addr.is_ipv4() {
udp_socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address");
socket2::SockRef::from(&udp_socket)
.set_tos(CS4 << 2)
.unwrap();
} else {
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address");
#[cfg(not(target_os = "windows"))]
socket2::SockRef::from(&udp_socket)
.set_tclass_v6(CS4 << 2)
.unwrap();
};
udp_socket.connect(peer_addr).unwrap();
udp_socket.set_nonblocking(true).unwrap();
let mut stream = std::net::TcpStream::connect(peer_addr).unwrap();
let regid = (player_number + 1) as u32;
let mut request: [u8; 8] = [
TCP_REGISTER_PLAYER,
player_number,
0, //plugin/pak
0, //rawdata
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
];
request[4..8].copy_from_slice(&regid.to_be_bytes());
stream.write_all(&request).unwrap();
let mut response: [u8; 2] = [0, 0];
stream.read_exact(&mut response).unwrap();
if response[0] != 1 {
panic!("Failed to register player");
}
let buffer_target = response[1];
let request: [u8; 1] = [TCP_GET_REGISTRATION];
stream.write_all(&request).unwrap();
let mut response: [u8; 24] = [0; 24];
stream.read_exact(&mut response).unwrap();
let mut reg_id: [u32; 4] = [0; 4];
for i in 0..4 {
// reg_id of 0 means no player connected
reg_id[i] = u32::from_be_bytes(response[(i * 6)..(i * 6) + 4].try_into().unwrap());
}
Netplay {
udp_socket,
tcp_stream: stream,
player_number,
vi_counter: 0,
status: 0,
buffer_target,
fast_forward: false,
error_notifier,
gui_ctx,
player_data: [
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[0],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[1],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[2],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[3],
input_events: std::collections::HashMap::new(),
},
],
}
}
pub fn close(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
let regid = (netplay.player_number + 1) as u32;
let mut request: [u8; 5] = [TCP_DISCONNECT_NOTICE, 0, 0, 0, 0];
request[1..5].copy_from_slice(&regid.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
netplay.tcp_stream.flush().unwrap();
netplay
.tcp_stream
.shutdown(std::net::Shutdown::Both)
.unwrap();
}
+199
View File
@@ -0,0 +1,199 @@
use crate::{device, ui};
#[cfg(target_arch = "aarch64")]
use device::__m128i;
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeSeq, Serializer};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
struct M128iArrayVisitor<const N: usize>;
impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor<N> {
type Value = [__m128i; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(&format!("an array of {} 128-bit integers", N))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut arr: [__m128i; N] = [device::zero_m128i(); N];
for (index, item) in arr.iter_mut().enumerate().take(N) {
match seq.next_element::<u128>()? {
Some(value) => *item = unsafe { std::mem::transmute::<u128, __m128i>(value) },
None => return Err(serde::de::Error::invalid_length(index, &self)),
}
}
Ok(arr)
}
}
pub fn deserialize_m128i_array<'de, D, const N: usize>(
deserializer: D,
) -> Result<[__m128i; N], D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(M128iArrayVisitor)
}
pub fn serialize_m128i<S>(data: &__m128i, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes: u128 = unsafe { std::mem::transmute(*data) };
bytes.serialize(serializer)
}
pub fn deserialize_m128i<'de, D>(deserializer: D) -> Result<__m128i, D::Error>
where
D: Deserializer<'de>,
{
let bytes = u128::deserialize(deserializer)?;
Ok(unsafe { std::mem::transmute::<u128, __m128i>(bytes) })
}
pub fn serialize_m128i_array<S>(value: &[__m128i], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(value.len()))?;
for item in value {
let bytes: u128 = unsafe { std::mem::transmute(*item) };
seq.serialize_element(&bytes)?;
}
seq.end()
}
pub fn create_savestate(device: &device::Device) {
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
(&postcard::to_stdvec(&device.ui.saves).unwrap(), "saves"),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(device.ui.paths.savestate_file_path.clone(), compressed_file).unwrap();
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&mut device.ui.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
device.ui.saves = postcard::from_bytes(&save_bytes).unwrap();
device.cpu = state.cpu;
device.pif = state.pif;
let rom = device.cart.rom.clone();
device.cart = state.cart;
device.cart.rom = rom;
device.memory = state.memory;
device.rsp = state.rsp;
device.rdp = state.rdp;
device.rdram = state.rdram;
device.mi = state.mi;
device.pi = state.pi;
device.vi = state.vi;
device.ai = state.ai;
device.si = state.si;
device.ri = state.ri;
device.vru = state.vru;
device::memory::init(device);
device::vi::set_expected_refresh_rate(device);
device::cpu::map_instructions(device);
device::cop0::map_instructions(device);
device::cop1::map_instructions(device);
device::cop2::map_instructions(device);
device::rsp_cpu::map_instructions(device);
let mut mem_addr = 0x1000;
while mem_addr < 0x2000 {
let data =
u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap());
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func =
device::rsp_cpu::decode_opcode(device, data);
device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data;
mem_addr += 4;
}
for line_index in 0..512 {
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
}
}
}
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device);
}
}
pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) {
device::controller::mempak::read
}
pub fn default_instruction() -> fn(&mut device::Device, u32) {
device::cop0::reserved
}
pub fn default_instructions<const N: usize>() -> [fn(&mut device::Device, u32); N]
where
[fn(&mut device::Device, u32); N]: Sized,
{
[device::cop0::reserved; N]
}
pub fn default_memory_read(
) -> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::rdram::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::rdram::write_mem; 0x2000]
}
+104 -47
View File
@@ -6,75 +6,109 @@ pub mod storage;
pub mod video;
pub mod vru;
pub struct Dirs {
pub config_dir: std::path::PathBuf,
pub cache_dir: std::path::PathBuf,
pub data_dir: std::path::PathBuf,
}
pub struct Ui {
pub dirs: Dirs,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
pub config_file_path: std::path::PathBuf,
pub config: config::Config,
pub save_type: Vec<storage::SaveTypes>,
pub game_id: String,
pub game_hash: String,
pub paths: storage::Paths,
pub saves: storage::Saves,
pub sdl_context: Option<sdl2::Sdl>,
pub video_subsystem: Option<sdl2::VideoSubsystem>,
pub audio_subsystem: Option<sdl2::AudioSubsystem>,
pub joystick_subsystem: Option<sdl2::JoystickSubsystem>,
#[allow(dead_code)]
pub controller_subsystem: Option<sdl2::GameControllerSubsystem>,
pub window: Option<sdl2::video::Window>,
pub audio_device: Option<sdl2::audio::AudioQueue<i16>>,
pub pak_audio: audio::PakAudio,
pub window: *mut sdl3_sys::video::SDL_Window,
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_device: u32,
pub num_joysticks: i32,
pub fullscreen: bool,
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
pub with_sdl: bool,
}
impl Drop for Ui {
fn drop(&mut self) {
write_config(self);
if self.with_sdl {
unsafe {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
}
}
}
fn write_config(ui: &Ui) {
let f = std::fs::File::create(ui.config_file_path.clone()).unwrap();
serde_json::to_writer_pretty(f, &ui.config).unwrap();
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
unsafe {
let init = sdl3_sys::init::SDL_WasInit(0);
if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
.to_str()
.unwrap();
panic!("Could not initialize SDL subsystem: {}, {}", flag, err);
}
}
}
pub fn get_dirs() -> Dirs {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
Dirs {
config_dir,
cache_dir,
data_dir,
}
}
impl Ui {
pub fn new(config_dir: std::path::PathBuf) -> Ui {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let audio_subsystem = sdl_context.audio().unwrap();
let joystick_subsystem = sdl_context.joystick().unwrap();
let controller_subsystem = sdl_context.game_controller().unwrap();
let config_file_path = config_dir.join("config.json");
let config_file = std::fs::read(config_file_path.clone());
let mut config_map = config::Config::new();
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if result.is_ok() {
config_map = result.unwrap();
}
}
fn construct_ui(num_joysticks: i32, joysticks: *mut u32, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
controllers: [
input::Controllers {
game_controller: None,
joystick: None,
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: None,
joystick: None,
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: None,
joystick: None,
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: None,
joystick: None,
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
],
config_file_path,
config: config_map,
keyboard_state: std::ptr::null_mut(),
config: config::Config::new(),
save_type: vec![],
game_id: String::new(),
game_hash: String::new(),
@@ -83,22 +117,45 @@ impl Ui {
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
sdcard_file_path: std::path::PathBuf::new(),
romsave_file_path: std::path::PathBuf::new(),
savestate_file_path: std::path::PathBuf::new(),
},
saves: storage::Saves {
eeprom: (Vec::new(), false),
sram: (Vec::new(), false),
flash: (Vec::new(), false),
mempak: (Vec::new(), false),
romsave: (serde_json::Map::new(), false),
sdcard: (Vec::new(), false),
romsave: (std::collections::HashMap::new(), false),
},
sdl_context: Some(sdl_context),
video_subsystem: Some(video_subsystem),
audio_subsystem: Some(audio_subsystem),
joystick_subsystem: Some(joystick_subsystem),
controller_subsystem: Some(controller_subsystem),
window: None,
audio_device: None,
pak_audio: audio::PakAudio {
mempak: include_bytes!("../data/mempak.wav").to_vec(),
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
},
window: std::ptr::null_mut(),
audio_stream: std::ptr::null_mut(),
pak_audio_stream: std::ptr::null_mut(),
audio_device: 0,
fullscreen: false,
num_joysticks,
joysticks,
dirs,
with_sdl,
}
}
pub fn default() -> Ui {
Self::construct_ui(0, std::ptr::null_mut(), false)
}
pub fn new() -> Ui {
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
let mut num_joysticks = 0;
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
if joysticks.is_null() {
panic!("Could not get joystick list");
}
Self::construct_ui(num_joysticks, joysticks, true)
}
}
+113 -20
View File
@@ -1,24 +1,100 @@
use crate::device;
use crate::ui;
pub struct PakAudio {
pub mempak: Vec<u8>,
pub rumblepak: Vec<u8>,
}
pub fn init(ui: &mut ui::Ui, frequency: u64) {
let desired_spec = sdl2::audio::AudioSpecDesired {
freq: Some(frequency as i32),
channels: Some(2),
samples: None,
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: frequency as i32,
channels: 2,
};
ui.audio_device = Some(
ui.audio_subsystem
.as_mut()
.unwrap()
.open_queue::<i16, _>(None, &desired_spec)
.unwrap(),
);
ui.audio_device.as_mut().unwrap().resume();
ui.audio_device = unsafe {
sdl3_sys::audio::SDL_OpenAudioDevice(
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&audio_spec,
)
};
if ui.audio_device == 0 {
panic!("Could not open audio device");
}
let mut dst = Default::default();
if !unsafe {
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
} {
panic!("Could not get audio device format");
}
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
if ui.audio_stream.is_null() {
return;
}
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
panic!("Could not bind audio stream");
}
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: 24000,
channels: 1,
};
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
panic!("Could not bind audio stream");
}
}
pub fn close(ui: &mut ui::Ui) {
unsafe {
if !ui.audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio_stream);
ui.audio_stream = std::ptr::null_mut();
}
if !ui.pak_audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.pak_audio_stream);
ui.pak_audio_stream = std::ptr::null_mut();
}
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio_device);
ui.audio_device = 0;
}
}
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
if ui.pak_audio_stream.is_null() {
return;
}
let sound;
if pak == device::controller::PakType::RumblePak {
sound = &ui.pak_audio.rumblepak;
} else if pak == device::controller::PakType::MemPak {
sound = &ui.pak_audio.mempak;
} else {
return;
}
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
ui.pak_audio_stream,
sound.as_ptr() as *const std::ffi::c_void,
sound.len() as i32,
)
} {
panic!("Could not play audio");
}
}
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let audio_device = device.ui.audio_device.as_mut().unwrap();
if device.ui.audio_stream.is_null() {
return;
}
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
let mut i = 0;
while i < length as usize / 2 {
@@ -32,16 +108,33 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
i += 2;
}
let audio_queued = audio_device.size() as f64;
let acceptable_latency = (audio_device.spec().freq as f64 * 0.2) * 4.0;
let min_latency = (audio_device.spec().freq as f64 * 0.02) * 4.0;
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64;
let acceptable_latency = (device.ai.freq as f64 * 0.2) * 4.0;
let min_latency = (device.ai.freq as f64 * 0.02) * 4.0;
if audio_queued < min_latency {
let silence_buffer: Vec<i16> = vec![0; ((min_latency - audio_queued) * 2.0) as usize & !1];
audio_device.queue_audio(&silence_buffer).unwrap();
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
silence_buffer.as_ptr() as *const std::ffi::c_void,
silence_buffer.len() as i32,
)
} {
panic!("Could not play audio");
}
}
if audio_queued < acceptable_latency {
audio_device.queue_audio(&primary_buffer).unwrap();
if audio_queued < acceptable_latency
&& !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
}
{
panic!("Could not play audio");
}
}
+36 -7
View File
@@ -2,12 +2,13 @@ use crate::ui;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct InputProfile {
pub keys: [(bool, i32); 18],
pub controller_buttons: [(bool, i32); 14],
pub controller_axis: [(bool, i32, i16); 18],
pub joystick_buttons: [(bool, u32); 14],
pub joystick_hat: [(bool, u32, u8); 14],
pub joystick_axis: [(bool, u32, i16); 18],
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
pub joystick_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
pub joystick_hat: [(bool, i32, u8); ui::input::PROFILE_SIZE],
pub joystick_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
pub dinput: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -21,6 +22,8 @@ pub struct Input {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Video {
pub upscale: bool,
pub integer_scaling: bool,
pub fullscreen: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Config {
@@ -28,8 +31,30 @@ pub struct Config {
pub video: Video,
}
impl Drop for Config {
fn drop(&mut self) {
write_config(self);
}
}
fn write_config(config: &Config) {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let f = std::fs::File::create(file_path).unwrap();
serde_json::to_writer_pretty(f, &config).unwrap();
}
impl Config {
pub fn new() -> Config {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let config_file = std::fs::read(file_path);
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if let Ok(result) = result {
return result;
}
}
let mut input_profiles = std::collections::HashMap::new();
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
Config {
@@ -45,7 +70,11 @@ impl Config {
controller_enabled: [true, false, false, false],
emulate_vru: false,
},
video: Video { upscale: false },
video: Video {
upscale: false,
integer_scaling: false,
fullscreen: false,
},
}
}
}
+373 -217
View File
@@ -1,253 +1,431 @@
use crate::device;
use crate::ui;
use crate::{device, netplay, ui};
use eframe::egui;
pub mod gui_netplay;
pub struct GopherEguiApp {
config_dir: std::path::PathBuf,
cache_dir: std::path::PathBuf,
data_dir: std::path::PathBuf,
dirs: ui::Dirs,
configure_profile: bool,
profile_name: String,
controllers: Vec<String>,
controller_names: Vec<String>,
selected_controller: [i32; 4],
selected_profile: [String; 4],
input_profiles: Vec<String>,
controller_enabled: [bool; 4],
controller_paths: Vec<String>,
upscale: bool,
integer_scaling: bool,
fullscreen: bool,
emulate_vru: bool,
dinput: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<std::sync::mpsc::Receiver<Vec<String>>>,
vru_word_notifier: Option<std::sync::mpsc::Sender<String>>,
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
netplay_error_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
pub netplay: gui_netplay::GuiNetplay,
}
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
struct SaveConfig {
selected_controller: [i32; 4],
selected_profile: [String; 4],
controller_enabled: [bool; 4],
upscale: bool,
integer_scaling: bool,
fullscreen: bool,
emulate_vru: bool,
}
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
let mut profiles = vec![];
for key in game_ui.config.input.input_profiles.keys() {
profiles.push((*key).clone())
for key in config.input.input_profiles.keys() {
profiles.push(key.clone())
}
profiles
}
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
for i in 0..num_joysticks {
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
for offset in 0..game_ui.num_joysticks as isize {
let name = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
*(game_ui.joysticks.offset(offset)),
))
};
controllers.push(name.to_string_lossy().to_string());
}
controllers
}
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
let mut controller_paths: Vec<String> = vec![];
for offset in 0..game_ui.num_joysticks as isize {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(game_ui.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
};
controller_paths.push(path);
}
controller_paths
}
impl GopherEguiApp {
pub fn new(
cc: &eframe::CreationContext<'_>,
config_dir: std::path::PathBuf,
cache_dir: std::path::PathBuf,
data_dir: std::path::PathBuf,
controller_paths: Vec<String>,
controller_names: Vec<String>,
) -> GopherEguiApp {
add_japanese_font(&cc.egui_ctx);
let game_ui = ui::Ui::new(config_dir.clone());
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
let mut guids: Vec<String> = vec![];
for i in 0..num_joysticks {
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
}
add_fonts(&cc.egui_ctx);
let config = ui::config::Config::new();
let mut selected_controller = [-1, -1, -1, -1];
for (pos, item) in game_ui
.config
.input
.controller_assignment
.iter()
.enumerate()
{
for (pos, item) in config.input.controller_assignment.iter().enumerate() {
if item.is_some() {
for (guid_pos, guid) in guids.iter().enumerate() {
if item.as_deref().unwrap() == *guid {
selected_controller[pos] = guid_pos as i32;
for (path_pos, path) in controller_paths.iter().enumerate() {
if item.as_deref().unwrap() == *path {
selected_controller[pos] = path_pos as i32;
break;
}
}
}
}
GopherEguiApp {
cache_dir: cache_dir.clone(),
config_dir: config_dir.clone(),
data_dir: data_dir.clone(),
configure_profile: false,
profile_name: "".to_string(),
selected_profile: game_ui.config.input.input_profile_binding.clone(),
selected_profile: config.input.input_profile_binding.clone(),
selected_controller,
controllers: get_controllers(&game_ui),
input_profiles: get_input_profiles(&game_ui),
controller_enabled: game_ui.config.input.controller_enabled,
upscale: game_ui.config.video.upscale,
emulate_vru: game_ui.config.input.emulate_vru,
controller_names,
input_profiles: get_input_profiles(&config),
controller_enabled: config.input.controller_enabled,
upscale: config.video.upscale,
integer_scaling: config.video.integer_scaling,
fullscreen: config.video.fullscreen,
emulate_vru: config.input.emulate_vru,
show_vru_dialog: false,
dinput: false,
controller_paths,
netplay_error_receiver: None,
vru_window_receiver: None,
vru_word_notifier: None,
vru_word_list: Vec::new(),
netplay: Default::default(),
dirs: ui::get_dirs(),
}
}
}
fn save_config(
game_ui: &mut ui::Ui,
selected_controller: [i32; 4],
selected_profile: [String; 4],
controller_enabled: [bool; 4],
upscale: bool,
emulate_vru: bool,
config: &mut ui::config::Config,
controller_paths: Vec<String>,
save_config_items: SaveConfig,
) {
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
for (pos, item) in selected_controller.iter().enumerate() {
for (pos, item) in save_config_items.selected_controller.iter().enumerate() {
if *item != -1 {
game_ui.config.input.controller_assignment[pos] = Some(
joystick_subsystem
.device_guid(*item as u32)
.unwrap()
.to_string(),
);
config.input.controller_assignment[pos] =
Some(controller_paths[*item as usize].clone());
} else {
game_ui.config.input.controller_assignment[pos] = None
config.input.controller_assignment[pos] = None
}
}
game_ui.config.input.input_profile_binding = selected_profile;
game_ui.config.input.controller_enabled = controller_enabled;
config.input.input_profile_binding = save_config_items.selected_profile;
config.input.controller_enabled = save_config_items.controller_enabled;
game_ui.config.video.upscale = upscale;
game_ui.config.input.emulate_vru = emulate_vru;
config.video.upscale = save_config_items.upscale;
config.video.integer_scaling = save_config_items.integer_scaling;
config.video.fullscreen = save_config_items.fullscreen;
config.input.emulate_vru = save_config_items.emulate_vru;
}
impl Drop for GopherEguiApp {
fn drop(&mut self) {
let mut game_ui = ui::Ui::new(self.config_dir.clone());
let save_config_items = SaveConfig {
selected_controller: self.selected_controller,
selected_profile: self.selected_profile.clone(),
controller_enabled: self.controller_enabled,
upscale: self.upscale,
integer_scaling: self.integer_scaling,
fullscreen: self.fullscreen,
emulate_vru: self.emulate_vru,
};
let mut config = ui::config::Config::new();
save_config(
&mut game_ui,
self.selected_controller,
self.selected_profile.clone(),
self.controller_enabled,
self.upscale,
self.emulate_vru,
&mut config,
self.controller_paths.clone(),
save_config_items,
);
}
}
fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name:");
ui.text_edit_singleline(&mut app.profile_name)
.labelled_by(name_label.id);
});
ui.checkbox(&mut app.dinput, "Use DirectInput");
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = app.profile_name.clone();
let dinput = app.dinput;
std::thread::spawn(move || {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
});
app.configure_profile = false;
if !app.profile_name.is_empty()
&& !app.input_profiles.contains(&app.profile_name)
{
app.input_profiles.push(app.profile_name.clone())
}
};
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
app.configure_profile = false
};
})
});
});
}
fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("vru_dialog"),
egui::ViewportBuilder::default()
.with_title("What would you like to say?")
.with_always_on_top(),
|ctx, class| {
assert!(
class == egui::ViewportClass::Immediate,
"This egui backend doesn't support multiple viewports"
);
egui::CentralPanel::default().show(ctx, |ui| {
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in app.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
}
});
});
if ctx.input(|i| i.viewport().close_requested()) {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
}
},
);
}
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context) {
let netplay;
let selected_controller = app.selected_controller;
let selected_profile = app.selected_profile.clone();
let controller_enabled = app.controller_enabled;
let upscale = app.upscale;
let integer_scaling = app.integer_scaling;
let fullscreen = app.fullscreen;
let emulate_vru = app.emulate_vru;
let peer_addr;
let session;
let player_number;
let cache_dir = app.dirs.cache_dir.clone();
let controller_paths = app.controller_paths.clone();
if app.netplay.player_name.is_empty() {
netplay = false;
peer_addr = None;
session = None;
player_number = None;
} else {
netplay = true;
peer_addr = app.netplay.peer_addr;
session = app.netplay.waiting_session.clone();
player_number = Some(app.netplay.player_number);
}
let (netplay_error_notifier, netplay_error_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
let (vru_window_notifier, vru_window_receiver): (
tokio::sync::mpsc::Sender<Vec<String>>,
tokio::sync::mpsc::Receiver<Vec<String>>,
) = tokio::sync::mpsc::channel(1);
let (vru_word_notifier, vru_word_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
if netplay {
app.netplay_error_receiver = Some(netplay_error_receiver);
}
if emulate_vru && !netplay {
app.vru_window_receiver = Some(vru_window_receiver);
app.vru_word_notifier = Some(vru_word_notifier);
} else {
app.vru_window_receiver = None;
app.vru_word_notifier = None;
}
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
let mut task = None;
if !netplay {
task = Some(rfd::AsyncFileDialog::new().pick_file());
}
tokio::spawn(async move {
let file = if !netplay { task.unwrap().await } else { None };
std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
upscale,
integer_scaling,
fullscreen,
emulate_vru,
};
if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
netplay_error_notifier,
gui_ctx,
));
device.ui.fullscreen = fullscreen;
device::run_game(rom_contents, &mut device);
netplay::close(&mut device);
} else {
if emulate_vru {
device.vru_window.window_notifier = Some(vru_window_notifier);
device.vru_window.word_receiver = Some(vru_word_receiver);
device.vru_window.gui_ctx = Some(gui_ctx);
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device.ui.fullscreen = fullscreen;
device::run_game(rom_contents, &mut device);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
})
.unwrap();
});
}
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.netplay.create {
gui_netplay::netplay_create(self, ctx);
}
if self.netplay.join {
gui_netplay::netplay_join(self, ctx);
}
if self.netplay.wait {
gui_netplay::netplay_wait(self, ctx);
}
if self.netplay_error_receiver.is_some() {
let result = self.netplay_error_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.netplay.error = result.unwrap();
}
}
if !self.netplay.error.is_empty() {
gui_netplay::netplay_error(self, ctx, self.netplay.error.clone());
}
if self.configure_profile {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name: ");
ui.text_edit_singleline(&mut self.profile_name)
.labelled_by(name_label.id);
});
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = self.profile_name.clone();
let config_dir = self.config_dir.clone();
execute(async {
let mut game_ui = ui::Ui::new(config_dir);
ui::input::configure_input_profile(&mut game_ui, profile_name);
});
self.configure_profile = false;
if !self.profile_name.is_empty()
&& !self.input_profiles.contains(&self.profile_name)
{
self.input_profiles.push(self.profile_name.clone())
}
};
if ui.button("Close").clicked() {
self.configure_profile = false
};
});
});
configure_profile(self, ctx);
}
egui::CentralPanel::default().show(ctx, |ui| {
if self.configure_profile {
ui.disable()
}
egui::Grid::new("button_grid")
.min_col_width(200.0)
.show(ui, |ui| {
if ui.button("Open ROM").clicked() {
open_rom(self, ctx);
}
if ui.button("Netplay: Create Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.create = true;
}
if ui.button("Open ROM").clicked() {
// Spawn dialog on main thread
let task = rfd::AsyncFileDialog::new().pick_file();
let selected_controller = self.selected_controller;
let selected_profile = self.selected_profile.clone();
let controller_enabled = self.controller_enabled;
let upscale = self.upscale;
let emulate_vru = self.emulate_vru;
let config_dir = self.config_dir.clone();
let cache_dir = self.cache_dir.clone();
let data_dir = self.data_dir.clone();
ui.end_row();
let (vru_window_notifier, vru_window_receiver): (
std::sync::mpsc::Sender<Vec<String>>,
std::sync::mpsc::Receiver<Vec<String>>,
) = std::sync::mpsc::channel();
if ui.button("Configure Input Profile").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.configure_profile = true;
}
let (vru_word_notifier, vru_word_receiver): (
std::sync::mpsc::Sender<String>,
std::sync::mpsc::Receiver<String>,
) = std::sync::mpsc::channel();
if emulate_vru {
self.vru_window_receiver = Some(vru_window_receiver);
self.vru_word_notifier = Some(vru_word_notifier);
}
let gui_ctx = ctx.clone();
execute(async move {
let file = task.await;
if let Some(file) = file {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new(config_dir);
save_config(
&mut device.ui,
selected_controller,
selected_profile,
controller_enabled,
upscale,
emulate_vru,
);
if emulate_vru {
device.vru.window_notifier = Some(vru_window_notifier);
device.vru.word_receiver = Some(vru_word_receiver);
device.vru.gui_ctx = Some(gui_ctx);
}
device::run_game(
std::path::Path::new(file.path()),
data_dir,
&mut device,
false,
);
let result = std::fs::remove_file(running_file.clone());
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
if ui.button("Netplay: Join Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.join = true;
}
});
}
if ui.button("Configure Input Profile").clicked() {
if self.cache_dir.join("game_running").exists() {
return;
}
self.configure_profile = true;
}
ui.add_space(32.0);
ui.label("Controller Config:");
@@ -276,7 +454,7 @@ impl eframe::App for GopherEguiApp {
let controller_text = if self.selected_controller[i] == -1 {
"None".to_string()
} else {
self.controllers[self.selected_controller[i] as usize].clone()
self.controller_names[self.selected_controller[i] as usize].clone()
};
egui::ComboBox::from_id_salt(format!("controller-combo-{}", i))
.selected_text(controller_text)
@@ -286,11 +464,11 @@ impl eframe::App for GopherEguiApp {
-1,
"None".to_string(),
);
for j in 0..self.controllers.len() {
for j in 0..self.controller_names.len() {
ui.selectable_value(
&mut self.selected_controller[i],
j as i32,
self.controllers[j].clone(),
self.controller_names[j].clone(),
);
}
});
@@ -299,16 +477,21 @@ impl eframe::App for GopherEguiApp {
});
ui.add_space(32.0);
ui.checkbox(&mut self.upscale, "High-Res Graphics");
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
ui.add_space(32.0);
ui.checkbox(
&mut self.emulate_vru,
"Emulate VRU (connects VRU to controller port 4)",
);
ui.add_space(32.0);
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
});
if self.emulate_vru && self.vru_window_receiver.is_some() {
let result = self.vru_window_receiver.as_ref().unwrap().try_recv();
let result = self.vru_window_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.show_vru_dialog = true;
self.vru_word_list = result.unwrap();
@@ -316,53 +499,26 @@ impl eframe::App for GopherEguiApp {
}
if self.show_vru_dialog {
ctx.show_viewport_immediate(
egui::ViewportId::from_hash_of("vru_dialog"),
egui::ViewportBuilder::default()
.with_title("What would you like to say?")
.with_always_on_top(),
|ctx, class| {
assert!(
class == egui::ViewportClass::Immediate,
"This egui backend doesn't support multiple viewports"
);
egui::CentralPanel::default().show(ctx, |ui| {
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in self.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
self.vru_word_notifier
.as_ref()
.unwrap()
.send(v.clone())
.unwrap();
self.show_vru_dialog = false;
}
}
});
});
if ctx.input(|i| i.viewport().close_requested()) {
self.vru_word_notifier
.as_ref()
.unwrap()
.send(String::from(""))
.unwrap();
self.show_vru_dialog = false;
}
},
);
show_vru_dialog(self, ctx);
}
}
}
fn execute<F: std::future::Future<Output = ()> + Send + 'static>(f: F) {
std::thread::spawn(move || futures::executor::block_on(f));
}
fn add_japanese_font(ctx: &egui::Context) {
fn add_fonts(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"regular_font",
egui::FontData::from_static(include_bytes!("../../data/Roboto-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Highest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Highest,
},
],
));
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
+875
View File
@@ -0,0 +1,875 @@
use crate::device;
use crate::ui::gui::GopherEguiApp;
use crate::ui::{self, gui};
use eframe::egui;
use sha2::{Digest, Sha256};
const NETPLAY_VERSION: i32 = 17;
const EMU_NAME: &str = "gopher64";
type GameInfo = (String, String, String, Vec<u8>);
#[derive(Default)]
pub struct GuiNetplay {
pub create: bool,
pub join: bool,
pub wait: bool,
pub session_name: String,
pub password: String,
pub player_name: String,
pub error: String,
pub create_rom_label: String,
pub join_rom_label: String,
pub send_chat: bool,
pub have_sessions: Option<(String, String)>,
pub begin_game: bool,
pub chat_log: String,
pub chat_message: String,
pub selected_session: Option<NetplayRoom>,
pub pending_begin: bool,
pub peer_addr: Option<std::net::SocketAddr>,
pub motd: String,
pub sessions: Vec<NetplayRoom>,
pub rom_contents: Vec<u8>,
pub player_number: u8,
pub player_names: [String; 4],
pub server: (String, String),
pub socket_waiting: bool,
pub game_info: GameInfo,
pub servers: std::collections::HashMap<String, String>,
pub waiting_session: Option<NetplayRoom>,
pub socket:
Option<tungstenite::WebSocket<tungstenite::stream::MaybeTlsStream<std::net::TcpStream>>>,
pub server_receiver:
Option<tokio::sync::mpsc::Receiver<std::collections::HashMap<String, String>>>,
pub game_info_receiver: Option<tokio::sync::mpsc::Receiver<GameInfo>>,
pub broadcast_socket: Option<std::net::UdpSocket>,
pub broadcast_timer: Option<std::time::Instant>,
}
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Default, Clone)]
pub struct NetplayRoom {
room_name: Option<String>,
password: Option<String>,
protected: Option<bool>,
#[serde(rename = "MD5")]
md5: Option<String>,
game_name: Option<String>,
pub port: Option<i32>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct NetplayMessage {
#[serde(rename = "type")]
message_type: String,
player_name: Option<String>,
client_sha: Option<String>,
netplay_version: Option<i32>,
emulator: Option<String>,
room: Option<NetplayRoom>,
accept: Option<i32>,
message: Option<String>,
auth: Option<String>,
player_names: Option<[String; 4]>,
#[serde(rename = "authTime")]
auth_time: Option<String>,
rooms: Option<Vec<NetplayRoom>>,
}
fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.servers.is_empty() {
if app.netplay.broadcast_socket.is_none() {
app.netplay.broadcast_socket = Some(
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address"),
);
let socket = app.netplay.broadcast_socket.as_ref().unwrap();
socket
.set_broadcast(true)
.expect("set_broadcast call failed");
socket
.set_nonblocking(true)
.expect("could not set up socket");
let data: [u8; 1] = [1];
socket
.send_to(&data, (std::net::Ipv4Addr::BROADCAST, 45000))
.expect("couldn't send data");
app.netplay.broadcast_timer =
Some(std::time::Instant::now() + std::time::Duration::from_secs(5));
ctx.request_repaint();
}
if app.netplay.server_receiver.is_none() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.server_receiver = Some(rx);
let gui_ctx = ctx.clone();
let task = reqwest::get("https://m64p.s3.amazonaws.com/servers.json");
tokio::spawn(async move {
let response = task.await;
if let Ok(response) = response {
tx.send(response.json().await.unwrap()).await.unwrap();
gui_ctx.request_repaint();
}
});
}
}
if app.netplay.broadcast_timer.is_some()
&& std::time::Instant::now() > app.netplay.broadcast_timer.unwrap()
{
app.netplay.broadcast_timer = None;
}
if app.netplay.broadcast_socket.is_some() && app.netplay.broadcast_timer.is_some() {
let mut buffer = [0; 1024];
let result = app
.netplay
.broadcast_socket
.as_ref()
.unwrap()
.recv_from(&mut buffer);
if result.is_ok() {
let (amt, _src) = result.unwrap();
let data: std::collections::HashMap<String, String> =
serde_json::from_slice(&buffer[..amt]).unwrap();
for server in data.iter() {
let (server_name, server_ip) = server;
app.netplay
.servers
.insert(server_name.to_string(), server_ip.to_string());
app.netplay.server = (server.0.clone(), server.1.clone());
}
app.netplay.broadcast_socket = None;
}
ctx.request_repaint();
}
if app.netplay.server_receiver.is_some() {
let result = app.netplay.server_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.servers.extend(result.unwrap());
app.netplay.server_receiver = None;
if app.netplay.server.0.is_empty() {
let first_server = app.netplay.servers.iter().next().unwrap();
app.netplay.server = (first_server.0.clone(), first_server.1.clone());
}
}
ctx.request_repaint();
}
}
pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::Window::new("Create Netplay Session").show(ctx, |ui| {
egui::Grid::new("netplay_create_grid").show(ui, |ui| {
let profile_name_label = ui.label("Session Name:");
let mut size = ui.spacing().interact_size;
size.x = 200.0;
ui.add_sized(size, |ui: &mut egui::Ui| {
ui.text_edit_singleline(&mut app.netplay.session_name)
.labelled_by(profile_name_label.id)
});
ui.end_row();
let password_label = ui.label("Password (Optional):");
ui.text_edit_singleline(&mut app.netplay.password)
.labelled_by(password_label.id);
ui.end_row();
ui.label("ROM");
if app.netplay.create_rom_label.is_empty() {
app.netplay.create_rom_label = "Open ROM".to_string();
}
if ui.button(&app.netplay.create_rom_label).clicked() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.create_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
"".to_string(),
"Open ROM".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
});
}
ui.end_row();
let player_name_label = ui.label("Player Name:");
ui.text_edit_singleline(&mut app.netplay.player_name)
.labelled_by(player_name_label.id);
ui.end_row();
ui.label("Server:");
get_servers(app, ctx);
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
if !data.0.is_empty() {
app.netplay.game_info = data;
app.netplay.rom_contents = app.netplay.game_info.3.clone();
app.netplay.create_rom_label = app.netplay.game_info.2.clone();
} else {
app.netplay.create_rom_label = data.2;
}
}
ctx.request_repaint();
}
egui::ComboBox::from_id_salt("server-combobox")
.selected_text(app.netplay.server.0.to_string())
.show_ui(ui, |ui| {
for server in app.netplay.servers.iter() {
ui.selectable_value(
&mut app.netplay.server,
(server.0.clone(), server.1.clone()),
server.0,
);
}
});
ui.end_row();
if app.netplay.socket_waiting {
let data = app.netplay.socket.as_mut().unwrap().read();
if data.is_ok() {
let message: NetplayMessage =
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
if message.accept.unwrap() == 0 {
if message.message_type == "reply_create_room" {
app.netplay.create = false;
app.netplay.wait = true;
app.netplay.waiting_session = Some(message.room.unwrap());
}
} else {
app.netplay.error = message.message.unwrap();
}
app.netplay.socket_waiting = false;
}
ctx.request_repaint();
}
});
ui.horizontal(|ui| {
if ui.button("Create Session").clicked() {
if app.netplay.player_name.is_empty() {
app.netplay.error = "Player Name cannot be empty".to_string();
} else if app.netplay.session_name.is_empty() {
app.netplay.error = "Session Name cannot be empty".to_string();
} else if app.netplay.game_info.0.is_empty() {
app.netplay.error = "ROM not loaded".to_string();
} else {
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
let mut game_name = app.netplay.game_info.1.to_string();
if game_name.is_empty() {
// If the ROM doesn't report a name, use the filename
game_name = app.netplay.create_rom_label.clone();
}
let netplay_message = NetplayMessage {
message_type: "request_create_room".to_string(),
player_name: Some(app.netplay.player_name.clone()),
client_sha: Some(env!("GIT_HASH").to_string()),
netplay_version: Some(NETPLAY_VERSION),
emulator: Some(EMU_NAME.to_string()),
accept: None,
message: None,
rooms: None,
auth_time: Some(now_utc),
player_names: None,
auth: Some(format!("{:x}", hasher.finalize())),
room: Some(NetplayRoom {
room_name: Some(app.netplay.session_name.clone()),
password: Some(app.netplay.password.clone()),
game_name: Some(game_name),
md5: Some(app.netplay.game_info.0.clone()),
protected: None,
port: None,
}),
};
let (mut socket, _response) =
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&netplay_message).unwrap(),
)))
.unwrap();
match socket.get_mut() {
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
stream.set_nonblocking(true)
}
_ => unimplemented!(),
}
.expect("could not set socket to non-blocking");
app.netplay.socket_waiting = true;
app.netplay.socket = Some(socket);
}
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
})
});
});
}
fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.have_sessions.is_some()
&& app.netplay.server != *app.netplay.have_sessions.as_ref().unwrap()
{
// User has changed the server
app.netplay.have_sessions = None;
app.netplay.socket = None;
}
if app.netplay.socket.is_none() {
let (mut sock, _response) =
tungstenite::connect(&app.netplay.server.1).expect("Can't connect");
match sock.get_mut() {
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
stream.set_nonblocking(true)
}
_ => unimplemented!(),
}
.expect("could not set socket to non-blocking");
app.netplay.socket = Some(sock);
}
let socket = app.netplay.socket.as_mut().unwrap();
if app.netplay.have_sessions.is_none() {
let now_utc = chrono::Utc::now().timestamp_millis().to_string();
let hasher = Sha256::new().chain_update(&now_utc).chain_update(EMU_NAME);
let request_rooms = NetplayMessage {
message_type: "request_get_rooms".to_string(),
player_name: None,
client_sha: None,
netplay_version: Some(NETPLAY_VERSION),
player_names: None,
emulator: Some(EMU_NAME.to_string()),
accept: None,
rooms: None,
message: None,
auth_time: Some(now_utc),
auth: Some(format!("{:x}", hasher.finalize())),
room: None,
};
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&request_rooms).unwrap(),
)))
.unwrap();
app.netplay.have_sessions = Some(app.netplay.server.clone());
app.netplay.socket_waiting = true;
ctx.request_repaint();
}
}
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
tx.send((hash, game_name, file.file_name(), rom_contents))
.await
.unwrap();
} else {
tx.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await
.unwrap();
}
}
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
let data = socket.read();
if data.is_ok() {
let message: NetplayMessage =
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
if message.accept.unwrap() == 0 {
if message.message_type == "reply_get_rooms" {
if message.rooms.is_some() {
app.netplay.sessions = message.rooms.unwrap();
}
} else if message.message_type == "reply_join_room" {
app.netplay.join = false;
app.netplay.wait = true;
app.netplay.waiting_session = Some(message.room.unwrap());
}
app.netplay.socket_waiting = false;
} else {
app.netplay.error = message.message.unwrap();
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
}
}
ctx.request_repaint();
}
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
if !data.0.is_empty() {
app.netplay.game_info = data;
app.netplay.rom_contents = app.netplay.game_info.3.clone();
let netplay_message = NetplayMessage {
message_type: "request_join_room".to_string(),
player_name: Some(app.netplay.player_name.clone()),
client_sha: Some(env!("GIT_HASH").to_string()),
netplay_version: None,
emulator: None,
accept: None,
message: None,
rooms: None,
auth_time: None,
player_names: None,
auth: None,
room: Some(NetplayRoom {
room_name: None,
password: Some(app.netplay.password.clone()),
game_name: None,
md5: Some(app.netplay.game_info.0.clone()),
protected: None,
port: app.netplay.selected_session.as_ref().unwrap().port,
}),
};
let socket = app.netplay.socket.as_mut().unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&netplay_message).unwrap(),
)))
.unwrap();
app.netplay.socket_waiting = true;
} else {
app.netplay.error = data.2;
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
}
}
ctx.request_repaint();
}
egui::Window::new("Join Netplay Session").show(ctx, |ui| {
ui.horizontal(|ui| {
let mut size = ui.spacing().interact_size;
size.x = 100.0;
ui.add_sized(
size,
egui::TextEdit::singleline(&mut app.netplay.player_name).hint_text("Player name"),
);
get_servers(app, ctx);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Refresh").clicked() {
app.netplay.socket = None;
app.netplay.have_sessions = None;
app.netplay.selected_session = None;
ctx.request_repaint();
}
egui::ComboBox::from_id_salt("server-combobox")
.selected_text(app.netplay.server.0.to_string())
.show_ui(ui, |ui| {
for server in app.netplay.servers.iter() {
ui.selectable_value(
&mut app.netplay.server,
(server.0.clone(), server.1.clone()),
server.0,
);
}
});
});
});
if !app.netplay.server.0.is_empty() {
get_sessions(app, ctx);
}
ui.add_space(16.0);
if app.netplay.sessions.is_empty() {
ui.label("No sessions available");
} else {
egui::Grid::new("netplay_join_grid").show(ui, |ui| {
ui.label(egui::RichText::new("Session Name (click to select)").underline());
ui.label(egui::RichText::new("Game Name").underline());
ui.label(egui::RichText::new("Password Protected").underline());
ui.end_row();
for room in app.netplay.sessions.iter() {
ui.selectable_value(
&mut app.netplay.selected_session,
Some(room.clone()),
room.room_name.as_ref().unwrap(),
);
ui.label(room.game_name.as_ref().unwrap());
ui.label(room.protected.unwrap_or(false).to_string());
ui.end_row();
}
});
}
ui.add_space(16.0);
ui.horizontal(|ui| {
let mut size = ui.spacing().interact_size;
size.x = 130.0;
ui.add_sized(
size,
egui::TextEdit::singleline(&mut app.netplay.password)
.hint_text("Password (if required)"),
);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
if app.netplay.join_rom_label.is_empty() {
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
}
if ui.button(&app.netplay.join_rom_label).clicked() {
if app.netplay.player_name.is_empty() {
app.netplay.error = "Player Name cannot be empty".to_string();
} else if app.netplay.selected_session.is_none() {
app.netplay.error = "No session selected".to_string();
} else if app
.netplay
.selected_session
.as_ref()
.unwrap()
.protected
.unwrap()
&& app.netplay.password.is_empty()
{
app.netplay.error = "Session requires a password".to_string();
} else {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.join_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new().pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await;
} else {
tx.send((
"".to_string(),
"".to_string(),
"No ROM selected".to_string(),
vec![],
))
.await
.unwrap();
}
gui_ctx.request_repaint();
});
}
};
});
});
});
}
pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
let motd_message = NetplayMessage {
message_type: "request_motd".to_string(),
player_name: None,
client_sha: None,
netplay_version: None,
emulator: None,
accept: None,
rooms: None,
player_names: None,
message: None,
auth_time: None,
auth: None,
room: None,
};
let request_players = NetplayMessage {
message_type: "request_players".to_string(),
player_name: None,
client_sha: None,
netplay_version: None,
player_names: None,
emulator: None,
rooms: None,
accept: None,
message: None,
auth_time: None,
auth: None,
room: Some(NetplayRoom {
room_name: None,
password: None,
game_name: None,
md5: None,
protected: None,
port: app.netplay.waiting_session.as_ref().unwrap().port,
}),
};
if !app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&motd_message).unwrap(),
)))
.unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&request_players).unwrap(),
)))
.unwrap();
app.netplay.socket_waiting = true;
}
if app.netplay.begin_game {
let begin_game = NetplayMessage {
message_type: "request_begin_game".to_string(),
player_name: None,
client_sha: None,
netplay_version: None,
player_names: None,
rooms: None,
emulator: None,
accept: None,
message: None,
auth_time: None,
auth: None,
room: Some(NetplayRoom {
room_name: None,
password: None,
game_name: None,
md5: None,
protected: None,
port: app.netplay.waiting_session.as_ref().unwrap().port,
}),
};
let socket = app.netplay.socket.as_mut().unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&begin_game).unwrap(),
)))
.unwrap();
app.netplay.begin_game = false;
}
if app.netplay.send_chat {
let send_chat = NetplayMessage {
message_type: "request_chat_message".to_string(),
player_name: Some(app.netplay.player_name.clone()),
client_sha: None,
netplay_version: None,
player_names: None,
rooms: None,
emulator: None,
accept: None,
message: Some(app.netplay.chat_message.clone()),
auth_time: None,
auth: None,
room: Some(NetplayRoom {
room_name: None,
password: None,
game_name: None,
md5: None,
protected: None,
port: app.netplay.waiting_session.as_ref().unwrap().port,
}),
};
app.netplay.chat_message.clear();
let socket = app.netplay.socket.as_mut().unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&send_chat).unwrap(),
)))
.unwrap();
app.netplay.send_chat = false;
}
if app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
let data = socket.read();
if data.is_ok() {
let message: NetplayMessage =
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
if message.accept.unwrap() == 0 {
if message.message_type == "reply_motd" {
let re = regex::Regex::new(r"<[^>]*>").unwrap();
app.netplay.motd = re
.replace_all(message.message.unwrap().as_str(), "")
.into_owned();
} else if message.message_type == "reply_players" {
app.netplay.player_names = message.player_names.unwrap();
} else if message.message_type == "reply_chat_message" {
app.netplay.chat_log.push_str(&message.message.unwrap());
app.netplay.chat_log.push('\n');
} else if message.message_type == "reply_begin_game" {
let mut player = 0;
for (i, name) in app.netplay.player_names.iter().enumerate() {
if *name == app.netplay.player_name {
player = i;
break;
}
}
app.netplay.player_number = player as u8;
if message.accept.unwrap() == 0 {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
gui::open_rom(app, ctx);
app.netplay = Default::default();
return;
} else {
app.netplay.error = message.message.unwrap();
}
}
} else {
app.netplay.error = message.message.unwrap();
}
}
ctx.request_repaint();
}
egui::Window::new("Pending Netplay Session").show(ctx, |ui| {
egui::Grid::new("netplay_wait_grid_1").show(ui, |ui| {
ui.label("Session Name:");
let room_name = app
.netplay
.waiting_session
.as_ref()
.unwrap()
.room_name
.as_ref()
.unwrap();
let game_name = app
.netplay
.waiting_session
.as_ref()
.unwrap()
.game_name
.as_ref()
.unwrap();
ui.label(room_name);
ui.end_row();
ui.label("Game Name:");
ui.label(game_name);
ui.end_row();
for i in 0..4 {
ui.label(format!("Player {}:", i + 1));
ui.label(app.netplay.player_names[i].clone());
ui.end_row();
}
});
egui::ScrollArea::vertical()
.max_height(100.0)
.stick_to_bottom(true)
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut app.netplay.chat_log)
.interactive(false)
.desired_width(ui.available_width()),
);
});
ui.horizontal(|ui| {
let mut size = ui.spacing().interact_size;
size.x = 200.0;
ui.add_sized(
size,
egui::TextEdit::singleline(&mut app.netplay.chat_message)
.hint_text("Enter chat message here"),
);
if ui
.add_enabled(!app.netplay.send_chat, egui::Button::new("Send Message"))
.clicked()
&& !app.netplay.chat_message.is_empty()
{
app.netplay.send_chat = true;
}
});
ui.add_space(16.0);
ui.label(app.netplay.motd.clone());
ui.add_space(16.0);
ui.horizontal(|ui| {
let button_enabled = app.netplay.player_name == app.netplay.player_names[0]
&& !app.netplay.pending_begin;
if ui
.add_enabled(button_enabled, egui::Button::new("Start Session"))
.clicked()
{
app.netplay.begin_game = true;
app.netplay.pending_begin = true;
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
});
});
});
}
pub fn netplay_error(app: &mut GopherEguiApp, ctx: &egui::Context, error: String) {
egui::Window::new("Netplay Error").show(ctx, |ui| {
ui.label(error);
if ui.button("Close").clicked() {
app.netplay.error = "".to_string();
};
});
}
+482 -251
View File
@@ -16,22 +16,25 @@ const D_CBUTTON: usize = 10;
const U_CBUTTON: usize = 11;
const R_TRIG: usize = 12;
const L_TRIG: usize = 13;
const X_AXIS: usize = 16;
const Y_AXIS: usize = 24;
const AXIS_LEFT: usize = 14;
const AXIS_RIGHT: usize = 15;
const AXIS_UP: usize = 16;
const AXIS_DOWN: usize = 17;
const CHANGE_PAK: usize = 18;
pub const PROFILE_SIZE: usize = 19;
const X_AXIS_SHIFT: usize = 16;
const Y_AXIS_SHIFT: usize = 24;
const MAX_AXIS_VALUE: f64 = 85.0;
pub struct Controllers {
pub game_controller: Option<sdl2::controller::GameController>,
pub joystick: Option<sdl2::joystick::Joystick>,
pub rumble: bool,
pub game_controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
pub joystick: *mut sdl3_sys::joystick::SDL_Joystick,
}
pub fn bound_axis(x: &mut f64, y: &mut f64) {
fn bound_axis(x: &mut f64, y: &mut f64) {
let radius = 95.0; // this is roughly the maxium diagonal distance of the controller
// Calculate the distance from the origin (0, 0)
@@ -45,32 +48,40 @@ pub fn bound_axis(x: &mut f64, y: &mut f64) {
}
}
pub fn set_axis_from_joystick(
fn set_axis_from_joystick(
profile: &ui::config::InputProfile,
joystick: &sdl2::joystick::Joystick,
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
) -> (f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
if profile.joystick_axis[AXIS_LEFT].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_LEFT].1).unwrap();
let axis_position = unsafe {
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_LEFT].1)
};
if axis_position as isize * profile.joystick_axis[AXIS_LEFT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.joystick_axis[AXIS_RIGHT].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_RIGHT].1).unwrap();
let axis_position = unsafe {
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_RIGHT].1)
};
if axis_position as isize * profile.joystick_axis[AXIS_RIGHT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.joystick_axis[AXIS_DOWN].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_DOWN].1).unwrap();
let axis_position = unsafe {
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_DOWN].1)
};
if axis_position as isize * profile.joystick_axis[AXIS_DOWN].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
}
}
if profile.joystick_axis[AXIS_UP].0 {
let axis_position = joystick.axis(profile.joystick_axis[AXIS_UP].1).unwrap();
let axis_position = unsafe {
sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile.joystick_axis[AXIS_UP].1)
};
if axis_position as isize * profile.joystick_axis[AXIS_UP].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
}
@@ -78,68 +89,85 @@ pub fn set_axis_from_joystick(
(x, y)
}
fn get_axis_from_i32(axis: i32) -> sdl2::controller::Axis {
fn get_axis_from_i32(axis: i32) -> sdl3_sys::gamepad::SDL_GamepadAxis {
match axis {
0 => sdl2::controller::Axis::LeftX,
1 => sdl2::controller::Axis::LeftY,
2 => sdl2::controller::Axis::RightX,
3 => sdl2::controller::Axis::RightY,
4 => sdl2::controller::Axis::TriggerLeft,
5 => sdl2::controller::Axis::TriggerRight,
0 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTX,
1 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFTY,
2 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTX,
3 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHTY,
4 => sdl3_sys::gamepad::SDL_GamepadAxis::LEFT_TRIGGER,
5 => sdl3_sys::gamepad::SDL_GamepadAxis::RIGHT_TRIGGER,
_ => panic!("Invalid axis"),
}
}
fn get_button_from_i32(button: i32) -> sdl2::controller::Button {
fn get_button_from_i32(button: i32) -> sdl3_sys::gamepad::SDL_GamepadButton {
match button {
0 => sdl2::controller::Button::A,
1 => sdl2::controller::Button::B,
2 => sdl2::controller::Button::X,
3 => sdl2::controller::Button::Y,
4 => sdl2::controller::Button::Back,
5 => sdl2::controller::Button::Guide,
6 => sdl2::controller::Button::Start,
7 => sdl2::controller::Button::LeftStick,
8 => sdl2::controller::Button::RightStick,
9 => sdl2::controller::Button::LeftShoulder,
10 => sdl2::controller::Button::RightShoulder,
11 => sdl2::controller::Button::DPadUp,
12 => sdl2::controller::Button::DPadDown,
13 => sdl2::controller::Button::DPadLeft,
14 => sdl2::controller::Button::DPadRight,
0 => sdl3_sys::gamepad::SDL_GamepadButton::SOUTH,
1 => sdl3_sys::gamepad::SDL_GamepadButton::EAST,
2 => sdl3_sys::gamepad::SDL_GamepadButton::WEST,
3 => sdl3_sys::gamepad::SDL_GamepadButton::NORTH,
4 => sdl3_sys::gamepad::SDL_GamepadButton::BACK,
5 => sdl3_sys::gamepad::SDL_GamepadButton::GUIDE,
6 => sdl3_sys::gamepad::SDL_GamepadButton::START,
7 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_STICK,
8 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_STICK,
9 => sdl3_sys::gamepad::SDL_GamepadButton::LEFT_SHOULDER,
10 => sdl3_sys::gamepad::SDL_GamepadButton::RIGHT_SHOULDER,
11 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_UP,
12 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_DOWN,
13 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_LEFT,
14 => sdl3_sys::gamepad::SDL_GamepadButton::DPAD_RIGHT,
_ => panic!("Invalid button"),
}
}
pub fn set_axis_from_controller(
fn set_axis_from_controller(
profile: &ui::config::InputProfile,
controller: &sdl2::controller::GameController,
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
) -> (f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
if profile.controller_axis[AXIS_LEFT].0 {
let axis_position =
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1));
let axis_position = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadAxis(
controller,
get_axis_from_i32(profile.controller_axis[AXIS_LEFT].1),
)
};
if axis_position as isize * profile.controller_axis[AXIS_LEFT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.controller_axis[AXIS_RIGHT].0 {
let axis_position =
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1));
let axis_position = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadAxis(
controller,
get_axis_from_i32(profile.controller_axis[AXIS_RIGHT].1),
)
};
if axis_position as isize * profile.controller_axis[AXIS_RIGHT].2 as isize > 0 {
x = axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64;
}
}
if profile.controller_axis[AXIS_DOWN].0 {
let axis_position =
controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1));
let axis_position = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadAxis(
controller,
get_axis_from_i32(profile.controller_axis[AXIS_DOWN].1),
)
};
if axis_position as isize * profile.controller_axis[AXIS_DOWN].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
}
}
if profile.controller_axis[AXIS_UP].0 {
let axis_position = controller.axis(get_axis_from_i32(profile.controller_axis[AXIS_UP].1));
let axis_position = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadAxis(
controller,
get_axis_from_i32(profile.controller_axis[AXIS_UP].1),
)
};
if axis_position as isize * profile.controller_axis[AXIS_UP].2 as isize > 0 {
y = (axis_position as f64 * MAX_AXIS_VALUE / i16::MAX as f64).neg();
}
@@ -147,65 +175,61 @@ pub fn set_axis_from_controller(
(x, y)
}
pub fn set_axis_from_keys(
fn set_axis_from_keys(
profile: &ui::config::InputProfile,
keyboard_state: sdl2::keyboard::KeyboardState,
keyboard_state: *const bool,
) -> (f64, f64) {
let mut x = 0.0;
let mut y = 0.0;
if profile.keys[AXIS_LEFT].0
&& keyboard_state.is_scancode_pressed(
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_LEFT].1).unwrap(),
)
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_LEFT].1 as isize) }
{
x = -MAX_AXIS_VALUE
}
if profile.keys[AXIS_RIGHT].0
&& keyboard_state.is_scancode_pressed(
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_RIGHT].1).unwrap(),
)
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_RIGHT].1 as isize) }
{
x = MAX_AXIS_VALUE
}
if profile.keys[AXIS_DOWN].0
&& keyboard_state.is_scancode_pressed(
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_DOWN].1).unwrap(),
)
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_DOWN].1 as isize) }
{
y = -MAX_AXIS_VALUE
}
if profile.keys[AXIS_UP].0
&& keyboard_state.is_scancode_pressed(
sdl2::keyboard::Scancode::from_i32(profile.keys[AXIS_UP].1).unwrap(),
)
&& unsafe { *keyboard_state.offset(profile.keys[AXIS_UP].1 as isize) }
{
y = MAX_AXIS_VALUE
}
(x, y)
}
pub fn set_buttons_from_joystick(
fn set_buttons_from_joystick(
profile: &ui::config::InputProfile,
i: usize,
joystick: &sdl2::joystick::Joystick,
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
keys: &mut u32,
) {
let profile_joystick_button = profile.joystick_buttons[i];
if profile_joystick_button.0 {
*keys |= (joystick.button(profile_joystick_button.1).unwrap() as u32) << i;
*keys |= (unsafe {
sdl3_sys::joystick::SDL_GetJoystickButton(joystick, profile_joystick_button.1)
} as u32)
<< i;
}
let profile_joystick_hat = profile.joystick_hat[i];
if profile_joystick_hat.0
&& joystick.hat(profile_joystick_hat.1).unwrap()
== sdl2::joystick::HatState::from_raw(profile_joystick_hat.2)
&& unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, profile_joystick_hat.1) }
== profile_joystick_hat.2
{
*keys |= 1 << i;
}
let profile_joystick_axis = profile.joystick_axis[i];
if profile_joystick_axis.0 {
let axis_position = joystick.axis(profile_joystick_axis.1).unwrap();
let axis_position =
unsafe { sdl3_sys::joystick::SDL_GetJoystickAxis(joystick, profile_joystick_axis.1) };
if axis_position as isize * profile_joystick_axis.2 as isize > 0
&& axis_position.saturating_abs() > i16::MAX / 2
{
@@ -214,20 +238,31 @@ pub fn set_buttons_from_joystick(
}
}
pub fn set_buttons_from_controller(
fn set_buttons_from_controller(
profile: &ui::config::InputProfile,
i: usize,
controller: &sdl2::controller::GameController,
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
keys: &mut u32,
) {
let profile_controller_button = profile.controller_buttons[i];
if profile_controller_button.0 {
*keys |= (controller.button(get_button_from_i32(profile_controller_button.1)) as u32) << i;
*keys |= (unsafe {
sdl3_sys::gamepad::SDL_GetGamepadButton(
controller,
get_button_from_i32(profile_controller_button.1),
)
} as u32)
<< i;
}
let profile_controller_axis = profile.controller_axis[i];
if profile_controller_axis.0 {
let axis_position = controller.axis(get_axis_from_i32(profile_controller_axis.1));
let axis_position = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadAxis(
controller,
get_axis_from_i32(profile_controller_axis.1),
)
};
if axis_position as isize * profile_controller_axis.2 as isize > 0
&& axis_position.saturating_abs() > i16::MAX / 2
{
@@ -236,31 +271,81 @@ pub fn set_buttons_from_controller(
}
}
pub fn get(ui: &mut ui::Ui, channel: usize) -> u32 {
let context = ui.sdl_context.as_mut().unwrap();
let events = context.event_pump().unwrap();
let keyboard_state = events.keyboard_state();
pub fn set_rumble(ui: &mut ui::Ui, channel: usize, rumble: u8) {
if !ui.controllers[channel].rumble {
return;
}
let controller = ui.controllers[channel].game_controller;
let joystick = ui.controllers[channel].joystick;
if !controller.is_null() {
unsafe {
sdl3_sys::gamepad::SDL_RumbleGamepad(
controller,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u32 * 60000,
)
};
} else if !joystick.is_null() {
unsafe {
sdl3_sys::joystick::SDL_RumbleJoystick(
joystick,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u16 * u16::MAX,
(rumble & 1) as u32 * 60000,
)
};
}
}
fn change_paks(
profile: &ui::config::InputProfile,
joystick: *mut sdl3_sys::joystick::SDL_Joystick,
controller: *mut sdl3_sys::gamepad::SDL_Gamepad,
keyboard_state: *const bool,
) -> bool {
let controller_button = profile.controller_buttons[CHANGE_PAK];
let joystick_button = profile.joystick_buttons[CHANGE_PAK];
let joystick_hat = profile.joystick_hat[CHANGE_PAK];
let key = profile.keys[CHANGE_PAK];
let mut pressed = false;
if controller_button.0 && !controller.is_null() {
pressed = unsafe {
sdl3_sys::gamepad::SDL_GetGamepadButton(
controller,
get_button_from_i32(controller_button.1),
)
};
} else if joystick_button.0 && !joystick.is_null() {
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickButton(joystick, joystick_button.1) };
} else if joystick_hat.0 && !joystick.is_null() {
pressed = unsafe { sdl3_sys::joystick::SDL_GetJoystickHat(joystick, joystick_hat.1) }
== joystick_hat.2;
} else if key.0 {
pressed = unsafe { *keyboard_state.offset(key.1 as isize) };
}
pressed
}
pub fn get(ui: &mut ui::Ui, channel: usize) -> (u32, bool) {
let profile_name = ui.config.input.input_profile_binding[channel].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
let mut keys = 0;
let controller = &ui.controllers[channel].game_controller;
let joystick = &ui.controllers[channel].joystick;
let controller = ui.controllers[channel].game_controller;
let joystick = ui.controllers[channel].joystick;
for i in 0..14 {
if profile_name != "default" || channel == 0 {
let profile_key = profile.keys[i];
if profile_key.0 {
keys |= (keyboard_state
.is_scancode_pressed(sdl2::keyboard::Scancode::from_i32(profile_key.1).unwrap())
as u32)
<< i;
keys |= (unsafe { *ui.keyboard_state.offset(profile_key.1 as isize) } as u32) << i;
}
}
if joystick.is_some() {
set_buttons_from_joystick(profile, i, joystick.as_ref().unwrap(), &mut keys);
} else if controller.is_some() {
set_buttons_from_controller(profile, i, controller.as_ref().unwrap(), &mut keys);
if !controller.is_null() {
set_buttons_from_controller(profile, i, controller, &mut keys);
} else if !joystick.is_null() {
set_buttons_from_joystick(profile, i, joystick, &mut keys);
}
}
@@ -268,42 +353,35 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> u32 {
let mut y: f64 = 0.0;
if profile_name != "default" || channel == 0 {
(x, y) = set_axis_from_keys(profile, keyboard_state);
(x, y) = set_axis_from_keys(profile, ui.keyboard_state);
}
if joystick.is_some() {
(x, y) = set_axis_from_joystick(profile, joystick.as_ref().unwrap())
} else if controller.is_some() {
(x, y) = set_axis_from_controller(profile, controller.as_ref().unwrap())
if !controller.is_null() {
(x, y) = set_axis_from_controller(profile, controller)
} else if !joystick.is_null() {
(x, y) = set_axis_from_joystick(profile, joystick)
}
bound_axis(&mut x, &mut y);
keys |= (x.round() as i8 as u8 as u32) << X_AXIS;
keys |= (y.round() as i8 as u8 as u32) << Y_AXIS;
keys
keys |= (x.round() as i8 as u8 as u32) << X_AXIS_SHIFT;
keys |= (y.round() as i8 as u8 as u32) << Y_AXIS_SHIFT;
(
keys,
change_paks(profile, joystick, controller, ui.keyboard_state),
)
}
pub fn list_controllers(ui: &ui::Ui) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
if num_joysticks == 0 {
println!("No controllers connected")
}
for i in 0..num_joysticks {
println!("{}: {}", i, joystick_subsystem.name_for_index(i).unwrap())
}
}
pub fn assign_controller(ui: &mut ui::Ui, controller: u32, port: usize) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
if controller < num_joysticks {
ui.config.input.controller_assignment[port - 1] = Some(
joystick_subsystem
.device_guid(controller)
.unwrap()
.to_string(),
);
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
if controller < ui.num_joysticks {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.joysticks.offset(controller as isize)),
))
.to_string_lossy()
.to_string()
};
ui.config.input.controller_assignment[port - 1] = Some(path);
} else {
println!("Invalid controller number")
}
@@ -324,7 +402,21 @@ pub fn clear_bindings(ui: &mut ui::Ui) {
}
}
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) {
fn close_controllers(
open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick>,
open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad>,
) {
for joystick in open_joysticks {
unsafe { sdl3_sys::joystick::SDL_CloseJoystick(joystick) }
}
for controller in open_controllers {
unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller) }
}
}
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
if profile == "default" {
println!("Profile name cannot be default");
return;
@@ -333,24 +425,47 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) {
println!("Profile name cannot be empty");
return;
}
let joystick_subsystem = ui.sdl_context.as_ref().unwrap().joystick().unwrap();
let mut joysticks = vec![];
for i in 0..joystick_subsystem.num_joysticks().unwrap() {
joysticks.push(ui.sdl_context.as_ref().unwrap().joystick().unwrap().open(i));
let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new();
let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new();
for offset in 0..ui.num_joysticks as isize {
if !dinput {
let controller =
unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*ui.joysticks.offset(offset)) };
if !controller.is_null() {
open_controllers.push(controller);
}
} else {
let joystick =
unsafe { sdl3_sys::joystick::SDL_OpenJoystick(*ui.joysticks.offset(offset)) };
if !joystick.is_null() {
open_joysticks.push(joystick);
}
}
}
let mut builder =
ui.video_subsystem
.as_ref()
.unwrap()
.window("configure input profile", 640, 480);
builder.position_centered();
let window = builder.build().unwrap();
let mut canvas = window.into_canvas().build().unwrap();
let title = std::ffi::CString::new("configure input profile").unwrap();
let mut window: *mut sdl3_sys::video::SDL_Window = std::ptr::null_mut();
let mut renderer: *mut sdl3_sys::render::SDL_Renderer = std::ptr::null_mut();
if !unsafe {
sdl3_sys::render::SDL_CreateWindowAndRenderer(
title.as_ptr(),
640,
480,
0,
&mut window,
&mut renderer,
)
} {
panic!("Could not create window and renderer")
}
if !unsafe { sdl3_sys::video::SDL_ShowWindow(window) } {
panic!("Could not show window")
}
let font =
rusttype::Font::try_from_bytes(include_bytes!("../../data/Roboto-Regular.ttf")).unwrap();
let key_labels = [
let key_labels: [(&str, usize); PROFILE_SIZE] = [
("A", A_BUTTON),
("B", B_BUTTON),
("Start", START_BUTTON),
@@ -369,128 +484,206 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String) {
("Control Stick Down", AXIS_DOWN),
("Control Stick Left", AXIS_LEFT),
("Control Stick Right", AXIS_RIGHT),
("Change Pak", CHANGE_PAK),
];
let mut new_keys = [(false, 0); 18];
let mut new_joystick_buttons = [(false, 0u32); 14];
let mut new_joystick_hat = [(false, 0u32, 0); 14];
let mut new_joystick_axis = [(false, 0u32, 0); 18];
let mut new_keys = [(false, 0); PROFILE_SIZE];
let mut new_joystick_buttons = [(false, 0i32); PROFILE_SIZE];
let mut new_joystick_hat = [(false, 0i32, 0); PROFILE_SIZE];
let mut new_joystick_axis = [(false, 0i32, 0); PROFILE_SIZE];
let mut new_controller_buttons = [(false, 0i32); PROFILE_SIZE];
let mut new_controller_axis = [(false, 0i32, 0); PROFILE_SIZE];
let mut last_axis_result = (false, 0, 0);
let mut event_pump = ui.sdl_context.as_ref().unwrap().event_pump().unwrap();
let mut last_joystick_axis_result = (false, 0, 0);
for (key, value) in key_labels.iter() {
for _event in event_pump.poll_iter() {} // clear events
let mut event: sdl3_sys::events::SDL_Event = Default::default();
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {} // clear events
ui::video::draw_text(
format!("Select binding for: {key}").as_str(),
&mut canvas,
renderer,
&font,
);
let mut key_set = false;
while !key_set {
std::thread::sleep(std::time::Duration::from_millis(100));
for event in event_pump.poll_iter() {
match event {
sdl2::event::Event::Window {
win_event: sdl2::event::WindowEvent::Close,
..
} => return,
sdl2::event::Event::KeyDown {
scancode: Some(scancode),
..
} => {
new_keys[*value] = (true, scancode as i32);
while unsafe { sdl3_sys::events::SDL_PollEvent(&mut event) } {
let event_type = unsafe { event.r#type };
if event_type == u32::from(sdl3_sys::events::SDL_EVENT_WINDOW_CLOSE_REQUESTED) {
close_controllers(open_joysticks, open_controllers);
return;
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_KEY_DOWN) {
new_keys[*value] = (true, i32::from(unsafe { event.key.scancode }));
key_set = true
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
if !open_controllers.is_empty() {
new_controller_buttons[*value] =
(true, i32::from(unsafe { event.gbutton.button }));
key_set = true
}
sdl2::event::Event::JoyButtonDown { button_idx, .. } => {
new_joystick_buttons[*value] = (true, button_idx as u32);
key_set = true
}
sdl2::event::Event::JoyHatMotion { hat_idx, state, .. } => {
new_joystick_hat[*value] = (
true,
hat_idx as u32,
sdl2::joystick::HatState::to_raw(state),
);
key_set = true
}
sdl2::event::Event::JoyAxisMotion {
axis_idx,
value: axis_value,
..
} => {
if axis_value.saturating_abs() > 24576 {
let result = (
true,
axis_idx as u32,
axis_value / axis_value.saturating_abs(),
);
if result != last_axis_result {
new_joystick_axis[*value] = result;
last_axis_result = result;
key_set = true
}
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_GAMEPAD_AXIS_MOTION) {
let axis_value = unsafe { event.gaxis.value };
let axis = unsafe { event.gaxis.axis };
if !open_controllers.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
if result != last_joystick_axis_result {
new_controller_axis[*value] = result;
last_joystick_axis_result = result;
key_set = true
}
}
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_BUTTON_DOWN)
{
if !open_joysticks.is_empty() {
new_joystick_buttons[*value] =
(true, i32::from(unsafe { event.jbutton.button }));
key_set = true
}
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_HAT_MOTION) {
let state = unsafe { event.jhat.value };
let hat = unsafe { event.jhat.hat };
if !open_joysticks.is_empty() && state != sdl3_sys::joystick::SDL_HAT_CENTERED {
new_joystick_hat[*value] = (true, hat as i32, state);
key_set = true
}
} else if event_type == u32::from(sdl3_sys::events::SDL_EVENT_JOYSTICK_AXIS_MOTION)
{
let axis_value = unsafe { event.jaxis.value };
let axis = unsafe { event.jaxis.axis };
if !open_joysticks.is_empty() && axis_value.saturating_abs() > i16::MAX / 2 {
let result = (true, axis as i32, axis_value / axis_value.saturating_abs());
if result != last_joystick_axis_result {
new_joystick_axis[*value] = result;
last_joystick_axis_result = result;
key_set = true
}
}
_ => {}
}
}
}
}
close_controllers(open_joysticks, open_controllers);
unsafe {
sdl3_sys::render::SDL_DestroyRenderer(renderer);
sdl3_sys::video::SDL_DestroyWindow(window);
}
let new_profile = ui::config::InputProfile {
keys: new_keys,
controller_buttons: Default::default(),
controller_axis: Default::default(),
controller_buttons: new_controller_buttons,
controller_axis: new_controller_axis,
joystick_buttons: new_joystick_buttons,
joystick_hat: new_joystick_hat,
joystick_axis: new_joystick_axis,
dinput,
};
ui.config.input.input_profiles.insert(profile, new_profile);
}
pub fn get_default_profile() -> ui::config::InputProfile {
let mut default_controller_buttons = [(false, 0); 14];
let mut default_controller_axis = [(false, 0, 0); 18];
let mut default_keys = [(false, 0); 18];
default_keys[R_DPAD] = (true, sdl2::keyboard::Scancode::D as i32);
default_keys[L_DPAD] = (true, sdl2::keyboard::Scancode::A as i32);
default_keys[D_DPAD] = (true, sdl2::keyboard::Scancode::S as i32);
default_keys[U_DPAD] = (true, sdl2::keyboard::Scancode::W as i32);
default_keys[START_BUTTON] = (true, sdl2::keyboard::Scancode::Return as i32);
default_keys[Z_TRIG] = (true, sdl2::keyboard::Scancode::Z as i32);
default_keys[B_BUTTON] = (true, sdl2::keyboard::Scancode::LCtrl as i32);
default_keys[A_BUTTON] = (true, sdl2::keyboard::Scancode::LShift as i32);
default_keys[R_CBUTTON] = (true, sdl2::keyboard::Scancode::L as i32);
default_keys[L_CBUTTON] = (true, sdl2::keyboard::Scancode::J as i32);
default_keys[D_CBUTTON] = (true, sdl2::keyboard::Scancode::K as i32);
default_keys[U_CBUTTON] = (true, sdl2::keyboard::Scancode::I as i32);
default_keys[R_TRIG] = (true, sdl2::keyboard::Scancode::C as i32);
default_keys[L_TRIG] = (true, sdl2::keyboard::Scancode::X as i32);
default_keys[AXIS_LEFT] = (true, sdl2::keyboard::Scancode::Left as i32);
default_keys[AXIS_RIGHT] = (true, sdl2::keyboard::Scancode::Right as i32);
default_keys[AXIS_UP] = (true, sdl2::keyboard::Scancode::Up as i32);
default_keys[AXIS_DOWN] = (true, sdl2::keyboard::Scancode::Down as i32);
let mut default_controller_buttons = [(false, 0); PROFILE_SIZE];
let mut default_controller_axis = [(false, 0, 0); PROFILE_SIZE];
let mut default_keys = [(false, 0); PROFILE_SIZE];
default_keys[R_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_D));
default_keys[L_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_A));
default_keys[D_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_S));
default_keys[U_DPAD] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_W));
default_keys[START_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RETURN));
default_keys[Z_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_Z));
default_keys[B_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LCTRL));
default_keys[A_BUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LSHIFT));
default_keys[R_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_L));
default_keys[L_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_J));
default_keys[D_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_K));
default_keys[U_CBUTTON] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_I));
default_keys[R_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_C));
default_keys[L_TRIG] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_X));
default_keys[AXIS_LEFT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_LEFT));
default_keys[AXIS_RIGHT] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_RIGHT));
default_keys[AXIS_UP] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_UP));
default_keys[AXIS_DOWN] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_DOWN));
default_keys[CHANGE_PAK] = (true, i32::from(sdl3_sys::scancode::SDL_SCANCODE_COMMA));
default_controller_buttons[R_DPAD] = (true, sdl2::controller::Button::DPadRight as i32);
default_controller_buttons[L_DPAD] = (true, sdl2::controller::Button::DPadLeft as i32);
default_controller_buttons[D_DPAD] = (true, sdl2::controller::Button::DPadDown as i32);
default_controller_buttons[U_DPAD] = (true, sdl2::controller::Button::DPadUp as i32);
default_controller_buttons[START_BUTTON] = (true, sdl2::controller::Button::Start as i32);
default_controller_axis[Z_TRIG] = (true, sdl2::controller::Axis::TriggerLeft as i32, 1);
default_controller_buttons[B_BUTTON] = (true, sdl2::controller::Button::X as i32);
default_controller_buttons[A_BUTTON] = (true, sdl2::controller::Button::A as i32);
default_controller_axis[R_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, 1);
default_controller_axis[L_CBUTTON] = (true, sdl2::controller::Axis::RightX as i32, -1);
default_controller_axis[D_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, 1);
default_controller_axis[U_CBUTTON] = (true, sdl2::controller::Axis::RightY as i32, -1);
default_controller_buttons[R_TRIG] = (true, sdl2::controller::Button::RightShoulder as i32);
default_controller_buttons[L_TRIG] = (true, sdl2::controller::Button::LeftShoulder as i32);
default_controller_axis[AXIS_LEFT] = (true, sdl2::controller::Axis::LeftX as i32, -1);
default_controller_axis[AXIS_RIGHT] = (true, sdl2::controller::Axis::LeftX as i32, 1);
default_controller_axis[AXIS_UP] = (true, sdl2::controller::Axis::LeftY as i32, -1);
default_controller_axis[AXIS_DOWN] = (true, sdl2::controller::Axis::LeftY as i32, 1);
default_controller_buttons[R_DPAD] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_RIGHT),
);
default_controller_buttons[L_DPAD] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_LEFT),
);
default_controller_buttons[D_DPAD] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_DOWN),
);
default_controller_buttons[U_DPAD] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_DPAD_UP),
);
default_controller_buttons[START_BUTTON] =
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_START));
default_controller_axis[Z_TRIG] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
1,
);
default_controller_buttons[B_BUTTON] =
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_WEST));
default_controller_buttons[A_BUTTON] =
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_SOUTH));
default_controller_axis[R_CBUTTON] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
1,
);
default_controller_axis[L_CBUTTON] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTX),
-1,
);
default_controller_axis[D_CBUTTON] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
1,
);
default_controller_axis[U_CBUTTON] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_RIGHTY),
-1,
);
default_controller_buttons[R_TRIG] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER),
);
default_controller_buttons[L_TRIG] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_LEFT_SHOULDER),
);
default_controller_axis[AXIS_LEFT] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
-1,
);
default_controller_axis[AXIS_RIGHT] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTX),
1,
);
default_controller_axis[AXIS_UP] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
-1,
);
default_controller_axis[AXIS_DOWN] = (
true,
i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_AXIS_LEFTY),
1,
);
default_controller_buttons[CHANGE_PAK] =
(true, i32::from(sdl3_sys::gamepad::SDL_GAMEPAD_BUTTON_BACK));
ui::config::InputProfile {
keys: default_keys,
@@ -499,54 +692,79 @@ pub fn get_default_profile() -> ui::config::InputProfile {
joystick_buttons: Default::default(),
joystick_hat: Default::default(),
joystick_axis: Default::default(),
dinput: false,
}
}
pub fn init(ui: &mut ui::Ui) {
let joystick_subsystem = ui.joystick_subsystem.as_ref().unwrap();
ui.keyboard_state = unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) };
if ui.keyboard_state.is_null() {
panic!("Could not get keyboard state");
}
let mut taken = [false; 4];
for i in 0..4 {
let controller_assignment = &ui.config.input.controller_assignment[i];
if controller_assignment.is_some() {
let mut joystick_index = u32::MAX;
let guid = controller_assignment.clone().unwrap();
for i in 0..joystick_subsystem.num_joysticks().unwrap() {
if joystick_subsystem.device_guid(i).unwrap().to_string() == guid
&& !taken[i as usize]
{
joystick_index = i;
taken[i as usize] = true;
if controller_assignment.is_some() && ui.config.input.controller_enabled[i] {
let mut joystick_id = 0;
let assigned_path = controller_assignment.as_ref().unwrap();
for offset in 0..ui.num_joysticks as isize {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(ui.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
};
if path == *assigned_path && !taken[offset as usize] {
joystick_id = unsafe { *(ui.joysticks.offset(offset)) };
taken[offset as usize] = true;
break;
}
}
if joystick_index < u32::MAX {
if ui.config.input.input_profile_binding[i] == "default" {
let controller_result = ui
.sdl_context
.as_ref()
.unwrap()
.game_controller()
.unwrap()
.open(joystick_index);
if controller_result.is_ok() {
ui.controllers[i].game_controller = Some(controller_result.unwrap());
}
}
if ui.controllers[i].game_controller.is_none() {
let joystick_result = ui
.sdl_context
.as_ref()
.unwrap()
.joystick()
.unwrap()
.open(joystick_index);
if joystick_result.is_err() {
println!(
"could not connect joystick: {}",
joystick_result.err().unwrap()
)
if joystick_id != 0 {
let profile_name = ui.config.input.input_profile_binding[i].clone();
let profile = ui.config.input.input_profiles.get(&profile_name).unwrap();
if !profile.dinput {
let gamepad = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(joystick_id) };
if gamepad.is_null() {
println!("could not connect gamepad: {}", joystick_id)
} else {
ui.controllers[i].joystick = Some(joystick_result.unwrap());
ui.controllers[i].game_controller = gamepad;
let properties =
unsafe { sdl3_sys::gamepad::SDL_GetGamepadProperties(gamepad) };
if properties == 0 {
println!("could not get gamepad properties");
}
ui.controllers[i].rumble = unsafe {
sdl3_sys::properties::SDL_GetBooleanProperty(
properties,
sdl3_sys::gamepad::SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN,
false,
)
};
}
} else {
let joystick = unsafe { sdl3_sys::joystick::SDL_OpenJoystick(joystick_id) };
if joystick.is_null() {
println!("could not connect joystick: {}", joystick_id)
} else {
ui.controllers[i].joystick = joystick;
let properties =
unsafe { sdl3_sys::joystick::SDL_GetJoystickProperties(joystick) };
if properties == 0 {
println!("could not get joystick properties");
}
ui.controllers[i].rumble = unsafe {
sdl3_sys::properties::SDL_GetBooleanProperty(
properties,
sdl3_sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
false,
)
};
}
}
} else {
@@ -555,3 +773,16 @@ pub fn init(ui: &mut ui::Ui) {
}
}
}
pub fn close(ui: &mut ui::Ui) {
for controller in ui.controllers.iter_mut() {
if !controller.joystick.is_null() {
unsafe { sdl3_sys::joystick::SDL_CloseJoystick(controller.joystick) }
controller.joystick = std::ptr::null_mut();
}
if !controller.game_controller.is_null() {
unsafe { sdl3_sys::gamepad::SDL_CloseGamepad(controller.game_controller) }
controller.game_controller = std::ptr::null_mut();
}
}
}
+220 -54
View File
@@ -1,4 +1,6 @@
use crate::ui;
use crate::{netplay, ui};
use std::io::Read;
use std::io::Write;
#[derive(PartialEq)]
pub enum SaveTypes {
@@ -7,6 +9,7 @@ pub enum SaveTypes {
Sram,
Flash,
Mempak,
Sdcard,
Romsave,
}
@@ -15,20 +18,38 @@ pub struct Paths {
pub sra_file_path: std::path::PathBuf,
pub fla_file_path: std::path::PathBuf,
pub pak_file_path: std::path::PathBuf,
pub sdcard_file_path: std::path::PathBuf,
pub romsave_file_path: std::path::PathBuf,
pub savestate_file_path: std::path::PathBuf,
}
// the bool indicates whether the save has been written to
// if that is the case, it will be flushed to the disk when the program closes
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Saves {
pub eeprom: (Vec<u8>, bool),
pub sram: (Vec<u8>, bool),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub romsave: (serde_json::Map<String, serde_json::Value>, bool),
pub sdcard: (Vec<u8>, bool),
pub romsave: (std::collections::HashMap<u32, u8>, bool),
}
pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
let header_type = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap());
if header_type.is_ok() && header_type.unwrap() == "ED" {
let save_type = rom[0x3F] >> 4;
match save_type {
0 => return vec![],
1 => return vec![SaveTypes::Eeprom4k],
2 => return vec![SaveTypes::Eeprom16k],
3 => return vec![SaveTypes::Sram],
4 => panic!("Unsupported save type: {}", save_type),
5 => return vec![SaveTypes::Flash],
6 => panic!("Unsupported save type: {}", save_type),
_ => panic!("Unknown save type: {}", save_type),
}
}
match game_id {
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
@@ -86,85 +107,226 @@ pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
}
}
pub fn init(ui: &mut ui::Ui, data_dir: std::path::PathBuf) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
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 header_value.is_ok() {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re
.replace_all(header_value.unwrap(), "")
.trim()
.replace('\0', "");
}
game_name
}
let base_path = data_dir.join("saves");
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.save_type = get_save_type(rom, &ui.game_id);
ui.paths.eep_file_path.clone_from(&base_path);
let saves_path = ui.dirs.data_dir.join("saves");
let states_path = ui.dirs.data_dir.join("states");
let game_name = get_game_name(rom);
let prefix = if game_name.is_empty() {
&ui.game_id
} else {
&game_name
};
ui.paths.eep_file_path.clone_from(&saves_path);
ui.paths
.eep_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".eep");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
ui.paths.sra_file_path.clone_from(&base_path);
ui.paths.sra_file_path.clone_from(&saves_path);
ui.paths
.sra_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".sra");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
ui.paths.fla_file_path.clone_from(&base_path);
ui.paths.fla_file_path.clone_from(&saves_path);
ui.paths
.fla_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".fla");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
ui.paths.pak_file_path.clone_from(&base_path);
ui.paths.pak_file_path.clone_from(&saves_path);
ui.paths
.pak_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".mpk");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
ui.paths.romsave_file_path.clone_from(&base_path);
ui.paths.sdcard_file_path.clone_from(&saves_path);
ui.paths
.sdcard_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".img");
ui.paths.romsave_file_path.clone_from(&saves_path);
ui.paths
.romsave_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".romsave");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave");
ui.paths.savestate_file_path.clone_from(&states_path);
ui.paths
.savestate_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".state");
}
pub fn load_saves(ui: &mut ui::Ui) {
let eep = std::fs::read(&mut ui.paths.eep_file_path);
if eep.is_ok() {
ui.saves.eeprom.0 = eep.unwrap();
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
let eep = std::fs::read(&mut ui.paths.eep_file_path);
if eep.is_ok() {
ui.saves.eeprom.0 = eep.unwrap();
}
let sra = std::fs::read(&mut ui.paths.sra_file_path);
if sra.is_ok() {
ui.saves.sram.0 = sra.unwrap();
}
let fla = std::fs::read(&mut ui.paths.fla_file_path);
if fla.is_ok() {
ui.saves.flash.0 = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
if mempak.is_ok() {
ui.saves.mempak.0 = mempak.unwrap();
}
let sdcard = std::fs::read(&mut ui.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.saves.sdcard.0 = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
}
}
let sra = std::fs::read(&mut ui.paths.sra_file_path);
if sra.is_ok() {
ui.saves.sram.0 = sra.unwrap();
}
let fla = std::fs::read(&mut ui.paths.fla_file_path);
if fla.is_ok() {
ui.saves.flash.0 = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
if mempak.is_ok() {
ui.saves.mempak.0 = mempak.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
if netplay.is_some() {
if netplay.as_ref().unwrap().player_number == 0 {
netplay::send_save(
netplay.as_mut().unwrap(),
"eep",
&ui.saves.eeprom.0,
ui.saves.eeprom.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"sra",
&ui.saves.sram.0,
ui.saves.sram.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"fla",
&ui.saves.flash.0,
ui.saves.flash.0.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"mpk",
&ui.saves.mempak.0,
ui.saves.mempak.0.len(),
);
let mut compressed_sd = Vec::new();
if !ui.saves.sdcard.0.is_empty() {
compressed_sd = compress_file(&[(&ui.saves.sdcard.0, "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"img",
&compressed_sd,
compressed_sd.len(),
);
let mut compressed_romsave = Vec::new();
if !ui.saves.romsave.0.is_empty() {
compressed_romsave =
compress_file(&[(&postcard::to_stdvec(&ui.saves.romsave.0).unwrap(), "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"rom",
&compressed_romsave,
compressed_romsave.len(),
);
} else {
netplay::receive_save(netplay.as_mut().unwrap(), "eep", &mut ui.saves.eeprom.0);
netplay::receive_save(netplay.as_mut().unwrap(), "sra", &mut ui.saves.sram.0);
netplay::receive_save(netplay.as_mut().unwrap(), "fla", &mut ui.saves.flash.0);
netplay::receive_save(netplay.as_mut().unwrap(), "mpk", &mut ui.saves.mempak.0);
let mut compressed_sd = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
if !compressed_sd.is_empty() {
ui.saves.sdcard.0 = decompress_file(&compressed_sd, "save");
}
let mut compressed_romsave = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut compressed_romsave);
if !compressed_romsave.is_empty() {
let romsave_bytes = decompress_file(&compressed_romsave, "save");
ui.saves.romsave.0 = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
}
pub fn write_rom_save(ui: &ui::Ui) {
let f = std::fs::File::create(ui.paths.romsave_file_path.clone()).unwrap();
serde_json::to_writer(f, &ui.saves.romsave.0).unwrap();
pub fn decompress_file(input: &[u8], name: &str) -> Vec<u8> {
let mut decompressed_file = Vec::new();
{
let mut reader = zip::ZipArchive::new(std::io::Cursor::new(input)).unwrap();
let mut file = reader.by_name(name).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
pub fn write_saves(ui: &ui::Ui) {
if ui.saves.eeprom.1 {
write_save(ui, SaveTypes::Eeprom16k)
pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
let mut compressed_file = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut compressed_file));
for item in data {
writer
.start_file(
item.1,
zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Zstd),
)
.unwrap();
writer.write_all(item.0).unwrap();
}
}
if ui.saves.sram.1 {
write_save(ui, SaveTypes::Sram)
}
if ui.saves.flash.1 {
write_save(ui, SaveTypes::Flash)
}
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
compressed_file
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.saves.romsave.0).unwrap();
std::fs::write(ui.paths.romsave_file_path.clone(), data).unwrap();
}
pub fn write_saves(ui: &ui::Ui, netplay: &Option<netplay::Netplay>) {
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
if ui.saves.eeprom.1 {
write_save(ui, SaveTypes::Eeprom16k)
}
if ui.saves.sram.1 {
write_save(ui, SaveTypes::Sram)
}
if ui.saves.flash.1 {
write_save(ui, SaveTypes::Flash)
}
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.sdcard.1 {
write_save(ui, SaveTypes::Sdcard)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
}
}
}
pub fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
let path: &std::path::Path;
let data: &Vec<u8>;
match save_type {
@@ -184,6 +346,10 @@ pub fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.0.as_ref();
}
SaveTypes::Sdcard => {
path = ui.paths.sdcard_file_path.as_ref();
data = ui.saves.sdcard.0.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);
return;
+86 -40
View File
@@ -1,23 +1,37 @@
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
use crate::device;
use crate::{device, ui};
pub fn init(device: &mut device::Device, fullscreen: bool) {
let mut builder = device
.ui
.video_subsystem
.as_ref()
.unwrap()
.window("gopher64", 640, 480);
pub fn init(device: &mut device::Device) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
builder.position_centered().vulkan();
let title = std::ffi::CString::new("gopher64").unwrap();
if fullscreen {
builder.fullscreen_desktop();
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN;
if device.ui.fullscreen {
flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN;
} else {
builder.resizable();
flags |= sdl3_sys::video::SDL_WINDOW_RESIZABLE;
}
device.ui.window = Some(builder.build().unwrap());
flags |= sdl3_sys::video::SDL_WINDOW_INPUT_FOCUS;
let mut window_width = 640;
let mut window_height = 480;
if device.cart.pal {
window_width = 768;
window_height = 576;
}
device.ui.window = unsafe {
sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), window_width, window_height, flags)
};
if device.ui.window.is_null() {
panic!("Could not create window");
}
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.window) } {
panic!("Could not show window");
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
@@ -27,34 +41,60 @@ pub fn init(device: &mut device::Device, fullscreen: bool) {
DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize],
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
VI_H_START_REG: &mut device.vi.regs[device::vi::VI_H_START_REG as usize],
VI_V_START_REG: &mut device.vi.regs[device::vi::VI_V_START_REG as usize],
VI_X_SCALE_REG: &mut device.vi.regs[device::vi::VI_X_SCALE_REG as usize],
VI_Y_SCALE_REG: &mut device.vi.regs[device::vi::VI_Y_SCALE_REG as usize],
VI_WIDTH_REG: &mut device.vi.regs[device::vi::VI_WIDTH_REG as usize],
PAL: device.cart.pal,
};
unsafe {
rdp_init(
device.ui.window.as_mut().unwrap().raw() as *mut std::ffi::c_void,
device.ui.window as *mut std::ffi::c_void,
gfx_info,
fullscreen,
device.ui.config.video.upscale,
device.ui.config.video.integer_scaling,
device.ui.fullscreen,
)
}
}
pub fn close() {
pub fn close(ui: &ui::Ui) {
unsafe {
rdp_close();
sdl3_sys::video::SDL_DestroyWindow(ui.window);
}
}
pub fn update_screen() -> bool {
pub fn update_screen() {
// when the window is closed, running is set to false
unsafe { rdp_update_screen() }
}
pub fn load_state(device: &mut device::Device) {
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
DMEM: device.rsp.mem.as_mut_ptr(),
RDRAM_SIZE: device.rdram.size,
DPC_CURRENT_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_CURRENT_REG as usize],
DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize],
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
PAL: device.cart.pal,
};
unsafe {
rdp_new_processor(gfx_info, device.ui.config.video.upscale);
}
}
pub fn check_callback(device: &mut device::Device) {
let callback = unsafe { rdp_check_callback() };
device.cpu.running = callback.emu_running;
if device.netplay.is_none() {
if callback.save_state {
device.save_state = true;
} else if callback.load_state {
device.load_state = true;
}
}
}
pub fn set_register(reg: u32, value: u32) {
unsafe {
rdp_set_vi_register(reg, value);
@@ -65,25 +105,23 @@ pub fn process_rdp_list() -> u64 {
unsafe { rdp_process_commands() }
}
pub fn full_sync() {
unsafe {
rdp_full_sync();
}
}
pub fn draw_text(
text: &str,
canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
font: &rusttype::Font,
) {
pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) {
let text_size = 32;
let scale = rusttype::Scale::uniform(text_size as f32);
let v_metrics = font.v_metrics(scale);
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
// Clear the canvas
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0));
canvas.clear();
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
0,
0,
0,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderClear(renderer);
};
for glyph in font.layout(text, scale, offset) {
if let Some(bb) = glyph.pixel_bounding_box() {
@@ -91,15 +129,23 @@ pub fn draw_text(
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y + (240 - text_size);
if v > 0.5 {
canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255));
canvas
.draw_point(sdl2::rect::Point::new(x, y))
.expect("Error drawing pixel");
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
255,
255,
255,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
};
}
});
}
}
// Present the canvas
canvas.present();
if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } {
panic!("Could not present renderer");
}
}
+5 -5
View File
@@ -4,18 +4,18 @@ use crate::ui;
pub fn prompt_for_match(
words: &[String],
window_notifier: &std::sync::mpsc::Sender<Vec<String>>,
word_index_notifier: &std::sync::mpsc::Receiver<String>,
window_notifier: &tokio::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
gui_ctx: &egui::Context,
) -> u16 {
let mut dedup_words = words.to_owned();
dedup_words.sort();
dedup_words.dedup();
window_notifier.send(dedup_words).unwrap();
window_notifier.try_send(dedup_words).unwrap();
gui_ctx.request_repaint(); // this is so the window pops up right away
let mut result = word_index_notifier.try_recv();
let mut result = word_index_receiver.try_recv();
while result.is_err() {
result = word_index_notifier.try_recv();
result = word_index_receiver.try_recv();
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
ui::video::update_screen(); // so the OS doesn't complain about the game window being frozen
}