Compare commits

...

411 Commits

Author SHA1 Message Date
Logan McNaughton e7a21171f4 fix windows build (#636) 2025-12-11 21:48:28 +01:00
Logan McNaughton 413da0178e update parallel rdp 2025-12-11 17:57:59 +01:00
Logan McNaughton d8c6669da1 Update to Rust 1.92.0 (#634) 2025-12-11 17:46:50 +01:00
Logan McNaughton c6fcfddf8c some cleanup 2025-12-10 15:12:56 +01:00
Logan McNaughton 0aa5c3e63a cleanup variable 2025-12-10 12:35:22 +01:00
Logan McNaughton 6f8433138d clippy fixes (#632)
* clippy fixes

* fix netplay browser
2025-12-10 10:00:42 +01:00
Logan McNaughton ea5bbfb218 only update controller assignment after user input (#629) 2025-12-06 13:42:10 +01:00
Logan McNaughton 10af598223 cargo update 2025-11-26 16:48:39 +01:00
dependabot[bot] c08511adc5 Bump actions/checkout from 5 to 6 (#622)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 04:24:48 +01:00
Logan McNaughton f116617ac3 remove redundant SDL_PumpEvents 2025-11-22 14:31:08 +01:00
Logan McNaughton 1141095b65 some code cleanup 2025-11-22 14:14:22 +01:00
Logan McNaughton 162a6df56d move if statement for better performance 2025-11-22 13:40:26 +01:00
Logan McNaughton 72f47e5aff addressing hack to fix compatibility with latest libdragon (#620) 2025-11-22 10:04:31 +01:00
Logan McNaughton 6d40f5a12d cargo update 2025-11-21 10:44:52 +01:00
Logan McNaughton 64e6d75db1 Update Granite (#618)
* Update Granite

* add file
2025-11-21 10:44:09 +01:00
Logan McNaughton 20761faf8c rust 1.91.1 2025-11-11 12:43:54 +01:00
Adam Steinberg 79eb34d83d unsolicited main.rs refactor (#614)
* main.rs refactor

* remove anyhow

* forgive me clippy, for I have sinned
2025-11-08 21:34:35 +01:00
Logan McNaughton c4057c2f21 bump to 1.1.11 2025-11-05 15:29:56 +01:00
Logan McNaughton 581cc0b9aa cargo update 2025-11-05 15:16:57 +01:00
Logan McNaughton 6f6e21c718 tweak build options 2025-11-05 15:12:12 +01:00
Logan McNaughton 181774a1de pass netplay_id to workflow 2025-11-04 15:38:30 +01:00
Logan McNaughton 002fbbe8a4 set netplay id from environment (#606) 2025-11-04 08:27:32 +01:00
Logan McNaughton ab630cc8cb Use skia backend for gui (#605)
* Use skia backend for gui

* more

* use clang-cl

* more

* more
2025-11-03 23:07:10 +01:00
Logan McNaughton 06f9c56a79 bump to 1.1.10 2025-10-30 22:52:34 +01:00
Logan McNaughton c3f4b11519 update sdl3 2025-10-30 22:47:45 +01:00
Logan McNaughton 921a0d5362 Rust 1.91 (#599) 2025-10-30 20:14:39 +01:00
Logan McNaughton 24fc5b9638 update parallel-rdp 2025-10-30 13:59:20 +01:00
dependabot[bot] 1559856483 Bump signpath/github-action-submit-signing-request from 1.3 to 2.0 (#597)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 1.3 to 2.0.
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v1.3...v2.0)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '2.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 08:36:02 +01:00
dependabot[bot] 1deb91fc1e Bump actions/upload-artifact from 4 to 5 (#596)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-27 06:01:31 +01:00
Logan McNaughton 726a58f854 cargo update 2025-10-26 13:45:56 +01:00
Logan McNaughton fe0297690f pump events during vi (#593)
* pump events during vi

* add var

* more

* more
2025-10-26 13:42:25 +01:00
Logan McNaughton 847df61526 Add pause between key bindings (#594) 2025-10-26 13:33:40 +01:00
Logan McNaughton 6de8128f5b cargo update 2025-10-12 12:30:57 +02:00
Logan McNaughton 6c09176d12 add .srm for GB saves 2025-10-11 19:32:35 +02:00
Logan McNaughton 2050098735 fix out of bounds access (#590) 2025-10-06 09:02:50 +02:00
Logan McNaughton d3ecc37da6 fix compare interrupt issue (#588) 2025-10-01 10:05:03 +02:00
Logan McNaughton a0237e9f7d bump to 1.1.9 2025-09-28 10:00:37 +02:00
Logan McNaughton d6d608b674 Revert "more audio cleanup"
This reverts commit f3271cba63.
2025-09-28 09:59:02 +02:00
Logan McNaughton f254bec968 bump to 1.1.8 2025-09-27 21:33:21 +02:00
Logan McNaughton bd4aff2b2c cargo update 2025-09-27 21:32:27 +02:00
Logan McNaughton 0bf70a77ea correct load block size 2025-09-26 18:52:27 +02:00
Logan McNaughton e23e7f33c1 Re-add wgpu
I didn't see any evidence of crashing
2025-09-26 07:23:48 +02:00
Logan McNaughton 9d0516545e reorder items 2025-09-25 16:14:48 +02:00
Logan McNaughton e3023a902f further depth buffer tracking tweaks (#583) 2025-09-25 15:59:58 +02:00
Logan McNaughton 431c7c2a8d cargo update 2025-09-25 13:32:23 +02:00
Logan McNaughton cf99539f85 remove wgpu (#576) 2025-09-25 07:36:23 +02:00
Logan McNaughton c2493b829e watch z-buffer reads as well (#581) 2025-09-24 23:00:39 +02:00
Logan McNaughton 2f25de55d7 change depth buffer tracking (#580) 2025-09-24 20:06:39 +02:00
Logan McNaughton 116ffe9845 fix clippy warning 2025-09-22 09:15:41 +02:00
Logan McNaughton f3271cba63 more audio cleanup 2025-09-21 18:36:03 +02:00
Logan McNaughton 9037680cda cleanup audio code (#579) 2025-09-21 16:30:11 +02:00
Logan McNaughton fc41e26a6d expose 8x resolution (#578) 2025-09-21 13:36:13 +02:00
Logan McNaughton d1f28bb7a8 optimize rdp_check_framebuffers (#577) 2025-09-21 10:43:49 +02:00
Logan McNaughton 9db831fdef disable repeat mode on MI_CLR_INIT 2025-09-20 17:21:06 +02:00
Logan McNaughton 004b603e96 support basic mi repeat mode (#575)
* support basic mi repeat mode

* more

* more
2025-09-20 17:09:30 +02:00
Logan McNaughton 8661e2c9b1 always enable lto 2025-09-20 08:37:55 +02:00
Logan McNaughton c712f2a288 bump to 1.1.7 2025-09-19 12:57:05 +02:00
Logan McNaughton 366162e6fe cargo update 2025-09-19 12:56:07 +02:00
Logan McNaughton 2b93fb8aa1 re-add framebuffer tracking (#573) 2025-09-19 11:32:48 +02:00
Logan McNaughton a191701610 update to rust 1.90 (#574)
* update to rust 1.90

* more
2025-09-18 22:46:22 +02:00
Logan McNaughton 0605967c28 modify rsp sync points 2025-09-16 11:39:23 +02:00
Logan McNaughton d6e6a16814 reset device.rdp.last_status_value 2025-09-16 10:02:07 +02:00
Logan McNaughton e374825d5e add release-with-debug profile 2025-09-15 19:23:21 +02:00
Logan McNaughton f6d6746207 use addr_masked 2025-09-15 18:32:25 +02:00
Logan McNaughton 100238f198 optimize some reads/writes (#571) 2025-09-15 18:26:45 +02:00
Logan McNaughton daee53a626 Optimize VU instructions (#570) 2025-09-15 15:50:28 +02:00
Logan McNaughton 018d8ca3e3 fix parallel-rdp build 2025-09-15 12:28:54 +02:00
Logan McNaughton 24d26d864e update parallel-rdp 2025-09-15 12:21:26 +02:00
Logan McNaughton 7b8c549e8e remove rdp sync speed hack (#567) 2025-09-15 09:50:23 +02:00
Logan McNaughton f6c7e3d3c0 use wgpu for gui 2025-09-12 14:16:46 +02:00
Logan McNaughton 374b01dccf cargo update 2025-09-12 13:58:21 +02:00
Logan McNaughton 569b26a067 panic on attempted use of mi repeat mode 2025-09-10 13:16:57 +02:00
Logan McNaughton 380d196ae7 cargo update 2025-09-05 10:54:03 +02:00
Logan McNaughton 4fa1a8ad39 remove redundant get_rooms netplay call (#563)
* remove redundant get_rooms netplay call

* unwrap
2025-09-05 09:32:17 +02:00
Logan McNaughton 8acf3b9de8 bump to 1.1.6 2025-08-24 10:58:58 +02:00
Logan McNaughton b3297f82b2 cargo update 2025-08-24 10:58:01 +02:00
Logan McNaughton 2127705e7b set deadzon max to 99 2025-08-24 07:29:07 +02:00
Logan McNaughton cccb079d63 cleanup deadzone code (#558) 2025-08-24 00:05:03 +02:00
Logan McNaughton c52b680753 use saturating abs for deadzone (#557) 2025-08-23 22:52:04 +02:00
Logan McNaughton 8799650245 add deadzone option (#554)
* add deadzone option

* add deadzone calc

* more
2025-08-23 15:36:03 +02:00
Logan McNaughton 90be2130c1 move cheats back 2025-08-23 09:36:47 +02:00
Logan McNaughton 96af33c434 Split render frame and update screen, move pause loop (#556)
* split render and update screen functions

* move pause loop
2025-08-23 08:54:47 +02:00
Logan McNaughton 0a42152bcb move cheats 2025-08-22 22:42:09 +02:00
Logan McNaughton db335e24d2 render frame before speed limiter (#555) 2025-08-22 07:59:28 +02:00
Logan McNaughton 4857b86897 bump to 1.1.5 (#553) 2025-08-21 15:22:19 +02:00
Logan McNaughton 26022fff72 cargo update 2025-08-21 15:03:50 +02:00
Logan McNaughton 658dede9e9 use dependabot to update rust (#552) 2025-08-20 10:26:26 +02:00
Logan McNaughton 6ad1a31e34 correct joystick hat diagonal input (#551) 2025-08-20 09:15:41 +02:00
Logan McNaughton 4476d476de add frame advance (#549) 2025-08-18 10:42:29 +02:00
Logan McNaughton 8037fb426e UNF loader support (#526) 2025-08-18 08:44:12 +02:00
Logan McNaughton f0381a2cd1 add uppercase file extensions (#548) 2025-08-17 21:01:23 +02:00
Logan McNaughton c8e70e6125 cargo update 2025-08-17 20:56:02 +02:00
dependabot[bot] e9664b1087 Bump actions/checkout from 4 to 5 (#545)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 13:02:31 +02:00
Logan McNaughton 3f72700da0 bump to 1.1.4 (#541) 2025-08-07 19:21:54 +02:00
Logan McNaughton 9fcd2835eb Update to rust 1.89.0 (#540) 2025-08-07 13:17:39 +02:00
Logan McNaughton c6767c94a6 move sse2neon to submodule (#539) 2025-08-07 12:17:50 +02:00
Logan McNaughton f1968e3a58 use NearestClamp for image 2025-08-06 23:02:08 +02:00
Logan McNaughton 23fdfff1ac some clippy fixes (#538)
* some clippy fixes

* more
2025-08-06 12:18:40 +02:00
Logan McNaughton fc512d9517 fix netplay for unknown ROMs 2025-08-06 09:12:35 +02:00
dependabot[bot] 3c317460fd Bump signpath/github-action-submit-signing-request from 1.2 to 1.3 (#537)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 1.2 to 1.3.
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v1.2...v1.3)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '1.3'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-06 08:56:09 +02:00
Logan McNaughton b1fcaa5cf4 add minimum size to combobox 2025-08-05 21:55:41 +02:00
Logan McNaughton 98c9cf142d slight improvment to combobox sizes 2025-08-05 21:40:31 +02:00
Logan McNaughton 1eb1503981 Onscreen drawing (#536)
* onscreen messages

* more

* more

* more

* more

* use sdl_ttf

* more

* more

* more

* more

* test

* mroe

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-08-05 15:59:49 +02:00
Logan McNaughton 5aa9a65e7b add support for save state slots (#534)
* add support for save state slots

* add log
2025-08-04 14:59:50 +02:00
Logan McNaughton 71f1d98132 bump to 1.1.3 (#533) 2025-08-03 13:30:27 +02:00
Logan McNaughton 0037c0f344 Don't wait for signpath completion 2025-08-03 11:56:12 +02:00
Logan McNaughton 752e804101 refreshing session message 2025-08-03 09:38:23 +02:00
Logan McNaughton 73354246f3 netplay dispatcher (#532)
* netplay dispatcher

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-08-03 09:25:57 +02:00
Logan McNaughton a2d23c2f41 Fix crash in netplay windows (#530)
* Fix crash in netplay windows

* more
2025-07-29 23:02:07 +02:00
dependabot[bot] 6e652c7dbd Bump signpath/github-action-submit-signing-request from 1.1 to 1.2 (#529)
Bumps [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) from 1.1 to 1.2.
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/v1.1...v1.2)

---
updated-dependencies:
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '1.2'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 08:50:59 +02:00
Logan McNaughton 20749cf036 bump to 1.1.2 (#528) 2025-07-25 21:45:52 +02:00
Logan McNaughton d7f656cd6a update cargo packages 2025-07-25 21:26:54 +02:00
Logan McNaughton 8558334391 Add code signing policy 2025-07-25 16:23:46 +02:00
Logan McNaughton b6de872d78 Sign code using SignPath (#525)
* Sign code using SignPath

* more

* more

* more

* more

* more

* use release-signing
2025-07-25 11:45:11 +02:00
Logan McNaughton 712dc10c65 cleanup netplay error checking 2025-07-25 07:20:50 +02:00
Logan McNaughton cd2b5b4e92 error checking for netplay ui channels (#527)
* error checking for netplay ui channels

* more
2025-07-25 07:00:45 +02:00
Logan McNaughton 98b5d6506e add spacing in settings 2025-07-20 23:12:01 +02:00
Logan McNaughton f2e8bc6356 data access fixes 2025-07-20 21:33:07 +02:00
Logan McNaughton 328e1730a0 Update parallel-rdp (#523) 2025-07-20 09:55:36 +02:00
Logan McNaughton 8010a6211c Use default AR on windows (#522)
* Use default AR on windows

* use llvm-ar

* more
2025-07-20 08:16:04 +02:00
Logan McNaughton 41b1fd05be build without clang-cl on Windows (#521) 2025-07-20 07:51:00 +02:00
Logan McNaughton c5da2a795f use specific llvm version (#520)
* use specific llvm version

* more

* more

* more

* more
2025-07-19 18:30:17 +02:00
Logan McNaughton 7361a9bd5c remove unneeded flag (#519) 2025-07-19 16:45:27 +02:00
Logan McNaughton d4e8933352 Use rust-lld on Windows (#518) 2025-07-19 16:31:58 +02:00
Logan McNaughton fff376db5e cargo update 2025-07-19 15:51:57 +02:00
Logan McNaughton d7f1c8415a disable cargo dependabot 2025-07-19 15:51:23 +02:00
Logan McNaughton ac09be4202 Increase axis movement requirement when configuring controller (#514) 2025-07-17 15:22:21 +02:00
Logan McNaughton 022190384a Add support for custom netplay server url (#511)
* Add support for custom netplay server url

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-07-16 15:42:54 +02:00
Logan McNaughton e4dd5c2dee Fix crash in wrestlemania (#510) 2025-07-16 12:13:12 +02:00
Logan McNaughton 3e95059ec7 add pause hotkey (#509) 2025-07-15 20:43:31 +02:00
Logan McNaughton abc39e74ef Remember ROM dir (#508)
* Remember ROM dir

* more

* more

* more

* more

* more
2025-07-15 20:05:46 +02:00
Logan McNaughton 7109d28223 update c standard (#507)
* update c standard

* more

* more
2025-07-15 15:28:34 +02:00
Logan McNaughton 1d1e3829e4 Update icon 2025-07-14 15:47:11 +02:00
Logan McNaughton a46e4f505b re-enable mac build 2025-07-13 17:51:37 +02:00
Logan McNaughton 03fbe658f2 Update netplay server url (#491) 2025-07-12 15:01:59 +02:00
Logan McNaughton b96c9e4cdc bump to 1.1.1 (#490) 2025-07-11 21:28:53 +02:00
Logan McNaughton cea405ea30 bump to 1.1.0 (#489) 2025-07-11 21:14:18 +02:00
Logan McNaughton c13d354201 savestate speed improvements (#486)
* savestate speed improvements

* more
2025-07-10 18:13:16 +02:00
Logan McNaughton ff43f59f74 fix compile warning (#485)
* fix compile warning

* more
2025-07-10 14:38:28 +02:00
dependabot[bot] eb97b96daf Bump zip from 4.2.0 to 4.3.0 (#483)
Bumps [zip](https://github.com/zip-rs/zip2) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-version: 4.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-10 12:48:44 +02:00
Logan McNaughton 9bf43a16b4 Move SDL_PumpEvents (#482)
* Move SDL_PumpEvents

* more
2025-07-09 22:07:38 +02:00
Logan McNaughton b67247f935 Sync sdl window to prevent fullscreen issues (#481) 2025-07-09 19:55:24 +02:00
Logan McNaughton f10c591047 init rdp device 2025-07-09 10:45:12 +02:00
Logan McNaughton 82f1965f26 avoid syncing RDP too much (#479) 2025-07-09 10:31:20 +02:00
Logan McNaughton a5ddea353a print sdl errors (#478) 2025-07-09 10:19:44 +02:00
Logan McNaughton 6c977dbe5e Update README.md 2025-07-07 14:38:36 +02:00
Logan McNaughton 5dcfd72b18 bump to 1.0.21 (#477) 2025-07-06 20:07:31 +02:00
Logan McNaughton e578d6083d Don't use wgpu 2025-07-06 15:57:15 +02:00
Logan McNaughton e99a236070 use thin lto (#475) 2025-07-06 14:36:52 +02:00
Logan McNaughton e37633ecbd LTO2 (#474)
* LTO2

* more
2025-07-06 13:18:56 +02:00
Logan McNaughton d42e145b1f rename rdp build 2025-07-06 11:04:19 +02:00
Logan McNaughton 8ab31b0c28 Use Clang (#473) 2025-07-06 10:44:05 +02:00
Logan McNaughton 6d43191c56 cleanup release args 2025-07-06 07:28:34 +02:00
Logan McNaughton 75457c5f7a codegen-units = 16 2025-07-05 22:06:47 +02:00
Logan McNaughton 6d85358f2b re-order destruction of UI elements (#471)
* re-order destruction of UI elements

* more
2025-07-05 21:25:41 +02:00
Logan McNaughton ef4f93d6f0 bump to 1.0.20 (#470) 2025-07-05 18:14:18 +02:00
Logan McNaughton e4b7e291dd update socket2 (#469) 2025-07-05 17:52:29 +02:00
Logan McNaughton 16732d3d3c use is_multiple_of (#468) 2025-07-05 09:31:03 +02:00
Logan McNaughton 5857da0404 add cheats enabled var 2025-07-05 09:25:31 +02:00
Logan McNaughton 6c6d0f32a7 fix cheats 2025-07-04 22:33:25 +02:00
Logan McNaughton 7b400a0edc Update cheats (#467) 2025-07-04 22:13:52 +02:00
Logan McNaughton 8f6c6f1b94 Cheat support (#459)
* Cheat support

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-07-04 16:46:03 +02:00
Logan McNaughton 7fd43886de add a few vru phrases 2025-07-03 13:27:33 +02:00
Logan McNaughton 572807def0 add missing disable_expansion_pak in netplay features 2025-07-02 22:26:01 +02:00
Logan McNaughton 10a1e3a556 rename netplaydialog 2025-07-02 22:22:42 +02:00
Logan McNaughton c12dc460a7 update tokio 2025-07-02 22:17:34 +02:00
Logan McNaughton 72456b48e5 some code cleanup 2025-07-02 22:15:15 +02:00
Logan McNaughton 6ecb81fda1 move netplay_window to netplay.rs 2025-07-02 22:08:46 +02:00
Logan McNaughton 31b4e7944b non-empty default value for ram (#465) 2025-07-02 14:33:19 +02:00
Logan McNaughton dc1535ea04 better RAM access checks (#464)
* better RAM access checks

* more
2025-07-02 14:25:03 +02:00
Logan McNaughton 7a59714417 Make expansion pak optional (#463)
* Make expansion pak optional

* more
2025-07-02 09:59:01 +02:00
Logan McNaughton ead561933b Don't adjust variable limit after toggle (#458)
* Don't adjust variable limit after toggle

* more
2025-06-30 10:55:34 +02:00
Logan McNaughton 1d331e73fb cleanup aarch64 flags (#455) 2025-06-28 18:51:12 +02:00
Logan McNaughton 25aaa985f1 netplay feedback button (#454) 2025-06-28 16:50:59 +02:00
Logan McNaughton 11830b0222 add button for netplay discord (#452) 2025-06-28 14:19:50 +02:00
Logan McNaughton 3183a5eb0b add some more about buttons (#451) 2025-06-28 10:43:09 +02:00
Logan McNaughton 5d8ac37a0f Add new version button to main screen (#450) 2025-06-27 13:09:22 +02:00
Logan McNaughton baa6a6c635 Update download links 2025-06-27 10:15:25 +02:00
Logan McNaughton f389be0773 Fix netplay server url (#449) 2025-06-26 21:04:54 +02:00
Logan McNaughton 30eee885e4 Update to 1.0.19 (#448) 2025-06-26 20:58:41 +02:00
Logan McNaughton 3da98c58c5 use rust 1.88 (#447)
* Use Rust 1.88

* add win aarch64 (commented out)

* Clippy fixes
2025-06-26 20:36:10 +02:00
Logan McNaughton 6e9b8ae126 Don't enable VRU during netplay (#445) 2025-06-26 11:44:56 +02:00
Logan McNaughton ada6936b9a print message when creating/loading savestate 2025-06-26 09:50:22 +02:00
Logan McNaughton 0bb3d2cd5d Slint fixes (#433)
* Slint fixes

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-06-26 09:00:23 +02:00
Logan McNaughton f213b0b9a9 Use Slint for GUI (#432)
* Use Slint for GUI

* 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

* 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

* 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

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-06-25 12:38:33 +02:00
Logan McNaughton 69029f3a02 Don't panic on invalid transferpak access (#430) 2025-06-23 16:41:37 +02:00
dependabot[bot] 0c2b4b2aac Bump sevenz-rust2 from 0.14.1 to 0.15.1 (#428)
Bumps [sevenz-rust2](https://github.com/hasenbanck/sevenz-rust) from 0.14.1 to 0.15.1.
- [Changelog](https://github.com/hasenbanck/sevenz-rust2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hasenbanck/sevenz-rust/compare/v0.14.1...v0.15.1)

---
updated-dependencies:
- dependency-name: sevenz-rust2
  dependency-version: 0.15.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-23 07:10:51 +02:00
Logan McNaughton c7680f10a0 fix compiler warning 2025-06-16 06:31:23 +02:00
dependabot[bot] a28ed6766b Bump tungstenite from 0.26.2 to 0.27.0 (#426)
Bumps [tungstenite](https://github.com/snapview/tungstenite-rs) from 0.26.2 to 0.27.0.
- [Changelog](https://github.com/snapview/tungstenite-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tungstenite-rs/compare/v0.26.2...v0.27.0)

---
updated-dependencies:
- dependency-name: tungstenite
  dependency-version: 0.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 05:51:54 +02:00
dependabot[bot] 6e6b1d463d Bump zip from 4.0.0 to 4.1.0 (#424)
Bumps [zip](https://github.com/zip-rs/zip2) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 05:44:13 +02:00
dependabot[bot] 662c0060a1 Bump cc from 1.2.25 to 1.2.27 (#425)
Bumps [cc](https://github.com/rust-lang/cc-rs) from 1.2.25 to 1.2.27.
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.25...cc-v1.2.27)

---
updated-dependencies:
- dependency-name: cc
  dependency-version: 1.2.27
  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-06-16 05:36:29 +02:00
Logan McNaughton 79e4cb1807 Update mac runner 2025-06-13 11:04:05 +02:00
Logan McNaughton f3e819158a fix formatting 2025-06-12 18:41:44 +02:00
Logan McNaughton e83bcbb4ce remove session reference for netplay 2025-06-12 18:39:31 +02:00
Logan McNaughton eb5d255fd7 move get_controller_names 2025-06-12 13:34:59 +02:00
dependabot[bot] 776d127a84 Bump bindgen from 0.71.1 to 0.72.0 (#420)
Bumps [bindgen](https://github.com/rust-lang/rust-bindgen) from 0.71.1 to 0.72.0.
- [Release notes](https://github.com/rust-lang/rust-bindgen/releases)
- [Changelog](https://github.com/rust-lang/rust-bindgen/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/rust-bindgen/compare/v0.71.1...v0.72.0)

---
updated-dependencies:
- dependency-name: bindgen
  dependency-version: 0.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-09 06:56:56 +02:00
Logan McNaughton af5f292459 Bump to 1.0.18 (#418) 2025-06-05 12:34:20 +02:00
Logan McNaughton c25a56df50 Fix netplay crash when server is down (#417)
* Fix netplay crash when server is down

* more
2025-06-05 12:16:11 +02:00
Logan McNaughton 9d9f965b41 fix sevenz-rust2 version 2025-06-04 09:21:59 +02:00
Logan McNaughton 68197d235b update cargo packages 2025-06-03 16:11:58 +02:00
Logan McNaughton 1c91abbf2d Potential fix for code scanning alert no. 2: Workflow does not contain permissions (#416)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-03 16:06:38 +02:00
Logan McNaughton 9348594eb0 Potential fix for code scanning alert no. 4: Workflow does not contain permissions (#413)
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-06-03 15:45:47 +02:00
dependabot[bot] e97f4e7f16 Bump zip from 3.0.0 to 4.0.0 (#404)
Bumps [zip](https://github.com/zip-rs/zip2) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-22 06:28:35 +02:00
Logan McNaughton cea55914d9 update linux deps (#401) 2025-05-16 13:43:36 +02:00
Logan McNaughton 77a22f08b3 ignore settings.json 2025-05-16 13:32:54 +02:00
Logan McNaughton cf0f325ced Update rust to 1.87 (#400) 2025-05-15 20:57:22 +02:00
Logan McNaughton 21f1a274b4 update cargo packages 2025-05-06 13:02:59 +02:00
Logan McNaughton def8932922 bump to 1.0.17 (#384) 2025-04-15 23:31:22 +02:00
Logan McNaughton 5b6f9b76a6 fix interlaced image (#383) 2025-04-15 23:23:47 +02:00
Logan McNaughton 5a838464a4 update cargo packages 2025-04-15 22:44:24 +02:00
Logan McNaughton 383b568012 remove framebuffer tracking (#381)
* remove framebuffer tracking

* more
2025-04-15 11:08:40 +02:00
Logan McNaughton 27bb66df4c Cleanup some controller code (#378)
* controller hotplugging

* more

* more
2025-04-13 21:07:54 +02:00
Logan McNaughton 33f74e5062 update cargo packages 2025-04-08 07:52:02 +02:00
Logan McNaughton 68f90e1cd7 update 7z package (#370) 2025-04-05 09:30:12 +02:00
dependabot[bot] cc5f636912 Bump zip from 2.5.0 to 2.6.0 (#369)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.5.0...v2.6.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-04 06:51:11 +02:00
Logan McNaughton e3a9849ad7 bump to 1.0.16 (#368) 2025-04-03 12:13:20 +02:00
Logan McNaughton 7ed6867691 Update rust to 1.86.0 (#367) 2025-04-03 12:01:15 +02:00
Logan McNaughton 11a8e96564 better default for address tracking 2025-04-02 14:06:43 +02:00
Logan McNaughton 7111cc9761 update cargo packages 2025-04-02 12:26:48 +02:00
Logan McNaughton b4832b941f sync parallel rdp for texture images (#361)
* sync parallel rdp for texture images

* more

* more

* more

* more

* more
2025-04-02 12:24:30 +02:00
dependabot[bot] 82b1888f82 Bump governor from 0.9.0 to 0.10.0 (#362)
Bumps [governor](https://github.com/boinkor-net/governor) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/boinkor-net/governor/releases)
- [Changelog](https://github.com/boinkor-net/governor/blob/master/release.toml)
- [Commits](https://github.com/boinkor-net/governor/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: governor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-28 07:47:22 +01:00
Logan McNaughton f35869190b gather controller input even without focus (#360) 2025-03-27 08:55:52 +01:00
Logan McNaughton 64eda1a1e3 bump to 1.0.15 (#358) 2025-03-26 11:27:02 +01:00
Logan McNaughton 8026bf13d6 update cargo packages 2025-03-26 11:18:05 +01:00
Logan McNaughton 83103130e7 cleanup 2025-03-25 12:59:32 +01:00
dependabot[bot] 87717c4c7e Bump governor from 0.8.1 to 0.9.0 (#357)
Bumps [governor](https://github.com/boinkor-net/governor) from 0.8.1 to 0.9.0.
- [Release notes](https://github.com/boinkor-net/governor/releases)
- [Changelog](https://github.com/boinkor-net/governor/blob/master/release.toml)
- [Commits](https://github.com/boinkor-net/governor/compare/v0.8.1...v0.9.0)

---
updated-dependencies:
- dependency-name: governor
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-25 06:00:08 +01:00
Logan McNaughton 6355885a56 let server determine netplay buffer target (#356) 2025-03-24 15:03:50 +01:00
dependabot[bot] 5d180dfb65 Bump zip from 2.4.2 to 2.5.0 (#354)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.4.2 to 2.5.0.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.4.2...v2.5.0)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-24 11:28:28 +01:00
Logan McNaughton fd6965ff3b remove rusttype (#355)
* remove rusttype

* more
2025-03-24 11:06:17 +01:00
Logan McNaughton 2fcf202a5c move variable into function 2025-03-23 22:07:32 +01:00
Logan McNaughton 93630c4156 fix audit workflow (#350) 2025-03-23 21:31:24 +01:00
Logan McNaughton a66e8184e4 add workflow dispatch option 2025-03-23 19:25:15 +01:00
Logan McNaughton e4f05af38d add audit action (#349) 2025-03-23 19:24:17 +01:00
Logan McNaughton 8a09cb711f Add RDP to savestate (#348)
* organize RDP device

* more

* more

* more

* more

* more

* more

* more

* more
2025-03-22 21:24:39 +01:00
Logan McNaughton a58eed7dc9 add a few more loops (#347)
* add a few more loops

* more
2025-03-22 08:23:48 +01:00
Logan McNaughton 09ba7e4d13 use loop for cache (#346) 2025-03-21 22:37:04 +01:00
Logan McNaughton 7316e41c7e pin rust version 2025-03-21 15:37:11 +01:00
Logan McNaughton 1aa00de1b2 fix crash when attempting to read zip without rom (#345)
* fix crash when attempting to read zip without rom

* more
2025-03-19 18:37:35 +01:00
Logan McNaughton f71ec9d54a add cic entry for libdragon ipl3 (#344) 2025-03-19 14:15:00 +01:00
Logan McNaughton bf2734e59c update cargo packages 2025-03-19 09:56:00 +01:00
dependabot[bot] 7ee26961f1 Bump zip from 2.2.3 to 2.4.1 (#341)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.2.3 to 2.4.1.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.2.3...v2.4.1)

---
updated-dependencies:
- dependency-name: zip
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 09:21:31 +01:00
Logan McNaughton 5d692318fe better region check 2025-03-17 10:06:26 +01:00
Logan McNaughton f2966230f9 Fix freeze in Bass Hunter 64 (#339) 2025-03-17 09:46:09 +01:00
Logan McNaughton 48575f9c52 fix windows cross compile (#336) 2025-03-16 20:31:57 +01:00
Logan McNaughton dbab58f4a7 catch possible error in loading save state (#335) 2025-03-16 17:10:16 +01:00
Logan McNaughton f6b0b59bbc fix savestate default memory 2025-03-16 10:32:32 +01:00
Logan McNaughton ca61695dd1 update cargo packages 2025-03-16 10:13:23 +01:00
Logan McNaughton a182fbb1f9 cleanup function 2025-03-16 10:11:50 +01:00
Logan McNaughton da9b2247f8 remove more mut (#334) 2025-03-16 10:10:16 +01:00
Logan McNaughton 1bcadb3a8b remove un-needed mut (#333)
* remove un-needed mut

* more

* more
2025-03-16 09:48:32 +01:00
Logan McNaughton 433f08c7e0 Update README.md 2025-03-16 07:55:10 +01:00
Logan McNaughton b07dde8768 disable mempak for Chameleon Twist (#332) 2025-03-16 07:35:37 +01:00
Logan McNaughton cba7bc7477 ensure gain setting persists 2025-03-16 07:18:05 +01:00
Logan McNaughton ea6f203e33 allow up to 200% audio volume (#331) 2025-03-15 20:21:46 +01:00
Logan McNaughton e48ad9bdff add way to change audio volume (#330)
* add way to change audio volume

* more
2025-03-15 11:11:18 +01:00
Logan McNaughton 11b1137412 update netplay server name 2025-03-13 21:03:50 +01:00
Logan McNaughton 5528f233c0 fix compile warning 2025-03-13 16:51:41 +01:00
Logan McNaughton 9d18673257 bump to 1.0.14 (#328) 2025-03-13 07:20:29 +01:00
Logan McNaughton 2a3fa03b35 update cargo packages 2025-03-13 07:09:52 +01:00
Logan McNaughton ec2f47e5d3 cleanup vector code 2025-03-11 15:08:35 +01:00
Logan McNaughton 139d66eb82 use vector for rdram_dirty (#322) 2025-03-11 14:52:57 +01:00
Logan McNaughton 6e10bee216 use glow for egui (#321)
* Revert "use GL backend for GUI to avoid conflicts with game window (#287)"

This reverts commit 00964f06f0.

* use glow
2025-03-11 09:43:09 +01:00
Logan McNaughton dff9fc5381 bump to 1.0.13 (#318) 2025-03-10 23:01:21 +01:00
Logan McNaughton 84dcc6c154 fix image scale when using upscaling (#317) 2025-03-10 22:53:16 +01:00
Logan McNaughton 8aa23e1665 bump to 1.0.12 (#316) 2025-03-10 17:22:51 +01:00
Logan McNaughton f2b75a2dcc update cargo packages 2025-03-10 17:14:04 +01:00
Logan McNaughton 5bac846bda update shaders (#315) 2025-03-10 17:13:45 +01:00
Logan McNaughton e93c275795 Add option for CRT shader (#314)
* add CRT shader

* more

* more

* update type

* more

* more

* more

* more
2025-03-10 16:52:25 +01:00
Logan McNaughton 67a8bb5e4c show VRU dialog in main window (#311) 2025-03-09 22:20:16 +01:00
Logan McNaughton 2828accee1 re-arrange UI struct (#310)
* re-arrange UI struct

* format
2025-03-09 10:30:43 +01:00
Logan McNaughton 16a9754bda GB MBC1 support (#309) 2025-03-08 21:48:51 +01:00
Logan McNaughton fbe95767f5 update zip 2025-03-07 18:40:11 +01:00
Logan McNaughton 0989be1e0f update cargo packages 2025-03-07 18:36:08 +01:00
Logan McNaughton e73816541e condense code (#308) 2025-03-07 18:35:48 +01:00
Logan McNaughton f6b2342e0e always make the window resizeable (#305) 2025-03-07 10:43:35 +01:00
Logan McNaughton 7ab57d592b bump to 1.0.11 (#301) 2025-03-05 15:20:42 +01:00
Logan McNaughton a2cda03c8d update cargo packages 2025-03-05 15:12:05 +01:00
Logan McNaughton a07237aa64 set netplay buffer target (#300) 2025-03-04 21:23:33 +01:00
Logan McNaughton fdc17f4ae5 make is viewer optional (#299)
* make is viewer optional

* more
2025-03-04 18:52:47 +01:00
Logan McNaughton 4802f3b71a remove pub 2025-03-04 12:03:30 +01:00
Logan McNaughton a4c505c72d remove some code duplication (#298) 2025-03-04 10:12:56 +01:00
Logan McNaughton 30cb3f8339 add some randomness (#297) 2025-03-04 09:45:25 +01:00
Logan McNaughton a5f5867ebb update cargo packages 2025-03-03 10:03:44 +01:00
Logan McNaughton ee8e34e17c Add audio file 2025-03-01 21:59:35 +01:00
Logan McNaughton da1e686c1a Add option for overclocking (#293)
* add option to overclock CPU

* netplay

* more

* more

* more

* more

* more
2025-02-28 21:34:33 +01:00
Logan McNaughton 5a3609d501 add option to crop out letterbox (#292) 2025-02-28 09:47:34 +01:00
Logan McNaughton 7b51ca82eb add widescreen stretch option (#291) 2025-02-27 22:05:19 +01:00
Logan McNaughton ee77e75855 bump to 1.0.10 (#290) 2025-02-27 17:22:19 +01:00
Logan McNaughton ff093a9f7f Update README.md 2025-02-27 15:55:07 +01:00
Logan McNaughton f7cb0db348 update cargo packages 2025-02-27 11:31:18 +01:00
Logan McNaughton eb9f37f1f5 add button to open save folder (#289) 2025-02-27 11:29:30 +01:00
Logan McNaughton 37b902eb31 use deterministic rtc time (#288)
* use deterministic rtc time

* netplay
2025-02-27 10:59:44 +01:00
Logan McNaughton 00964f06f0 use GL backend for GUI to avoid conflicts with game window (#287) 2025-02-27 10:04:53 +01:00
Logan McNaughton 319d7c8553 some mbc3 rtc code (#284) 2025-02-26 21:00:15 +01:00
Logan McNaughton 82b4adb4ad flash cart menu support (#283)
* flash cart menu support

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-02-26 10:04:44 +01:00
Logan McNaughton 8fbc63a677 add links to wiki and discord (#282) 2025-02-24 18:50:29 +01:00
Logan McNaughton 48ee50dea3 fix Stadium 2 GB games (#281) 2025-02-24 15:31:19 +01:00
Logan McNaughton 34a09925b4 fix GB tower (#280) 2025-02-24 14:43:06 +01:00
Logan McNaughton 168347b46e Transfer Pak UI (#279)
* Transfer Pak UI

* more

* more

* more

* more
2025-02-24 12:49:33 +01:00
Logan McNaughton 95a3d3d5ec fix type 2025-02-23 22:28:07 +01:00
Logan McNaughton f6349f13da tpak cleanup (#278)
* tpak cleanup

* more

* more
2025-02-23 16:10:53 +01:00
Logan McNaughton fb2cf7fcb9 move UI elements around (#277) 2025-02-23 13:25:23 +01:00
Logan McNaughton 9980302d37 more tpak code (#276)
* more tpak code

* more
2025-02-23 13:10:50 +01:00
Logan McNaughton 4692e2bcb9 more tpak code (#275)
* more tpak code

* more
2025-02-23 11:59:15 +01:00
Logan McNaughton 04390301c9 more tpak code (#274) 2025-02-22 21:41:21 +01:00
Logan McNaughton bc415821d6 check for new version (#273)
* check for new version

* more
2025-02-21 23:02:16 +01:00
Logan McNaughton a068363787 Add note about command line flatpak usage 2025-02-20 20:12:25 +01:00
Logan McNaughton 97839fae40 Bump to 1.0.9 (#272) 2025-02-20 18:44:15 +01:00
Logan McNaughton b6c805a27d update cargo packages 2025-02-20 18:21:15 +01:00
Logan McNaughton b7509a2425 use rust 2024 edition (#268) 2025-02-20 18:20:55 +01:00
Logan McNaughton 6ecedde329 add note about building on linux 2025-02-20 16:12:31 +01:00
Logan McNaughton 09dfd69283 use better data struct for netplay servers (#269)
* use better data struct for netplay servers

* more
2025-02-20 12:46:44 +01:00
Logan McNaughton d262567084 use struct in netplay code (#265)
* use struct in netplay code

* more
2025-02-19 20:57:03 +01:00
Logan McNaughton c363e78fbb add slider for resolution (#264)
* add slider for resolution

* more
2025-02-19 14:48:36 +01:00
Logan McNaughton 792a3a6476 reduce binary size (#263) 2025-02-19 11:26:55 +01:00
Logan McNaughton 5bdb54a911 Update README.md 2025-02-19 10:52:26 +01:00
Logan McNaughton b01c2d1885 use key down for input 2025-02-18 14:25:24 +01:00
Logan McNaughton bb2da243b3 fix hotkeys 2025-02-18 13:38:38 +01:00
Logan McNaughton 938a99caa9 clean up data struct (#262)
* clean up data struct

* add check

* more

* more

* more
2025-02-18 10:05:48 +01:00
dependabot[bot] f7bfcfccba Bump clap from 4.5.29 to 4.5.30 (#261)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.29 to 4.5.30.
- [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.29...clap_complete-v4.5.30)

---
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-18 05:42:01 +01:00
Logan McNaughton 656e390171 track RDP RDRAM writes (#259)
* track RDP RDRAM writes

* more

* more

* more

* more

* more
2025-02-17 22:13:45 +01:00
Logan McNaughton 17990dfebc toggle speed limiter (#260) 2025-02-17 13:40:25 +01:00
Logan McNaughton 671a961789 Use struct instead of tuple (#258)
* Use struct instead of tuple

* more
2025-02-17 09:19:25 +01:00
Logan McNaughton aac68a4a1b rename artifacts (#257) 2025-02-16 11:35:28 +01:00
Logan McNaughton dc5a78a75e use audio for netplay messages (#256)
* use audio for netplay messages

* more
2025-02-16 11:04:59 +01:00
Logan McNaughton 91a5483452 bump to 1.0.8 (#255) 2025-02-15 23:11:38 +01:00
Logan McNaughton baee7ffb82 update cargo packages 2025-02-15 23:10:04 +01:00
Logan McNaughton c2780c78b3 Transferpak stub (#254)
* transferpak support

* more

* more
2025-02-15 18:32:36 +01:00
Logan McNaughton ba006ddd53 add transferpak wav 2025-02-15 15:54:25 +01:00
Logan McNaughton e04a151d49 set DPC_PIPEBUSY_REG (#253)
* set DPC_PIPEBUSY_REG

* revert hidden rdram change

* more
2025-02-15 08:29:37 +01:00
Logan McNaughton 8566eee906 fix possible crash in is viewer 2025-02-14 22:20:15 +01:00
Logan McNaughton a1c2ac21be fix hidden rdram size 2025-02-14 21:32:11 +01:00
Logan McNaughton b7d194e12d alt_return to toggle fullscreen (#252)
* alt_return to toggle fullscreen

* more
2025-02-14 12:29:49 +01:00
Logan McNaughton df9ad377d3 add cycles for rsp mfc0 (#251)
* add cycles for rsp mfc0

* more
2025-02-14 10:07:58 +01:00
Logan McNaughton 8f20abe813 update_screen after speed limiter (#250) 2025-02-13 22:09:17 +01:00
Logan McNaughton 3aca3c1664 bump to 1.0.7 (#249) 2025-02-13 21:28:47 +01:00
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
107 changed files with 17859 additions and 13804 deletions
+11
View File
@@ -1,5 +1,16 @@
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.'cfg(target_os = "windows")']
linker = "rust-lld"
[target.'cfg(target_arch = "x86_64")']
rustflags = ["-C", "target-cpu=x86-64-v3"]
[target.'cfg(target_arch = "aarch64")']
rustflags = ["-C", "target-cpu=cortex-a76"]
[env]
CC = "clang"
CXX = "clang++"
AR = "llvm-ar"
+7 -3
View File
@@ -3,8 +3,12 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "cargo"
interval: "weekly"
- package-ecosystem: "rust-toolchain"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
# - package-ecosystem: "cargo"
# directory: "/"
# schedule:
# interval: "daily"
+98 -100
View File
@@ -1,8 +1,12 @@
name: Rust
permissions:
contents: read
on:
push:
branches: ["main"]
tags:
- "v*"
pull_request:
branches: ["main"]
@@ -11,141 +15,130 @@ 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
arch: linux-x86_64
llvm: X64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
arch: linux-aarch64
llvm: ARM64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
cat <<EOF > deb822sources
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://security.ubuntu.com/ubuntu/
Suites: noble-security
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble-updates
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://archive.ubuntu.com/ubuntu/
Suites: noble-backports
Components: main multiverse universe
Architectures: amd64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-security
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-updates
Components: main multiverse universe
Architectures: arm64
Types: deb
URIs: http://ports.ubuntu.com/
Suites: noble-backports
Components: main multiverse universe
Architectures: arm64
EOF
sudo mv deb822sources /etc/apt/sources.list.d/ubuntu.sources
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get -y dist-upgrade
sudo apt-get -y install aptitude
sudo aptitude -y install build-essential:arm64 git:arm64 make:arm64 \
pkg-config:arm64 cmake:arm64 ninja-build:arm64 gnome-desktop-testing:arm64 libasound2-dev:arm64 libpulse-dev:arm64 \
libaudio-dev:arm64 libjack-dev:arm64 libsndio-dev:arm64 libx11-dev:arm64 libxext-dev:arm64 \
libxrandr-dev:arm64 libxcursor-dev:arm64 libxfixes-dev:arm64 libxi-dev:arm64 libxss-dev:arm64 \
libxkbcommon-dev:arm64 libdrm-dev:arm64 libgbm-dev:arm64 libgl1-mesa-dev:arm64 libgles2-mesa-dev:arm64 \
libegl1-mesa-dev:arm64 libdbus-1-dev:arm64 libibus-1.0-dev:arm64 libudev-dev:arm64 fcitx-libs-dev:arm64 \
libpipewire-0.3-dev:arm64 libwayland-dev:arm64 libdecor-0-dev:arm64 liburing-dev:arm64 g++-aarch64-linux-gnu
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get -y dist-upgrade
sudo apt-get -y install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
- name: Set aarch64 linker
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
# 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 libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
version: ${{ matrix.target }}-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{ matrix.llvm }}.tar.xz
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
env:
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: gopher64-${{ matrix.target }}
path: target/${{ matrix.target }}/release/gopher64
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
build-windows:
runs-on: windows-2025
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
target: [x86_64-pc-windows-msvc]
include:
- os: windows-2025
target: x86_64-pc-windows-msvc
arch: windows-x86_64
llvm: win64
#- os: windows-11-arm
# target: aarch64-pc-windows-msvc
# arch: windows-aarch64
# llvm: woa64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Set LLVM_VERSION
run: |
$llvm_version = rustc --version --verbose | Select-String "LLVM version" | ForEach-Object { $_.Line.Split(":")[1].Trim() }
echo "LLVM_VERSION=$llvm_version" >> $env:GITHUB_ENV
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm }}.exe
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
- name: Upload file
uses: actions/upload-artifact@v4
mkdir output
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
env:
CC: clang-cl
CXX: clang-cl
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload unsigned artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v5
with:
name: gopher64-${{ matrix.target }}
path: target/${{ matrix.target }}/release/gopher64.exe
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}.exe
- name: SignPath
if: ${{ github.ref_type == 'tag' }}
uses: signpath/github-action-submit-signing-request@v2.0
with:
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
project-slug: "gopher64"
artifact-configuration-slug: "gopher64-${{ matrix.arch }}"
signing-policy-slug: "release-signing"
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
wait-for-completion: false
build-macos:
runs-on: macos-latest
if: false
runs-on: macos-15
strategy:
fail-fast: false
matrix:
target: [x86_64-apple-darwin, aarch64-apple-darwin]
include:
- target: x86_64-apple-darwin
arch: mac-x86_64
- target: aarch64-apple-darwin
arch: mac-aarch64
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -155,8 +148,13 @@ jobs:
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
env:
AR: ar
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: gopher64-${{ matrix.target }}
path: target/${{ matrix.target }}/release/gopher64
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
+22 -11
View File
@@ -6,6 +6,9 @@ on:
pull_request:
branches: ["main"]
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
@@ -13,23 +16,31 @@ jobs:
lint-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
run: |
sudo apt-get update
sudo apt-get -y install build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
# 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 libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
version: lint-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
+18
View File
@@ -0,0 +1,18 @@
name: Security audit
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
audit:
permissions:
issues: write
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
+1 -5
View File
@@ -3,17 +3,13 @@
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
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
.vscode/settings.json
# Added by cargo
/target
+5 -1
View File
@@ -1,3 +1,7 @@
[submodule "parallel-rdp/parallel-rdp-standalone"]
path = parallel-rdp/parallel-rdp-standalone
url = https://github.com/Themaister/parallel-rdp-standalone.git
url = https://github.com/gopher64/parallel-rdp-standalone.git
branch = gopher64
[submodule "src/compat/sse2neon"]
path = src/compat/sse2neon
url = https://github.com/DLTcollab/sse2neon.git
Generated
+7273
View File
File diff suppressed because it is too large Load Diff
+34 -27
View File
@@ -1,47 +1,54 @@
[package]
name = "gopher64"
version = "0.1.22"
edition = "2021"
rust-version = "1.82"
version = "1.1.11"
edition = "2024"
rust-version = "1.92.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dirs = "6.0"
zip = "2.1"
governor = "0.8"
sevenz-rust = "0.6"
zip = "6.0"
governor = "0.10"
sevenz-rust2 = { version = "0.20", default-features = false }
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.30"
serde-big-array = "0.5"
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia-vulkan"] }
open = "5.3"
sha2 = "0.10"
rusttype = "0.9"
tokio = { version = "1.43", features = ["rt-multi-thread", "macros"] }
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
sdl3-sys = { version = "0.5", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.2", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
rfd = { version = "0.16", default-features = false, features = ["xdg-portal", "tokio", "wayland"] }
tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
regex = "1.11"
tungstenite = "0.26"
socket2 = "0.5"
clap = { version = "4.4", features = ["derive"] }
[dependencies.reqwest]
version = "0.12"
default-features = false
features = ["http2","rustls-tls","charset","json"]
[dependencies.rfd]
version = "0.15"
default-features = false
features = ["xdg-portal","tokio"]
tokio-tungstenite = "0.28"
futures = "0.3"
socket2 = { version = "0.6", features = ["all"] }
rand_chacha = "0.9"
fatfs = "0.3"
semver = "1.0"
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"
bindgen = "0.71"
winresource = "0.1"
bindgen = "0.72"
slint-build = "1.12"
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
lto = "thin"
strip = true
[profile.release-with-debug]
inherits = "release"
debug = true
strip = false
panic = "unwind"
+27 -46
View File
@@ -1,76 +1,57 @@
# gopher64
(gopher64 also has a GUI that can be launched by just running the executable)
## download
```
Usage: gopher64 [OPTIONS] [GAME]
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
Arguments:
[GAME]
Linux: https://flathub.org/apps/io.github.gopher64.gopher64
## wiki
https://github.com/gopher64/gopher64/wiki
Options:
-f, --fullscreen
-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
Lists connected controllers which can be used in --assign-controller
-a, --assign-controller <CONTROLLER_NUMBER>
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`
Keys are mapped according to [these defaults](https://github.com/gopher64/gopher64/wiki/Default-Keyboard-Setup). Xbox-style controllers also have a [default mapping applied](https://github.com/gopher64/gopher64/wiki/Default-Gamepad-Setup).
## 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.
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the server (https://github.com/gopher64/gopher64-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.
If you would like to keep all the game data in the same folder as the executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## goals
## flatpak
1. Performance. I want to be able to use this emulator on my laptop.
2. Easy to use.
3. Easy to work on. Dynamic recompilers perform well, but they are very hard to read and understand. This emulator will only have interpreters for the CPU and RSP. Additionally, it is completely written in Rust (besides Parallel-RDP), a modern programming language with a growing user base. I've tried to avoid the use of macros, which can reduce some repetitiveness in the code, but also reduce readability.
If you want to run the flatpak from the command line, you need to add the `--filesystem=host:ro` option, for example:
```
flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
```
## building and usage
1. Install rust: https://www.rust-lang.org/tools/install
2. `git clone --recursive https://github.com/gopher64/gopher64.git`
3. `cd gopher64`
4. `cargo build --release`
5. `./target/release/gopher64 /path/to/rom.z64`
1. Linux only: install the SDL3 dependencies: https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
2. Install rust: https://www.rust-lang.org/tools/install
3. `git clone --recursive https://github.com/gopher64/gopher64.git`
4. `cd gopher64`
5. `cargo build --release`
6. `./target/release/gopher64 /path/to/rom.z64`
## 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.
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
## license
Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have been adapted from mupen64plus and/or ares. The license for mupen64plus can be found here: https://github.com/mupen64plus/mupen64plus-core/blob/master/LICENSES. The license for ares can be found here: https://github.com/ares-emulator/ares/blob/master/LICENSE.
## where to download
## privacy and code signing policy
https://github.com/gopher64/gopher64/releases
Free code signing for the Windows release is provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
During online netplay sessions, the server logs your IP address and basic session information (game title and session name) for operational purposes. No additional personal data is collected or stored.
+74 -34
View File
@@ -2,12 +2,21 @@ fn main() {
println!("cargo::rerun-if-changed=parallel-rdp");
println!("cargo::rerun-if-changed=src/compat");
let slint_config = slint_build::CompilerConfiguration::new().with_style("cosmic".into());
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
let mut simd_build = cc::Build::new();
let mut build = cc::Build::new();
build
let mut volk_build = cc::Build::new();
volk_build
.std("c17")
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
.file("parallel-rdp/parallel-rdp-standalone/volk/volk.c");
let mut rdp_build = cc::Build::new();
rdp_build
.cpp(true)
.warnings(false)
.std("c++17")
.std("c++23")
.flag("-Wno-unused-parameter")
.flag("-Wno-missing-field-initializers")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_device.cpp")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_dump_write.cpp")
@@ -27,9 +36,11 @@ fn main() {
.file("parallel-rdp/parallel-rdp-standalone/vulkan/image.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/indirect_layout.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/memory_allocator.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_cache.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_event.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/query_pool.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/render_pass.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/rtas.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/sampler.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/semaphore.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/semaphore_manager.cpp")
@@ -44,7 +55,7 @@ fn main() {
.file("parallel-rdp/parallel-rdp-standalone/util/timeline_trace_file.cpp")
.file("parallel-rdp/parallel-rdp-standalone/util/environment.cpp")
.file("parallel-rdp/parallel-rdp-standalone/util/thread_name.cpp")
.file("parallel-rdp/parallel-rdp-standalone/volk/volk.c")
.file("parallel-rdp/parallel-rdp-standalone/util/slab_allocator.cpp")
.file("parallel-rdp/interface.cpp")
.file("parallel-rdp/wsi_platform.cpp")
.include("parallel-rdp/parallel-rdp-standalone/parallel-rdp")
@@ -55,40 +66,42 @@ fn main() {
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
.join("include"),
)
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").to_owned().unwrap())
.join("include"),
);
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if os == "windows" {
if arch == "x86_64" {
build.flag("/arch:AVX2");
} else if arch == "aarch64" {
panic!("unsupported platform")
} else {
panic!("unknown arch")
}
build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
let opt_flag = if arch == "x86_64" {
"-march=x86-64-v3"
} else if arch == "aarch64" {
"-march=armv8.2-a"
} else {
panic!("unknown arch")
};
winres::WindowsResource::new()
.set_icon("data/icon.ico")
volk_build.flag(opt_flag);
rdp_build.flag(opt_flag);
simd_build.flag(opt_flag);
if os == "windows" {
volk_build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
rdp_build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
winresource::WindowsResource::new()
.set_icon("data/icon/icon.ico")
.compile()
.unwrap();
} else if os == "linux" || os == "macos" {
if arch == "x86_64" {
build.flag("-march=x86-64-v3");
} else if arch == "aarch64" {
build.flag("-march=armv8.2-a");
simd_build.flag("-march=armv8.2-a");
simd_build.flag("-DSSE2NEON_SUPPRESS_WARNINGS");
simd_build.file("src/compat/aarch64.c");
} else {
panic!("unknown arch")
}
} else {
panic!("unknown OS")
}
build.compile("parallel-rdp");
volk_build.flag("-flto=thin");
rdp_build.flag("-flto=thin");
simd_build.flag("-flto=thin");
volk_build.compile("volk");
rdp_build.compile("parallel-rdp");
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
@@ -98,7 +111,15 @@ fn main() {
.allowlist_function("rdp_close")
.allowlist_function("rdp_set_vi_register")
.allowlist_function("rdp_update_screen")
.allowlist_function("rdp_render_frame")
.allowlist_function("rdp_process_commands")
.allowlist_function("rdp_onscreen_message")
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.allowlist_function("rdp_check_framebuffers")
.allowlist_function("rdp_state_size")
.allowlist_function("rdp_save_state")
.allowlist_function("rdp_load_state")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
@@ -109,7 +130,7 @@ fn main() {
if arch == "aarch64" {
let simd_bindings = bindgen::Builder::default()
.header("src/compat/sse2neon.h")
.header("src/compat/sse2neon/sse2neon.h")
.allowlist_function("_mm_setzero_si128")
.allowlist_function("_mm_set_epi8")
.allowlist_function("_mm_movemask_epi8")
@@ -143,6 +164,7 @@ fn main() {
.allowlist_function("_mm_cmplt_epi16")
.allowlist_function("_mm_subs_epu16")
.allowlist_function("_mm_set1_epi32")
.allowlist_function("_mm_set1_epi16")
.blocklist_type("__m128i")
.blocklist_type("int64x2_t")
.wrap_static_fns(true)
@@ -154,8 +176,26 @@ fn main() {
.write_to_file(out_path.join("simd_bindings.rs"))
.expect("Couldn't write bindings!");
simd_build.file(std::env::temp_dir().join("bindgen").join("extern.c"));
simd_build.include(".");
simd_build.compile("simd");
simd_build
.std("c17")
.flag("-D_POSIX_C_SOURCE=200112L")
.flag("-DSSE2NEON_SUPPRESS_WARNINGS")
.file("src/compat/aarch64.c")
.file(std::env::temp_dir().join("bindgen").join("extern.c"))
.include(".")
.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}");
let netplay_id = std::env::var("NETPLAY_ID").unwrap_or("gopher64".to_string());
println!("cargo:rustc-env=NETPLAY_ID={netplay_id}");
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
}
+1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 79 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
magick gopher64.png -resize 512x512 gopher64_512x512.png
magick gopher64.png -resize 256x256 gopher64_256x256.png
magick gopher64.png -resize 128x128 gopher64_128x128.png
magick gopher64.png -resize 256x256 icon.ico
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
View File
Binary file not shown.
Binary file not shown.
+135
View File
@@ -0,0 +1,135 @@
#version 450
/*
CRT Shader by EasyMode
License: GPL
*/
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OutputSize;
} params;
#define SHARPNESS_IMAGE 1.0
#define SHARPNESS_EDGES 3.0
#define GLOW_WIDTH 0.5
#define GLOW_HEIGHT 0.5
#define GLOW_HALATION 0.1
#define GLOW_DIFFUSION 0.05
#define MASK_COLORS 2.0
#define MASK_STRENGTH 0.3
#define MASK_SIZE 1.0
#define SCANLINE_SIZE_MIN 0.5
#define SCANLINE_SIZE_MAX 1.5
#define SCANLINE_SHAPE 2.5
#define SCANLINE_OFFSET 1.0
#define GAMMA_INPUT 2.4
#define GAMMA_OUTPUT 2.4
#define BRIGHTNESS 1.5
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
} global;
#define FIX(c) max(abs(c), 1e-5)
#define PI 3.141592653589
#define TEX2D(c) pow(texture(tex, c).rgb, vec3(GAMMA_INPUT))
#define saturate(c) clamp(c, 0.0, 1.0)
mat3x3 get_color_matrix(sampler2D tex, vec2 co, vec2 dx)
{
return mat3x3(TEX2D(co - dx), TEX2D(co), TEX2D(co + dx));
}
vec3 blur(mat3 m, float dist, float rad)
{
vec3 x = vec3(dist - 1.0, dist, dist + 1.0) / rad;
vec3 w = exp2(x * x * -1.0);
return (m[0] * w.x + m[1] * w.y + m[2] * w.z) / (w.x + w.y + w.z);
}
vec3 filter_gaussian(sampler2D tex, vec2 co, vec2 tex_size)
{
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
vec2 dy = vec2(0.0, 1.0 / tex_size.y);
vec2 pix_co = co * tex_size;
vec2 tex_co = (floor(pix_co) + 0.5) / tex_size;
vec2 dist = (fract(pix_co) - 0.5) * -1.0;
mat3x3 line0 = get_color_matrix(tex, tex_co - dy, dx);
mat3x3 line1 = get_color_matrix(tex, tex_co, dx);
mat3x3 line2 = get_color_matrix(tex, tex_co + dy, dx);
mat3x3 column = mat3x3(blur(line0, dist.x, GLOW_WIDTH),
blur(line1, dist.x, GLOW_WIDTH),
blur(line2, dist.x, GLOW_WIDTH));
return blur(column, dist.y, GLOW_HEIGHT);
}
vec3 filter_lanczos(sampler2D tex, vec2 co, vec2 tex_size, float sharp)
{
tex_size.x *= sharp;
vec2 dx = vec2(1.0 / tex_size.x, 0.0);
vec2 pix_co = co * tex_size - vec2(0.5, 0.0);
vec2 tex_co = (floor(pix_co) + vec2(0.5, 0.001)) / tex_size;
vec2 dist = fract(pix_co);
vec4 coef = PI * vec4(dist.x + 1.0, dist.x, dist.x - 1.0, dist.x - 2.0);
coef = FIX(coef);
coef = 2.0 * sin(coef) * sin(coef / 2.0) / (coef * coef);
coef /= dot(coef, vec4(1.0));
vec4 col1 = vec4(TEX2D(tex_co), 1.0);
vec4 col2 = vec4(TEX2D(tex_co + dx), 1.0);
return (mat4x4(col1, col1, col2, col2) * coef).rgb;
}
vec3 get_scanline_weight(float x, vec3 col)
{
vec3 beam = mix(vec3(SCANLINE_SIZE_MIN), vec3(SCANLINE_SIZE_MAX), pow(col, vec3(1.0 / SCANLINE_SHAPE)));
vec3 x_mul = 2.0 / beam;
vec3 x_offset = x_mul * 0.5;
return smoothstep(0.0, 1.0, 1.0 - abs(x * x_mul - x_offset)) * x_offset;
}
vec3 get_mask_weight(float x)
{
float i = mod(floor(x * params.OutputSize.x * params.SourceSize.x / (params.SourceSize.x * MASK_SIZE)), MASK_COLORS);
if (i == 0.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), MASK_COLORS - 2.0);
else if (i == 1.0) return vec3(0.0, 1.0, 0.0);
else return vec3(0.0, 0.0, 1.0);
}
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 0) uniform sampler2D Source;
void main()
{
float scale = floor(params.OutputSize.y * params.SourceSize.w);
float offset = 1.0 / scale * 0.5;
if (bool(mod(scale, 2.0))) offset = 0.0;
vec2 co = (vTexCoord * params.SourceSize.xy - vec2(0.0, offset * SCANLINE_OFFSET)) * params.SourceSize.zw;
vec3 col_glow = filter_gaussian(Source, co, params.SourceSize.xy);
vec3 col_soft = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_IMAGE);
vec3 col_sharp = filter_lanczos(Source, co, params.SourceSize.xy, SHARPNESS_EDGES);
vec3 col = sqrt(col_sharp * col_soft);
col *= get_scanline_weight(fract(co.y * params.SourceSize.y), col_soft);
col_glow = saturate(col_glow - col);
col += col_glow * col_glow * GLOW_HALATION;
col = mix(col, col * get_mask_weight(vTexCoord.x) * MASK_COLORS, MASK_STRENGTH);
col += col_glow * GLOW_DIFFUSION;
col = pow(col * BRIGHTNESS, vec3(1.0 / GAMMA_OUTPUT));
FragColor = vec4(col, 1.0);
}
+453
View File
@@ -0,0 +1,453 @@
{0x07230203,0x00010300,0x000d000b,0x000004fd,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0007000f,0x00000004,0x00000004,0x6e69616d,
0x00000000,0x000001ba,0x0000021a,0x00030010,
0x00000004,0x00000007,0x00030047,0x00000185,
0x00000002,0x00050048,0x00000185,0x00000000,
0x00000023,0x00000000,0x00050048,0x00000185,
0x00000001,0x00000023,0x00000010,0x00040047,
0x000001ba,0x0000001e,0x00000000,0x00040047,
0x000001ca,0x00000021,0x00000000,0x00040047,
0x000001ca,0x00000022,0x00000000,0x00040047,
0x0000021a,0x0000001e,0x00000000,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,
0x00030016,0x00000006,0x00000020,0x00090019,
0x00000007,0x00000006,0x00000001,0x00000000,
0x00000000,0x00000000,0x00000001,0x00000000,
0x0003001b,0x00000008,0x00000007,0x00040020,
0x00000009,0x00000000,0x00000008,0x00040017,
0x0000000a,0x00000006,0x00000002,0x00040017,
0x0000000c,0x00000006,0x00000003,0x00040017,
0x00000037,0x00000006,0x00000004,0x0004002b,
0x00000006,0x0000003a,0x4019999a,0x0006002c,
0x0000000c,0x0000003b,0x0000003a,0x0000003a,
0x0000003a,0x0004002b,0x00000006,0x00000049,
0x3f800000,0x0004002b,0x00000006,0x0000004a,
0x00000000,0x0004002b,0x00000006,0x00000068,
0xbf800000,0x00040015,0x0000006b,0x00000020,
0x00000001,0x0004002b,0x0000006b,0x0000006c,
0x00000000,0x00040015,0x0000006f,0x00000020,
0x00000000,0x0004002b,0x0000006f,0x00000070,
0x00000000,0x0004002b,0x0000006b,0x00000074,
0x00000001,0x0004002b,0x0000006f,0x00000077,
0x00000001,0x0004002b,0x00000006,0x000000a1,
0x3f000000,0x0005002c,0x0000000a,0x000000fc,
0x000000a1,0x0000004a,0x0004002b,0x00000006,
0x00000101,0x3a83126f,0x0005002c,0x0000000a,
0x00000102,0x000000a1,0x00000101,0x0004002b,
0x00000006,0x0000010b,0x40490fdb,0x0004002b,
0x00000006,0x00000116,0x40000000,0x0004002b,
0x00000006,0x0000011c,0x3727c5ac,0x0007002c,
0x00000037,0x0000012c,0x00000049,0x00000049,
0x00000049,0x00000049,0x00040018,0x00000149,
0x00000037,0x00000004,0x0006002c,0x0000000c,
0x00000165,0x000000a1,0x000000a1,0x000000a1,
0x0004002b,0x00000006,0x00000166,0x3fc00000,
0x0006002c,0x0000000c,0x00000167,0x00000166,
0x00000166,0x00000166,0x0004002b,0x00000006,
0x00000169,0x3ecccccd,0x0006002c,0x0000000c,
0x0000016a,0x00000169,0x00000169,0x00000169,
0x0004001e,0x00000185,0x00000037,0x00000037,
0x00040020,0x00000186,0x00000009,0x00000185,
0x0004003b,0x00000186,0x00000187,0x00000009,
0x00040020,0x00000188,0x00000009,0x00000006,
0x00020014,0x00000196,0x0006002c,0x0000000c,
0x0000019a,0x00000049,0x0000004a,0x00000049,
0x0006002c,0x0000000c,0x000001a1,0x0000004a,
0x00000049,0x0000004a,0x0006002c,0x0000000c,
0x000001a4,0x0000004a,0x0000004a,0x00000049,
0x0004002b,0x0000006f,0x000001aa,0x00000003,
0x00040020,0x000001b9,0x00000001,0x0000000a,
0x0004003b,0x000001b9,0x000001ba,0x00000001,
0x00040020,0x000001bc,0x00000009,0x00000037,
0x0004003b,0x00000009,0x000001ca,0x00000000,
0x0004002b,0x00000006,0x000001dc,0x40400000,
0x0004002b,0x00000006,0x000001ff,0x3dcccccd,
0x00040020,0x00000206,0x00000001,0x00000006,
0x0004002b,0x00000006,0x0000020c,0x3e99999a,
0x0004002b,0x00000006,0x00000210,0x3d4ccccd,
0x0004002b,0x00000006,0x00000216,0x3ed55555,
0x0006002c,0x0000000c,0x00000217,0x00000216,
0x00000216,0x00000216,0x00040020,0x00000219,
0x00000003,0x00000037,0x0004003b,0x00000219,
0x0000021a,0x00000003,0x0005002c,0x0000000a,
0x000004f3,0x000000a1,0x000000a1,0x0007002c,
0x00000037,0x000004f4,0x0000011c,0x0000011c,
0x0000011c,0x0000011c,0x0006002c,0x0000000c,
0x000004f6,0x00000116,0x00000116,0x00000116,
0x0006002c,0x0000000c,0x000004f7,0x00000049,
0x00000049,0x00000049,0x0006002c,0x0000000c,
0x000004f8,0x0000004a,0x0000004a,0x0000004a,
0x0006002c,0x0000000c,0x000004f9,0x0000020c,
0x0000020c,0x0000020c,0x0007002c,0x00000037,
0x000004fa,0x000000a1,0x000000a1,0x000000a1,
0x000000a1,0x0004002b,0x00000006,0x000004fb,
0x3eaaaaab,0x00050036,0x00000002,0x00000004,
0x00000000,0x00000003,0x000200f8,0x00000005,
0x00060041,0x00000188,0x000001a8,0x00000187,
0x00000074,0x00000077,0x0004003d,0x00000006,
0x000001a9,0x000001a8,0x00060041,0x00000188,
0x000001ab,0x00000187,0x0000006c,0x000001aa,
0x0004003d,0x00000006,0x000001ac,0x000001ab,
0x00050085,0x00000006,0x000001ad,0x000001a9,
0x000001ac,0x0006000c,0x00000006,0x000001ae,
0x00000001,0x00000008,0x000001ad,0x00050088,
0x00000006,0x000001b2,0x000000a1,0x000001ae,
0x0005008d,0x00000006,0x000001b4,0x000001ae,
0x00000116,0x000500b7,0x00000196,0x000001b5,
0x000001b4,0x0000004a,0x000600a9,0x00000006,
0x000004fc,0x000001b5,0x0000004a,0x000001b2,
0x0004003d,0x0000000a,0x000001bb,0x000001ba,
0x00050041,0x000001bc,0x000001bd,0x00000187,
0x0000006c,0x0004003d,0x00000037,0x000001be,
0x000001bd,0x0007004f,0x0000000a,0x000001bf,
0x000001be,0x000001be,0x00000000,0x00000001,
0x00050085,0x0000000a,0x000001c0,0x000001bb,
0x000001bf,0x00050050,0x0000000a,0x000001c3,
0x0000004a,0x000004fc,0x00050083,0x0000000a,
0x000001c4,0x000001c0,0x000001c3,0x0007004f,
0x0000000a,0x000001c7,0x000001be,0x000001be,
0x00000002,0x00000003,0x00050085,0x0000000a,
0x000001c8,0x000001c4,0x000001c7,0x00050051,
0x00000006,0x00000249,0x000001be,0x00000000,
0x00050088,0x00000006,0x0000024a,0x00000049,
0x00000249,0x00050050,0x0000000a,0x0000024b,
0x0000024a,0x0000004a,0x00050051,0x00000006,
0x0000024d,0x000001be,0x00000001,0x00050088,
0x00000006,0x0000024e,0x00000049,0x0000024d,
0x00050050,0x0000000a,0x0000024f,0x0000004a,
0x0000024e,0x00050085,0x0000000a,0x00000252,
0x000001c8,0x000001bf,0x0006000c,0x0000000a,
0x00000254,0x00000001,0x00000008,0x00000252,
0x00050081,0x0000000a,0x00000256,0x00000254,
0x000004f3,0x00050088,0x0000000a,0x00000258,
0x00000256,0x000001bf,0x0006000c,0x0000000a,
0x0000025a,0x00000001,0x0000000a,0x00000252,
0x00050083,0x0000000a,0x0000025c,0x0000025a,
0x000004f3,0x0005008e,0x0000000a,0x0000025d,
0x0000025c,0x00000068,0x00050083,0x0000000a,
0x00000260,0x00000258,0x0000024f,0x0004003d,
0x00000008,0x0000028a,0x000001ca,0x00050083,
0x0000000a,0x0000028d,0x00000260,0x0000024b,
0x00050057,0x00000037,0x0000028e,0x0000028a,
0x0000028d,0x0008004f,0x0000000c,0x0000028f,
0x0000028e,0x0000028e,0x00000000,0x00000001,
0x00000002,0x0007000c,0x0000000c,0x00000290,
0x00000001,0x0000001a,0x0000028f,0x0000003b,
0x00050057,0x00000037,0x00000293,0x0000028a,
0x00000260,0x0008004f,0x0000000c,0x00000294,
0x00000293,0x00000293,0x00000000,0x00000001,
0x00000002,0x0007000c,0x0000000c,0x00000295,
0x00000001,0x0000001a,0x00000294,0x0000003b,
0x00050081,0x0000000a,0x00000299,0x00000260,
0x0000024b,0x00050057,0x00000037,0x0000029a,
0x0000028a,0x00000299,0x0008004f,0x0000000c,
0x0000029b,0x0000029a,0x0000029a,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x0000029c,0x00000001,0x0000001a,0x0000029b,
0x0000003b,0x00050083,0x0000000a,0x000002af,
0x00000258,0x0000024b,0x00050057,0x00000037,
0x000002b0,0x0000028a,0x000002af,0x0008004f,
0x0000000c,0x000002b1,0x000002b0,0x000002b0,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002b2,0x00000001,0x0000001a,
0x000002b1,0x0000003b,0x00050057,0x00000037,
0x000002b5,0x0000028a,0x00000258,0x0008004f,
0x0000000c,0x000002b6,0x000002b5,0x000002b5,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002b7,0x00000001,0x0000001a,
0x000002b6,0x0000003b,0x00050081,0x0000000a,
0x000002bb,0x00000258,0x0000024b,0x00050057,
0x00000037,0x000002bc,0x0000028a,0x000002bb,
0x0008004f,0x0000000c,0x000002bd,0x000002bc,
0x000002bc,0x00000000,0x00000001,0x00000002,
0x0007000c,0x0000000c,0x000002be,0x00000001,
0x0000001a,0x000002bd,0x0000003b,0x00050081,
0x0000000a,0x00000268,0x00000258,0x0000024f,
0x00050083,0x0000000a,0x000002d1,0x00000268,
0x0000024b,0x00050057,0x00000037,0x000002d2,
0x0000028a,0x000002d1,0x0008004f,0x0000000c,
0x000002d3,0x000002d2,0x000002d2,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000002d4,0x00000001,0x0000001a,0x000002d3,
0x0000003b,0x00050057,0x00000037,0x000002d7,
0x0000028a,0x00000268,0x0008004f,0x0000000c,
0x000002d8,0x000002d7,0x000002d7,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000002d9,0x00000001,0x0000001a,0x000002d8,
0x0000003b,0x00050081,0x0000000a,0x000002dd,
0x00000268,0x0000024b,0x00050057,0x00000037,
0x000002de,0x0000028a,0x000002dd,0x0008004f,
0x0000000c,0x000002df,0x000002de,0x000002de,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x000002e0,0x00000001,0x0000001a,
0x000002df,0x0000003b,0x00050051,0x00000006,
0x0000026d,0x0000025d,0x00000000,0x00050083,
0x00000006,0x000002f3,0x0000026d,0x00000049,
0x00050081,0x00000006,0x000002f6,0x0000026d,
0x00000049,0x00060050,0x0000000c,0x000002f7,
0x000002f3,0x0000026d,0x000002f6,0x00050085,
0x0000000c,0x000002fa,0x000002f7,0x000004f6,
0x00050085,0x0000000c,0x000002fd,0x000002fa,
0x000002fa,0x0005008e,0x0000000c,0x000002fe,
0x000002fd,0x00000068,0x0006000c,0x0000000c,
0x000002ff,0x00000001,0x0000001d,0x000002fe,
0x00050051,0x00000006,0x00000303,0x000002ff,
0x00000000,0x0005008e,0x0000000c,0x00000304,
0x00000290,0x00000303,0x00050051,0x00000006,
0x00000308,0x000002ff,0x00000001,0x0005008e,
0x0000000c,0x00000309,0x00000295,0x00000308,
0x00050081,0x0000000c,0x0000030a,0x00000304,
0x00000309,0x00050051,0x00000006,0x0000030e,
0x000002ff,0x00000002,0x0005008e,0x0000000c,
0x0000030f,0x0000029c,0x0000030e,0x00050081,
0x0000000c,0x00000310,0x0000030a,0x0000030f,
0x00050081,0x00000006,0x00000315,0x00000303,
0x00000308,0x00050081,0x00000006,0x00000318,
0x00000315,0x0000030e,0x00060050,0x0000000c,
0x00000319,0x00000318,0x00000318,0x00000318,
0x00050088,0x0000000c,0x0000031a,0x00000310,
0x00000319,0x0005008e,0x0000000c,0x00000331,
0x000002b2,0x00000303,0x0005008e,0x0000000c,
0x00000336,0x000002b7,0x00000308,0x00050081,
0x0000000c,0x00000337,0x00000331,0x00000336,
0x0005008e,0x0000000c,0x0000033c,0x000002be,
0x0000030e,0x00050081,0x0000000c,0x0000033d,
0x00000337,0x0000033c,0x00050088,0x0000000c,
0x00000347,0x0000033d,0x00000319,0x0005008e,
0x0000000c,0x0000035e,0x000002d4,0x00000303,
0x0005008e,0x0000000c,0x00000363,0x000002d9,
0x00000308,0x00050081,0x0000000c,0x00000364,
0x0000035e,0x00000363,0x0005008e,0x0000000c,
0x00000369,0x000002e0,0x0000030e,0x00050081,
0x0000000c,0x0000036a,0x00000364,0x00000369,
0x00050088,0x0000000c,0x00000374,0x0000036a,
0x00000319,0x00050051,0x00000006,0x00000286,
0x0000025d,0x00000001,0x00050083,0x00000006,
0x0000037a,0x00000286,0x00000049,0x00050081,
0x00000006,0x0000037d,0x00000286,0x00000049,
0x00060050,0x0000000c,0x0000037e,0x0000037a,
0x00000286,0x0000037d,0x00050085,0x0000000c,
0x00000381,0x0000037e,0x000004f6,0x00050085,
0x0000000c,0x00000384,0x00000381,0x00000381,
0x0005008e,0x0000000c,0x00000385,0x00000384,
0x00000068,0x0006000c,0x0000000c,0x00000386,
0x00000001,0x0000001d,0x00000385,0x00050051,
0x00000006,0x0000038a,0x00000386,0x00000000,
0x0005008e,0x0000000c,0x0000038b,0x0000031a,
0x0000038a,0x00050051,0x00000006,0x0000038f,
0x00000386,0x00000001,0x0005008e,0x0000000c,
0x00000390,0x00000347,0x0000038f,0x00050081,
0x0000000c,0x00000391,0x0000038b,0x00000390,
0x00050051,0x00000006,0x00000395,0x00000386,
0x00000002,0x0005008e,0x0000000c,0x00000396,
0x00000374,0x00000395,0x00050081,0x0000000c,
0x00000397,0x00000391,0x00000396,0x00050081,
0x00000006,0x0000039c,0x0000038a,0x0000038f,
0x00050081,0x00000006,0x0000039f,0x0000039c,
0x00000395,0x00060050,0x0000000c,0x000003a0,
0x0000039f,0x0000039f,0x0000039f,0x00050088,
0x0000000c,0x000003a1,0x00000397,0x000003a0,
0x00060052,0x0000000a,0x000004e1,0x00000249,
0x000001bf,0x00000000,0x00050085,0x0000000a,
0x000003b6,0x000001c8,0x000004e1,0x00050083,
0x0000000a,0x000003b7,0x000003b6,0x000000fc,
0x0006000c,0x0000000a,0x000003b9,0x00000001,
0x00000008,0x000003b7,0x00050081,0x0000000a,
0x000003ba,0x000003b9,0x00000102,0x00050088,
0x0000000a,0x000003bc,0x000003ba,0x000004e1,
0x0006000c,0x0000000a,0x000003be,0x00000001,
0x0000000a,0x000003b7,0x00050051,0x00000006,
0x000003c0,0x000003be,0x00000000,0x00050081,
0x00000006,0x000003c1,0x000003c0,0x00000049,
0x00050083,0x00000006,0x000003c6,0x000003c0,
0x00000049,0x00050083,0x00000006,0x000003c9,
0x000003c0,0x00000116,0x00070050,0x00000037,
0x000003ca,0x000003c1,0x000003c0,0x000003c6,
0x000003c9,0x0005008e,0x00000037,0x000003cb,
0x000003ca,0x0000010b,0x0006000c,0x00000037,
0x000003cd,0x00000001,0x00000004,0x000003cb,
0x0007000c,0x00000037,0x000003cf,0x00000001,
0x00000028,0x000003cd,0x000004f4,0x0006000c,
0x00000037,0x000003d1,0x00000001,0x0000000d,
0x000003cf,0x0005008e,0x00000037,0x000003d2,
0x000003d1,0x00000116,0x00050085,0x00000037,
0x000003d5,0x000003cf,0x000004fa,0x0006000c,
0x00000037,0x000003d6,0x00000001,0x0000000d,
0x000003d5,0x00050085,0x00000037,0x000003d7,
0x000003d2,0x000003d6,0x00050085,0x00000037,
0x000003da,0x000003cf,0x000003cf,0x00050088,
0x00000037,0x000003db,0x000003d7,0x000003da,
0x00050094,0x00000006,0x000003dd,0x000003db,
0x0000012c,0x00070050,0x00000037,0x000003df,
0x000003dd,0x000003dd,0x000003dd,0x000003dd,
0x00050088,0x00000037,0x000003e0,0x000003db,
0x000003df,0x00050057,0x00000037,0x000003e3,
0x0000028a,0x000003bc,0x0008004f,0x0000000c,
0x000003e4,0x000003e3,0x000003e3,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000003e5,0x00000001,0x0000001a,0x000003e4,
0x0000003b,0x00050051,0x00000006,0x000003e6,
0x000003e5,0x00000000,0x00050051,0x00000006,
0x000003e7,0x000003e5,0x00000001,0x00050051,
0x00000006,0x000003e8,0x000003e5,0x00000002,
0x00050081,0x0000000a,0x000003ed,0x000003bc,
0x0000024b,0x00050057,0x00000037,0x000003ee,
0x0000028a,0x000003ed,0x0008004f,0x0000000c,
0x000003ef,0x000003ee,0x000003ee,0x00000000,
0x00000001,0x00000002,0x0007000c,0x0000000c,
0x000003f0,0x00000001,0x0000001a,0x000003ef,
0x0000003b,0x00050051,0x00000006,0x000003f1,
0x000003f0,0x00000000,0x00050051,0x00000006,
0x000003f2,0x000003f0,0x00000001,0x00050051,
0x00000006,0x000003f3,0x000003f0,0x00000002,
0x00070050,0x00000037,0x00000407,0x000003e6,
0x000003e7,0x000003e8,0x00000049,0x00070050,
0x00000037,0x00000409,0x000003f1,0x000003f2,
0x000003f3,0x00000049,0x00070050,0x00000149,
0x0000040b,0x00000407,0x00000407,0x00000409,
0x00000409,0x00050091,0x00000037,0x0000040d,
0x0000040b,0x000003e0,0x0008004f,0x0000000c,
0x0000040e,0x0000040d,0x0000040d,0x00000000,
0x00000001,0x00000002,0x00050085,0x00000006,
0x0000041b,0x00000249,0x000001dc,0x00060052,
0x0000000a,0x000004e9,0x0000041b,0x000001bf,
0x00000000,0x00050088,0x00000006,0x0000041f,
0x000004fb,0x00000249,0x00050050,0x0000000a,
0x00000420,0x0000041f,0x0000004a,0x00050085,
0x0000000a,0x00000423,0x000001c8,0x000004e9,
0x00050083,0x0000000a,0x00000424,0x00000423,
0x000000fc,0x0006000c,0x0000000a,0x00000426,
0x00000001,0x00000008,0x00000424,0x00050081,
0x0000000a,0x00000427,0x00000426,0x00000102,
0x00050088,0x0000000a,0x00000429,0x00000427,
0x000004e9,0x0006000c,0x0000000a,0x0000042b,
0x00000001,0x0000000a,0x00000424,0x00050051,
0x00000006,0x0000042d,0x0000042b,0x00000000,
0x00050081,0x00000006,0x0000042e,0x0000042d,
0x00000049,0x00050083,0x00000006,0x00000433,
0x0000042d,0x00000049,0x00050083,0x00000006,
0x00000436,0x0000042d,0x00000116,0x00070050,
0x00000037,0x00000437,0x0000042e,0x0000042d,
0x00000433,0x00000436,0x0005008e,0x00000037,
0x00000438,0x00000437,0x0000010b,0x0006000c,
0x00000037,0x0000043a,0x00000001,0x00000004,
0x00000438,0x0007000c,0x00000037,0x0000043c,
0x00000001,0x00000028,0x0000043a,0x000004f4,
0x0006000c,0x00000037,0x0000043e,0x00000001,
0x0000000d,0x0000043c,0x0005008e,0x00000037,
0x0000043f,0x0000043e,0x00000116,0x00050085,
0x00000037,0x00000442,0x0000043c,0x000004fa,
0x0006000c,0x00000037,0x00000443,0x00000001,
0x0000000d,0x00000442,0x00050085,0x00000037,
0x00000444,0x0000043f,0x00000443,0x00050085,
0x00000037,0x00000447,0x0000043c,0x0000043c,
0x00050088,0x00000037,0x00000448,0x00000444,
0x00000447,0x00050094,0x00000006,0x0000044a,
0x00000448,0x0000012c,0x00070050,0x00000037,
0x0000044c,0x0000044a,0x0000044a,0x0000044a,
0x0000044a,0x00050088,0x00000037,0x0000044d,
0x00000448,0x0000044c,0x00050057,0x00000037,
0x00000450,0x0000028a,0x00000429,0x0008004f,
0x0000000c,0x00000451,0x00000450,0x00000450,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x00000452,0x00000001,0x0000001a,
0x00000451,0x0000003b,0x00050051,0x00000006,
0x00000453,0x00000452,0x00000000,0x00050051,
0x00000006,0x00000454,0x00000452,0x00000001,
0x00050051,0x00000006,0x00000455,0x00000452,
0x00000002,0x00050081,0x0000000a,0x0000045a,
0x00000429,0x00000420,0x00050057,0x00000037,
0x0000045b,0x0000028a,0x0000045a,0x0008004f,
0x0000000c,0x0000045c,0x0000045b,0x0000045b,
0x00000000,0x00000001,0x00000002,0x0007000c,
0x0000000c,0x0000045d,0x00000001,0x0000001a,
0x0000045c,0x0000003b,0x00050051,0x00000006,
0x0000045e,0x0000045d,0x00000000,0x00050051,
0x00000006,0x0000045f,0x0000045d,0x00000001,
0x00050051,0x00000006,0x00000460,0x0000045d,
0x00000002,0x00070050,0x00000037,0x00000474,
0x00000453,0x00000454,0x00000455,0x00000049,
0x00070050,0x00000037,0x00000476,0x0000045e,
0x0000045f,0x00000460,0x00000049,0x00070050,
0x00000149,0x00000478,0x00000474,0x00000474,
0x00000476,0x00000476,0x00050091,0x00000037,
0x0000047a,0x00000478,0x0000044d,0x0008004f,
0x0000000c,0x0000047b,0x0000047a,0x0000047a,
0x00000000,0x00000001,0x00000002,0x00050085,
0x0000000c,0x000001e8,0x0000047b,0x0000040e,
0x0006000c,0x0000000c,0x000001e9,0x00000001,
0x0000001f,0x000001e8,0x00050051,0x00000006,
0x000001eb,0x000001c8,0x00000001,0x00060041,
0x00000188,0x000001ec,0x00000187,0x0000006c,
0x00000077,0x0004003d,0x00000006,0x000001ed,
0x000001ec,0x00050085,0x00000006,0x000001ee,
0x000001eb,0x000001ed,0x0006000c,0x00000006,
0x000001ef,0x00000001,0x0000000a,0x000001ee,
0x0007000c,0x0000000c,0x00000482,0x00000001,
0x0000001a,0x0000040e,0x0000016a,0x0008000c,
0x0000000c,0x00000483,0x00000001,0x0000002e,
0x00000165,0x00000167,0x00000482,0x00050088,
0x0000000c,0x00000486,0x000004f6,0x00000483,
0x0005008e,0x0000000c,0x00000488,0x00000486,
0x000000a1,0x0005008e,0x0000000c,0x0000048b,
0x00000486,0x000001ef,0x00050083,0x0000000c,
0x0000048d,0x0000048b,0x00000488,0x0006000c,
0x0000000c,0x0000048e,0x00000001,0x00000004,
0x0000048d,0x00050083,0x0000000c,0x00000490,
0x000004f7,0x0000048e,0x0008000c,0x0000000c,
0x00000493,0x00000001,0x00000031,0x000004f8,
0x000004f7,0x00000490,0x00050085,0x0000000c,
0x00000495,0x00000493,0x00000488,0x00050085,
0x0000000c,0x000001f5,0x000001e9,0x00000495,
0x00050083,0x0000000c,0x000001f8,0x000003a1,
0x000001f5,0x0008000c,0x0000000c,0x000001fb,
0x00000001,0x0000002b,0x000001f8,0x000004f8,
0x000004f7,0x00050085,0x0000000c,0x000001fe,
0x000001fb,0x000001fb,0x0005008e,0x0000000c,
0x00000200,0x000001fe,0x000001ff,0x00050081,
0x0000000c,0x00000202,0x000001f5,0x00000200,
0x00050041,0x00000206,0x00000207,0x000001ba,
0x00000070,0x0004003d,0x00000006,0x00000208,
0x00000207,0x000300f7,0x000004b3,0x00000000,
0x000300fb,0x00000070,0x0000049b,0x000200f8,
0x0000049b,0x00060041,0x00000188,0x0000049d,
0x00000187,0x00000074,0x00000070,0x0004003d,
0x00000006,0x0000049e,0x0000049d,0x00050085,
0x00000006,0x0000049f,0x00000208,0x0000049e,
0x0006000c,0x00000006,0x000004a7,0x00000001,
0x00000008,0x0000049f,0x0005008d,0x00000006,
0x000004a8,0x000004a7,0x00000116,0x000500b4,
0x00000196,0x000004aa,0x000004a8,0x0000004a,
0x000300f7,0x000004b2,0x00000000,0x000400fa,
0x000004aa,0x000004ab,0x000004ac,0x000200f8,
0x000004ac,0x000500b4,0x00000196,0x000004ae,
0x000004a8,0x00000049,0x000300f7,0x000004b1,
0x00000000,0x000400fa,0x000004ae,0x000004af,
0x000004b0,0x000200f8,0x000004b0,0x000200f9,
0x000004b3,0x000200f8,0x000004af,0x000200f9,
0x000004b3,0x000200f8,0x000004b1,0x000100ff,
0x000200f8,0x000004ab,0x000200f9,0x000004b3,
0x000200f8,0x000004b2,0x000100ff,0x000200f8,
0x000004b3,0x000900f5,0x0000000c,0x000004f1,
0x0000019a,0x000004ab,0x000001a1,0x000004af,
0x000001a4,0x000004b0,0x00050085,0x0000000c,
0x0000020a,0x00000202,0x000004f1,0x0005008e,
0x0000000c,0x0000020b,0x0000020a,0x00000116,
0x0008000c,0x0000000c,0x0000020e,0x00000001,
0x0000002e,0x00000202,0x0000020b,0x000004f9,
0x0005008e,0x0000000c,0x00000211,0x000001fb,
0x00000210,0x00050081,0x0000000c,0x00000213,
0x0000020e,0x00000211,0x0005008e,0x0000000c,
0x00000215,0x00000213,0x00000166,0x0007000c,
0x0000000c,0x00000218,0x00000001,0x0000001a,
0x00000215,0x00000217,0x00050051,0x00000006,
0x0000021c,0x00000218,0x00000000,0x00050051,
0x00000006,0x0000021d,0x00000218,0x00000001,
0x00050051,0x00000006,0x0000021e,0x00000218,
0x00000002,0x00070050,0x00000037,0x0000021f,
0x0000021c,0x0000021d,0x0000021e,0x00000049,
0x0003003e,0x0000021a,0x0000021f,0x000100fd,
0x00010038}
+10
View File
@@ -0,0 +1,10 @@
#version 450
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 0) uniform sampler2D uTexture;
layout(location = 0) in vec2 vUV;
void main()
{
FragColor = textureLod(uTexture, vUV, 0.0);
}
+21
View File
@@ -0,0 +1,21 @@
#version 450
layout(location = 0) out vec2 vUV;
void main()
{
if (gl_VertexIndex == 0)
{
gl_Position = vec4(-1.0, -1.0, 0.0, 1.0);
}
else if (gl_VertexIndex == 1)
{
gl_Position = vec4(-1.0, +3.0, 0.0, 1.0);
}
else
{
gl_Position = vec4(+3.0, -1.0, 0.0, 1.0);
}
vUV = gl_Position.xy * 0.5 + 0.5;
}
+32
View File
@@ -0,0 +1,32 @@
{0x07230203,0x00010300,0x000d000b,0x00000015,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0007000f,0x00000004,0x00000004,0x6e69616d,
0x00000000,0x00000009,0x00000011,0x00030010,
0x00000004,0x00000007,0x00040047,0x00000009,
0x0000001e,0x00000000,0x00040047,0x0000000d,
0x00000021,0x00000000,0x00040047,0x0000000d,
0x00000022,0x00000000,0x00040047,0x00000011,
0x0000001e,0x00000000,0x00020013,0x00000002,
0x00030021,0x00000003,0x00000002,0x00030016,
0x00000006,0x00000020,0x00040017,0x00000007,
0x00000006,0x00000004,0x00040020,0x00000008,
0x00000003,0x00000007,0x0004003b,0x00000008,
0x00000009,0x00000003,0x00090019,0x0000000a,
0x00000006,0x00000001,0x00000000,0x00000000,
0x00000000,0x00000001,0x00000000,0x0003001b,
0x0000000b,0x0000000a,0x00040020,0x0000000c,
0x00000000,0x0000000b,0x0004003b,0x0000000c,
0x0000000d,0x00000000,0x00040017,0x0000000f,
0x00000006,0x00000002,0x00040020,0x00000010,
0x00000001,0x0000000f,0x0004003b,0x00000010,
0x00000011,0x00000001,0x0004002b,0x00000006,
0x00000013,0x00000000,0x00050036,0x00000002,
0x00000004,0x00000000,0x00000003,0x000200f8,
0x00000005,0x0004003d,0x0000000b,0x0000000e,
0x0000000d,0x0004003d,0x0000000f,0x00000012,
0x00000011,0x00070058,0x00000007,0x00000014,
0x0000000e,0x00000012,0x00000002,0x00000013,
0x0003003e,0x00000009,0x00000014,0x000100fd,
0x00010038}
+72
View File
@@ -0,0 +1,72 @@
{0x07230203,0x00010300,0x000d000b,0x00000034,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0008000f,0x00000000,0x00000004,0x6e69616d,
0x00000000,0x00000008,0x00000016,0x0000002b,
0x00040047,0x00000008,0x0000000b,0x0000002a,
0x00030047,0x00000014,0x00000002,0x00050048,
0x00000014,0x00000000,0x0000000b,0x00000000,
0x00050048,0x00000014,0x00000001,0x0000000b,
0x00000001,0x00050048,0x00000014,0x00000002,
0x0000000b,0x00000003,0x00050048,0x00000014,
0x00000003,0x0000000b,0x00000004,0x00040047,
0x0000002b,0x0000001e,0x00000000,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,
0x00040015,0x00000006,0x00000020,0x00000001,
0x00040020,0x00000007,0x00000001,0x00000006,
0x0004003b,0x00000007,0x00000008,0x00000001,
0x0004002b,0x00000006,0x0000000a,0x00000000,
0x00020014,0x0000000b,0x00030016,0x0000000f,
0x00000020,0x00040017,0x00000010,0x0000000f,
0x00000004,0x00040015,0x00000011,0x00000020,
0x00000000,0x0004002b,0x00000011,0x00000012,
0x00000001,0x0004001c,0x00000013,0x0000000f,
0x00000012,0x0006001e,0x00000014,0x00000010,
0x0000000f,0x00000013,0x00000013,0x00040020,
0x00000015,0x00000003,0x00000014,0x0004003b,
0x00000015,0x00000016,0x00000003,0x0004002b,
0x0000000f,0x00000017,0xbf800000,0x0004002b,
0x0000000f,0x00000018,0x00000000,0x0004002b,
0x0000000f,0x00000019,0x3f800000,0x0007002c,
0x00000010,0x0000001a,0x00000017,0x00000017,
0x00000018,0x00000019,0x00040020,0x0000001b,
0x00000003,0x00000010,0x0004002b,0x00000006,
0x0000001f,0x00000001,0x0004002b,0x0000000f,
0x00000023,0x40400000,0x0007002c,0x00000010,
0x00000024,0x00000017,0x00000023,0x00000018,
0x00000019,0x0007002c,0x00000010,0x00000027,
0x00000023,0x00000017,0x00000018,0x00000019,
0x00040017,0x00000029,0x0000000f,0x00000002,
0x00040020,0x0000002a,0x00000003,0x00000029,
0x0004003b,0x0000002a,0x0000002b,0x00000003,
0x0004002b,0x0000000f,0x0000002f,0x3f000000,
0x0005002c,0x00000029,0x00000033,0x0000002f,
0x0000002f,0x00050036,0x00000002,0x00000004,
0x00000000,0x00000003,0x000200f8,0x00000005,
0x0004003d,0x00000006,0x00000009,0x00000008,
0x000500aa,0x0000000b,0x0000000c,0x00000009,
0x0000000a,0x000300f7,0x0000000e,0x00000000,
0x000400fa,0x0000000c,0x0000000d,0x0000001d,
0x000200f8,0x0000000d,0x00050041,0x0000001b,
0x0000001c,0x00000016,0x0000000a,0x0003003e,
0x0000001c,0x0000001a,0x000200f9,0x0000000e,
0x000200f8,0x0000001d,0x000500aa,0x0000000b,
0x00000020,0x00000009,0x0000001f,0x000300f7,
0x00000022,0x00000000,0x000400fa,0x00000020,
0x00000021,0x00000026,0x000200f8,0x00000021,
0x00050041,0x0000001b,0x00000025,0x00000016,
0x0000000a,0x0003003e,0x00000025,0x00000024,
0x000200f9,0x00000022,0x000200f8,0x00000026,
0x00050041,0x0000001b,0x00000028,0x00000016,
0x0000000a,0x0003003e,0x00000028,0x00000027,
0x000200f9,0x00000022,0x000200f8,0x00000022,
0x000200f9,0x0000000e,0x000200f8,0x0000000e,
0x00050041,0x0000001b,0x0000002c,0x00000016,
0x0000000a,0x0004003d,0x00000010,0x0000002d,
0x0000002c,0x0007004f,0x00000029,0x0000002e,
0x0000002d,0x0000002d,0x00000000,0x00000001,
0x0005008e,0x00000029,0x00000030,0x0000002e,
0x0000002f,0x00050081,0x00000029,0x00000032,
0x00000030,0x00000033,0x0003003e,0x0000002b,
0x00000032,0x000100fd,0x00010038}
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
glslc -O crt-aperture.frag -o crt_frag.spv --target-env=vulkan1.1 -mfmt=c
glslc -O main.frag -o main_frag.spv --target-env=vulkan1.1 -mfmt=c
glslc -O main.vert -o main_vert.spv --target-env=vulkan1.1 -mfmt=c
+452 -100
View File
@@ -3,6 +3,9 @@
#include "rdp_device.hpp"
#include "interface.hpp"
#include "spirv.hpp"
#include "spirv_crt.hpp"
#include <SDL3/SDL_vulkan.h>
#include <SDL3_ttf/SDL_ttf.h>
using namespace Vulkan;
@@ -50,17 +53,55 @@ enum vi_registers
VI_REGS_COUNT
};
static bool fullscreen;
static bool integer_scaling;
typedef struct
{
uint32_t depthbuffer_address;
uint32_t framebuffer_address;
uint32_t framebuffer_y_offset;
uint32_t texture_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t texture_pixel_size;
uint32_t texture_width;
uint32_t framebuffer_height;
bool depth_buffer_enabled;
} FrameBufferInfo;
typedef struct
{
uint32_t cmd_data[0x00040000 >> 2];
int cmd_cur;
int cmd_ptr;
uint32_t region;
FrameBufferInfo frame_buffer_info;
} RDP_DEVICE;
static SDL_Window *window;
static RDP::CommandProcessor *processor;
static SDL_WSIPlatform *wsi_platform;
static WSI *wsi;
static uint32_t cmd_data[0x00040000 >> 2];
static int cmd_cur;
static int cmd_ptr;
static bool emu_running;
static RDP_DEVICE rdp_device;
static bool crop_letterbox;
static CALL_BACK callback;
static GFX_INFO gfx_info;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
std::vector<bool> rdram_dirty;
uint64_t sync_signal;
static TTF_Font *message_font;
static std::queue<std::string> messages;
static uint64_t message_timer;
#define MESSAGE_TIME 3000 // 3 seconds
typedef struct
{
float SourceSize[4];
float OutputSize[4];
} Push;
static const unsigned cmd_len_lut[64] = {
1,
@@ -133,18 +174,75 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
{
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
{
emu_running = false;
callback.paused = false;
callback.emu_running = false;
}
else if (event->type == SDL_EVENT_WINDOW_RESIZED && emu_running)
else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running)
{
wsi_platform->do_resize();
}
else if (event->type == SDL_EVENT_KEY_DOWN && fullscreen)
else if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat)
{
switch (event->key.scancode)
{
case SDL_SCANCODE_RETURN:
if (event->key.mod & SDL_KMOD_ALT)
{
gfx_info.fullscreen = !gfx_info.fullscreen;
SDL_SetWindowFullscreen(window, gfx_info.fullscreen);
}
break;
case SDL_SCANCODE_F:
if (event->key.mod & SDL_KMOD_ALT)
{
callback.enable_speedlimiter = !callback.enable_speedlimiter;
}
break;
case SDL_SCANCODE_P:
if (event->key.mod & SDL_KMOD_ALT)
{
callback.paused = !callback.paused;
}
break;
case SDL_SCANCODE_ESCAPE:
emu_running = false;
if (gfx_info.fullscreen)
callback.emu_running = false;
break;
case SDL_SCANCODE_F4:
crop_letterbox = !crop_letterbox;
break;
case SDL_SCANCODE_F5:
callback.save_state = true;
break;
case SDL_SCANCODE_F7:
callback.load_state = true;
break;
case SDL_SCANCODE_LEFTBRACKET:
callback.lower_volume = true;
break;
case SDL_SCANCODE_RIGHTBRACKET:
callback.raise_volume = true;
break;
case SDL_SCANCODE_SLASH:
callback.frame_advance = true;
break;
case SDL_SCANCODE_0:
case SDL_SCANCODE_1:
case SDL_SCANCODE_2:
case SDL_SCANCODE_3:
case SDL_SCANCODE_4:
case SDL_SCANCODE_5:
case SDL_SCANCODE_6:
case SDL_SCANCODE_7:
case SDL_SCANCODE_8:
case SDL_SCANCODE_9:
if (event->key.mod & SDL_KMOD_ALT)
{
if (event->key.scancode == SDL_SCANCODE_0)
callback.save_state_slot = 0;
else
callback.save_state_slot = event->key.scancode - SDL_SCANCODE_1 + 1;
}
break;
default:
break;
@@ -154,9 +252,60 @@ bool sdl_event_filter(void *userdata, SDL_Event *event)
return 0;
}
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen)
void rdp_new_processor(GFX_INFO _gfx_info)
{
gfx_info = _gfx_info;
sync_signal = 0;
rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false);
if (processor)
{
delete processor;
}
RDP::CommandProcessorFlags flags = 0;
if (gfx_info.upscale == 2)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
}
else if (gfx_info.upscale == 4)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_4X_BIT;
}
else if (gfx_info.upscale == 8)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_SUPER_SAMPLED_DITHER_BIT;
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_8X_BIT;
}
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
}
static ImageHandle create_message_image(Vulkan::Device &device, int width, const char *message)
{
SDL_Color fg = {255, 255, 255, 255};
SDL_Color bg = {0, 0, 0, 0};
SDL_Surface *surface = TTF_RenderText_LCD_Wrapped(message_font, message, 0, fg, bg, width);
ImageCreateInfo info = ImageCreateInfo::immutable_2d_image(surface->w, surface->h, VK_FORMAT_B8G8R8A8_UNORM, false);
ImageInitialData initial_data = {};
initial_data.data = surface->pixels;
initial_data.row_length = surface->pitch / 4;
initial_data.image_height = surface->h;
ImageHandle handle = device.create_image(info, &initial_data);
SDL_DestroySurface(surface);
return handle;
}
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size)
{
memset(&rdp_device, 0, sizeof(RDP_DEVICE));
window = (SDL_Window *)_window;
SDL_SyncWindow(window);
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
if (!result)
{
@@ -165,8 +314,18 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
}
gfx_info = _gfx_info;
fullscreen = _fullscreen;
integer_scaling = _integer_scaling;
if (gfx_info.crt)
{
fragment_spirv = crt_fragment_spirv;
fragment_size = sizeof(crt_fragment_spirv);
}
else
{
fragment_spirv = plain_fragment_spirv;
fragment_size = sizeof(plain_fragment_spirv);
}
bool window_vsync = 0;
wsi = new WSI;
wsi_platform = new SDL_WSIPlatform;
@@ -175,38 +334,54 @@ void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_sc
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader(nullptr))
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
{
rdp_close();
return;
}
if (!wsi->init_simple(1, handles))
{
rdp_close();
return;
}
RDP::CommandProcessorFlags flags = 0;
if (_upscale)
{
flags |= RDP::COMMAND_PROCESSOR_FLAG_UPSCALING_2X_BIT;
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);
rdp_new_processor(gfx_info);
if (!processor->device_is_supported())
{
delete processor;
delete wsi;
processor = nullptr;
rdp_close();
return;
}
message_font = TTF_OpenFontIO(SDL_IOFromConstMem(font, font_size), true, 30.0);
if (!message_font)
{
rdp_close();
return;
}
wsi->begin_frame();
emu_running = true;
callback.emu_running = true;
callback.enable_speedlimiter = true;
callback.paused = false;
callback.save_state_slot = 0;
crop_letterbox = false;
messages = std::queue<std::string>();
message_timer = 0;
}
void rdp_close()
{
wsi->end_frame();
if (wsi)
wsi->end_frame();
if (message_font)
{
TTF_CloseFont(message_font);
message_font = nullptr;
}
if (processor)
{
delete processor;
@@ -224,15 +399,14 @@ void rdp_close()
}
}
static void calculate_viewport(float *x, float *y, float *width, float *height)
static void calculate_viewport(float *x, float *y, float *width, float *height, uint32_t display_height)
{
const int32_t display_width = 320;
const int32_t display_height = 240;
uint32_t display_width = gfx_info.widescreen ? display_height * 16 / 9 : display_height * 4 / 3;
int w, h;
SDL_GetWindowSize(window, &w, &h);
if (integer_scaling)
if (gfx_info.integer_scaling)
{
// Integer scaling path
int scale_x = w / display_width;
@@ -247,6 +421,13 @@ static void calculate_viewport(float *x, float *y, float *width, float *height)
*width = scaled_width;
*height = scaled_height;
// Center the viewport
int integer_x = (w - *width) / 2.0f;
int integer_y = (h - *height) / 2.0f;
*x = integer_x;
*y = integer_y;
}
else
{
@@ -257,11 +438,11 @@ static void calculate_viewport(float *x, float *y, float *width, float *height)
*width = display_width * scale;
*height = display_height * scale;
}
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
}
}
static void render_frame(Vulkan::Device &device)
@@ -270,17 +451,34 @@ static void render_frame(Vulkan::Device &device)
options.persist_frame_on_invalid_input = true;
options.blend_previous_frame = true;
options.upscale_deinterlacing = false;
if (crop_letterbox && gfx_info.widescreen)
{
options.crop_rect.enable = true;
if (gfx_info.PAL)
{
options.crop_rect.top = 36;
options.crop_rect.bottom = 36;
}
else
{
options.crop_rect.top = 30;
options.crop_rect.bottom = 30;
}
}
Vulkan::ImageHandle image = processor->scanout(options);
// Normally reflection is automated.
Vulkan::ResourceLayout vertex_layout = {};
Vulkan::ResourceLayout fragment_layout = {};
fragment_layout.output_mask = 1 << 0;
fragment_layout.sets[0].sampled_image_mask = 1 << 0;
if (gfx_info.crt)
fragment_layout.push_constant_size = sizeof(Push);
// This request is cached.
auto *program = device.request_program(vertex_spirv, sizeof(vertex_spirv),
fragment_spirv, sizeof(fragment_spirv),
fragment_spirv, fragment_size,
&vertex_layout,
&fragment_layout);
@@ -290,9 +488,6 @@ static void render_frame(Vulkan::Device &device)
auto rp = device.get_swapchain_render_pass(Vulkan::SwapchainRenderPass::ColorOnly);
cmd->begin_render_pass(rp);
VkViewport vp = cmd->get_viewport();
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height);
cmd->set_program(program);
// Basic default render state.
@@ -303,11 +498,43 @@ static void render_frame(Vulkan::Device &device)
// If we don't have an image, we just get a cleared screen in the render pass.
if (image)
{
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::LinearClamp);
VkViewport vp = cmd->get_viewport();
calculate_viewport(&vp.x, &vp.y, &vp.width, &vp.height, image->get_height() / gfx_info.upscale);
if (gfx_info.crt)
{
// Set shader parameters
Push push = {
{float(image->get_width()), float(image->get_height()), 1.0f / float(image->get_width()), 1.0f / float(image->get_height())},
{vp.width, vp.height, 1.0f / vp.width, 1.0f / vp.height},
};
cmd->push_constants(&push, 0, sizeof(push));
}
cmd->set_texture(0, 0, image->get_view(), Vulkan::StockSampler::NearestClamp);
cmd->set_viewport(vp);
// The vertices are constants in the shader.
// Draws fullscreen quad using oversized triangle.
cmd->draw(3);
if (!messages.empty())
{
Vulkan::ImageHandle message_image = create_message_image(device, vp.width, messages.front().c_str());
cmd->set_texture(0, 0, message_image->get_view(), Vulkan::StockSampler::NearestClamp);
vp.x = vp.x + (vp.width - message_image->get_width()) / 2;
vp.y = vp.y + vp.height - message_image->get_height();
vp.height = message_image->get_height();
vp.width = message_image->get_width();
cmd->set_viewport(vp);
cmd->draw(3);
if (SDL_GetTicks() > message_timer)
{
messages.pop();
message_timer = SDL_GetTicks() + MESSAGE_TIME;
}
}
}
cmd->end_render_pass();
@@ -320,52 +547,90 @@ 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_render_frame()
{
auto &device = wsi->get_device();
render_frame(device);
}
void rdp_update_screen()
{
wsi->end_frame();
wsi->begin_frame();
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;
CALL_BACK return_value = callback;
callback.save_state = false;
callback.load_state = false;
callback.lower_volume = false;
callback.raise_volume = false;
callback.frame_advance = false;
return return_value;
}
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)
void rdp_check_framebuffers(uint32_t address, uint32_t length)
{
if (sync_signal)
{
delta = width;
}
address >>= 3;
length = (length + 7) >> 3;
return (delta * scale) / 0x400;
if (address >= rdram_dirty.size())
return;
uint32_t end_addr = std::min(address + length, static_cast<uint32_t>(rdram_dirty.size()));
auto it = std::find(rdram_dirty.begin() + address, rdram_dirty.begin() + end_addr, true);
if (it != rdram_dirty.begin() + end_addr)
{
processor->wait_for_timeline(sync_signal);
rdram_dirty.assign(gfx_info.RDRAM_SIZE >> 3, false);
sync_signal = 0;
}
}
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
size_t rdp_state_size()
{
if (yscale == 0)
return 240;
return sizeof(RDP_DEVICE);
}
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);
void rdp_save_state(uint8_t *state)
{
processor->wait_for_timeline(processor->signal_timeline());
memcpy(state, &rdp_device, sizeof(RDP_DEVICE));
}
return (delta * scale) / 0x800;
void rdp_load_state(const uint8_t *state)
{
memcpy(&rdp_device, state, sizeof(RDP_DEVICE));
}
void rdp_onscreen_message(const char *_message)
{
if (messages.empty())
message_timer = SDL_GetTicks() + MESSAGE_TIME;
messages.push(_message);
}
uint32_t pixel_size(uint32_t pixel_type, uint32_t area)
{
switch (pixel_type)
{
case 0:
return area / 2;
case 1:
return area;
case 2:
return area * 2;
case 3:
return area * 4;
default:
printf("Invalid pixel size: %u\n", pixel_type);
return 0;
}
}
uint64_t rdp_process_commands()
@@ -379,21 +644,19 @@ uint64_t rdp_process_commands()
return interrupt_timer;
length = unsigned(length) >> 3;
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
if ((rdp_device.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_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));
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
offset += sizeof(uint64_t);
cmd_ptr++;
rdp_device.cmd_ptr++;
} while (--length > 0);
}
else
@@ -407,55 +670,144 @@ uint64_t rdp_process_commands()
do
{
offset &= 0xFFFFF8;
cmd_data[2 * cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
cmd_data[2 * cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset);
rdp_device.cmd_data[2 * rdp_device.cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>(gfx_info.RDRAM + offset + 4);
offset += sizeof(uint64_t);
cmd_ptr++;
rdp_device.cmd_ptr++;
} while (--length > 0);
}
}
while (cmd_cur - cmd_ptr < 0)
while (rdp_device.cmd_cur - rdp_device.cmd_ptr < 0)
{
uint32_t w1 = cmd_data[2 * cmd_cur];
uint32_t w1 = rdp_device.cmd_data[2 * rdp_device.cmd_cur];
uint32_t w2 = rdp_device.cmd_data[2 * rdp_device.cmd_cur + 1];
uint32_t command = (w1 >> 24) & 63;
int cmd_length = cmd_len_lut[command];
if (cmd_ptr - cmd_cur - cmd_length < 0)
if (rdp_device.cmd_ptr - rdp_device.cmd_cur - cmd_length < 0)
{
*gfx_info.DPC_START_REG = *gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;
}
if (command >= 8)
processor->enqueue_command(cmd_length * 2, &cmd_data[2 * cmd_cur]);
processor->enqueue_command(cmd_length * 2, &rdp_device.cmd_data[2 * rdp_device.cmd_cur]);
if (RDP::Op(command) == RDP::Op::SyncFull)
switch (RDP::Op(command))
{
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)
case RDP::Op::FillTriangle:
case RDP::Op::FillZBufferTriangle:
case RDP::Op::TextureTriangle:
case RDP::Op::TextureZBufferTriangle:
case RDP::Op::ShadeTriangle:
case RDP::Op::ShadeZBufferTriangle:
case RDP::Op::ShadeTextureTriangle:
case RDP::Op::ShadeTextureZBufferTriangle:
case RDP::Op::TextureRectangle:
case RDP::Op::TextureRectangleFlip:
case RDP::Op::FillRectangle:
{
uint32_t offset_address = (rdp_device.frame_buffer_info.framebuffer_address + pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
width = 320;
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.framebuffer_pixel_size, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
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);
if (rdp_device.frame_buffer_info.depth_buffer_enabled)
{
offset_address = (rdp_device.frame_buffer_info.depthbuffer_address + pixel_size(2, rdp_device.frame_buffer_info.framebuffer_y_offset * rdp_device.frame_buffer_info.framebuffer_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
uint32_t end_addr = std::min(offset_address + ((pixel_size(2, rdp_device.frame_buffer_info.framebuffer_width * rdp_device.frame_buffer_info.framebuffer_height) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
}
break;
case RDP::Op::LoadTLut:
case RDP::Op::LoadTile:
{
uint32_t upper_left_t = (w1 & 0xFFF) >> 2;
uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
uint32_t lower_right_t = (w2 & 0xFFF) >> 2;
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, (lower_right_t - upper_left_t) * rdp_device.frame_buffer_info.texture_width) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
break;
case RDP::Op::LoadBlock:
{
uint32_t upper_left_s = ((w1 >> 12) & 0xFFF);
uint32_t upper_left_t = (w1 & 0xFFF);
uint32_t offset_address = (rdp_device.frame_buffer_info.texture_address + pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, upper_left_s + upper_left_t * rdp_device.frame_buffer_info.texture_width)) >> 3;
if (offset_address < rdram_dirty.size() && !rdram_dirty[offset_address])
{
uint32_t lower_right_s = ((w2 >> 12) & 0xFFF);
uint32_t end_addr = std::min(offset_address + ((pixel_size(rdp_device.frame_buffer_info.texture_pixel_size, lower_right_s - upper_left_s) + 7) >> 3), static_cast<uint32_t>(rdram_dirty.size()));
std::fill(rdram_dirty.begin() + offset_address, rdram_dirty.begin() + end_addr, true);
}
}
break;
case RDP::Op::SetColorImage:
rdp_device.frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF);
rdp_device.frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
break;
case RDP::Op::SetMaskImage:
rdp_device.frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF);
break;
case RDP::Op::SetTextureImage:
rdp_device.frame_buffer_info.texture_address = (w2 & 0x00FFFFFF);
rdp_device.frame_buffer_info.texture_pixel_size = (w1 >> 19) & 0x3;
rdp_device.frame_buffer_info.texture_width = (w1 & 0x3FF) + 1;
break;
case RDP::Op::SetScissor:
{
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
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;
if (lower_right_x > upper_left_x && lower_right_y > upper_left_y)
{
rdp_device.region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
}
else
{
rdp_device.region = 0;
}
rdp_device.frame_buffer_info.framebuffer_y_offset = upper_left_y;
rdp_device.frame_buffer_info.framebuffer_height = lower_right_y - upper_left_y;
}
break;
case RDP::Op::SetOtherModes:
{
uint8_t cycle_type = (w1 >> 20) & 3;
uint8_t depth_read_write = (w2 >> 4) & 3;
rdp_device.frame_buffer_info.depth_buffer_enabled = ((cycle_type & 2) == 0) && (depth_read_write != 0);
}
break;
case RDP::Op::SyncFull:
sync_signal = processor->signal_timeline();
interrupt_timer = rdp_device.region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
break;
default:
break;
}
cmd_cur += cmd_length;
rdp_device.cmd_cur += cmd_length;
}
cmd_ptr = 0;
cmd_cur = 0;
rdp_device.cmd_ptr = 0;
rdp_device.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;
}
+30 -7
View File
@@ -2,6 +2,7 @@
#ifdef __cplusplus
#include <cstdint>
#include <stddef.h>
extern "C"
{
@@ -16,18 +17,40 @@ 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;
bool widescreen;
bool fullscreen;
bool integer_scaling;
uint32_t upscale;
bool crt;
} GFX_INFO;
void rdp_init(void *_window, GFX_INFO _gfx_info, bool _upscale, bool _integer_scaling, bool _fullscreen);
typedef struct
{
bool emu_running;
bool save_state;
bool load_state;
bool enable_speedlimiter;
bool lower_volume;
bool raise_volume;
bool paused;
bool frame_advance;
uint32_t save_state_slot;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size);
void rdp_close();
void rdp_set_vi_register(uint32_t reg, uint32_t value);
bool rdp_update_screen();
void rdp_update_screen();
void rdp_render_frame();
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_onscreen_message(const char *_message);
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address, uint32_t length);
size_t rdp_state_size();
void rdp_save_state(uint8_t *state);
void rdp_load_state(const uint8_t *state);
#ifdef __cplusplus
}
+11 -11
View File
@@ -1,19 +1,19 @@
#pragma once
static const uint32_t vertex_spirv[] =
{0x07230203, 0x00010000, 0x000d000a, 0x00000034,
{0x07230203, 0x00010300, 0x000d000b, 0x00000034,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0008000f, 0x00000000, 0x00000004, 0x6e69616d,
0x00000000, 0x00000008, 0x00000016, 0x0000002b,
0x00040047, 0x00000008, 0x0000000b, 0x0000002a,
0x00050048, 0x00000014, 0x00000000, 0x0000000b,
0x00000000, 0x00050048, 0x00000014, 0x00000001,
0x0000000b, 0x00000001, 0x00050048, 0x00000014,
0x00000002, 0x0000000b, 0x00000003, 0x00050048,
0x00000014, 0x00000003, 0x0000000b, 0x00000004,
0x00030047, 0x00000014, 0x00000002, 0x00040047,
0x00030047, 0x00000014, 0x00000002, 0x00050048,
0x00000014, 0x00000000, 0x0000000b, 0x00000000,
0x00050048, 0x00000014, 0x00000001, 0x0000000b,
0x00000001, 0x00050048, 0x00000014, 0x00000002,
0x0000000b, 0x00000003, 0x00050048, 0x00000014,
0x00000003, 0x0000000b, 0x00000004, 0x00040047,
0x0000002b, 0x0000001e, 0x00000000, 0x00020013,
0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00040015, 0x00000006, 0x00000020, 0x00000001,
@@ -74,8 +74,8 @@ static const uint32_t vertex_spirv[] =
0x00000030, 0x00000033, 0x0003003e, 0x0000002b,
0x00000032, 0x000100fd, 0x00010038};
static const uint32_t fragment_spirv[] =
{0x07230203, 0x00010000, 0x000d000a, 0x00000015,
static const uint32_t plain_fragment_spirv[] =
{0x07230203, 0x00010300, 0x000d000b, 0x00000015,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
@@ -83,8 +83,8 @@ static const uint32_t fragment_spirv[] =
0x00000000, 0x00000009, 0x00000011, 0x00030010,
0x00000004, 0x00000007, 0x00040047, 0x00000009,
0x0000001e, 0x00000000, 0x00040047, 0x0000000d,
0x00000022, 0x00000000, 0x00040047, 0x0000000d,
0x00000021, 0x00000000, 0x00040047, 0x00000011,
0x00000021, 0x00000000, 0x00040047, 0x0000000d,
0x00000022, 0x00000000, 0x00040047, 0x00000011,
0x0000001e, 0x00000000, 0x00020013, 0x00000002,
0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007,
+456
View File
@@ -0,0 +1,456 @@
#pragma once
static const uint32_t crt_fragment_spirv[] =
{0x07230203, 0x00010300, 0x000d000b, 0x000004fd,
0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e,
0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0007000f, 0x00000004, 0x00000004, 0x6e69616d,
0x00000000, 0x000001ba, 0x0000021a, 0x00030010,
0x00000004, 0x00000007, 0x00030047, 0x00000185,
0x00000002, 0x00050048, 0x00000185, 0x00000000,
0x00000023, 0x00000000, 0x00050048, 0x00000185,
0x00000001, 0x00000023, 0x00000010, 0x00040047,
0x000001ba, 0x0000001e, 0x00000000, 0x00040047,
0x000001ca, 0x00000021, 0x00000000, 0x00040047,
0x000001ca, 0x00000022, 0x00000000, 0x00040047,
0x0000021a, 0x0000001e, 0x00000000, 0x00020013,
0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00030016, 0x00000006, 0x00000020, 0x00090019,
0x00000007, 0x00000006, 0x00000001, 0x00000000,
0x00000000, 0x00000000, 0x00000001, 0x00000000,
0x0003001b, 0x00000008, 0x00000007, 0x00040020,
0x00000009, 0x00000000, 0x00000008, 0x00040017,
0x0000000a, 0x00000006, 0x00000002, 0x00040017,
0x0000000c, 0x00000006, 0x00000003, 0x00040017,
0x00000037, 0x00000006, 0x00000004, 0x0004002b,
0x00000006, 0x0000003a, 0x4019999a, 0x0006002c,
0x0000000c, 0x0000003b, 0x0000003a, 0x0000003a,
0x0000003a, 0x0004002b, 0x00000006, 0x00000049,
0x3f800000, 0x0004002b, 0x00000006, 0x0000004a,
0x00000000, 0x0004002b, 0x00000006, 0x00000068,
0xbf800000, 0x00040015, 0x0000006b, 0x00000020,
0x00000001, 0x0004002b, 0x0000006b, 0x0000006c,
0x00000000, 0x00040015, 0x0000006f, 0x00000020,
0x00000000, 0x0004002b, 0x0000006f, 0x00000070,
0x00000000, 0x0004002b, 0x0000006b, 0x00000074,
0x00000001, 0x0004002b, 0x0000006f, 0x00000077,
0x00000001, 0x0004002b, 0x00000006, 0x000000a1,
0x3f000000, 0x0005002c, 0x0000000a, 0x000000fc,
0x000000a1, 0x0000004a, 0x0004002b, 0x00000006,
0x00000101, 0x3a83126f, 0x0005002c, 0x0000000a,
0x00000102, 0x000000a1, 0x00000101, 0x0004002b,
0x00000006, 0x0000010b, 0x40490fdb, 0x0004002b,
0x00000006, 0x00000116, 0x40000000, 0x0004002b,
0x00000006, 0x0000011c, 0x3727c5ac, 0x0007002c,
0x00000037, 0x0000012c, 0x00000049, 0x00000049,
0x00000049, 0x00000049, 0x00040018, 0x00000149,
0x00000037, 0x00000004, 0x0006002c, 0x0000000c,
0x00000165, 0x000000a1, 0x000000a1, 0x000000a1,
0x0004002b, 0x00000006, 0x00000166, 0x3fc00000,
0x0006002c, 0x0000000c, 0x00000167, 0x00000166,
0x00000166, 0x00000166, 0x0004002b, 0x00000006,
0x00000169, 0x3ecccccd, 0x0006002c, 0x0000000c,
0x0000016a, 0x00000169, 0x00000169, 0x00000169,
0x0004001e, 0x00000185, 0x00000037, 0x00000037,
0x00040020, 0x00000186, 0x00000009, 0x00000185,
0x0004003b, 0x00000186, 0x00000187, 0x00000009,
0x00040020, 0x00000188, 0x00000009, 0x00000006,
0x00020014, 0x00000196, 0x0006002c, 0x0000000c,
0x0000019a, 0x00000049, 0x0000004a, 0x00000049,
0x0006002c, 0x0000000c, 0x000001a1, 0x0000004a,
0x00000049, 0x0000004a, 0x0006002c, 0x0000000c,
0x000001a4, 0x0000004a, 0x0000004a, 0x00000049,
0x0004002b, 0x0000006f, 0x000001aa, 0x00000003,
0x00040020, 0x000001b9, 0x00000001, 0x0000000a,
0x0004003b, 0x000001b9, 0x000001ba, 0x00000001,
0x00040020, 0x000001bc, 0x00000009, 0x00000037,
0x0004003b, 0x00000009, 0x000001ca, 0x00000000,
0x0004002b, 0x00000006, 0x000001dc, 0x40400000,
0x0004002b, 0x00000006, 0x000001ff, 0x3dcccccd,
0x00040020, 0x00000206, 0x00000001, 0x00000006,
0x0004002b, 0x00000006, 0x0000020c, 0x3e99999a,
0x0004002b, 0x00000006, 0x00000210, 0x3d4ccccd,
0x0004002b, 0x00000006, 0x00000216, 0x3ed55555,
0x0006002c, 0x0000000c, 0x00000217, 0x00000216,
0x00000216, 0x00000216, 0x00040020, 0x00000219,
0x00000003, 0x00000037, 0x0004003b, 0x00000219,
0x0000021a, 0x00000003, 0x0005002c, 0x0000000a,
0x000004f3, 0x000000a1, 0x000000a1, 0x0007002c,
0x00000037, 0x000004f4, 0x0000011c, 0x0000011c,
0x0000011c, 0x0000011c, 0x0006002c, 0x0000000c,
0x000004f6, 0x00000116, 0x00000116, 0x00000116,
0x0006002c, 0x0000000c, 0x000004f7, 0x00000049,
0x00000049, 0x00000049, 0x0006002c, 0x0000000c,
0x000004f8, 0x0000004a, 0x0000004a, 0x0000004a,
0x0006002c, 0x0000000c, 0x000004f9, 0x0000020c,
0x0000020c, 0x0000020c, 0x0007002c, 0x00000037,
0x000004fa, 0x000000a1, 0x000000a1, 0x000000a1,
0x000000a1, 0x0004002b, 0x00000006, 0x000004fb,
0x3eaaaaab, 0x00050036, 0x00000002, 0x00000004,
0x00000000, 0x00000003, 0x000200f8, 0x00000005,
0x00060041, 0x00000188, 0x000001a8, 0x00000187,
0x00000074, 0x00000077, 0x0004003d, 0x00000006,
0x000001a9, 0x000001a8, 0x00060041, 0x00000188,
0x000001ab, 0x00000187, 0x0000006c, 0x000001aa,
0x0004003d, 0x00000006, 0x000001ac, 0x000001ab,
0x00050085, 0x00000006, 0x000001ad, 0x000001a9,
0x000001ac, 0x0006000c, 0x00000006, 0x000001ae,
0x00000001, 0x00000008, 0x000001ad, 0x00050088,
0x00000006, 0x000001b2, 0x000000a1, 0x000001ae,
0x0005008d, 0x00000006, 0x000001b4, 0x000001ae,
0x00000116, 0x000500b7, 0x00000196, 0x000001b5,
0x000001b4, 0x0000004a, 0x000600a9, 0x00000006,
0x000004fc, 0x000001b5, 0x0000004a, 0x000001b2,
0x0004003d, 0x0000000a, 0x000001bb, 0x000001ba,
0x00050041, 0x000001bc, 0x000001bd, 0x00000187,
0x0000006c, 0x0004003d, 0x00000037, 0x000001be,
0x000001bd, 0x0007004f, 0x0000000a, 0x000001bf,
0x000001be, 0x000001be, 0x00000000, 0x00000001,
0x00050085, 0x0000000a, 0x000001c0, 0x000001bb,
0x000001bf, 0x00050050, 0x0000000a, 0x000001c3,
0x0000004a, 0x000004fc, 0x00050083, 0x0000000a,
0x000001c4, 0x000001c0, 0x000001c3, 0x0007004f,
0x0000000a, 0x000001c7, 0x000001be, 0x000001be,
0x00000002, 0x00000003, 0x00050085, 0x0000000a,
0x000001c8, 0x000001c4, 0x000001c7, 0x00050051,
0x00000006, 0x00000249, 0x000001be, 0x00000000,
0x00050088, 0x00000006, 0x0000024a, 0x00000049,
0x00000249, 0x00050050, 0x0000000a, 0x0000024b,
0x0000024a, 0x0000004a, 0x00050051, 0x00000006,
0x0000024d, 0x000001be, 0x00000001, 0x00050088,
0x00000006, 0x0000024e, 0x00000049, 0x0000024d,
0x00050050, 0x0000000a, 0x0000024f, 0x0000004a,
0x0000024e, 0x00050085, 0x0000000a, 0x00000252,
0x000001c8, 0x000001bf, 0x0006000c, 0x0000000a,
0x00000254, 0x00000001, 0x00000008, 0x00000252,
0x00050081, 0x0000000a, 0x00000256, 0x00000254,
0x000004f3, 0x00050088, 0x0000000a, 0x00000258,
0x00000256, 0x000001bf, 0x0006000c, 0x0000000a,
0x0000025a, 0x00000001, 0x0000000a, 0x00000252,
0x00050083, 0x0000000a, 0x0000025c, 0x0000025a,
0x000004f3, 0x0005008e, 0x0000000a, 0x0000025d,
0x0000025c, 0x00000068, 0x00050083, 0x0000000a,
0x00000260, 0x00000258, 0x0000024f, 0x0004003d,
0x00000008, 0x0000028a, 0x000001ca, 0x00050083,
0x0000000a, 0x0000028d, 0x00000260, 0x0000024b,
0x00050057, 0x00000037, 0x0000028e, 0x0000028a,
0x0000028d, 0x0008004f, 0x0000000c, 0x0000028f,
0x0000028e, 0x0000028e, 0x00000000, 0x00000001,
0x00000002, 0x0007000c, 0x0000000c, 0x00000290,
0x00000001, 0x0000001a, 0x0000028f, 0x0000003b,
0x00050057, 0x00000037, 0x00000293, 0x0000028a,
0x00000260, 0x0008004f, 0x0000000c, 0x00000294,
0x00000293, 0x00000293, 0x00000000, 0x00000001,
0x00000002, 0x0007000c, 0x0000000c, 0x00000295,
0x00000001, 0x0000001a, 0x00000294, 0x0000003b,
0x00050081, 0x0000000a, 0x00000299, 0x00000260,
0x0000024b, 0x00050057, 0x00000037, 0x0000029a,
0x0000028a, 0x00000299, 0x0008004f, 0x0000000c,
0x0000029b, 0x0000029a, 0x0000029a, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x0000029c, 0x00000001, 0x0000001a, 0x0000029b,
0x0000003b, 0x00050083, 0x0000000a, 0x000002af,
0x00000258, 0x0000024b, 0x00050057, 0x00000037,
0x000002b0, 0x0000028a, 0x000002af, 0x0008004f,
0x0000000c, 0x000002b1, 0x000002b0, 0x000002b0,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002b2, 0x00000001, 0x0000001a,
0x000002b1, 0x0000003b, 0x00050057, 0x00000037,
0x000002b5, 0x0000028a, 0x00000258, 0x0008004f,
0x0000000c, 0x000002b6, 0x000002b5, 0x000002b5,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002b7, 0x00000001, 0x0000001a,
0x000002b6, 0x0000003b, 0x00050081, 0x0000000a,
0x000002bb, 0x00000258, 0x0000024b, 0x00050057,
0x00000037, 0x000002bc, 0x0000028a, 0x000002bb,
0x0008004f, 0x0000000c, 0x000002bd, 0x000002bc,
0x000002bc, 0x00000000, 0x00000001, 0x00000002,
0x0007000c, 0x0000000c, 0x000002be, 0x00000001,
0x0000001a, 0x000002bd, 0x0000003b, 0x00050081,
0x0000000a, 0x00000268, 0x00000258, 0x0000024f,
0x00050083, 0x0000000a, 0x000002d1, 0x00000268,
0x0000024b, 0x00050057, 0x00000037, 0x000002d2,
0x0000028a, 0x000002d1, 0x0008004f, 0x0000000c,
0x000002d3, 0x000002d2, 0x000002d2, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000002d4, 0x00000001, 0x0000001a, 0x000002d3,
0x0000003b, 0x00050057, 0x00000037, 0x000002d7,
0x0000028a, 0x00000268, 0x0008004f, 0x0000000c,
0x000002d8, 0x000002d7, 0x000002d7, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000002d9, 0x00000001, 0x0000001a, 0x000002d8,
0x0000003b, 0x00050081, 0x0000000a, 0x000002dd,
0x00000268, 0x0000024b, 0x00050057, 0x00000037,
0x000002de, 0x0000028a, 0x000002dd, 0x0008004f,
0x0000000c, 0x000002df, 0x000002de, 0x000002de,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x000002e0, 0x00000001, 0x0000001a,
0x000002df, 0x0000003b, 0x00050051, 0x00000006,
0x0000026d, 0x0000025d, 0x00000000, 0x00050083,
0x00000006, 0x000002f3, 0x0000026d, 0x00000049,
0x00050081, 0x00000006, 0x000002f6, 0x0000026d,
0x00000049, 0x00060050, 0x0000000c, 0x000002f7,
0x000002f3, 0x0000026d, 0x000002f6, 0x00050085,
0x0000000c, 0x000002fa, 0x000002f7, 0x000004f6,
0x00050085, 0x0000000c, 0x000002fd, 0x000002fa,
0x000002fa, 0x0005008e, 0x0000000c, 0x000002fe,
0x000002fd, 0x00000068, 0x0006000c, 0x0000000c,
0x000002ff, 0x00000001, 0x0000001d, 0x000002fe,
0x00050051, 0x00000006, 0x00000303, 0x000002ff,
0x00000000, 0x0005008e, 0x0000000c, 0x00000304,
0x00000290, 0x00000303, 0x00050051, 0x00000006,
0x00000308, 0x000002ff, 0x00000001, 0x0005008e,
0x0000000c, 0x00000309, 0x00000295, 0x00000308,
0x00050081, 0x0000000c, 0x0000030a, 0x00000304,
0x00000309, 0x00050051, 0x00000006, 0x0000030e,
0x000002ff, 0x00000002, 0x0005008e, 0x0000000c,
0x0000030f, 0x0000029c, 0x0000030e, 0x00050081,
0x0000000c, 0x00000310, 0x0000030a, 0x0000030f,
0x00050081, 0x00000006, 0x00000315, 0x00000303,
0x00000308, 0x00050081, 0x00000006, 0x00000318,
0x00000315, 0x0000030e, 0x00060050, 0x0000000c,
0x00000319, 0x00000318, 0x00000318, 0x00000318,
0x00050088, 0x0000000c, 0x0000031a, 0x00000310,
0x00000319, 0x0005008e, 0x0000000c, 0x00000331,
0x000002b2, 0x00000303, 0x0005008e, 0x0000000c,
0x00000336, 0x000002b7, 0x00000308, 0x00050081,
0x0000000c, 0x00000337, 0x00000331, 0x00000336,
0x0005008e, 0x0000000c, 0x0000033c, 0x000002be,
0x0000030e, 0x00050081, 0x0000000c, 0x0000033d,
0x00000337, 0x0000033c, 0x00050088, 0x0000000c,
0x00000347, 0x0000033d, 0x00000319, 0x0005008e,
0x0000000c, 0x0000035e, 0x000002d4, 0x00000303,
0x0005008e, 0x0000000c, 0x00000363, 0x000002d9,
0x00000308, 0x00050081, 0x0000000c, 0x00000364,
0x0000035e, 0x00000363, 0x0005008e, 0x0000000c,
0x00000369, 0x000002e0, 0x0000030e, 0x00050081,
0x0000000c, 0x0000036a, 0x00000364, 0x00000369,
0x00050088, 0x0000000c, 0x00000374, 0x0000036a,
0x00000319, 0x00050051, 0x00000006, 0x00000286,
0x0000025d, 0x00000001, 0x00050083, 0x00000006,
0x0000037a, 0x00000286, 0x00000049, 0x00050081,
0x00000006, 0x0000037d, 0x00000286, 0x00000049,
0x00060050, 0x0000000c, 0x0000037e, 0x0000037a,
0x00000286, 0x0000037d, 0x00050085, 0x0000000c,
0x00000381, 0x0000037e, 0x000004f6, 0x00050085,
0x0000000c, 0x00000384, 0x00000381, 0x00000381,
0x0005008e, 0x0000000c, 0x00000385, 0x00000384,
0x00000068, 0x0006000c, 0x0000000c, 0x00000386,
0x00000001, 0x0000001d, 0x00000385, 0x00050051,
0x00000006, 0x0000038a, 0x00000386, 0x00000000,
0x0005008e, 0x0000000c, 0x0000038b, 0x0000031a,
0x0000038a, 0x00050051, 0x00000006, 0x0000038f,
0x00000386, 0x00000001, 0x0005008e, 0x0000000c,
0x00000390, 0x00000347, 0x0000038f, 0x00050081,
0x0000000c, 0x00000391, 0x0000038b, 0x00000390,
0x00050051, 0x00000006, 0x00000395, 0x00000386,
0x00000002, 0x0005008e, 0x0000000c, 0x00000396,
0x00000374, 0x00000395, 0x00050081, 0x0000000c,
0x00000397, 0x00000391, 0x00000396, 0x00050081,
0x00000006, 0x0000039c, 0x0000038a, 0x0000038f,
0x00050081, 0x00000006, 0x0000039f, 0x0000039c,
0x00000395, 0x00060050, 0x0000000c, 0x000003a0,
0x0000039f, 0x0000039f, 0x0000039f, 0x00050088,
0x0000000c, 0x000003a1, 0x00000397, 0x000003a0,
0x00060052, 0x0000000a, 0x000004e1, 0x00000249,
0x000001bf, 0x00000000, 0x00050085, 0x0000000a,
0x000003b6, 0x000001c8, 0x000004e1, 0x00050083,
0x0000000a, 0x000003b7, 0x000003b6, 0x000000fc,
0x0006000c, 0x0000000a, 0x000003b9, 0x00000001,
0x00000008, 0x000003b7, 0x00050081, 0x0000000a,
0x000003ba, 0x000003b9, 0x00000102, 0x00050088,
0x0000000a, 0x000003bc, 0x000003ba, 0x000004e1,
0x0006000c, 0x0000000a, 0x000003be, 0x00000001,
0x0000000a, 0x000003b7, 0x00050051, 0x00000006,
0x000003c0, 0x000003be, 0x00000000, 0x00050081,
0x00000006, 0x000003c1, 0x000003c0, 0x00000049,
0x00050083, 0x00000006, 0x000003c6, 0x000003c0,
0x00000049, 0x00050083, 0x00000006, 0x000003c9,
0x000003c0, 0x00000116, 0x00070050, 0x00000037,
0x000003ca, 0x000003c1, 0x000003c0, 0x000003c6,
0x000003c9, 0x0005008e, 0x00000037, 0x000003cb,
0x000003ca, 0x0000010b, 0x0006000c, 0x00000037,
0x000003cd, 0x00000001, 0x00000004, 0x000003cb,
0x0007000c, 0x00000037, 0x000003cf, 0x00000001,
0x00000028, 0x000003cd, 0x000004f4, 0x0006000c,
0x00000037, 0x000003d1, 0x00000001, 0x0000000d,
0x000003cf, 0x0005008e, 0x00000037, 0x000003d2,
0x000003d1, 0x00000116, 0x00050085, 0x00000037,
0x000003d5, 0x000003cf, 0x000004fa, 0x0006000c,
0x00000037, 0x000003d6, 0x00000001, 0x0000000d,
0x000003d5, 0x00050085, 0x00000037, 0x000003d7,
0x000003d2, 0x000003d6, 0x00050085, 0x00000037,
0x000003da, 0x000003cf, 0x000003cf, 0x00050088,
0x00000037, 0x000003db, 0x000003d7, 0x000003da,
0x00050094, 0x00000006, 0x000003dd, 0x000003db,
0x0000012c, 0x00070050, 0x00000037, 0x000003df,
0x000003dd, 0x000003dd, 0x000003dd, 0x000003dd,
0x00050088, 0x00000037, 0x000003e0, 0x000003db,
0x000003df, 0x00050057, 0x00000037, 0x000003e3,
0x0000028a, 0x000003bc, 0x0008004f, 0x0000000c,
0x000003e4, 0x000003e3, 0x000003e3, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000003e5, 0x00000001, 0x0000001a, 0x000003e4,
0x0000003b, 0x00050051, 0x00000006, 0x000003e6,
0x000003e5, 0x00000000, 0x00050051, 0x00000006,
0x000003e7, 0x000003e5, 0x00000001, 0x00050051,
0x00000006, 0x000003e8, 0x000003e5, 0x00000002,
0x00050081, 0x0000000a, 0x000003ed, 0x000003bc,
0x0000024b, 0x00050057, 0x00000037, 0x000003ee,
0x0000028a, 0x000003ed, 0x0008004f, 0x0000000c,
0x000003ef, 0x000003ee, 0x000003ee, 0x00000000,
0x00000001, 0x00000002, 0x0007000c, 0x0000000c,
0x000003f0, 0x00000001, 0x0000001a, 0x000003ef,
0x0000003b, 0x00050051, 0x00000006, 0x000003f1,
0x000003f0, 0x00000000, 0x00050051, 0x00000006,
0x000003f2, 0x000003f0, 0x00000001, 0x00050051,
0x00000006, 0x000003f3, 0x000003f0, 0x00000002,
0x00070050, 0x00000037, 0x00000407, 0x000003e6,
0x000003e7, 0x000003e8, 0x00000049, 0x00070050,
0x00000037, 0x00000409, 0x000003f1, 0x000003f2,
0x000003f3, 0x00000049, 0x00070050, 0x00000149,
0x0000040b, 0x00000407, 0x00000407, 0x00000409,
0x00000409, 0x00050091, 0x00000037, 0x0000040d,
0x0000040b, 0x000003e0, 0x0008004f, 0x0000000c,
0x0000040e, 0x0000040d, 0x0000040d, 0x00000000,
0x00000001, 0x00000002, 0x00050085, 0x00000006,
0x0000041b, 0x00000249, 0x000001dc, 0x00060052,
0x0000000a, 0x000004e9, 0x0000041b, 0x000001bf,
0x00000000, 0x00050088, 0x00000006, 0x0000041f,
0x000004fb, 0x00000249, 0x00050050, 0x0000000a,
0x00000420, 0x0000041f, 0x0000004a, 0x00050085,
0x0000000a, 0x00000423, 0x000001c8, 0x000004e9,
0x00050083, 0x0000000a, 0x00000424, 0x00000423,
0x000000fc, 0x0006000c, 0x0000000a, 0x00000426,
0x00000001, 0x00000008, 0x00000424, 0x00050081,
0x0000000a, 0x00000427, 0x00000426, 0x00000102,
0x00050088, 0x0000000a, 0x00000429, 0x00000427,
0x000004e9, 0x0006000c, 0x0000000a, 0x0000042b,
0x00000001, 0x0000000a, 0x00000424, 0x00050051,
0x00000006, 0x0000042d, 0x0000042b, 0x00000000,
0x00050081, 0x00000006, 0x0000042e, 0x0000042d,
0x00000049, 0x00050083, 0x00000006, 0x00000433,
0x0000042d, 0x00000049, 0x00050083, 0x00000006,
0x00000436, 0x0000042d, 0x00000116, 0x00070050,
0x00000037, 0x00000437, 0x0000042e, 0x0000042d,
0x00000433, 0x00000436, 0x0005008e, 0x00000037,
0x00000438, 0x00000437, 0x0000010b, 0x0006000c,
0x00000037, 0x0000043a, 0x00000001, 0x00000004,
0x00000438, 0x0007000c, 0x00000037, 0x0000043c,
0x00000001, 0x00000028, 0x0000043a, 0x000004f4,
0x0006000c, 0x00000037, 0x0000043e, 0x00000001,
0x0000000d, 0x0000043c, 0x0005008e, 0x00000037,
0x0000043f, 0x0000043e, 0x00000116, 0x00050085,
0x00000037, 0x00000442, 0x0000043c, 0x000004fa,
0x0006000c, 0x00000037, 0x00000443, 0x00000001,
0x0000000d, 0x00000442, 0x00050085, 0x00000037,
0x00000444, 0x0000043f, 0x00000443, 0x00050085,
0x00000037, 0x00000447, 0x0000043c, 0x0000043c,
0x00050088, 0x00000037, 0x00000448, 0x00000444,
0x00000447, 0x00050094, 0x00000006, 0x0000044a,
0x00000448, 0x0000012c, 0x00070050, 0x00000037,
0x0000044c, 0x0000044a, 0x0000044a, 0x0000044a,
0x0000044a, 0x00050088, 0x00000037, 0x0000044d,
0x00000448, 0x0000044c, 0x00050057, 0x00000037,
0x00000450, 0x0000028a, 0x00000429, 0x0008004f,
0x0000000c, 0x00000451, 0x00000450, 0x00000450,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x00000452, 0x00000001, 0x0000001a,
0x00000451, 0x0000003b, 0x00050051, 0x00000006,
0x00000453, 0x00000452, 0x00000000, 0x00050051,
0x00000006, 0x00000454, 0x00000452, 0x00000001,
0x00050051, 0x00000006, 0x00000455, 0x00000452,
0x00000002, 0x00050081, 0x0000000a, 0x0000045a,
0x00000429, 0x00000420, 0x00050057, 0x00000037,
0x0000045b, 0x0000028a, 0x0000045a, 0x0008004f,
0x0000000c, 0x0000045c, 0x0000045b, 0x0000045b,
0x00000000, 0x00000001, 0x00000002, 0x0007000c,
0x0000000c, 0x0000045d, 0x00000001, 0x0000001a,
0x0000045c, 0x0000003b, 0x00050051, 0x00000006,
0x0000045e, 0x0000045d, 0x00000000, 0x00050051,
0x00000006, 0x0000045f, 0x0000045d, 0x00000001,
0x00050051, 0x00000006, 0x00000460, 0x0000045d,
0x00000002, 0x00070050, 0x00000037, 0x00000474,
0x00000453, 0x00000454, 0x00000455, 0x00000049,
0x00070050, 0x00000037, 0x00000476, 0x0000045e,
0x0000045f, 0x00000460, 0x00000049, 0x00070050,
0x00000149, 0x00000478, 0x00000474, 0x00000474,
0x00000476, 0x00000476, 0x00050091, 0x00000037,
0x0000047a, 0x00000478, 0x0000044d, 0x0008004f,
0x0000000c, 0x0000047b, 0x0000047a, 0x0000047a,
0x00000000, 0x00000001, 0x00000002, 0x00050085,
0x0000000c, 0x000001e8, 0x0000047b, 0x0000040e,
0x0006000c, 0x0000000c, 0x000001e9, 0x00000001,
0x0000001f, 0x000001e8, 0x00050051, 0x00000006,
0x000001eb, 0x000001c8, 0x00000001, 0x00060041,
0x00000188, 0x000001ec, 0x00000187, 0x0000006c,
0x00000077, 0x0004003d, 0x00000006, 0x000001ed,
0x000001ec, 0x00050085, 0x00000006, 0x000001ee,
0x000001eb, 0x000001ed, 0x0006000c, 0x00000006,
0x000001ef, 0x00000001, 0x0000000a, 0x000001ee,
0x0007000c, 0x0000000c, 0x00000482, 0x00000001,
0x0000001a, 0x0000040e, 0x0000016a, 0x0008000c,
0x0000000c, 0x00000483, 0x00000001, 0x0000002e,
0x00000165, 0x00000167, 0x00000482, 0x00050088,
0x0000000c, 0x00000486, 0x000004f6, 0x00000483,
0x0005008e, 0x0000000c, 0x00000488, 0x00000486,
0x000000a1, 0x0005008e, 0x0000000c, 0x0000048b,
0x00000486, 0x000001ef, 0x00050083, 0x0000000c,
0x0000048d, 0x0000048b, 0x00000488, 0x0006000c,
0x0000000c, 0x0000048e, 0x00000001, 0x00000004,
0x0000048d, 0x00050083, 0x0000000c, 0x00000490,
0x000004f7, 0x0000048e, 0x0008000c, 0x0000000c,
0x00000493, 0x00000001, 0x00000031, 0x000004f8,
0x000004f7, 0x00000490, 0x00050085, 0x0000000c,
0x00000495, 0x00000493, 0x00000488, 0x00050085,
0x0000000c, 0x000001f5, 0x000001e9, 0x00000495,
0x00050083, 0x0000000c, 0x000001f8, 0x000003a1,
0x000001f5, 0x0008000c, 0x0000000c, 0x000001fb,
0x00000001, 0x0000002b, 0x000001f8, 0x000004f8,
0x000004f7, 0x00050085, 0x0000000c, 0x000001fe,
0x000001fb, 0x000001fb, 0x0005008e, 0x0000000c,
0x00000200, 0x000001fe, 0x000001ff, 0x00050081,
0x0000000c, 0x00000202, 0x000001f5, 0x00000200,
0x00050041, 0x00000206, 0x00000207, 0x000001ba,
0x00000070, 0x0004003d, 0x00000006, 0x00000208,
0x00000207, 0x000300f7, 0x000004b3, 0x00000000,
0x000300fb, 0x00000070, 0x0000049b, 0x000200f8,
0x0000049b, 0x00060041, 0x00000188, 0x0000049d,
0x00000187, 0x00000074, 0x00000070, 0x0004003d,
0x00000006, 0x0000049e, 0x0000049d, 0x00050085,
0x00000006, 0x0000049f, 0x00000208, 0x0000049e,
0x0006000c, 0x00000006, 0x000004a7, 0x00000001,
0x00000008, 0x0000049f, 0x0005008d, 0x00000006,
0x000004a8, 0x000004a7, 0x00000116, 0x000500b4,
0x00000196, 0x000004aa, 0x000004a8, 0x0000004a,
0x000300f7, 0x000004b2, 0x00000000, 0x000400fa,
0x000004aa, 0x000004ab, 0x000004ac, 0x000200f8,
0x000004ac, 0x000500b4, 0x00000196, 0x000004ae,
0x000004a8, 0x00000049, 0x000300f7, 0x000004b1,
0x00000000, 0x000400fa, 0x000004ae, 0x000004af,
0x000004b0, 0x000200f8, 0x000004b0, 0x000200f9,
0x000004b3, 0x000200f8, 0x000004af, 0x000200f9,
0x000004b3, 0x000200f8, 0x000004b1, 0x000100ff,
0x000200f8, 0x000004ab, 0x000200f9, 0x000004b3,
0x000200f8, 0x000004b2, 0x000100ff, 0x000200f8,
0x000004b3, 0x000900f5, 0x0000000c, 0x000004f1,
0x0000019a, 0x000004ab, 0x000001a1, 0x000004af,
0x000001a4, 0x000004b0, 0x00050085, 0x0000000c,
0x0000020a, 0x00000202, 0x000004f1, 0x0005008e,
0x0000000c, 0x0000020b, 0x0000020a, 0x00000116,
0x0008000c, 0x0000000c, 0x0000020e, 0x00000001,
0x0000002e, 0x00000202, 0x0000020b, 0x000004f9,
0x0005008e, 0x0000000c, 0x00000211, 0x000001fb,
0x00000210, 0x00050081, 0x0000000c, 0x00000213,
0x0000020e, 0x00000211, 0x0005008e, 0x0000000c,
0x00000215, 0x00000213, 0x00000166, 0x0007000c,
0x0000000c, 0x00000218, 0x00000001, 0x0000001a,
0x00000215, 0x00000217, 0x00050051, 0x00000006,
0x0000021c, 0x00000218, 0x00000000, 0x00050051,
0x00000006, 0x0000021d, 0x00000218, 0x00000001,
0x00050051, 0x00000006, 0x0000021e, 0x00000218,
0x00000002, 0x00070050, 0x00000037, 0x0000021f,
0x0000021c, 0x0000021d, 0x0000021e, 0x00000049,
0x0003003e, 0x0000021a, 0x0000021f, 0x000100fd,
0x00010038};
+1 -3
View File
@@ -28,7 +28,7 @@ std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
}
std::vector<const char *> extensionNames;
for (int i = 0; i < extensionCount; ++i)
for (unsigned int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}
@@ -56,12 +56,10 @@ bool SDL_WSIPlatform::alive(Vulkan::WSI &wsi)
void SDL_WSIPlatform::poll_input()
{
SDL_PumpEvents();
}
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
{
SDL_PumpEvents();
}
void SDL_WSIPlatform::set_window(SDL_Window *_window)
+2
View File
@@ -0,0 +1,2 @@
[toolchain]
channel = "1.92.0"
+189
View File
@@ -0,0 +1,189 @@
use crate::device;
use crate::ui;
#[derive(serde::Serialize, serde::Deserialize, Copy, Clone)]
pub struct DecodedCheat {
code_type: u8,
address: u32,
data: u16,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cheats {
pub cheats: Vec<Vec<DecodedCheat>>,
pub boot: bool,
pub enabled: bool,
}
pub fn init(
device: &mut device::Device,
cheat_settings: std::collections::HashMap<String, Option<String>>,
) {
let cheats =
serde_json::from_slice::<ui::cheats::Cheats>(include_bytes!("../data/cheats.json"))
.unwrap()
.get(&ui::storage::get_game_crc(&device.cart.rom))
.cloned()
.unwrap_or_default();
let re = regex::Regex::new(r"\?+").unwrap();
for cheat_setting in cheat_settings.iter() {
if let Some(cheat_data) = cheats.get(cheat_setting.0) {
let mut option_value = None;
if let Some(option) = cheat_setting.1 {
if let Some(found_option_value) = cheat_data
.options
.as_ref()
.cloned()
.unwrap_or_default()
.get(option)
{
option_value = Some(found_option_value.to_string());
} else {
panic!("Cheat option: {option} not found");
}
}
println!(
"Cheat: {} Option: {}",
cheat_setting.0,
cheat_setting.1.clone().unwrap_or("none".to_string())
);
let mut decoded_cheat: Vec<DecodedCheat> = vec![];
for code in cheat_data.data.iter() {
let mut result = code.clone();
if let Some(option_value) = option_value.as_ref() {
result = re.replace_all(code, option_value).into_owned();
}
let mut split = result.split_whitespace();
let first_part = u32::from_str_radix(split.next().unwrap(), 16).unwrap();
decoded_cheat.push(DecodedCheat {
code_type: (first_part >> 24) as u8,
address: first_part & 0x00FFFFFF,
data: u16::from_str_radix(split.next().unwrap(), 16).unwrap(),
});
}
device.cheats.cheats.push(decoded_cheat);
} else {
println!("Could not find cheat: {}", cheat_setting.0);
}
}
if !device.cheats.cheats.is_empty() {
device.cheats.enabled = true;
ui::video::onscreen_message(&device.ui, "Cheats enabled");
}
}
fn write_byte(device: &mut device::Device, cheat_line: &DecodedCheat) {
*device
.rdram
.mem
.get_mut(cheat_line.address as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = cheat_line.data as u8;
}
fn write_half(device: &mut device::Device, cheat_line: &DecodedCheat) {
*device
.rdram
.mem
.get_mut(cheat_line.address as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = (cheat_line.data >> 8) as u8;
*device
.rdram
.mem
.get_mut((cheat_line.address + 1) as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = cheat_line.data as u8;
}
fn equal_byte(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool {
let current_value = *device
.rdram
.mem
.get(cheat_line.address as usize ^ device.byte_swap)
.unwrap_or(&0);
current_value == (cheat_line.data as u8)
}
fn equal_half(device: &mut device::Device, cheat_line: &DecodedCheat) -> bool {
let current_value1 = *device
.rdram
.mem
.get(cheat_line.address as usize ^ device.byte_swap)
.unwrap_or(&0);
let current_value2 = *device
.rdram
.mem
.get((cheat_line.address + 1) as usize ^ device.byte_swap)
.unwrap_or(&0);
current_value1 == (cheat_line.data >> 8) as u8 && current_value2 == cheat_line.data as u8
}
pub fn execute_cheats(device: &mut device::Device, cheats: Vec<Vec<DecodedCheat>>) {
for cheat in cheats.iter() {
let mut valid = true;
let mut cheat_iter = cheat.iter().peekable();
while let Some(cheat_line) = cheat_iter.next() {
match cheat_line.code_type {
0x50 => {
if valid {
let mut expanded_cheat: Vec<DecodedCheat> = vec![];
let compressed_cheat = *cheat_iter.peek().unwrap();
let count = (cheat_line.address & 0xFF00) >> 8;
let offset = cheat_line.address & 0xFF;
for i in 0..count {
let line = DecodedCheat {
code_type: compressed_cheat.code_type,
address: compressed_cheat.address + (i * offset),
data: compressed_cheat.data
+ (i as i16 * cheat_line.data as i16) as u16,
};
expanded_cheat.push(line);
}
execute_cheats(device, [expanded_cheat].to_vec());
}
valid = false; // skip the next line
}
0x80 | 0xA0 => {
if valid {
write_byte(device, cheat_line);
}
valid = true;
}
0x81 | 0xA1 => {
if valid {
write_half(device, cheat_line);
}
valid = true;
}
0xF0 => {
if device.cheats.boot && valid {
write_byte(device, cheat_line);
valid = true;
}
}
0xF1 => {
if device.cheats.boot && valid {
write_half(device, cheat_line);
valid = true;
}
}
0xD0 => {
valid = equal_byte(device, cheat_line);
}
0xD1 => {
valid = equal_half(device, cheat_line);
}
0xD2 => {
valid = !equal_byte(device, cheat_line);
}
0xD3 => {
valid = !equal_half(device, cheat_line);
}
0x88 | 0x89 | 0xD8 | 0xD9 | 0xDA | 0xDB => { /* GS button not emulated */ }
_ => panic!("Unknown cheat code type: {:X}", cheat_line.code_type),
}
}
}
device.cheats.boot = false;
}
+1 -1
View File
@@ -1,4 +1,4 @@
#include "src/compat/sse2neon.h"
#include "src/compat/sse2neon/sse2neon.h"
// Static wrappers
Submodule src/compat/sse2neon added at 3cf69760cc
File diff suppressed because it is too large Load Diff
+131 -60
View File
@@ -10,16 +10,14 @@ include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use crate::netplay;
use crate::ui;
use std::collections::HashMap;
use std::fs;
use std::io::Read;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use crate::{cheats, 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;
@@ -42,54 +40,74 @@ 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 fn run_game(
rom_contents: Vec<u8>,
data_dir: std::path::PathBuf,
device: &mut Device,
fullscreen: bool,
) {
cart_rom::init(device, rom_contents); // cart needs to come before rdram
pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, game_settings: ui::gui::GameSettings) {
device.ui.video.fullscreen = game_settings.fullscreen;
device.cpu.overclock = game_settings.overclock;
if game_settings.disable_expansion_pak {
device.rdram.size = 0x400000;
}
init_rng(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::video::init(device);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::input::init(&mut device.ui);
mi::init(device);
pif::init(device);
if device.ui.config.input.emulate_vru {
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
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::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
cart_rom::load_rom_save(device);
cheats::init(device, game_settings.cheats);
cpu::run(device);
ui::storage::write_saves(device);
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);
ui::video::close(&device.ui);
}
fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
fn set_rng() -> rand_chacha::ChaCha8Rng {
rand_chacha::ChaCha8Rng::from_os_rng()
}
fn init_rng(device: &mut Device) {
let mut rng_seed = set_rng().next_u64();
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rng(netplay, rng_seed);
} else {
rng_seed = netplay::receive_rng(netplay);
}
}
device.rng = rand_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
}
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
let test = u32::from_be_bytes(contents[0..4].try_into().unwrap());
if test == 0x80371240 {
// z64
contents
Some(contents)
} else if test == 0x37804012 {
// v64
let mut data: Vec<u8> = vec![0; contents.len()];
@@ -97,7 +115,7 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let temp = u16::from_ne_bytes(contents[i..i + 2].try_into().unwrap());
data[i..i + 2].copy_from_slice(&temp.to_be_bytes());
}
return data;
Some(data)
} else if test == 0x40123780 {
// n64
let mut data: Vec<u8> = vec![0; contents.len()];
@@ -105,14 +123,13 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
let temp = u32::from_ne_bytes(contents[i..i + 4].try_into().unwrap());
data[i..i + 4].copy_from_slice(&temp.to_be_bytes());
}
return data;
Some(data)
} else {
let data: Vec<u8> = vec![];
data
None
}
}
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
@@ -133,12 +150,12 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
}
} else if file_path.extension().unwrap().eq_ignore_ascii_case("7z") {
let mut archive =
sevenz_rust::SevenZReader::open(file_path, sevenz_rust::Password::empty()).unwrap();
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
let mut found = false;
archive
.for_each_entries(
&mut |entry: &sevenz_rust::SevenZArchiveEntry, reader: &mut dyn std::io::Read| {
&mut |entry: &sevenz_rust2::ArchiveEntry, reader: &mut dyn std::io::Read| {
let name = entry.name().to_ascii_lowercase();
if !found
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
@@ -159,32 +176,49 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
contents = fs::read(file_path).expect("Should have been able to read the file");
}
swap_rom(contents)
if contents.is_empty() {
None
} else {
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,
pub byte_swap: usize,
pub save_state: bool,
pub load_state: bool,
pub cpu: cpu::Cpu,
pif: pif::Pif,
cart: cart_rom::Cart,
memory: memory::Memory,
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 ai: ai::Ai,
pub si: si::Si,
pub ri: ri::Ri,
#[serde(skip, default = "set_rng")]
pub rng: rand_chacha::ChaCha8Rng,
pub vru: controller::vru::Vru,
pub vru_window: controller::vru::VruWindow,
pub transferpaks: [controller::transferpak::TransferPak; 4],
pub cheats: cheats::Cheats,
}
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
@@ -194,13 +228,17 @@ impl Device {
}
Device {
netplay: None,
ui: ui::Ui::new(config_dir),
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![
@@ -266,6 +304,7 @@ impl Device {
pc: 0xBFC00000,
pc_phys: 0,
llbit: false,
overclock: false,
lo: 0,
hi: 0,
running: false,
@@ -275,10 +314,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],
@@ -293,18 +331,33 @@ impl Device {
change_pak: controller::PakType::None,
}; 5],
},
cart: cart_rom::Cart {
cart: cart::Cart {
rom: Vec::new(),
is_viewer_buffer: [0; 0xFFFF],
is_viewer_buffer: vec![0; 0xFFFF],
pal: false,
latch: 0,
cic_seed: 0,
cic_type: cart_rom::CicType::CicNus6102,
rdram_size_offset: 0,
rtc_timestamp: 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: vec![0; 8192],
writeback_sector: vec![0; 256],
usb_buffer: vec![],
},
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],
fast_read: [unmapped::read_mem_fast; 0x2000],
memory_map_read: [unmapped::read_mem; 0x2000],
memory_map_write: [unmapped::write_mem; 0x2000],
icache: [cache::ICache {
@@ -341,6 +394,7 @@ impl Device {
pc: 0,
},
broken: false,
running: false,
halted: false,
sync_point: false,
cycle_counter: 0,
@@ -372,6 +426,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,
@@ -383,6 +439,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],
@@ -393,6 +450,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,
@@ -414,15 +472,19 @@ impl Device {
field: 0,
delay: 0,
count_per_scanline: 0,
enable_speed_limiter: true,
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,
},
vru: controller::vru::Vru {
status: 0,
@@ -433,9 +495,18 @@ impl Device {
words: Vec::new(),
talking: false,
word_mappings: HashMap::new(),
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
rng: set_rng(),
transferpaks: [
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
],
cheats: cheats::Cheats {
cheats: vec![],
boot: true,
enabled: false,
},
}
}
+17 -15
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,
}
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
fn get_remaining_dma_length(device: &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;
}
@@ -64,11 +65,10 @@ fn do_dma(device: &mut device::Device) {
/* schedule end of dma event */
device::events::create_event(
device,
device::events::EventType::AI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.ai.fifo[0].duration,
dma_event,
device::events::EVENT_TYPE_AI,
device.ai.fifo[0].duration,
);
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_AI);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_AI);
}
fn fifo_push(device: &mut device::Device) {
@@ -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,9 +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::close(&mut device.ui);
ui::audio::init(&mut device.ui, frequency)
device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::close_game_audio(&mut device.ui);
ui::audio::init_game_audio(&mut device.ui, device.ai.freq)
}
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
}
@@ -153,7 +155,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
}
fn dma_event(device: &mut device::Device) {
pub fn dma_event(device: &mut device::Device) {
if device.ai.last_read != 0 {
let diff = device.ai.fifo[0].length - device.ai.last_read;
ui::audio::play_audio(
+40 -163
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,
@@ -29,54 +30,14 @@ pub fn icache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.icache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.icache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.icache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.icache[line_index].words[3],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device.memory.icache[line_index].words[4],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device.memory.icache[line_index].words[5],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device.memory.icache[line_index].words[6],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device.memory.icache[line_index].words[7],
0xFFFFFFFF,
);
for i in 0..8 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.icache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
}
pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@@ -87,71 +48,17 @@ pub fn icache_fill(device: &mut device::Device, line_index: usize, phys_address:
let cache_address = ((device.memory.icache[line_index].tag
| (device.memory.icache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.icache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[4] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x10,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[5] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x14,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[6] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x18,
device::memory::AccessSize::Icache,
);
device.memory.icache[line_index].words[7] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x1C,
device::memory::AccessSize::Icache,
);
for i in 0..8 {
device.memory.icache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Icache,
);
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.memory.icache[line_index].instruction[i as usize] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i as usize]);
}
}
pub fn icache_fetch(device: &mut device::Device, phys_address: u64) {
@@ -179,30 +86,15 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address,
device.memory.dcache[line_index].words[0],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device.memory.dcache[line_index].words[1],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device.memory.dcache[line_index].words[2],
0xFFFFFFFF,
);
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device.memory.dcache[line_index].words[3],
0xFFFFFFFF,
);
for i in 0..4 {
device.memory.memory_map_write[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device.memory.dcache[line_index].words[i as usize],
0xFFFFFFFF,
);
}
}
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
@@ -215,30 +107,15 @@ fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64
let cache_address = ((device.memory.dcache[line_index].tag
| (device.memory.dcache[line_index].index) as u32)
& 0x1ffffffc) as u64;
device.memory.dcache[line_index].words[0] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[1] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x4,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[2] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0x8,
device::memory::AccessSize::Dcache,
);
device.memory.dcache[line_index].words[3] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | 0xC,
device::memory::AccessSize::Dcache,
);
for i in 0..4 {
device.memory.dcache[line_index].words[i as usize] = device.memory.memory_map_read
[(cache_address >> 16) as usize](
device,
cache_address | (i * 4),
device::memory::AccessSize::Dcache,
);
}
}
pub fn dcache_read(device: &mut device::Device, phys_address: u64) -> u32 {
+39 -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;
@@ -18,6 +21,7 @@ const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
const EEPROM_BLOCK_SIZE: usize = 8;
pub const EEPROM_MAX_SIZE: usize = 0x800;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct AfRtc {
pub control: u16,
}
@@ -27,6 +31,20 @@ fn byte2bcd(mut n: u32) -> u8 {
(((n / 10) << 4) | (n % 10)) as u8
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
pub is_viewer_buffer: Vec<u8>,
pub pal: bool,
pub latch: u32,
pub cic_seed: u8,
pub rtc: AfRtc,
pub sc64: sc64::Sc64,
pub flashram: sram::Flashram,
pub rtc_timestamp: i64,
}
pub fn process(device: &mut device::Device, channel: usize) {
let cmd = device.pif.ram[device.pif.channels[channel].tx_buf.unwrap()];
@@ -35,12 +53,14 @@ pub fn process(device: &mut device::Device, channel: usize) {
let eeprom_type;
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom16k)
{
eeprom_type = JDT_EEPROM_16K;
} else if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
@@ -95,9 +115,15 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
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 format_eeprom(device: &mut device::Device) {
if device.ui.storage.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
device
.ui
.storage
.saves
.eeprom
.data
.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
@@ -106,8 +132,9 @@ fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
format_eeprom(device);
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]
.copy_from_slice(&device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]);
device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE].copy_from_slice(
&device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
);
}
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
@@ -115,16 +142,18 @@ fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize,
format_eeprom(device);
device.ui.saves.eeprom.0[address..address + EEPROM_BLOCK_SIZE]
device.ui.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE]
.copy_from_slice(&device.pif.ram[offset..offset + EEPROM_BLOCK_SIZE]);
device.pif.ram[status] = 0x00;
device.ui.saves.eeprom.1 = true
device.ui.storage.saves.eeprom.written = true
}
fn time2data(device: &mut device::Device, offset: usize) {
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
let timestamp =
device.cart.rtc_timestamp + (device.vi.frame_time * device.vi.vi_counter as f64) as i64;
let now = chrono::DateTime::from_timestamp(timestamp, 0).unwrap();
device.pif.ram[offset] = byte2bcd(now.second());
device.pif.ram[offset + 1] = byte2bcd(now.minute());
+203
View File
@@ -0,0 +1,203 @@
use crate::device;
use crate::netplay;
use sha2::{Digest, Sha256};
pub const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = [0; 4];
for i in 0..4 {
if let Some(value) = device
.ui
.storage
.saves
.romsave
.data
.get(&(address as u32 + i))
{
data[i as usize] = *value;
} else {
data[i as usize] = *device.cart.rom.get(address + i as usize).unwrap_or(&0);
}
}
u32::from_be_bytes(data)
}
pub fn read_mem_fast(
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & CART_MASK;
read_cart_word(device, masked_address)
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let cycles = device::pi::calculate_cycles(device, 1, 4);
device::cop0::add_cycles(device, cycles);
// well known cart ROM oddity, if a read is perfomed while PI_STATUS_IO_BUSY is set, the latched value is returned rather than the data at the specified address
if device.pi.regs[device::pi::PI_STATUS_REG as usize] & device::pi::PI_STATUS_IO_BUSY != 0 {
device.cart.latch
} else {
let masked_address = address as usize & CART_MASK;
read_cart_word(device, masked_address)
}
}
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
.storage
.saves
.romsave
.data
.insert((masked_address + i) as u32, *item);
}
device.ui.storage.saves.romsave.written = true;
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
let cycles = device::pi::calculate_cycles(device, 1, 4);
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
}
pub fn dma_read(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
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 {
device.ui.storage.saves.romsave.data.insert(
cart_addr + i,
*device
.rdram
.mem
.get((dram_addr + i) as usize ^ device.byte_swap)
.unwrap_or(&0),
);
}
device.ui.storage.saves.romsave.written = true;
}
device::pi::calculate_cycles(device, 1, length)
}
// cart is big endian, rdram is native endian
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 &= CART_MASK as u32;
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length {
if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *value;
} else {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *device.cart.rom.get(j as usize).unwrap_or(&0);
}
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
device.cart.rtc_timestamp = now.naive_local().and_utc().timestamp();
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
} else {
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
}
}
device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 1;
device.cart.rom = rom_file;
device.cart.pal = is_system_pal(device.cart.rom[0x3E]);
set_cic(device);
device.ui.game_hash = calculate_hash(&device.cart.rom);
device.ui.game_id = String::from_utf8(device.cart.rom[0x3B..0x3E].to_vec()).unwrap();
if device.ui.game_id.contains('\0') {
device.ui.game_id = String::from("UNK");
}
}
fn is_system_pal(country: u8) -> bool {
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 {
return true;
}
}
false
}
fn set_cic(device: &mut device::Device) {
let hash = calculate_hash(&device.cart.rom[0x40..0x1000]);
match hash.as_str() {
"B99F06C4802C2377E31E388435955EF3E99C618A6D55D24699D828EB1075F1EB" => {
device.cart.cic_seed = 0x3F; // CicNus6101
}
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
device.cart.cic_seed = 0x3F; // CicNus6102
}
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
device.cart.cic_seed = 0x78; // CicNus6103
}
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
device.cart.cic_seed = 0x91; // CicNus6105
}
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
device.cart.cic_seed = 0x85; // CicNus6106
}
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
device.cart.cic_seed = 0xdd; // CicNus5167
device.cart.sc64.cfg[device::cart::sc64::SC64_ROM_WRITE_ENABLE as usize] = 1;
}
_ => {
device.cart.cic_seed = 0x3F; // CicNus6102
//println!("unknown IPL3 {}", hash)
}
}
}
pub fn calculate_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:X}", hasher.finalize())
}
+482
View File
@@ -0,0 +1,482 @@
use crate::device;
use crate::ui;
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_BOOTLOADER_SWITCH: u32 = 0;
pub const SC64_ROM_WRITE_ENABLE: u32 = 1;
pub const SC64_SAVE_TYPE: u32 = 6;
pub const SC64_CFG_COUNT: u32 = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
const SC64_EEPROM_MASK: usize = 0xFFF;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Sc64 {
pub buffer: Vec<u8>,
pub regs: [u32; SC64_REGS_COUNT as usize],
pub regs_locked: bool,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub sector: u32,
pub writeback_sector: Vec<u32>,
pub usb_buffer: Vec<u8>,
}
fn format_sdcard(device: &mut device::Device) {
if device.ui.storage.saves.sdcard.data.is_empty() {
device.ui.storage.saves.sdcard.data.resize(SDCARD_SIZE, 0);
let buf = std::io::Cursor::new(&mut device.ui.storage.saves.sdcard.data);
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 {reg} address {address:#x}"),
}
}
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() {
'V' => {
// get version
device.cart.sc64.regs[SC64_DATA0_REG as usize] = (2 << 16) | 20;
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 2;
}
'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
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
// if save type is being written, we are probably booting a game using the flash cart menu
// we shouldn't write saves to disk in this case (they are written to the SD card)
device.ui.storage.saves.write_to_disk = false;
device.ui.storage.save_type =
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
0 => {
vec![]
}
1 => {
vec![ui::storage::SaveTypes::Eeprom4k]
}
2 => {
vec![ui::storage::SaveTypes::Eeprom16k]
}
3 => {
vec![ui::storage::SaveTypes::Sram]
}
4 => {
vec![ui::storage::SaveTypes::Flash]
}
_ => {
panic!(
"unknown sc64 save type: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
)
}
}
}
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 {
let data = u32::from_be_bytes(
device
.ui
.storage
.saves
.sdcard
.data
.get((offset + i)..(offset + i + 4))
.unwrap_or(&[0; 4])
.try_into()
.unwrap_or_default(),
);
device::memory::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
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 {
let data = device::memory::data_read(
device,
address + i as u64,
device::memory::AccessSize::Word,
false,
)
.to_be_bytes();
device
.ui
.storage
.saves
.sdcard
.data
.get_mut((offset + i)..(offset + i + 4))
.unwrap_or(&mut [0; 4])
.copy_from_slice(&data);
i += 4;
}
device.ui.storage.saves.sdcard.written = true;
}
'U' => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
}
'u' => {
// used to notify the game that there is data to read
if let Some(cart_rx) = device.ui.usb.cart_rx.as_mut() {
match cart_rx.try_recv() {
Ok(data) => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] =
data.data_type & 0xFF; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] =
data.data_size & 0xFFFFFF; // length
device.cart.sc64.usb_buffer = data.data; // store the data to be read
}
Err(err) => {
match err {
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
panic!("cart_rx lagged: {err}");
}
_ => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
}
}
}
}
} else {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
}
}
'M' => {
// Send data from from flashcart to USB
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length =
device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize & 0xFFFFFF;
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
let mut usb_buffer = vec![0; length];
let mut i = 0;
if address < device.rdram.size as u64 {
while i < length {
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
.rdram
.mem
.get((address as usize + i) ^ device.byte_swap)
.unwrap_or(&0);
i += 1;
}
} else if address >= device::memory::MM_CART_ROM as u64
&& address < device::memory::MM_PIF_MEM as u64
{
while i < length {
*usb_buffer.get_mut(i).unwrap_or(&mut 0) = *device
.ui
.storage
.saves
.romsave
.data
.get(
&(((address as usize + i)
& device::cart::rom::CART_MASK)
as u32),
)
.unwrap_or(
device
.cart
.rom
.get(
(address as usize + i)
& device::cart::rom::CART_MASK,
)
.unwrap_or(&0),
);
i += 1;
}
} else {
panic!("Unknown address {address:#x} for SC64 M command");
}
ui::usb::send_to_usb(
usb_tx,
ui::usb::UsbData {
data: usb_buffer,
data_type: device.cart.sc64.regs[SC64_DATA1_REG as usize] >> 24,
data_size: device.cart.sc64.regs[SC64_DATA1_REG as usize]
& 0xFFFFFF,
},
);
}
}
'm' => {
// Receive data from USB to flashcart
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length = device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize;
let mut i = 0;
if address < device.rdram.size as u64 {
while i < length {
*device
.rdram
.mem
.get_mut((address as usize + i) ^ device.byte_swap)
.unwrap_or(&mut 0) =
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0);
i += 1;
}
} else if address >= device::memory::MM_CART_ROM as u64
&& address < device::memory::MM_PIF_MEM as u64
{
while i < length {
device.ui.storage.saves.romsave.data.insert(
((address as usize + i) & device::cart::rom::CART_MASK) as u32,
*device.cart.sc64.usb_buffer.get(i).unwrap_or(&0),
);
i += 1;
}
} else {
panic!("Unknown address {address:#x} for SC64 m command");
}
}
'w' => {
// SD card writeback pending
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
}
'W' => {
let writeback_sectors_address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
for i in 0..256 {
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
device,
writeback_sectors_address + (i * 4) as u64,
device::memory::AccessSize::Word,
false,
);
}
}
_ => {
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 {
if address & 0x2000 != 0 {
device::cart::format_eeprom(device);
let masked_address = address as usize & SC64_EEPROM_MASK;
u32::from_be_bytes(
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
} else {
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) {
if address & 0x2000 != 0 {
device::cart::format_eeprom(device);
let masked_address = address as usize & SC64_EEPROM_MASK;
let mut data = u32::from_be_bytes(
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.storage.saves.eeprom.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.storage.saves.eeprom.written = true
} else {
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;
let buffer = if cart_addr & 0x2000 != 0 {
device::cart::format_eeprom(device);
cart_addr &= SC64_EEPROM_MASK as u32;
device.ui.storage.saves.eeprom.written = true;
&mut device.ui.storage.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
&mut device.cart.sc64.buffer
};
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length {
*buffer.get_mut(j as usize).unwrap_or(&mut 0) = *device
.rdram
.mem
.get(i as usize ^ device.byte_swap)
.unwrap_or(&0);
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;
let buffer = if cart_addr & 0x2000 != 0 {
device::cart::format_eeprom(device);
cart_addr &= SC64_EEPROM_MASK as u32;
&device.ui.storage.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
&device.cart.sc64.buffer
};
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *buffer.get(j as usize).unwrap_or(&0);
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
+133 -71
View File
@@ -1,5 +1,4 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
const SRAM_MASK: usize = 0xFFFF;
pub const SRAM_SIZE: usize = 0x8000;
@@ -9,7 +8,7 @@ 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,23 +18,31 @@ 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],
}
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)
if device.ui.storage.saves.sram.data.len() < SRAM_SIZE {
device.ui.storage.saves.sram.data.resize(SRAM_SIZE, 0xFF)
}
}
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)
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
device
.ui
.storage
.saves
.flash
.data
.resize(FLASHRAM_SIZE, 0xFF)
}
}
@@ -45,20 +52,21 @@ fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
format_sram(device);
u32::from_be_bytes(
device.ui.saves.sram.0[masked_address..masked_address + 4]
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
}
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
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;
0
} else {
/* other accesses are not implemented */
panic!("unknown flashram read")
@@ -73,7 +81,12 @@ pub fn read_mem(
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::cop0::add_cycles(device, cycles);
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
read_mem_sram(device, address)
} else {
read_mem_flash(device, address)
@@ -86,20 +99,21 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
format_sram(device);
let mut data = u32::from_be_bytes(
device.ui.saves.sram.0[masked_address..masked_address + 4]
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
.unwrap(),
);
device::memory::masked_write_32(&mut data, value, mask);
device.ui.saves.sram.0[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.saves.sram.1 = true
device.ui.storage.saves.sram.written = true
}
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
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);
@@ -111,7 +125,12 @@ fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask:
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
write_mem_sram(device, address, value, mask)
} else {
write_mem_flash(device, address, value, mask)
@@ -120,12 +139,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::events::create_event(
device,
device::events::EventType::PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
device::pi::dma_event,
);
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
}
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
@@ -136,13 +150,24 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
format_sram(device);
while i < dram_addr + length && i < device.rdram.size {
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
while i < dram_addr + length {
*device
.ui
.storage
.saves
.sram
.data
.get_mut(j as usize)
.unwrap_or(&mut 0) = *device
.rdram
.mem
.get(i as usize ^ device.byte_swap)
.unwrap_or(&0);
i += 1;
j += 1;
}
device.ui.saves.sram.1 = true
device.ui.storage.saves.sram.written = true
}
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
@@ -150,12 +175,15 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
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.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
device.cart.flashram.page_buf[i as usize] = *device
.rdram
.mem
.get((dram_addr + i) as usize ^ device.byte_swap)
.unwrap_or(&0);
}
} else {
/* other accesses are not implemented */
@@ -165,7 +193,12 @@ fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, l
// cart is big endian, rdram is native endian
pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
dma_read_sram(device, cart_addr, dram_addr, length)
} else {
dma_read_flash(device, cart_addr, dram_addr, length)
@@ -186,8 +219,19 @@ fn dma_write_sram(
format_sram(device);
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
while i < dram_addr + length {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *device
.ui
.storage
.saves
.sram
.data
.get(j as usize)
.unwrap_or(&0);
i += 1;
j += 1;
}
@@ -203,20 +247,30 @@ 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());
device
.rdram
.mem
.get_mut(dram_addr as usize..dram_addr as usize + 4)
.unwrap_or(&mut [0; 4])
.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 {
device
.rdram
.mem
.get_mut(dram_addr as usize..dram_addr as usize + 4)
.unwrap_or(&mut [0; 4])
.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;
@@ -227,8 +281,11 @@ fn dma_write_flash(
/* do actual DMA */
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] =
device.ui.saves.flash.0[(cart_addr + i) as usize];
*device
.rdram
.mem
.get_mut((dram_addr + i) as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = device.ui.storage.saves.flash.data[(cart_addr + i) as usize];
}
} else {
/* other accesses are not implemented */
@@ -238,7 +295,12 @@ fn dma_write_flash(
// cart is big endian, rdram is native endian
pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) -> u64 {
if device.ui.save_type.contains(&ui::storage::SaveTypes::Sram) {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Sram)
{
dma_write_sram(device, cart_addr, dram_addr, length)
} else {
dma_write_flash(device, cart_addr, dram_addr, length)
@@ -250,77 +312,77 @@ 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.storage.saves.flash.data[offset + i] = 0xFF;
}
device.ui.saves.flash.1 = true
} else if device.flashram.mode == FlashramMode::ChipErase {
device.ui.storage.saves.flash.written = true
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.saves.flash.0[i] = 0xFF;
device.ui.storage.saves.flash.data[i] = 0xFF;
}
device.ui.saves.flash.1 = true
device.ui.storage.saves.flash.written = true
} else {
panic!("Unexpected flash erase command")
}
/* 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.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.saves.flash.1 = true;
device.ui.storage.saves.flash.written = 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;
}
_ => {
-202
View File
@@ -1,202 +0,0 @@
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,
}
pub fn read_mem_fast(
device: &mut device::Device,
address: u64,
_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(),
)
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let cycles = device::pi::calculate_cycles(device, 1, 4);
device::cop0::add_cycles(device, cycles);
// well known cart ROM oddity, if a read is perfomed while PI_STATUS_IO_BUSY is set, the latched value is returned rather than the data at the specified address
if device.pi.regs[device::pi::PI_STATUS_REG as usize] & device::pi::PI_STATUS_IO_BUSY != 0 {
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(),
)
}
}
pub fn write_mem(device: &mut device::Device, _address: u64, value: u32, mask: u32) {
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
let cycles = device::pi::calculate_cycles(device, 1, 4);
device::events::create_event(
device,
device::events::EventType::PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
device::pi::dma_event,
);
}
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 &= 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];
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::pi::calculate_cycles(device, 1, length)
}
// cart is big endian, rdram is native endian
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 &= CART_MASK as u32;
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];
i += 1;
j += 1;
}
while i < dram_addr + length {
// DMAs that extend past the end of the ROM return 0's for the portion that extends past the ROM length
device.rdram.mem[i as usize ^ device.byte_swap] = 0;
i += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
device.cart.rom = rom_file;
set_system_region(device, device.cart.rom[0x3E]);
set_cic(device);
device.ui.game_hash = calculate_hash(&device.cart.rom);
device.ui.game_id = String::from_utf8(device.cart.rom[0x3B..0x3E].to_vec()).unwrap();
if device.ui.game_id.contains('\0') {
device.ui.game_id = String::from("UNK");
}
}
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()
}
}
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 {
device.cart.pal = true
}
}
}
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_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
}
"61E88238552C356C23D19409FE5570EE6910419586BC6FC740F638F761ADC46E" => {
device.cart.cic_type = CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
}
"BF3620D30817007091EBE9BDDD1B88C23B8A0052170B3309CDE5B6B4238E45E7" => {
device.cart.cic_type = CicType::CicNus6103;
device.cart.cic_seed = 0x78;
device.cart.rdram_size_offset = 0x318;
}
"04B7BC6717A9F0EB724CF927E74AD3876C381CBB280D841736FC5E55580B756B" => {
device.cart.cic_type = CicType::CicNus6105;
device.cart.cic_seed = 0x91;
device.cart.rdram_size_offset = 0x3F0;
}
"36ADC40148AF56F0D78CD505EB6A90117D1FD6F11C6309E52ED36BC4C6BA340E" => {
device.cart.cic_type = CicType::CicNus6106;
device.cart.cic_seed = 0x85;
device.cart.rdram_size_offset = 0x318;
}
"53C0088FB777870D0AF32F0251E964030E2E8B72E830C26042FD191169508C05" => {
device.cart.cic_type = CicType::CicNus5167;
device.cart.cic_seed = 0xdd;
device.cart.rdram_size_offset = 0x318;
}
_ => {
device.cart.cic_type = CicType::CicNus6102;
device.cart.cic_seed = 0x3F;
device.cart.rdram_size_offset = 0x318;
println!("unknown IPL3 {}", hash)
}
}
}
pub fn calculate_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:X}", hasher.finalize())
}
+64 -42
View File
@@ -1,9 +1,9 @@
use crate::device;
use crate::netplay;
use crate::ui;
use crate::{device, netplay, savestates, ui};
pub mod gbcart;
pub mod mempak;
pub mod rumble;
pub mod transferpak;
pub mod vru;
pub const JCMD_STATUS: u8 = 0x00;
@@ -21,16 +21,19 @@ 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, PartialEq)]
#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum PakType {
None = 0,
MemPak = 1,
RumblePak = 2,
TransferPak = 3,
}
#[derive(Copy, Clone)]
#[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,
}
@@ -53,31 +56,27 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
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);
let input = if let Some(netplay) = &mut device.netplay {
if netplay.player_number as usize == channel {
let local_input = ui::input::get(&device.ui, 0);
netplay::send_input(netplay, local_input);
}
netplay::get_input(device, channel)
} else {
ui::input::get(&device.ui, channel)
};
device.pif.ram[offset..offset + 4].copy_from_slice(&input.0.to_ne_bytes());
if input.1 {
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
if input.pak_change_pressed {
// pak change button pressed
if device::events::get_event(device, device::events::EventType::PakSwitch).is_none()
{
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::EventType::PakSwitch,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ (device.cpu.clock_rate), // 1 second
pak_switch_event,
device::events::EVENT_TYPE_PAK,
device.cpu.clock_rate, // 1 second
)
}
}
@@ -96,7 +95,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.channels[channel].rx_buf.unwrap(),
channel,
),
_ => println!("unknown controller command {}", cmd),
_ => println!("unknown controller command {cmd}"),
}
}
@@ -111,8 +110,8 @@ fn pak_read_block(
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
let handler = device.pif.channels[channel].pak_handler;
if handler.is_some() {
(handler.unwrap().read)(device, channel, address, data, PAK_CHUNK_SIZE);
if let Some(handler) = handler {
(handler.read)(device, channel, address, data, PAK_CHUNK_SIZE);
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
} else {
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
@@ -130,8 +129,8 @@ fn pak_write_block(
((device.pif.ram[addr_acrc] as u16) << 8) | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
let handler = device.pif.channels[channel].pak_handler;
if handler.is_some() {
(handler.unwrap().write)(device, channel, address, data, PAK_CHUNK_SIZE);
if let Some(handler) = handler {
(handler.write)(device, channel, address, data, PAK_CHUNK_SIZE);
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
} else {
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
@@ -158,31 +157,54 @@ fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
crc
}
fn pak_switch_event(device: &mut device::Device) {
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);
//stop rumble if it is on
if let Some(netplay) = &device.netplay {
if netplay.player_number as usize == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
}
} else {
device::ui::input::set_rumble(&device.ui, i, 0);
}
let handler = device::controller::PakHandler {
let new_pak_type = match channel.change_pak {
PakType::MemPak => PakType::RumblePak,
PakType::RumblePak => {
if device.transferpaks[i].cart.rom.is_empty() {
PakType::MemPak
} else {
PakType::TransferPak
}
}
PakType::TransferPak => PakType::MemPak,
_ => {
panic!("Invalid pak type");
}
};
if new_pak_type == PakType::MemPak {
channel.pak_handler = Some(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 {
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "MemPak selected");
} else if new_pak_type == PakType::RumblePak {
channel.pak_handler = Some(device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
channel.pak_handler = Some(handler);
ui::audio::play_pak_switch(&mut device.ui, PakType::RumblePak);
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "RumblePak selected");
} else if new_pak_type == PakType::TransferPak {
channel.pak_handler = Some(device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "TransferPak selected");
}
channel.change_pak = PakType::None;
}
+328
View File
@@ -0,0 +1,328 @@
use crate::device;
#[derive(Default, serde::Serialize, serde::Deserialize)]
pub struct GbCart {
pub enabled: bool,
#[serde(skip)]
pub rom: Vec<u8>,
pub ram: Vec<u8>,
pub cart_type: device::controller::gbcart::CartType,
pub ram_enabled: bool,
pub mbc1_mode: bool,
pub ram_bank: u16,
pub rom_bank: u32,
pub set_latch: bool,
pub latch_second: u8,
pub latch_minute: u8,
pub latch_hour: u8,
pub latch_day: u16,
}
#[derive(Default, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum CartType {
#[default]
None,
MBC1RamBatt,
MBC3RamBatt,
MBC3RamBattRtc,
MBC5RamBatt,
}
fn write_mbc1(
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
let value = pif_ram[data + size - 1];
if address < 0x2000 {
cart.ram_enabled = value & 0xf == 0xa;
} else if address < 0x4000 {
let bank = value & 0x1f;
cart.rom_bank = bank as u32;
if cart.rom_bank == 0 {
cart.rom_bank = 1;
}
} else if address < 0x6000 {
cart.ram_bank = (value & 0x3) as u16;
} else if address < 0x8000 {
cart.mbc1_mode = (value & 0x1) != 0;
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
}
if cart.mbc1_mode {
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
cart.ram[banked_address as usize..banked_address as usize + size]
.copy_from_slice(&pif_ram[data..data + size]);
} else {
let banked_address = address - 0xA000;
cart.ram[banked_address as usize..banked_address as usize + size]
.copy_from_slice(&pif_ram[data..data + size]);
}
}
}
fn read_mbc1(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
if address < 0x4000 {
let banked_address = address & 0x3FFF;
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if address < 0x8000 {
let banked_address = address as u32 - 0x4000 + (cart.rom_bank * 0x4000);
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
for i in 0..size {
pif_ram[data + i] = 0xff;
}
return;
}
if cart.ram_bank > 3 {
for i in 0..size {
pif_ram[data + i] = 0;
}
} else {
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
pif_ram[data..data + size].copy_from_slice(
&cart.ram[banked_address as usize..banked_address as usize + size],
);
}
}
}
fn write_mbc3(
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
let value = pif_ram[data + size - 1];
if address < 0x2000 {
cart.ram_enabled = value & 0xf == 0xa;
} else if address < 0x4000 {
let bank = value & 0x7f;
cart.rom_bank = bank as u32;
if cart.rom_bank == 0 {
cart.rom_bank = 1;
}
} else if address < 0x6000 {
cart.ram_bank = (value & 0xf) as u16;
} else if address < 0x8000 {
if !cart.set_latch && value != 0 {
// not implemented
}
cart.set_latch = value != 0;
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
}
if cart.ram_bank < 0x8 {
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
cart.ram[banked_address as usize..banked_address as usize + size]
.copy_from_slice(&pif_ram[data..data + size]);
} else {
match cart.ram_bank {
0x8 => {
cart.latch_second = value;
}
0x9 => {
cart.latch_minute = value;
}
0xA => {
cart.latch_hour = value;
}
0xB => {
cart.latch_day &= 0xFF00;
cart.latch_day |= value as u16;
}
0xC => {
cart.latch_day &= 0x00FF;
cart.latch_day |= (value as u16) << 8;
}
_ => {
panic!("Unsupported ram bank {:x}", cart.ram_bank);
}
}
}
} else {
panic!("Unsupported write address {address:x}");
}
}
fn read_mbc3(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
if address < 0x4000 {
let banked_address = address & 0x3FFF;
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if address < 0x8000 {
let banked_address = address as u32 - 0x4000 + (cart.rom_bank * 0x4000);
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
for i in 0..size {
pif_ram[data + i] = 0xff;
}
return;
}
if cart.ram_bank < 0x8 {
if cart.ram_bank > 3 {
for i in 0..size {
pif_ram[data + i] = 0;
}
} else {
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
pif_ram[data..data + size].copy_from_slice(
&cart.ram[banked_address as usize..banked_address as usize + size],
);
}
} else {
let latch = match cart.ram_bank {
0x8 => cart.latch_second,
0x9 => cart.latch_minute,
0xA => cart.latch_hour,
0xB => cart.latch_day as u8,
0xC => (cart.latch_day >> 8) as u8,
_ => {
panic!("Unsupported ram bank {:x}", cart.ram_bank);
}
};
for i in 0..size {
pif_ram[data + i] = latch;
}
}
} else {
panic!("Unsupported read address {address:x}");
}
}
fn write_mbc5(
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
let value = pif_ram[data + size - 1];
if address < 0x2000 {
cart.ram_enabled = value & 0xf == 0xa;
} else if address < 0x3000 {
cart.rom_bank &= 0xff00;
cart.rom_bank |= value as u32;
} else if address < 0x4000 {
cart.rom_bank &= 0x00ff;
cart.rom_bank |= ((value & 0x1) as u32) << 8;
} else if address < 0x6000 {
cart.ram_bank = (value & 0xf) as u16;
} else if address < 0xa000 {
println!("Unknown MBC5 write address {address:x}");
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
}
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
cart.ram[banked_address as usize..banked_address as usize + size]
.copy_from_slice(&pif_ram[data..data + size]);
} else {
panic!("Unsupported write address {address:x}");
}
}
fn read_mbc5(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
if address < 0x4000 {
let banked_address = address & 0x3FFF;
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if address < 0x8000 {
let banked_address = address as u32 - 0x4000 + (cart.rom_bank << 14);
pif_ram[data..data + size]
.copy_from_slice(&cart.rom[banked_address as usize..banked_address as usize + size]);
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
for i in 0..size {
pif_ram[data + i] = 0xff;
}
return;
}
let banked_address = address - 0xA000 + (cart.ram_bank << 13);
pif_ram[data..data + size]
.copy_from_slice(&cart.ram[banked_address as usize..banked_address as usize + size]);
} else {
panic!("Unsupported read address {address:x}");
}
}
pub fn read(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
if !cart.enabled {
for i in 0..size {
pif_ram[data + i] = 0x00;
}
return;
}
match cart.cart_type {
CartType::MBC1RamBatt => read_mbc1(pif_ram, cart, address, data, size),
CartType::MBC3RamBatt => read_mbc3(pif_ram, cart, address, data, size),
CartType::MBC3RamBattRtc => read_mbc3(pif_ram, cart, address, data, size),
CartType::MBC5RamBatt => read_mbc5(pif_ram, cart, address, data, size),
_ => panic!("Unsupported cart type"),
}
}
pub fn write(
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
) {
if !cart.enabled {
return;
}
match cart.cart_type {
CartType::MBC1RamBatt => write_mbc1(pif_ram, cart, address, data, size),
CartType::MBC3RamBatt => write_mbc3(pif_ram, cart, address, data, size),
CartType::MBC3RamBattRtc => write_mbc3(pif_ram, cart, address, data, size),
CartType::MBC5RamBatt => write_mbc5(pif_ram, cart, address, data, size),
_ => panic!("Unsupported cart type"),
}
}
pub fn get_cart_type(data: u8) -> CartType {
match data {
0x03 => CartType::MBC1RamBatt,
0x10 => CartType::MBC3RamBattRtc,
0x13 => CartType::MBC3RamBatt,
0x1b => CartType::MBC5RamBatt,
_ => panic!("Unsupported cart type {data:x}"),
}
}
+22 -13
View File
@@ -4,8 +4,14 @@ pub const MEMPAK_SIZE: usize = 0x8000;
const MPK_PAGE_SIZE: usize = 256;
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);
if device.ui.storage.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
device
.ui
.storage
.saves
.mempak
.data
.resize(MEMPAK_SIZE * 4, 0);
let page_0: [u8; MPK_PAGE_SIZE] = [
/* Label area */
@@ -38,28 +44,31 @@ fn format_mempak(device: &mut device::Device) {
let offset = i * MEMPAK_SIZE;
/* Fill Page 0 with pre-initialized content */
device.ui.saves.mempak.0[offset..offset + MPK_PAGE_SIZE].copy_from_slice(&page_0);
device.ui.storage.saves.mempak.data[offset..offset + MPK_PAGE_SIZE]
.copy_from_slice(&page_0);
/* Fill INODE page 1 and update it's checkum */
let start_page = 5;
for i in MPK_PAGE_SIZE..2 * start_page {
device.ui.saves.mempak.0[offset + i] = 0;
device.ui.storage.saves.mempak.data[offset + i] = 0;
}
for i in (MPK_PAGE_SIZE + 2 * start_page..2 * MPK_PAGE_SIZE).step_by(2) {
device.ui.saves.mempak.0[offset + i] = 0x00;
device.ui.saves.mempak.0[offset + i + 1] = 0x03;
device.ui.storage.saves.mempak.data[offset + i] = 0x00;
device.ui.storage.saves.mempak.data[offset + i + 1] = 0x03;
}
device.ui.saves.mempak.0[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
device.ui.storage.saves.mempak.data[offset + (MPK_PAGE_SIZE + 1)] = 0x71;
/* Page 2 is identical to page 1 */
let page1 = offset + MPK_PAGE_SIZE;
let page2 = offset + 2 * MPK_PAGE_SIZE;
let page1data = device.ui.saves.mempak.0[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.saves.mempak.0[page2..page2 + MPK_PAGE_SIZE].copy_from_slice(&page1data);
let page1data =
device.ui.storage.saves.mempak.data[page1..page1 + MPK_PAGE_SIZE].to_vec();
device.ui.storage.saves.mempak.data[page2..page2 + MPK_PAGE_SIZE]
.copy_from_slice(&page1data);
/* Remaining pages DIR+DATA (3...) are initialized with 0x00 */
for i in 3 * MPK_PAGE_SIZE..MEMPAK_SIZE - 3 * MPK_PAGE_SIZE {
device.ui.saves.mempak.0[offset + i] = 0;
device.ui.storage.saves.mempak.data[offset + i] = 0;
}
}
}
@@ -71,7 +80,7 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.pif.ram[data..data + size]
.copy_from_slice(&device.ui.saves.mempak.0[offset..offset + size])
.copy_from_slice(&device.ui.storage.saves.mempak.data[offset..offset + size])
} else {
for i in 0..size {
device.pif.ram[data + i] = 0;
@@ -84,9 +93,9 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
format_mempak(device);
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.ui.saves.mempak.0[offset..offset + size]
device.ui.storage.saves.mempak.data[offset..offset + size]
.copy_from_slice(&device.pif.ram[data..data + size]);
device.ui.saves.mempak.1 = true
device.ui.storage.saves.mempak.written = true
}
}
+7 -4
View File
@@ -15,10 +15,13 @@ pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: us
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);
if let Some(netplay) = &device.netplay {
if netplay.player_number as usize == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
} else {
device::ui::input::set_rumble(&device.ui, channel, rumble);
}
}
}
+133
View File
@@ -0,0 +1,133 @@
use crate::device;
#[derive(Default, serde::Serialize, serde::Deserialize)]
pub struct TransferPak {
pub enabled: bool,
pub reset_state: u8,
pub bank: u16,
pub cart: device::controller::gbcart::GbCart,
}
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
let pak = &mut device.transferpaks[channel];
match address >> 12 {
0x8 => {
let value = if pak.enabled { 0x84 } else { 0x00 };
for i in 0..size {
device.pif.ram[data + i] = value;
}
return;
}
_ => {
if !pak.enabled {
for i in 0..size {
device.pif.ram[data + i] = 0x00;
}
return;
}
}
}
match address >> 12 {
0xB => {
let mut value = 0;
if pak.cart.enabled {
value |= 1 << 0;
}
value |= (pak.reset_state & 3) << 2;
if pak.enabled {
value |= 1 << 7;
}
if pak.cart.enabled && pak.reset_state == 3 {
pak.reset_state = 2
} else if !pak.cart.enabled && pak.reset_state == 2 {
pak.reset_state = 1
} else if !pak.cart.enabled && pak.reset_state == 1 {
pak.reset_state = 0
}
for i in 0..size {
device.pif.ram[data + i] = value;
}
}
0xC..=0xF => {
device::controller::gbcart::read(
&mut device.pif.ram,
&pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
);
}
_ => {
println!("unknown transfer pak read {:x}", address >> 12);
}
}
}
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
let pak = &mut device.transferpaks[channel];
let value = device.pif.ram[data + size - 1];
match address >> 12 {
0x8 => {
match value {
0xFE => {
pak.enabled = false;
}
0x84 => {
if !pak.enabled {
pak.bank = 3;
pak.cart.enabled = false;
pak.reset_state = 0;
}
pak.enabled = true;
}
_ => println!("unknown transfer pak value"),
}
return;
}
_ => {
if !pak.enabled {
return;
}
}
}
match address >> 12 {
0xA => {
pak.bank = value as u16;
if pak.bank > 3 {
pak.bank = 0;
}
}
0xB => {
if value & 1 != 0 {
if !pak.cart.enabled {
pak.reset_state = 3;
pak.cart.rom_bank = 1;
pak.cart.ram_bank = 0;
pak.cart.ram_enabled = false;
pak.cart.cart_type =
device::controller::gbcart::get_cart_type(pak.cart.rom[0x147]);
}
pak.cart.enabled = true;
} else {
pak.cart.enabled = false;
}
}
0xC..=0xF => {
device::controller::gbcart::write(
&device.pif.ram,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
);
}
_ => {
println!("unknown transfer pak write {:x}", address >> 12);
}
}
}
+131 -376
View File
@@ -1,5 +1,4 @@
use crate::{device, ui};
use eframe::egui;
use std::collections::HashMap;
const JCMD_VRU_READ: u8 = 0x09;
@@ -17,18 +16,25 @@ 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<tokio::sync::mpsc::Sender<Vec<String>>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
pub gui_ctx: Option<egui::Context>,
}
pub fn init(device: &mut device::Device) {
@@ -120,7 +126,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
let (res, _enc, errors) = encoding_rs::SHIFT_JIS.decode(&data);
if errors {
panic!("Failed to decode Japanese word {:X?}", data);
panic!("Failed to decode Japanese word {data:X?}");
} else {
device.vru.words.push(res.to_string());
}
@@ -138,7 +144,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
if let Some(result) = word {
device.vru.words.push(result.clone());
} else {
panic!("Unknown VRU word {}", data);
panic!("Unknown VRU word {data}");
}
}
} else {
@@ -160,14 +166,12 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.vru.voice_init = 2;
device::events::create_event(
device,
device::events::EventType::Vru,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]
+ (device.cpu.clock_rate * 2), // 2 seconds
vru_talking_event,
device::events::EVENT_TYPE_VRU,
device.cpu.clock_rate * 2, // 2 seconds
)
} 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 +182,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 let Some(window_notifier) = &device.vru_window.window_notifier {
ui::vru::prompt_for_match(
&device.vru.words,
device.vru.window_notifier.as_ref().unwrap(),
device.vru.word_receiver.as_mut().unwrap(),
device.vru.gui_ctx.as_ref().unwrap(),
window_notifier,
device.vru_window.word_receiver.as_mut().unwrap(),
device.vi.frame_time,
)
} else {
0x7FFF
@@ -246,11 +250,11 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
device.vru.status = 0; /* status is always set to 0 after a write */
}
_ => panic!("unknown VRU command {}", cmd),
_ => panic!("unknown VRU command {cmd}"),
}
}
fn vru_talking_event(device: &mut device::Device) {
pub fn vru_talking_event(device: &mut device::Device) {
device.vru.talking = false
}
@@ -288,10 +292,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003035D001200F900EA"),
String::from("pikachu"),
),
(
String::from("040801740024"),
String::from("hey!"),
),
(String::from("040801740024"), String::from("hey!")),
(
String::from("03CF00C603360405000F0234"),
String::from("come-here"),
@@ -324,10 +325,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("040B00C002EB0213"),
String::from("hello"),
),
(String::from("040B00C002EB0213"), String::from("hello")),
(
String::from("043B0213000303AB00A5034803FC006903FC00A503270024"),
String::from("open-sesame"),
@@ -356,14 +354,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC012602F10024"),
String::from("im-sorry"),
),
(
String::from("03FC023A0024"),
String::from("sorry"),
),
(
String::from("03FC012602F10024"),
String::from("sorry"),
),
(String::from("03FC023A0024"), String::from("sorry")),
(String::from("03FC012602F10024"), String::from("sorry")),
(
String::from("042F01B00003036000ED03DB030C00EA"),
String::from("i-choose-you"),
@@ -392,10 +384,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303D8000303CF00C9"),
String::from("pikka"),
),
(
String::from("03A50024000303CF00A8"),
String::from("pika"),
),
(String::from("03A50024000303CF00A8"), String::from("pika")),
(
String::from("03A50024000303CF00A8000303A50024000303CF00A8"),
String::from("pikapika"),
@@ -440,10 +429,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03F600C60348000603870234"),
String::from("thunder"),
),
(
String::from("03F600C6033F0234"),
String::from("thunder"),
),
(String::from("03F600C6033F0234"), String::from("thunder")),
(
String::from("0432009F02E20066000303D8000303C602F10045000303D802F40087000303C6"),
String::from("electric-rat"),
@@ -489,7 +475,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("boing-boing"),
),
(
String::from("043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366"),
String::from(
"043801290348000303C000ED036C00A5033C0066000303D803FC000303C603FC000303BA018F00060366",
),
String::from("on-to-the-next-stage!"),
),
(
@@ -516,10 +504,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02EE0006038700A5033C0087000303AB009F02EE"),
String::from("golden-apple"),
),
(
String::from("03A5002400030360"),
String::from("peach"),
),
(String::from("03A5002400030360"), String::from("peach")),
(
String::from("03FC000303C602FD012C00060372029D0024"),
String::from("strawberry"),
@@ -556,10 +541,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032101A702EE00060390040B02340006037E"),
String::from("wild-herb"),
),
(
String::from("03D502D60348"),
String::from("corn"),
),
(String::from("03D502D60348"), String::from("corn")),
(
String::from("03B1014D000303B4000303D502D60348"),
String::from("popcorn"),
@@ -592,18 +574,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CC008102F700A8000303C6"),
String::from("carrot"),
),
(
String::from("03CC02A300A8000303C6"),
String::from("carrot"),
),
(String::from("03CC02A300A8000303C6"), String::from("carrot")),
(
String::from("043200C60339030900A50348"),
String::from("onion"),
),
(
String::from("043200A5033F00A50348"),
String::from("onion"),
),
(String::from("043200A5033F00A50348"), String::from("onion")),
(
String::from("043501080339030900A50348"),
String::from("onion"),
@@ -621,7 +597,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AB00C9000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -732,10 +710,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303B4000303CC018F000303D8"),
String::from("cupcake"),
),
(
String::from("03840174002703DB0024"),
String::from("daisy"),
),
(String::from("03840174002703DB0024"), String::from("daisy")),
(
String::from("03A50045000303C000E70339030900A8"),
String::from("petunia"),
@@ -768,10 +743,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("02FD021603DE0006037500C900060390"),
String::from("rose-bud"),
@@ -852,10 +824,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D5014D000303AB0234000303D501CE0348"),
String::from("copper-coin"),
),
(
String::from("02FA00EA0006036F0024"),
String::from("ruby"),
),
(String::from("02FA00EA0006036F0024"), String::from("ruby")),
(
String::from("03FC008A03F001AA0300"),
String::from("sapphire"),
@@ -904,10 +873,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("037502340006039003F00069036C0234"),
String::from("bird-feather"),
),
(
String::from("037B020A02EE000303C6"),
String::from("bolt"),
),
(String::from("037B020A02EE000303C6"), String::from("bolt")),
(
String::from("032A0087000603A203390045000303C6"),
String::from("magnet"),
@@ -920,10 +886,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0333025500060375009F02EE"),
String::from("marble"),
),
(
String::from("03D802F4018F000303C6"),
String::from("crate"),
),
(String::from("03D802F4018F000303C6"), String::from("crate")),
(
String::from("03D50255000603900006037B02D9000603900006037B014D000303D803FC"),
String::from("cardboard-box"),
@@ -936,10 +899,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("043B021603EA022804050297"),
String::from("over-here"),
),
(
String::from("040B009F02EB0213"),
String::from("hello"),
),
(String::from("040B009F02EB0213"), String::from("hello")),
(
String::from("039C010B00060390034501950024000303C6"),
String::from("good-night"),
@@ -972,18 +932,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("036C0087000303C603FC03F001AD0348"),
String::from("thats-fine"),
),
(
String::from("043B0213000303CC01740024"),
String::from("ok"),
),
(
String::from("04020276"),
String::from("sure"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("043B0213000303CC01740024"), String::from("ok")),
(String::from("04020276"), String::from("sure")),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("02E2006903FC0006038A00ED036C0087000303C6"),
String::from("lets-do-that"),
@@ -1012,18 +963,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CF00C9000303BD00A8000303C301F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03450213"),
String::from("no"),
),
(String::from("03450213"), String::from("no")),
(
String::from("0318018F0006038700A80006039F0213"),
String::from("way-to-go"),
),
(
String::from("03F002EB01DD0234"),
String::from("flower"),
),
(String::from("03F002EB01DD0234"), String::from("flower")),
(
String::from("038400840348000303C603FC"),
String::from("dance"),
@@ -1104,14 +1049,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03F90009030C00E102E2018F000303BD0234"),
String::from("see-you-later"),
),
(
String::from("03F90009030C00EA"),
String::from("see-you"),
),
(
String::from("037B01B0"),
String::from("bye"),
),
(String::from("03F90009030C00EA"), String::from("see-you")),
(String::from("037B01B0"), String::from("bye")),
(
String::from("0411031B00A8000303BD0219030900A80006038A00CF0042035A"),
String::from("what-are-you-doing?"),
@@ -1164,10 +1103,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("04290045000303C603FC021603EA0237036C02B8"),
String::from("its-over-there"),
),
(
String::from("02DF003C02DF0024"),
String::from("lily"),
),
(String::from("02DF003C02DF0024"), String::from("lily")),
(
String::from("03FC00C6034803F002EB01DD0234"),
String::from("sunflower"),
@@ -1188,10 +1124,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
@@ -1200,14 +1133,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD033603FC02BE0024"),
String::from("im-sorry"),
),
(
String::from("03FC02BE0024"),
String::from("sorry"),
),
(
String::from("03CC0084033F00A50348"),
String::from("cannon"),
),
(String::from("03FC02BE0024"), String::from("sorry")),
(String::from("03CC0084033F00A50348"), String::from("cannon")),
(
String::from("03810045000603930045000303C301F2000303C6"),
String::from("dig-it-out"),
@@ -1216,18 +1143,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A2036C017A0234"),
String::from("dig-there"),
),
(
String::from("03810045000603A2"),
String::from("dig"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(
String::from("033F00C9000303C6"),
String::from("nut"),
),
(String::from("03810045000603A2"), String::from("dig")),
(String::from("03AE010202EE"), String::from("pull")),
(String::from("033F00C9000303C6"), String::from("nut")),
(
String::from("03780108033C0084033F00C9"),
String::from("banana"),
@@ -1244,10 +1162,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0372029D002400060390000303C602F4006903E40234"),
String::from("buried-treasure"),
),
(
String::from("0408005D02EB0213"),
String::from("hello"),
),
(String::from("0408005D02EB0213"), String::from("hello")),
(
String::from("042F01A702E500CC03E7030C00EA"),
String::from("i-love-you"),
@@ -1312,26 +1227,17 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("041101290348000303BD0234"),
String::from("haunter"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("036C0087000303C603DE02FD0129035A"),
String::from("thats-wrong"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B700450006038D01F2000303C6"),
String::from("cut-it-out"),
),
(
String::from("03CF00A503270297"),
String::from("c-mere"),
),
(String::from("03CF00A503270297"), String::from("c-mere")),
(
String::from("03CF00A5033604050297"),
String::from("come-here"),
@@ -1348,26 +1254,17 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0321012C0003036001F2000303C6"),
String::from("watch-out"),
),
(
String::from("036C02B8"),
String::from("there"),
),
(String::from("036C02B8"), String::from("there")),
(
String::from("03FC03150042035A034501F2"),
String::from("swing-now"),
),
(
String::from("03FC03150042035A"),
String::from("swing"),
),
(String::from("03FC03150042035A"), String::from("swing")),
(
String::from("039F020D02FD01B0000303C6"),
String::from("go-right"),
),
(
String::from("02FD01B0000303C6"),
String::from("right"),
),
(String::from("02FD01B0000303C6"), String::from("right")),
(
String::from("03F00258036C022E02FD01B0000303C6"),
String::from("farther-right"),
@@ -1376,10 +1273,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F020A02E2006903F0000303C6"),
String::from("go-left"),
),
(
String::from("02E2006903F0000303C6"),
String::from("left"),
),
(String::from("02E2006903F0000303C6"), String::from("left")),
(
String::from("03F00258036C022B02E2006903F0000303C6"),
String::from("farther-left"),
@@ -1472,10 +1366,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02DF004803F900420348000303BD00A503270024"),
String::from("listen-to-me"),
),
(
String::from("03F002FA00EA000303C6"),
String::from("fruit"),
),
(String::from("03F002FA00EA000303C6"), String::from("fruit")),
(
String::from("03450213000303C6000303A8008700060390"),
String::from("notepad"),
@@ -1500,14 +1391,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0411014D000303C603FC03150024000303B4000303BA018F0006038D0213"),
String::from("hot-sweet-potato"),
),
(
String::from("03D200E102EE"),
String::from("cool"),
),
(
String::from("03F001AD0348"),
String::from("fine"),
),
(String::from("03D200E102EE"), String::from("cool")),
(String::from("03F001AD0348"), String::from("fine")),
(
String::from("0309023703FC0213000303C9030C00EA000303C6"),
String::from("youre-so-cute"),
@@ -1572,14 +1457,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C6"),
String::from("magnet"),
),
(
String::from("03D501CE0348"),
String::from("coin"),
),
(
String::from("036600D5009F02EE"),
String::from("jewel"),
),
(String::from("03D501CE0348"), String::from("coin")),
(String::from("036600D5009F02EE"), String::from("jewel")),
(
String::from("0333025500060375009F02EE"),
String::from("marble"),
@@ -1596,10 +1475,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D50315004803DE000303C301AD0336"),
String::from("quiz-time"),
),
(
String::from("03B7002703E70024"),
String::from("tv"),
),
(String::from("03B7002703E70024"), String::from("tv")),
(
String::from("042F01B0000303D503150045000303C6"),
String::from("i-quit"),
@@ -1616,10 +1492,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03150024000303CF00A50348000303B402E2018F"),
String::from("we-can-play"),
),
(
String::from("0372008700060390"),
String::from("bad"),
),
(String::from("0372008700060390"), String::from("bad")),
(
String::from("0345014D000303C90045000303C3012F03F0"),
String::from("knock-it-off"),
@@ -1636,10 +1509,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01AD03360345014D000303C6000303B402E201740042035A"),
String::from("im-not-playing"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
@@ -1660,10 +1530,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D802DF00210348036C0087000303BD00C9000303B4"),
String::from("clean-that-up"),
),
(
String::from("03B70048040200EA"),
String::from("tissue"),
),
(String::from("03B70048040200EA"), String::from("tissue")),
(
String::from("037500C002EE0006037500AB03FC02D9"),
String::from("bulbasaur"),
@@ -1768,10 +1635,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303A502880213"),
String::from("spearow"),
),
(
String::from("03ED02880213"),
String::from("fearow"),
),
(String::from("03ED02880213"), String::from("fearow")),
(
String::from("04260024000303CF00A5034803DE"),
String::from("ekans"),
@@ -1788,10 +1652,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A50024000303CF00A80003036000EA"),
String::from("pikachu"),
),
(
String::from("02FD01B00003036000EA"),
String::from("raichu"),
),
(String::from("02FD01B00003036000EA"), String::from("raichu")),
(
String::from("03FC00840348040202FA00EA"),
String::from("sandshrew"),
@@ -1884,22 +1745,13 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0438012C0006038100480402"),
String::from("oddish"),
),
(
String::from("03A202E800E70336"),
String::from("gloom"),
),
(String::from("03A202E800E70336"), String::from("gloom")),
(
String::from("03EA01A702EE000303B402E800E70336"),
String::from("vileplume"),
),
(
String::from("03B1024000AB03FC"),
String::from("paras"),
),
(
String::from("03A802A300AB03FC"),
String::from("paras"),
),
(String::from("03B1024000AB03FC"), String::from("paras")),
(String::from("03A802A300AB03FC"), String::from("paras")),
(
String::from("03B1024000AB03FC0066000303D8000303C6"),
String::from("parasect"),
@@ -1920,10 +1772,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03810045000603A202E500A8000303C6"),
String::from("diglett"),
),
(
String::from("0327001501F503F6"),
String::from("meowth"),
),
(String::from("0327001501F503F6"), String::from("meowth")),
(
String::from("03AB023703E400A50348"),
String::from("persian"),
@@ -2024,14 +1873,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03A202F4008A03EA02E50234"),
String::from("graveler"),
),
(
String::from("039F020A02E500A50336"),
String::from("golem"),
),
(
String::from("039F014402EE0336"),
String::from("golem"),
),
(String::from("039F020A02E500A50336"), String::from("golem")),
(String::from("039F014402EE0336"), String::from("golem")),
(
String::from("03B1021003390024000303BD00A8"),
String::from("ponyta"),
@@ -2060,18 +1903,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A2033F00A8000303C3014A0348"),
String::from("magneton"),
),
(
String::from("0008"),
String::from("farfetch"),
),
(String::from("0008"), String::from("farfetch")),
(
String::from("038D02130006039002F100150213"),
String::from("dodrio"),
),
(
String::from("03F9001B02EE"),
String::from("seel"),
),
(String::from("03F9001B02EE"), String::from("seel")),
(
String::from("038A00EA0006039F014A035A"),
String::from("dewgong"),
@@ -2080,14 +1917,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("038A00EA0006039F014A0348"),
String::from("dewgong"),
),
(
String::from("03A202FD01AD032D0234"),
String::from("grimer"),
),
(
String::from("032D00C9000303D8"),
String::from("muk"),
),
(String::from("03A202FD01AD032D0234"), String::from("grimer")),
(String::from("032D00C9000303D8"), String::from("muk")),
(
String::from("0402005D02EE000603870234"),
String::from("shellder"),
@@ -2100,10 +1931,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0396008A03FC000303C602DF00"),
String::from("gastly"),
),
(
String::from("0396008A03FC02DF0024"),
String::from("gastly"),
),
(String::from("0396008A03FC02DF0024"), String::from("gastly")),
(
String::from("041101290348000303BD0234"),
String::from("haunter"),
@@ -2232,10 +2060,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03CC018C035A0006039900AB03FC000303D5014A0348"),
String::from("kangaskhan"),
),
(
String::from("041102DC03F90024"),
String::from("horsea"),
),
(String::from("041102DC03F90024"), String::from("horsea")),
(
String::from("03F900240006039002F700A8"),
String::from("seadra"),
@@ -2260,10 +2085,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0327004803FC000303BD0231033301AD0336"),
String::from("mr.mime"),
),
(
String::from("03FC01B3036C0234"),
String::from("scyther"),
),
(String::from("03FC01B3036C0234"), String::from("scyther")),
(
String::from("03630042035A000303D803FC"),
String::from("jynx"),
@@ -2280,18 +2102,9 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("032A0087000603A203330255"),
String::from("magmar"),
),
(
String::from("03A50042034803FC0234"),
String::from("pinsir"),
),
(
String::from("03C3020D02FD012F03FC"),
String::from("tauros"),
),
(
String::from("03C30246021603FC"),
String::from("tauros"),
),
(String::from("03A50042034803FC0234"), String::from("pinsir")),
(String::from("03C3020D02FD012F03FC"), String::from("tauros")),
(String::from("03C30246021603FC"), String::from("tauros")),
(
String::from("032A0087000603630045000303D50255000303B4"),
String::from("magikarp"),
@@ -2312,14 +2125,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E20087000303B402F700AB03FC"),
String::from("lapras"),
),
(
String::from("03810045000303C30213"),
String::from("ditto"),
),
(
String::from("0426002703E70024"),
String::from("eevee"),
),
(String::from("03810045000303C30213"), String::from("ditto")),
(String::from("0426002703E70024"), String::from("eevee")),
(
String::from("03EA018F000303B102BE0015014A0348"),
String::from("vaporeon"),
@@ -2416,10 +2223,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("0327030C00EA000303C000EA"),
String::from("mewtwo"),
),
(
String::from("0327030C00EA"),
String::from("mew"),
),
(String::from("0327030C00EA"), String::from("mew")),
(
String::from("03C3014D000303AB00AB03EA000603630045000603A202DF0024000303AB00AB03F0"),
String::from("top-of-jigglypuff"),
@@ -2456,10 +2260,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("038700C9000603A2000303C602F100150213"),
String::from("dugtrio"),
),
(
String::from("03A50045000603630024"),
String::from("pidgey"),
),
(String::from("03A50045000603630024"), String::from("pidgey")),
(
String::from("038D02130006038A00DB0213"),
String::from("doduo"),
@@ -2528,30 +2329,18 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E2006903FC000303B402E2018F"),
String::from("lets-play"),
),
(
String::from("02FD0129035A"),
String::from("wrong"),
),
(String::from("02FD0129035A"), String::from("wrong")),
(
String::from("0372008700060390000303A50024000303CF00A80003036000EA"),
String::from("bad-pikachu"),
),
(
String::from("0327004803FC"),
String::from("miss"),
),
(String::from("0327004803FC"), String::from("miss")),
(
String::from("038D02100348000303C6000303D5012302DF0045000303C6"),
String::from("dont-call-it"),
),
(
String::from("041101F503FC"),
String::from("house"),
),
(
String::from("041102100336"),
String::from("home"),
),
(String::from("041101F503FC"), String::from("house")),
(String::from("041102100336"), String::from("home")),
(
String::from("03EA00A202F1004500060381000F00C60348"),
String::from("viridian"),
@@ -2560,10 +2349,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03EA00A202F1004500060381000F00C6034803F002C400AB03FC000303C6"),
String::from("viridian-forest"),
),
(
String::from("043B0213000303CF0234"),
String::from("ochre"),
),
(String::from("043B0213000303CF0234"), String::from("ochre")),
(
String::from("043B0213000303CF0225031E010B0006039003DE"),
String::from("ochre-woods"),
@@ -2604,10 +2390,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03D502130006037E03ED004803FF0042035A0411020A02EE"),
String::from("cobalt-fishing-hole"),
),
(
String::from("03D5021603FC000303C6"),
String::from("coast"),
),
(String::from("03D5021603FC000303C6"), String::from("coast")),
(
String::from("03D502130006037B012302EE000303C6000303D5021603FC000303C6"),
String::from("cobalt-coast"),
@@ -2620,16 +2403,15 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03BD0231033F00A8000303B4"),
String::from("turnip"),
),
(
String::from("03BD02310348000303B4"),
String::from("turnip"),
),
(String::from("03BD02310348000303B4"), String::from("turnip")),
(
String::from("03FC03150024000303C6000303AB00A8000303BA018F0006038D0213"),
String::from("sweet-potato"),
),
(
String::from("0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213"),
String::from(
"0411014D000303C603FC03150024000303C6000303AE010B000303BA018F0006038D0213",
),
String::from("hot-sweet-potato"),
),
(
@@ -2640,18 +2422,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("04080084034B00420348036C02B8"),
String::from("hang-in-there"),
),
(
String::from("03AE010202EE"),
String::from("pull"),
),
(String::from("03AE010202EE"), String::from("pull")),
(
String::from("02F1001B02DF0045000303B700420348"),
String::from("reel-it-in"),
),
(
String::from("034501F2"),
String::from("now"),
),
(String::from("034501F2"), String::from("now")),
(
String::from("03CC00870003035D0045000303C6"),
String::from("catch-it"),
@@ -2676,14 +2452,8 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02F1003C02DF002703F90045000303C6"),
String::from("release-it"),
),
(
String::from("02E500C6034800030360"),
String::from("lunch"),
),
(
String::from("03FC033C0087000303D8"),
String::from("snack"),
),
(String::from("02E500C6034800030360"), String::from("lunch")),
(String::from("03FC033C0087000303D8"), String::from("snack")),
(
String::from("02E2006903F90024000303C6"),
String::from("lets-eat"),
@@ -2700,10 +2470,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("042F01B0000603930045000303B70045000303C6"),
String::from("i-get-it"),
),
(
String::from("0411032101B0"),
String::from("why?"),
),
(String::from("0411032101B0"), String::from("why?")),
(
String::from("041101F2000303CF00C60336"),
String::from("how-come?"),
@@ -2728,10 +2495,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC000303C3014D000303B4036C0087000303C6"),
String::from("stop-that"),
),
(
String::from("03D503150045000303C6"),
String::from("quit"),
),
(String::from("03D503150045000303C6"), String::from("quit")),
(
String::from("03CF00C9000303B70045000303C301F2000303C6"),
String::from("cut-it-out"),
@@ -2744,10 +2508,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("02E20066000303C603DE0006039F0213"),
String::from("lets-go"),
),
(
String::from("031B0225012F03F0"),
String::from("we-re-off"),
),
(String::from("031B0225012F03F0"), String::from("we-re-off")),
(
String::from("03BA008700060393030902190045000303C6"),
String::from("tag-youre-it"),
@@ -2804,18 +2565,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("039F0213000303BD00C6033302460213"),
String::from("go-tomorrow"),
),
(
String::from("02FD014D000303D8"),
String::from("rock"),
),
(String::from("02FD014D000303D8"), String::from("rock")),
(
String::from("03F9004803DE023703FC"),
String::from("scissors"),
),
(
String::from("03A8018F000303AB0234"),
String::from("paper"),
),
(String::from("03A8018F000303AB0234"), String::from("paper")),
(
String::from("0381002A000303B10255000303C6"),
String::from("depart"),
@@ -2872,10 +2627,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03450213000602E50042032A002A000303C6"),
String::from("no-limit"),
),
(
String::from("03B7005A0348"),
String::from("ten"),
),
(String::from("03B7005A0348"), String::from("ten")),
(
String::from("03E7002A03F6000303B700090348"),
String::from("fifteen"),
@@ -2888,18 +2640,12 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03C30318004B0339038A002403EA019E03EA"),
String::from("twenty-five"),
),
(
String::from("036C02AC03BA0027"),
String::from("thirty"),
),
(String::from("036C02AC03BA0027"), String::from("thirty")),
(
String::from("036C02AC03BA002703EA019E03EA"),
String::from("thirty-five"),
),
(
String::from("03EA02D6000603900048"),
String::from("forty"),
),
(String::from("03EA02D6000603900048"), String::from("forty")),
(
String::from("03EA02D600060390004803EA019E03EA"),
String::from("forty-five"),
@@ -2928,10 +2674,7 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("03FC004B036C00630348000303B7000903EA019E03EA"),
String::from("seventy-five"),
),
(
String::from("042C018C03B70009"),
String::from("eighty"),
),
(String::from("042C018C03B70009"), String::from("eighty")),
(
String::from("042C018C03B7000903EA019E03EA"),
String::from("eighty-five"),
@@ -2984,5 +2727,17 @@ fn create_word_mappings(device: &mut device::Device) {
String::from("82B582E382C182CF82C282AF82A282A982A2"),
String::from("restricted-depart"),
),
(
String::from("009903720366006903C903FF0045033C"),
String::from("objection"),
),
(
String::from("041100CF02EE0381004503B7"),
String::from("hold-it"),
),
(
String::from("03BA019203C9036C0087000303C6"),
String::from("take-that"),
),
]);
}
+57 -55
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,15 +88,20 @@ 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,
}
fn mfc0(device: &mut device::Device, opcode: u32) {
@@ -209,11 +214,11 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
device::events::create_event(
device,
device::events::EventType::Compare,
device.cpu.cop0.regs[COP0_COUNT_REG as usize] + ((compare_event_diff as u64) << 1),
compare_event,
device::events::EVENT_TYPE_COMPARE,
(compare_event_diff as u64) << 1,
);
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 {
@@ -230,15 +235,12 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
device::exceptions::check_pending_interrupts(device);
}
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::events::create_event(
pub fn compare_event(device: &mut device::Device) {
device.cpu.cop0.pending_compare_interrupt = true;
device::events::create_event_at(
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);
}
@@ -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)
}
+16 -5
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],
}
@@ -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 {
+8 -2
View File
@@ -1,6 +1,8 @@
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,
}
@@ -76,7 +78,7 @@ 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);
}
+26 -6
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::{device, savestates, ui};
#[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,
@@ -27,11 +30,15 @@ pub struct Cpu {
pub hi: u64,
pub running: bool,
pub llbit: bool,
pub overclock: 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 +54,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 +221,21 @@ 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 = if !device.cpu.overclock {
93750000
} else {
ui::video::onscreen_message(
&device.ui,
"Overclocking enabled, setting clock rate to 125 MHz",
);
125000000
};
map_instructions(device);
device::cop0::init(device);
device::cop1::init(device);
device::cop2::init(device);
+50 -32
View File
@@ -1,59 +1,77 @@
use crate::device;
#[derive(PartialEq, Copy, Clone)]
pub enum EventType {
AI,
SI,
VI,
PI,
DP,
SP,
InterruptCheck,
SPDma,
Compare,
Vru,
PakSwitch,
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,
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + when,
};
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 create_event_at(device: &mut device::Device, name: usize, when: u64) {
device.cpu.events[name] = Event {
enabled: true,
count: when,
};
set_next_event(device);
}
pub fn get_event(device: &device::Device, name: usize) -> Option<&Event> {
if device.cpu.events[name].enabled {
return Some(&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);
}
+26 -9
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,32 @@ 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;
}
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, 0);
}
}
pub fn interrupt_exception(device: &mut device::Device) {
exception_general(device, 0x180);
}
+4 -3
View File
@@ -21,9 +21,10 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
let length = (value & mask) as u64;
let data = std::str::from_utf8(
&device.cart.is_viewer_buffer[0x20_usize..(0x20 + length) as usize],
)
.unwrap();
print!("{}", data);
);
if let Ok(data) = data {
print!("{}", data);
}
} else {
let mut data = u32::from_be_bytes(
device.cart.is_viewer_buffer[masked_address..masked_address + 4]
+37 -15
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 {
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_fast")]
pub fast_read: [fn(&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],
}
@@ -53,11 +62,16 @@ pub fn masked_write_64(dst: &mut u64, value: u64, mask: u64) {
pub fn translate_address(
device: &mut device::Device,
address: u64,
mut address: u64,
access_type: AccessType,
) -> (u64, bool, bool) {
if (address & 0xc0000000) != 0x80000000 {
return device::tlb::get_physical_address(device, address, access_type);
if (address & 0xFFFFFFFF00000000) == 0x9000000000000000 {
// this is a hack to support 64-bit addresses used by some homebrew
address |= 0xA0000000;
} else {
return device::tlb::get_physical_address(device, address, access_type);
}
}
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
}
@@ -142,19 +156,27 @@ 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;
} 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;
} else if i >= MM_IS_VIEWER >> 16 && i <= (MM_IS_VIEWER + 0xFFFF) >> 16 {
device.memory.memory_map_read[i] = device::cart::sram::read_mem;
device.memory.memory_map_write[i] = device::cart::sram::write_mem;
} else if device.cart.rom.len() <= MM_IS_VIEWER - MM_CART_ROM
&& 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 (MM_CART_ROM >> 16..=(MM_CART_ROM + 0x0FBFFFFF) >> 16).contains(&i) {
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_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)
}
+14 -34
View File
@@ -1,9 +1,9 @@
use crate::device;
const MI_INIT_MODE_REG: u32 = 0;
pub 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 */
@@ -29,7 +29,7 @@ const MI_CLR_DP: u32 = 1 << 10;
const MI_SET_DP: u32 = 1 << 11;
/* mode read */
const MI_INIT_MODE: u32 = 1 << 7;
pub const MI_INIT_MODE: u32 = 1 << 7;
const MI_EBUS_MODE: u32 = 1 << 8;
const MI_RDRAM_MODE: u32 = 1 << 9;
@@ -42,8 +42,9 @@ const MI_CLR_DP_INTR: u32 = 1 << 11;
const MI_CLR_RDRAM: u32 = 1 << 12;
const MI_SET_RDRAM: u32 = 1 << 13;
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Mi {
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,11 +66,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
}
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
} else {
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
@@ -80,9 +78,15 @@ fn update_init_mode(device: &mut device::Device, w: u32) {
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
if w & MI_CLR_INIT != 0 {
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem;
}
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_MODE
}
if w & MI_SET_INIT != 0 {
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem_repeat;
}
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_INIT_MODE
}
if w & MI_CLR_EBUS != 0 {
@@ -149,37 +153,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
}
+68 -24
View File
@@ -1,4 +1,6 @@
use crate::device;
use crate::ui;
use rand_chacha::rand_core::RngCore;
const PI_DRAM_ADDR_REG: u32 = 0;
const PI_CART_ADDR_REG: u32 = 1;
@@ -26,6 +28,7 @@ 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],
}
@@ -40,6 +43,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,
@@ -63,12 +67,7 @@ fn dma_read(device: &mut device::Device) {
let cycles = (handler.read)(device, cart_addr, dram_addr, length);
device::events::create_event(
device,
device::events::EventType::PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
dma_event,
);
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
@@ -94,14 +93,12 @@ fn dma_write(device: &mut device::Device) {
length -= dram_addr & 0x7;
}
// check RDP before PI writes to RDRAM
ui::video::check_framebuffers(dram_addr, length);
let cycles = (handler.write)(device, cart_addr, dram_addr, length);
device::events::create_event(
device,
device::events::EventType::PI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + cycles,
dma_event,
);
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
/* Update PI_DRAM_ADDR_REG and PI_CART_ADDR_REG */
device.pi.regs[PI_DRAM_ADDR_REG as usize] =
@@ -114,17 +111,27 @@ fn dma_write(device: &mut device::Device) {
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,15 +156,24 @@ 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),
}
}
pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u64 {
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
rng.next_u64() % 0x10
}
pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -> u64 {
let mut cycles: f64 = 0.0;
let (page_size, latency, pulse_width, release, pages);
let page_size_base: f64 = 2.0;
@@ -180,6 +196,7 @@ pub fn calculate_cycles(device: &device::Device, domain: i32, length: u32) -> u6
cycles += (14.0 + latency) * pages;
cycles += (pulse_width + release) * (length as f64 / 2.0);
cycles += 5.0 * pages;
cycles += randomize_interrupt_time(&mut device.rng) as f64;
(cycles * 1.5) as u64 // Converting RCP clock speed to CPU clock speed
}
@@ -189,3 +206,30 @@ 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
.get_mut((dram_addr + i) as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+57 -22
View File
@@ -1,18 +1,22 @@
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,
@@ -51,7 +55,7 @@ pub fn read_mem(
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let mut masked_address = address as usize & PIF_MASK;
if masked_address < PIF_RAM_OFFSET {
panic! {"write to pif rom"}
panic!("write to pif rom")
}
masked_address -= PIF_RAM_OFFSET;
let mut data = u32::from_be_bytes(
@@ -63,12 +67,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device.pif.ram[masked_address..masked_address + 4].copy_from_slice(&data.to_be_bytes());
device.si.dma_dir = device::si::DmaDir::Write;
device::events::create_event(
device,
device::events::EventType::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::events::create_event(device, device::events::EVENT_TYPE_SI, 3200); //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
}
@@ -217,6 +216,39 @@ 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 let Some(netplay) = &device.netplay {
if netplay.player_data[i].reg_id != 0 {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
fn get_default_handler(device: &device::Device) -> device::controller::PakHandler {
if device.ui.game_id == "NCT" {
// Chameleon Twist does not support the mempak
device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
}
} else {
device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
}
}
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@@ -226,28 +258,31 @@ pub fn init(device: &mut device::Device) {
device.pif.ram[0x26] = device.cart.cic_seed;
device.pif.ram[0x27] = device.cart.cic_seed;
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
let default_handler = get_default_handler(device);
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
connect_pif_channels(device);
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].pak_handler = Some(mempak_handler);
device.pif.channels[i].process = Some(device::controller::process);
if let Some(netplay) = &device.netplay {
if netplay.player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(default_handler);
}
} else if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_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 {
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
device.pif.channels[3].pak_handler = None;
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
fn process_cic_challenge(device: &mut device::Device) {
+45 -10
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,29 @@ pub fn read_regs_dpc(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device.rdp.regs_dpc[((address & 0xFFFF) >> 2) as usize]
if !device.rsp.cpu.running {
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) {
@@ -93,14 +116,21 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
if timer != 0 {
device::events::create_event(
device,
device::events::EventType::DP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rdp_interrupt_event,
)
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::EVENT_TYPE_DP, timer)
}
}
@@ -109,6 +139,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]
}
@@ -171,8 +202,12 @@ fn update_dpc_status(device: &mut device::Device, w: u32) {
pub fn init(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
}
fn rdp_interrupt_event(device: &mut device::Device) {
pub fn rdp_interrupt_event(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+87 -26
View File
@@ -1,5 +1,6 @@
use crate::device;
use std::alloc::{alloc_zeroed, Layout};
use crate::ui;
use std::alloc::{Layout, alloc_zeroed};
//const RDRAM_CONFIG_REG: u32 = 0;
//const RDRAM_DEVICE_ID_REG: u32 = 1;
@@ -15,6 +16,7 @@ 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,
@@ -22,15 +24,19 @@ pub struct Rdram {
}
pub fn read_mem_fast(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let masked_address = address as usize & RDRAM_MASK;
u32::from_ne_bytes(
device.rdram.mem[masked_address..masked_address + 4]
device
.rdram
.mem
.get(masked_address..masked_address + 4)
.unwrap_or(&[0; 4])
.try_into()
.unwrap(),
.unwrap_or_default(),
)
}
@@ -44,27 +50,71 @@ pub fn read_mem(
rdram_calculate_cycles(access_size as u64) / (access_size as u64 / 4),
);
let masked_address = address as usize & RDRAM_MASK;
if address < device.rdram.size as u64 {
u32::from_ne_bytes(
device.rdram.mem[masked_address..masked_address + 4]
.try_into()
.unwrap(),
)
} else {
0
}
ui::video::check_framebuffers(masked_address as u32, 4);
u32::from_ne_bytes(
device
.rdram
.mem
.get(masked_address..masked_address + 4)
.unwrap_or(&[0; 4])
.try_into()
.unwrap_or_default(),
)
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if address < device.rdram.size as u64 {
let mut data = u32::from_ne_bytes(
device.rdram.mem[address as usize..(address + 4) as usize]
.try_into()
.unwrap(),
ui::video::check_framebuffers(address as u32, 4);
let mut data = u32::from_ne_bytes(
device
.rdram
.mem
.get(address as usize..(address + 4) as usize)
.unwrap_or(&[0; 4])
.try_into()
.unwrap_or_default(),
);
device::memory::masked_write_32(&mut data, value, mask);
device
.rdram
.mem
.get_mut(address as usize..(address + 4) as usize)
.unwrap_or(&mut [0; 4])
.copy_from_slice(&data.to_ne_bytes());
}
pub fn write_mem_repeat(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if mask != 0xFFFFFFFF {
panic!("RDRAM write_mem_repeat called with mask {:#x}", mask);
}
let repeat_length = (device.mi.regs[device::mi::MI_INIT_MODE_REG as usize]
& device::mi::MI_INIT_LENGTH_MASK)
+ 1;
if !repeat_length.is_multiple_of(4) {
panic!(
"RDRAM write_mem_repeat called with non-word-aligned length {}",
repeat_length
);
device::memory::masked_write_32(&mut data, value, mask);
device.rdram.mem[address as usize..(address + 4) as usize]
.copy_from_slice(&data.to_ne_bytes());
}
ui::video::check_framebuffers(address as u32, repeat_length);
for i in (0..repeat_length as u64).step_by(4) {
device
.rdram
.mem
.get_mut((address + i) as usize..(address + i) as usize + 4)
.unwrap_or(&mut [0; 4])
.copy_from_slice(&value.to_ne_bytes());
}
device.mi.regs[device::mi::MI_INIT_MODE_REG as usize] &= !device::mi::MI_INIT_MODE;
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem;
}
}
@@ -73,16 +123,18 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
let chip_id = address >> 13 & 3;
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) {
let chip_id = address >> 13 & 3;
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
device::memory::masked_write_32(
&mut device.rdram.regs[chip_id as usize][reg as usize],
@@ -100,13 +152,22 @@ pub fn init(device: &mut device::Device) {
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
// hack, skip RDRAM initialization
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
device
.rdram
.mem
.get_mut(0x318..0x318 + 4)
.unwrap_or(&mut [0; 4])
.copy_from_slice(&device.rdram.size.to_ne_bytes());
// hack, skip RDRAM initialization
device
.rdram
.mem
.get_mut(0x3f0..0x3f0 + 4)
.unwrap_or(&mut [0; 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 {
+18 -9
View File
@@ -10,6 +10,7 @@ pub const RI_REFRESH_REG: u32 = 4;
//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,
@@ -20,17 +21,25 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
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::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
RI_SELECT_REG => {
if !device.ri.ram_init {
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
device.ri.ram_init = true;
}
0x14 // hack, skip RDRAM initialization
}
RI_REFRESH_REG => 0x00063634, // hack, skip RDRAM initialization
_ => device.ri.regs[reg as usize],
}
device.ri.regs[((address & 0xFFFF) >> 2) as usize]
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
device::memory::masked_write_32(
&mut device.ri.regs[((address & 0xFFFF) >> 2) as usize],
value,
mask,
);
let reg = (address & 0xFFFF) >> 2;
if reg as u32 == RI_SELECT_REG {
device.ri.ram_init = false;
}
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
}
+185 -123
View File
@@ -2,60 +2,115 @@
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,
pub pipeline_full: bool,
pub branch_state: BranchState,
pub broken: bool,
pub running: bool,
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],
}
@@ -71,6 +126,7 @@ pub fn in_delay_slot_taken(device: &device::Device) -> bool {
pub fn run(device: &mut device::Device) -> u64 {
device.rsp.cpu.broken = false;
device.rsp.cpu.cycle_counter = 0;
device.rsp.cpu.running = true;
while !device.rsp.cpu.sync_point {
device.rsp.cpu.instruction_type = InstructionType::Su;
device.rsp.cpu.gpr[0] = 0; // gpr 0 is read only
@@ -132,6 +188,7 @@ pub fn run(device: &mut device::Device) -> u64 {
}
}
}
device.rsp.cpu.running = false;
(device.rsp.cpu.cycle_counter as f64 * 1.5) as u64 // converting RCP clock to CPU clock
}
@@ -165,62 +222,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 +291,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 +599,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);
}
+74 -35
View File
@@ -1,4 +1,5 @@
use crate::device;
use crate::ui;
const SP_MEM_ADDR_REG: u32 = 0;
const SP_DRAM_ADDR_REG: u32 = 1;
@@ -60,14 +61,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,16 +76,20 @@ 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(
device: &mut device::Device,
device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -99,8 +104,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]
@@ -140,6 +147,7 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
let mut dram_addr = dma.dramaddr & 0xfffff8;
let offset = dma.memaddr & 0x1000;
ui::video::check_framebuffers(dram_addr, count * (length + skip) - skip);
if dma.dir == DmaDir::Read {
let mut j = 0;
while j < count {
@@ -151,7 +159,11 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
.try_into()
.unwrap(),
);
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
device
.rdram
.mem
.get_mut(dram_addr as usize..dram_addr as usize + 4)
.unwrap_or(&mut [0; 4])
.copy_from_slice(&data.to_ne_bytes());
mem_addr += 4;
dram_addr += 4;
@@ -165,14 +177,16 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
while j < count {
let mut i = 0;
while i < length {
let mut data = 0;
if dram_addr < device.rdram.size {
data = u32::from_ne_bytes(
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.try_into()
.unwrap(),
);
}
let data = u32::from_ne_bytes(
device
.rdram
.mem
.get(dram_addr as usize..dram_addr as usize + 4)
.unwrap_or(&[0; 4])
.try_into()
.unwrap_or_default(),
);
if offset != 0 {
// imem being updated
device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].func =
@@ -197,11 +211,8 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
device::events::create_event(
device,
device::events::EventType::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,
device::events::EVENT_TYPE_SPDMA,
device::rdram::rdram_calculate_cycles((count * length) as u64) + 9,
);
}
@@ -210,6 +221,8 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) {
panic!("RSP DMA already full")
}
device.rsp.cpu.sync_point = true;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] != 0 {
device.rsp.fifo[1].dir = dir;
if dir == DmaDir::Read {
@@ -237,7 +250,7 @@ fn fifo_push(device: &mut device::Device, dir: DmaDir) {
}
}
fn fifo_pop(device: &mut device::Device) {
pub fn fifo_pop(device: &mut device::Device) {
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
device.rsp.fifo[0].length = device.rsp.fifo[1].length;
@@ -249,7 +262,11 @@ fn fifo_pop(device: &mut device::Device) {
do_dma(device, device.rsp.fifo[0])
} else {
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
if device.rsp.run_after_dma {
device.rsp.run_after_dma = false;
do_task(device);
}
}
}
@@ -258,10 +275,33 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
if !device.rsp.cpu.running {
device::cop0::add_cycles(device, 20);
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SP_STATUS_REG => {
let value = device.rsp.regs[reg as usize]
& (SP_STATUS_SIG0
| SP_STATUS_SIG1
| SP_STATUS_SIG2
| SP_STATUS_SIG3
| SP_STATUS_SIG4
| SP_STATUS_SIG5
| SP_STATUS_SIG6
| SP_STATUS_SIG7);
if value == device.rsp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
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.regs[reg as usize] = 1;
value
}
@@ -284,9 +324,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 +333,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]
}
@@ -320,7 +359,7 @@ 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 +374,7 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
}
/* set SP interrupt */
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
}
/* clear / set single step */
@@ -421,23 +460,24 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
device.rsp.cpu.broken = false;
device.rsp.cpu.halted = false;
device.rsp.cpu.sync_point = false;
do_task(device);
}
}
fn do_task(device: &mut device::Device) {
let timer = device::rsp_cpu::run(device);
device.rsp.cpu.sync_point = false;
device.rsp.last_status_value = 0;
device.rdp.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, timer)
}
}
fn rsp_event(device: &mut device::Device) {
pub fn rsp_event(device: &mut device::Device) {
if device.rsp.cpu.broken {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT | SP_STATUS_BROKE;
@@ -450,7 +490,6 @@ fn rsp_event(device: &mut device::Device) {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
return;
}
device.rsp.cpu.sync_point = false;
do_task(device)
}
+161 -71
View File
@@ -304,26 +304,44 @@ pub fn lh(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 2];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFFE {
u16::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 2]
.try_into()
.unwrap(),
)
} else {
let w0 = device.rsp.mem[addr_masked];
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
((w0 as u16) << 8) | w1 as u16
};
device.rsp.cpu.gpr[rt(opcode) as usize] =
(((w[0] as u16) << 8) | w[1] as u16) as i16 as i32 as u32
device.rsp.cpu.gpr[rt(opcode) as usize] = value as i16 as i32 as u32;
}
pub fn lw(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFFC {
u32::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 4]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..4 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
}
value
};
device.rsp.cpu.gpr[rt(opcode) as usize] =
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
}
pub fn lbu(device: &mut device::Device, opcode: u32) {
@@ -337,25 +355,44 @@ pub fn lhu(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 2];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFFE {
u16::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 2]
.try_into()
.unwrap(),
)
} else {
let w0 = device.rsp.mem[addr_masked];
let w1 = device.rsp.mem[(addr_masked + 1) & 0xFFF];
((w0 as u16) << 8) | w1 as u16
};
device.rsp.cpu.gpr[rt(opcode) as usize] = (((w[0] as u16) << 8) | w[1] as u16) as u32
device.rsp.cpu.gpr[rt(opcode) as usize] = value as u32;
}
pub fn lwu(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
let mut w = [0; 4];
w[0] = device.rsp.mem[address as usize & 0xFFF];
w[1] = device.rsp.mem[(address as usize + 1) & 0xFFF];
w[2] = device.rsp.mem[(address as usize + 2) & 0xFFF];
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFFC {
u32::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 4]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..4 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
}
value
};
device.rsp.cpu.gpr[rt(opcode) as usize] =
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
device.rsp.cpu.gpr[rt(opcode) as usize] = value;
}
pub fn sb(device: &mut device::Device, opcode: u32) {
@@ -369,23 +406,31 @@ pub fn sh(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
device.rsp.mem[address as usize & 0xFFF] = (device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
device.rsp.mem[(address as usize + 1) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
let addr_masked = address as usize & 0xFFF;
let value = device.rsp.cpu.gpr[rt(opcode) as usize] as u16;
// Optimize for aligned access when possible
if addr_masked <= 0xFFE {
device.rsp.mem[addr_masked..addr_masked + 2].copy_from_slice(&value.to_be_bytes());
} else {
device.rsp.mem[addr_masked] = (value >> 8) as u8;
device.rsp.mem[(addr_masked + 1) & 0xFFF] = value as u8;
}
}
pub fn sw(device: &mut device::Device, opcode: u32) {
let address =
device.rsp.cpu.gpr[rs(opcode) as usize].wrapping_add(imm(opcode) as i16 as i32 as u32);
device.rsp.mem[address as usize & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 24) as u8;
device.rsp.mem[(address as usize + 1) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 16) as u8;
device.rsp.mem[(address as usize + 2) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8;
device.rsp.mem[(address as usize + 3) & 0xFFF] =
(device.rsp.cpu.gpr[rt(opcode) as usize]) as u8;
let addr_masked = address as usize & 0xFFF;
let value = device.rsp.cpu.gpr[rt(opcode) as usize];
// Optimize for aligned access when possible
if addr_masked <= 0xFFC {
device.rsp.mem[addr_masked..addr_masked + 4].copy_from_slice(&value.to_be_bytes());
} else {
for i in 0..4 {
device.rsp.mem[(addr_masked + i) & 0xFFF] = (value >> ((3 - i) * 8)) as u8;
}
}
}
pub fn sll(device: &mut device::Device, opcode: u32) {
@@ -552,6 +597,7 @@ pub fn bgezal(device: &mut device::Device, opcode: u32) {
}
pub fn mfc0(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.cycle_counter += 2;
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rsp_interface::read_regs(
device,
@@ -565,8 +611,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 +636,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) {
@@ -608,19 +651,19 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
pub fn cfc2(device: &mut device::Device, opcode: u32) {
let hi;
let lo;
let mut zero = unsafe { _mm_setzero_si128() };
let zero = unsafe { _mm_setzero_si128() };
match rd(opcode) & 3 {
0x00 => {
hi = &mut device.rsp.cpu.vcoh;
lo = &mut device.rsp.cpu.vcol;
hi = &device.rsp.cpu.vcoh;
lo = &device.rsp.cpu.vcol;
}
0x01 => {
hi = &mut device.rsp.cpu.vcch;
lo = &mut device.rsp.cpu.vccl;
hi = &device.rsp.cpu.vcch;
lo = &device.rsp.cpu.vccl;
}
0x02 | 0x03 => {
hi = &mut zero;
lo = &mut device.rsp.cpu.vce;
hi = &zero;
lo = &device.rsp.cpu.vce;
}
_ => {
panic!("unknown cfc2")
@@ -717,11 +760,11 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 2 == 0 {
if element.is_multiple_of(2) {
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 {
@@ -744,11 +787,22 @@ 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));
}
if element.is_multiple_of(4) {
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFFC {
u32::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 4]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..4 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u32) << (8 * (3 - i));
}
value
};
modify_vpr32(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 4,
@@ -774,11 +828,22 @@ 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));
}
if element.is_multiple_of(8) {
// Optimize for aligned access when possible
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFF8 {
u64::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 8]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..8 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u64) << (8 * (7 - i));
}
value
};
modify_vpr64(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element / 8,
@@ -804,11 +869,22 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element == 0 && address % 16 == 0 {
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
}
if element == 0 && address.is_multiple_of(16) {
// Optimize for aligned 16-byte access
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFF0 {
u128::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 16]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
}
value
};
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
@@ -831,10 +907,21 @@ pub fn lrv(device: &mut device::Device, opcode: u32) {
let mut element = 16u8.wrapping_sub(((address & 15) as u8).wrapping_sub(velement(opcode)));
address &= !15;
if element == 0 {
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u128) << (8 * (15 - i));
}
// Optimize for aligned 16-byte access
let addr_masked = address as usize & 0xFFF;
let value = if addr_masked <= 0xFF0 {
u128::from_be_bytes(
device.rsp.mem[addr_masked..addr_masked + 16]
.try_into()
.unwrap(),
)
} else {
let mut value = 0;
for i in 0..16 {
value |= (device.rsp.mem[(addr_masked + i) & 0xFFF] as u128) << (8 * (15 - i));
}
value
};
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
while element < 16 {
@@ -983,7 +1070,7 @@ pub fn ssv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 2 == 0 {
if element.is_multiple_of(2) {
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(),
@@ -1005,7 +1092,7 @@ pub fn slv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 4 == 0 {
if element.is_multiple_of(4) {
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(),
@@ -1027,7 +1114,7 @@ pub fn sdv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element % 8 == 0 {
if element.is_multiple_of(8) {
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(),
@@ -1049,7 +1136,7 @@ pub fn sqv(device: &mut device::Device, opcode: u32) {
let mut element = velement(opcode);
if element == 0 && address % 16 == 0 {
if element == 0 && address.is_multiple_of(16) {
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());
@@ -1207,6 +1294,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) {}
File diff suppressed because it is too large Load Diff
+30 -16
View File
@@ -1,4 +1,7 @@
use rand_chacha::rand_core::RngCore;
use crate::device;
use crate::ui;
const SI_DRAM_ADDR_REG: u32 = 0;
const SI_PIF_ADDR_RD64B_REG: u32 = 1;
@@ -14,13 +17,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,9 +35,14 @@ 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]
}
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
rng.next_u64() % 0x100
}
fn dma_read(device: &mut device::Device) {
device.si.dma_dir = DmaDir::Read;
@@ -41,12 +50,9 @@ fn dma_read(device: &mut device::Device) {
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
device::events::create_event(
device,
device::events::EventType::SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + duration,
dma_event,
)
let length = duration + randomize_interrupt_time(&mut device.rng);
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
}
fn dma_write(device: &mut device::Device) {
@@ -56,12 +62,9 @@ fn dma_write(device: &mut device::Device) {
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
device::events::create_event(
device,
device::events::EventType::SI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + 6000, //based on https://github.com/rasky/n64-systembench
dma_event,
)
let length = 6000 + randomize_interrupt_time(&mut device.rng); //based on https://github.com/rasky/n64-systembench
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -84,18 +87,29 @@ fn copy_pif_rdram(device: &mut device::Device) {
let mut i = 0;
while i < device::pif::PIF_RAM_SIZE {
let data = u32::from_ne_bytes(
device.rdram.mem[dram_addr + i..dram_addr + i + 4]
device
.rdram
.mem
.get(dram_addr + i..dram_addr + i + 4)
.unwrap_or(&[0; 4])
.try_into()
.unwrap(),
.unwrap_or_default(),
);
device.pif.ram[i..i + 4].copy_from_slice(&data.to_be_bytes());
i += 4;
}
} else if device.si.dma_dir == DmaDir::Read {
// check RDP before SI writes to RDRAM
ui::video::check_framebuffers(dram_addr as u32, device::pif::PIF_RAM_SIZE as u32);
let mut i = 0;
while i < device::pif::PIF_RAM_SIZE {
let data = u32::from_be_bytes(device.pif.ram[i..i + 4].try_into().unwrap());
device.rdram.mem[dram_addr + i..dram_addr + i + 4].copy_from_slice(&data.to_ne_bytes());
device
.rdram
.mem
.get_mut(dram_addr + i..dram_addr + i + 4)
.unwrap_or(&mut [0; 4])
.copy_from_slice(&data.to_ne_bytes());
i += 4;
}
} else {
+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,
@@ -146,7 +146,7 @@ pub fn probe(device: &mut device::Device) {
}
fn tlb_unmap(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
let e = &device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 {
let mut i = e.start_even;
@@ -184,7 +184,7 @@ fn tlb_unmap(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];
let e = &device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0
&& e.start_even < e.end_even
+10 -2
View File
@@ -1,7 +1,7 @@
use crate::device;
pub fn read_mem(
_device: &mut device::Device,
pub fn read_mem_fast(
_device: &device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
@@ -9,4 +9,12 @@ pub fn read_mem(
value | (value << 16)
}
pub fn read_mem(
device: &mut device::Device,
address: u64,
access_size: device::memory::AccessSize,
) -> u32 {
read_mem_fast(device, address, access_size)
}
pub fn write_mem(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {}
+110 -61
View File
@@ -1,72 +1,80 @@
use crate::device;
use crate::netplay;
use crate::ui;
use crate::{cheats, 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 enable_speed_limiter: bool,
pub vi_counter: u64,
pub last_origin: u32,
pub internal_frame_counter: u64,
pub min_wait_time: std::time::Duration,
pub frame_time: f64,
pub limit_freq: u64,
#[serde(skip)]
#[serde(default = "std::time::Instant::now")]
pub limit_freq_check: std::time::Instant,
}
//static mut FRAME_COUNTER: u64 = 0;
const MAX_LIMIT_FREQ: u64 = 3;
const LIMIT_BUFFER: u64 = 3;
fn set_expected_refresh_rate(device: &mut device::Device) {
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;
create_limiter(device);
}
fn create_limiter(device: &mut device::Device) {
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
(1.0 / expected_refresh_rate) * LIMIT_BUFFER as f64,
device.vi.frame_time * device.vi.limit_freq as f64,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
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::EventType::VI).is_none() {
device::events::create_event(
device,
device::events::EventType::VI,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + device.vi.delay,
vertical_interrupt_event,
)
if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() {
device::events::create_event(device, device::events::EVENT_TYPE_VI, device.vi.delay)
}
}
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);
if next_vi.is_some() {
let next_vi = device::events::get_event(device, device::events::EVENT_TYPE_VI);
if let Some(next_vi) = next_vi {
device.vi.regs[VI_CURRENT_REG as usize] = ((delay
- (next_vi.unwrap().count
- device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]))
- (next_vi.count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize]))
/ device.vi.count_per_scanline)
as u32;
@@ -77,7 +85,8 @@ fn set_current_line(device: &mut device::Device) {
}
/* update current field */
device.vi.regs[VI_CURRENT_REG as usize] =
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field
(device.vi.regs[VI_CURRENT_REG as usize] & !1) | device.vi.field;
ui::video::set_register(VI_CURRENT_REG, device.vi.regs[VI_CURRENT_REG as usize])
}
pub fn read_regs(
@@ -89,6 +98,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]
}
@@ -109,6 +119,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,36 +133,53 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
}
fn vertical_interrupt_event(device: &mut device::Device) {
device.cpu.running = ui::video::update_screen();
pub fn vertical_interrupt_event(device: &mut device::Device) {
if device.cheats.enabled {
cheats::execute_cheats(device, device.cheats.cheats.clone());
}
ui::video::render_frame();
let (mut speed_limiter_toggled, paused) = ui::video::check_callback(device);
if let Some(netplay) = &mut device.netplay {
netplay::send_sync_check(netplay, device.cpu.cop0.regs.as_ref());
if device.vi.enable_speed_limiter == netplay.fast_forward {
speed_limiter_toggled = true;
device.vi.enable_speed_limiter = !netplay.fast_forward;
}
}
if device.vi.vi_counter.is_multiple_of(device.vi.limit_freq) && device.vi.enable_speed_limiter {
speed_limiter(device, speed_limiter_toggled);
}
ui::video::update_screen();
device.vi.vi_counter += 1;
if device.netplay.is_none() && paused {
ui::video::pause_loop(device.vi.frame_time);
}
unsafe { sdl3_sys::events::SDL_PumpEvents() };
/*
unsafe {
FRAME_COUNTER += 1;
}
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;
}
*/
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 && enable_speed_limiter {
speed_limiter(device);
}
/* toggle vi field if in interlaced mode */
device.vi.field ^= (device.vi.regs[VI_STATUS_REG as usize] >> 6) & 0x1;
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_VI);
device::events::create_event(
device::events::create_event_at(
device,
device::events::EventType::VI,
device::events::EVENT_TYPE_VI,
device.cpu.next_event_count + device.vi.delay,
vertical_interrupt_event,
)
}
@@ -155,27 +189,42 @@ 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));
}
});
*/
}
fn speed_limiter(device: &device::Device) {
fn speed_limiter(device: &mut device::Device, speed_limiter_toggled: bool) {
let result = device.vi.limiter.as_ref().unwrap().check();
if result.is_err() {
let outcome = result.unwrap_err();
if let Err(outcome) = result {
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 !speed_limiter_toggled {
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();
}
}
+80 -109
View File
@@ -3,10 +3,13 @@
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod cheats;
mod device;
mod netplay;
mod savestates;
mod ui;
use clap::Parser;
use std::io::Error;
use ui::gui;
/// N64 emulator
@@ -20,15 +23,22 @@ struct Args {
short,
long,
value_name = "PROFILE_NAME",
help = "Create a new input profile (keyboard/gamepad mappings)."
help = "Create a new input profile (keyboard/gamepad mappings)"
)]
configure_input_profile: Option<String>,
#[arg(
short,
long,
help = "Use DirectInput when configuring a new input profile."
help = "Use DirectInput when configuring a new input profile"
)]
use_dinput: bool,
#[arg(
short,
long,
value_name = "DEADZONE_PERCENTAGE",
help = "Used along with --configure-input-profile to set the deadzone for analog sticks"
)]
deadzone: Option<i32>,
#[arg(
short,
long,
@@ -65,128 +75,89 @@ struct Args {
}
#[tokio::main]
async fn main() {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
let mut result = std::fs::create_dir_all(config_dir.clone());
if result.is_err() {
panic!("could not create config dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(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"));
if result.is_err() {
panic!("could not create data dir: {}", result.err().unwrap())
}
let running_file = cache_dir.join("game_running");
if running_file.exists() {
result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
async fn main() -> std::io::Result<()> {
let dirs = ui::get_dirs();
std::fs::create_dir_all(dirs.config_dir)?;
std::fs::create_dir_all(dirs.data_dir.join("saves"))?;
std::fs::create_dir_all(dirs.data_dir.join("states"))?;
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 let Some(game) = args.game {
let file_path = std::path::Path::new(&game);
let Some(rom_contents) = device::get_rom_contents(file_path) else {
return Err(Error::other(format!(
"Could not read ROM file: {}",
file_path.display()
)));
};
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();
let overclock = device.ui.config.emulation.overclock;
let disable_expansion_pak = device.ui.config.emulation.disable_expansion_pak;
let game_cheats = {
let game_crc = ui::storage::get_game_crc(&rom_contents);
let cheats = ui::config::Cheats::new();
cheats.cheats.get(&game_crc).cloned().unwrap_or_default()
};
device::run_game(
&mut device,
rom_contents,
ui::gui::GameSettings {
fullscreen: args.fullscreen,
overclock,
disable_expansion_pak,
cheats: game_cheats,
},
);
})?;
handle.join().unwrap();
} else if std::env::args().count() > 1 {
let mut ui = ui::Ui::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
return;
ui::input::clear_bindings(&mut ui);
return Ok(());
}
if args.port.is_some() {
let port = args.port.unwrap();
if !(1..=4).contains(&port) {
println!("Port must be betwen 1 and 4");
return;
}
if let Some(port) = args.port
&& !(1..=4).contains(&port)
{
return Err(Error::other("Port must be between 1 and 4"));
}
if args.list_controllers {
let controllers = gui::get_controller_names(&device.ui);
let controllers = ui::input::get_controller_names(&ui);
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {}: {}", i, controller);
println!("Controller {i}: {controller}");
}
return;
return Ok(());
}
if args.assign_controller.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::assign_controller(
&mut device.ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
return;
}
if args.bind_input_profile.is_some() {
if args.port.is_none() {
println!("Must specify port number");
return;
}
ui::input::bind_input_profile(
&mut device.ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
return;
}
if args.configure_input_profile.is_some() {
if let Some(profile) = args.configure_input_profile {
ui::input::configure_input_profile(
&mut device.ui,
args.configure_input_profile.unwrap(),
&mut ui,
profile,
args.use_dinput,
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
);
return;
return Ok(());
}
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;
}
device::run_game(rom_contents, data_dir, &mut device, args.fullscreen);
if let Some(assign_controller) = args.assign_controller {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::assign_controller(&mut ui, assign_controller, port);
}
if let Some(profile) = args.bind_input_profile {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::bind_input_profile(&mut ui, profile, port);
}
} else {
let options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([640.0, 480.0])
.with_icon(
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
.unwrap(),
),
..Default::default()
};
eframe::run_native(
"gopher64",
options,
Box::new(|cc| {
Ok(Box::new(ui::gui::GopherEguiApp::new(
cc, config_dir, cache_dir, data_dir,
)))
}),
)
.unwrap();
gui::app_window();
}
Ok(())
}
+65 -51
View File
@@ -1,6 +1,4 @@
use crate::device;
use crate::ui;
use eframe::egui;
use crate::{device, ui};
use std::io::{Read, Write};
//UDP packet formats
const UDP_SEND_KEY_INFO: u8 = 0;
@@ -11,12 +9,17 @@ const UDP_SYNC_DATA: u8 = 4;
//TCP packet formats
const TCP_SEND_SAVE: u8 = 1;
const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_SEND_SETTINGS: u8 = 3;
//const TCP_RECEIVE_SETTINGS: u8 = 4;
const TCP_REGISTER_PLAYER: u8 = 5;
const TCP_GET_REGISTRATION: u8 = 6;
const TCP_DISCONNECT_NOTICE: u8 = 7;
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
const TCP_SEND_RTC: u8 = 64;
const TCP_RECEIVE_RTC: u8 = 128;
const TCP_SEND_RNG: u8 = 65;
const TCP_RECEIVE_RNG: u8 = 129;
const CS4: u32 = 32;
@@ -29,8 +32,6 @@ pub struct Netplay {
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 {
@@ -45,10 +46,36 @@ struct InputEvent {
plugin: u8,
}
fn send_error(netplay: &mut Netplay, error: String) {
netplay.error_notifier.try_send(error).unwrap();
pub fn send_rtc(netplay: &mut Netplay, rtc: i64) {
let mut request: Vec<u8> = [TCP_SEND_RTC].to_vec();
let size: u32 = 8;
request.extend_from_slice(&size.to_be_bytes());
request.extend_from_slice(&rtc.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
}
netplay.gui_ctx.request_repaint(); // this is so the window pops up right away
pub fn receive_rtc(netplay: &mut Netplay) -> i64 {
let request: Vec<u8> = [TCP_RECEIVE_RTC].to_vec();
netplay.tcp_stream.write_all(&request).unwrap();
let mut rtc: [u8; 8] = [0; 8];
netplay.tcp_stream.read_exact(&mut rtc).unwrap();
i64::from_be_bytes(rtc)
}
pub fn send_rng(netplay: &mut Netplay, seed: u64) {
let mut request: Vec<u8> = [TCP_SEND_RNG].to_vec();
let size: u32 = 8;
request.extend_from_slice(&size.to_be_bytes());
request.extend_from_slice(&seed.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
}
pub fn receive_rng(netplay: &mut Netplay) -> u64 {
let request: Vec<u8> = [TCP_RECEIVE_RNG].to_vec();
netplay.tcp_stream.write_all(&request).unwrap();
let mut seed: [u8; 8] = [0; 8];
netplay.tcp_stream.read_exact(&mut seed).unwrap();
u64::from_be_bytes(seed)
}
pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size: usize) {
@@ -57,31 +84,32 @@ pub fn send_save(netplay: &mut Netplay, save_type: &str, save_data: &[u8], size:
request.push(0); // null terminate string
request.extend_from_slice(&(size as u32).to_be_bytes());
let mut send_data = save_data.to_owned();
send_data.resize(size, 0); // pad with zeros if needed
request.extend(send_data);
if size > 0 {
request.extend(save_data.to_owned());
}
netplay.tcp_stream.write_all(&request).unwrap();
}
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>, size: usize) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE].to_vec();
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
netplay.tcp_stream.write_all(&request).unwrap();
let mut response: Vec<u8> = vec![0; size];
let mut size: [u8; 4] = [0; 4];
netplay.tcp_stream.read_exact(&mut size).unwrap();
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
netplay.tcp_stream.read_exact(&mut response).unwrap();
*save_data = response;
}
pub fn send_sync_check(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
if netplay.vi_counter % 600 == 0 {
pub fn send_sync_check(netplay: &mut Netplay, regs: &[u64]) {
if netplay.vi_counter.is_multiple_of(600) {
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());
for item in regs.iter() {
request.extend_from_slice(&(*item as u32).to_be_bytes());
}
netplay.udp_socket.send(&request).unwrap();
@@ -89,25 +117,25 @@ pub fn send_sync_check(device: &mut device::Device) {
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
}
pub fn send_input(netplay: &Netplay, input: (u32, bool)) {
pub fn send_input(netplay: &Netplay, input: ui::input::InputData) {
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);
request.extend_from_slice(&(input.data).to_be_bytes());
request.push(input.pak_change_pressed as u8);
netplay.udp_socket.send(&request).unwrap();
}
pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
pub fn get_input(device: &mut device::Device, channel: usize) -> ui::input::InputData {
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
process_incoming(netplay, &device.ui); // 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);
@@ -118,10 +146,7 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
.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(),
);
ui::video::onscreen_message(&device.ui, "Lost connection to netplay server");
input = Some(InputEvent {
input: 0,
plugin: 0,
@@ -134,10 +159,10 @@ pub fn get_input(device: &mut device::Device, channel: usize) -> (u32, bool) {
&& 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,
)
ui::input::InputData {
data: input.as_ref().unwrap().input,
pak_change_pressed: input.as_ref().unwrap().plugin != 0,
}
}
fn request_input(netplay: &Netplay, channel: usize) {
@@ -152,7 +177,7 @@ fn request_input(netplay: &Netplay, channel: usize) {
netplay.udp_socket.send(&request).unwrap();
}
fn process_incoming(netplay: &mut Netplay) {
fn process_incoming(netplay: &mut Netplay, ui: &ui::Ui) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
match buf[0] {
@@ -166,15 +191,13 @@ fn process_incoming(netplay: &mut Netplay) {
}
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),
);
ui::video::onscreen_message(ui, "Netplay desync detected");
ui::video::onscreen_message(ui, "Netplay desync detected"); // queue this message twice to ensure they see it
}
for dis in 1..5 {
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
{
send_error(netplay, format!("Player {} has disconnected", dis));
ui::video::onscreen_message(ui, &format!("Player {dis} disconnected"));
}
}
netplay.status = current_status;
@@ -206,26 +229,19 @@ fn process_incoming(netplay: &mut Netplay) {
}
}
_ => {
panic! {"unknown UDP packet"}
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);
pub fn init(peer_addr: std::net::SocketAddr, player_number: u8) -> Netplay {
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)
.set_tos_v4(CS4 << 2)
.unwrap();
} else {
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
@@ -281,8 +297,6 @@ pub fn init(
status: 0,
buffer_target,
fast_forward: false,
error_notifier,
gui_ctx,
player_data: [
PlayerData {
lag: 0,
+247
View File
@@ -0,0 +1,247 @@
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 {N} 128-bit integers"))
}
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 mut rdp_state: Vec<u8> = vec![0; ui::video::state_size()];
ui::video::save_state(rdp_state.as_mut_ptr());
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
(
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
"saves",
),
(&rdp_state, "rdp_state"),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(
device.ui.storage.paths.savestate_file_path.clone(),
compressed_file,
)
.unwrap();
ui::video::onscreen_message(
&device.ui,
&format!(
"Savestate created in slot {}",
device.ui.storage.save_state_slot
),
);
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path);
if let Ok(savestate) = &savestate {
let device_bytes = ui::storage::decompress_file(savestate, "device");
let save_bytes = ui::storage::decompress_file(savestate, "saves");
let rdp_state = ui::storage::decompress_file(savestate, "rdp_state");
if let Ok(state) = postcard::from_bytes::<device::Device>(&device_bytes) {
device.ui.storage.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.cheats = state.cheats;
let mut tpak_rom = [vec![], vec![], vec![], vec![]];
for (i, item) in tpak_rom.iter_mut().enumerate() {
*item = device.transferpaks[i].cart.rom.clone();
}
device.transferpaks = state.transferpaks;
for (i, item) in tpak_rom.iter().enumerate() {
device.transferpaks[i].cart.rom = item.clone();
}
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 {
for i in 0..8 {
device.memory.icache[line_index].instruction[i] = device::cpu::decode_opcode(
device,
device.memory.icache[line_index].words[i],
);
}
}
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);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::TransferPak
{
let tpak_handler = device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: device::controller::PakType::TransferPak,
};
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
}
}
ui::audio::close_game_audio(&mut device.ui);
ui::audio::init_game_audio(&mut device.ui, device.ai.freq);
ui::video::load_state(device, rdp_state.as_ptr());
ui::video::onscreen_message(
&device.ui,
&format!(
"Savestate loaded from slot {}",
device.ui.storage.save_state_slot
),
);
} else {
ui::video::onscreen_message(
&device.ui,
&format!(
"Failed to load savestate from slot {}",
device.ui.storage.save_state_slot
),
);
}
}
}
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_fast()
-> [fn(&device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::unmapped::read_mem_fast; 0x2000]
}
pub fn default_memory_read()
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::unmapped::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::unmapped::write_mem; 0x2000]
}
+178 -88
View File
@@ -1,128 +1,218 @@
pub mod audio;
pub mod cheats;
pub mod config;
pub mod gui;
pub mod input;
pub mod netplay;
pub mod storage;
pub mod usb;
pub mod video;
pub mod vru;
pub struct Ui {
pub struct Dirs {
pub config_dir: std::path::PathBuf,
pub data_dir: std::path::PathBuf,
}
pub struct Audio {
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub gain: f32,
}
pub struct Input {
pub joysticks: Vec<sdl3_sys::joystick::SDL_JoystickID>,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
pub config_file_path: std::path::PathBuf,
pub config: config::Config,
}
pub struct Storage {
pub save_type: Vec<storage::SaveTypes>,
pub game_id: String,
pub game_hash: String,
pub paths: storage::Paths,
pub saves: storage::Saves,
pub pak_audio: audio::PakAudio,
pub save_state_slot: u32,
}
pub struct Video {
pub window: *mut sdl3_sys::video::SDL_Window,
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub pak_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_freq: f64,
pub audio_device: u32,
pub num_joysticks: i32,
pub joysticks: *mut sdl3_sys::joystick::SDL_JoystickID,
pub fullscreen: bool,
}
pub struct Usb {
pub usb_tx: Option<tokio::sync::broadcast::Sender<usb::UsbData>>,
pub cart_rx: Option<tokio::sync::broadcast::Receiver<usb::UsbData>>,
}
pub struct Ui {
pub dirs: Dirs,
pub config: config::Config,
pub game_id: String,
pub game_hash: String,
pub with_sdl: bool,
pub audio: Audio,
pub input: Input,
pub storage: Storage,
pub video: Video,
pub usb: Usb,
}
impl Drop for Ui {
fn drop(&mut self) {
unsafe {
sdl3_sys::stdinc::SDL_free(self.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
if self.with_sdl {
unsafe {
sdl3_ttf_sys::ttf::TTF_Quit();
sdl3_sys::init::SDL_Quit();
}
}
write_config(self);
}
}
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) {
panic!("Could not initialize SDL subsystem: {}", 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 ttf_init() {
unsafe {
if !sdl3_ttf_sys::ttf::TTF_Init() {
panic!("Could not initialize TTF");
}
}
}
pub fn get_dirs() -> Dirs {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
Dirs {
config_dir,
data_dir,
}
}
impl Ui {
pub fn new(config_dir: std::path::PathBuf) -> Ui {
fn construct_ui(joysticks: Vec<u32>, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
input: Input {
controllers: [
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
],
keyboard_state: std::ptr::null_mut(),
joysticks,
},
storage: Storage {
save_state_slot: 0,
save_type: vec![],
paths: storage::Paths {
eep_file_path: std::path::PathBuf::new(),
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 {
write_to_disk: true,
eeprom: storage::Save {
data: Vec::new(),
written: false,
},
sram: storage::Save {
data: Vec::new(),
written: false,
},
flash: storage::Save {
data: Vec::new(),
written: false,
},
mempak: storage::Save {
data: Vec::new(),
written: false,
},
sdcard: storage::Save {
data: Vec::new(),
written: false,
},
romsave: storage::RomSave {
data: std::collections::HashMap::new(),
written: false,
},
},
},
config: config::Config::new(),
game_id: String::new(),
game_hash: String::new(),
audio: Audio {
audio_stream: std::ptr::null_mut(),
gain: 1.0,
},
video: Video {
window: std::ptr::null_mut(),
fullscreen: false,
},
usb: Usb {
usb_tx: None,
cart_rx: None,
},
dirs,
with_sdl,
}
}
pub fn default() -> Ui {
Self::construct_ui(vec![], 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");
}
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();
}
}
Ui {
controllers: [
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
input::Controllers {
game_controller: std::ptr::null_mut(),
joystick: std::ptr::null_mut(),
rumble: false,
},
],
keyboard_state: std::ptr::null_mut(),
config_file_path,
config: config_map,
save_type: vec![],
game_id: String::new(),
game_hash: String::new(),
paths: storage::Paths {
eep_file_path: std::path::PathBuf::new(),
fla_file_path: std::path::PathBuf::new(),
sra_file_path: std::path::PathBuf::new(),
pak_file_path: std::path::PathBuf::new(),
romsave_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),
},
pak_audio: audio::PakAudio {
mempak: include_bytes!("../data/mempak.wav").to_vec(),
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
},
window: std::ptr::null_mut(),
audio_stream: std::ptr::null_mut(),
pak_audio_stream: std::ptr::null_mut(),
audio_freq: 0.0,
audio_device: 0,
num_joysticks,
joysticks,
let mut joystick_vec = vec![];
for i in 0..num_joysticks {
joystick_vec.push(unsafe { *joysticks.add(i as usize) });
}
unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }
Self::construct_ui(joystick_vec, true)
}
}
+51 -78
View File
@@ -1,99 +1,72 @@
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) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
init_game_audio(ui, frequency);
}
let audio_spec = sdl3_sys::audio::SDL_AudioSpec {
pub fn init_game_audio(ui: &mut ui::Ui, frequency: u64) {
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: frequency as i32,
channels: 2,
};
ui.audio_device = unsafe {
sdl3_sys::audio::SDL_OpenAudioDevice(
ui.audio.audio_stream = unsafe {
sdl3_sys::audio::SDL_OpenAudioDeviceStream(
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&audio_spec,
&game_audio_spec,
None,
std::ptr::null_mut(),
)
};
if ui.audio_device == 0 {
panic!("Could not open audio device");
if ui.audio.audio_stream.is_null() {
panic!("Could not create audio stream");
}
let mut dst = Default::default();
if !unsafe {
sdl3_sys::audio::SDL_GetAudioDeviceFormat(ui.audio_device, &mut dst, std::ptr::null_mut())
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain)
&& sdl3_sys::audio::SDL_ResumeAudioStreamDevice(ui.audio.audio_stream)
} {
panic!("Could not get audio device format");
panic!("Could not resume audio stream");
}
ui.audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&audio_spec, &dst) };
if ui.audio_stream.is_null() {
return;
}
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.audio_stream) } {
panic!("Could not bind audio stream");
}
let wav_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: 24000,
channels: 1,
};
ui.pak_audio_stream = unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
if !unsafe { sdl3_sys::audio::SDL_BindAudioStream(ui.audio_device, ui.pak_audio_stream) } {
panic!("Could not bind audio stream");
}
ui.audio_freq = audio_spec.freq as f64;
}
pub fn close(ui: &mut ui::Ui) {
close_game_audio(ui);
}
pub fn close_game_audio(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.audio.audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.audio_stream);
ui.audio.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 lower_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) - 0.05;
if ui.audio.gain < 0.0 {
ui.audio.gain = 0.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
}
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
if device.ui.audio_stream.is_null() {
pub fn raise_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) + 0.05;
if ui.audio.gain > 2.0 {
ui.audio.gain = 2.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
}
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
if device.ui.audio.audio_stream.is_null() {
return;
}
@@ -101,25 +74,25 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let mut i = 0;
while i < length as usize / 2 {
// Left channel
primary_buffer[i] = device.rdram.mem[dram_addr + (i * 2) + 2] as i16
| ((device.rdram.mem[dram_addr + (i * 2) + 3] as i16) << 8);
primary_buffer[i] = *device.rdram.mem.get(dram_addr + (i * 2) + 2).unwrap_or(&0) as i16
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 3).unwrap_or(&0) as i16) << 8);
// Right channel
primary_buffer[i + 1] = device.rdram.mem[dram_addr + (i * 2)] as i16
| ((device.rdram.mem[dram_addr + (i * 2) + 1] as i16) << 8);
primary_buffer[i + 1] = *device.rdram.mem.get(dram_addr + (i * 2)).unwrap_or(&0) as i16
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 1).unwrap_or(&0) as i16) << 8);
i += 2;
}
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio_stream) } as f64;
let acceptable_latency = (device.ui.audio_freq * 0.2) * 4.0;
let min_latency = (device.ui.audio_freq * 0.02) * 4.0;
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.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<u8> = vec![0; (min_latency - audio_queued) as usize & !3];
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
device.ui.audio.audio_stream,
silence_buffer.as_ptr() as *const std::ffi::c_void,
silence_buffer.len() as i32,
)
@@ -131,7 +104,7 @@ pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
if audio_queued < acceptable_latency
&& !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio_stream,
device.ui.audio.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
+156
View File
@@ -0,0 +1,156 @@
use crate::device;
use crate::ui;
use crate::ui::gui::AppWindow;
use crate::ui::gui::ErrorDialog;
use slint::ComponentHandle;
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CheatData {
note: String,
pub data: Vec<String>,
pub options: Option<std::collections::BTreeMap<String, String>>,
}
pub type Cheats = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
pub fn cheats_window(app: &AppWindow) {
let weak = app.as_weak();
app.on_cheats_select_rom_clicked(move |rom_dir| {
let select_rom = if !rom_dir.is_empty()
&& let Ok(exists) = std::fs::exists(&rom_dir)
&& exists
{
rfd::AsyncFileDialog::new().set_directory(rom_dir)
} else {
rfd::AsyncFileDialog::new()
}
.set_title("Select ROM")
.add_filter("ROM files", &ui::gui::N64_EXTENSIONS)
.pick_file();
let weak = weak.clone();
tokio::spawn(async move {
if let Some(file) = select_rom.await
&& let Some(rom_contents) = device::get_rom_contents(file.path())
{
let game_crc = ui::storage::get_game_crc(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
weak.upgrade_in_event_loop(move |handle| {
handle.set_cheat_game_name(game_name.into());
})
.unwrap();
let cheats: Cheats =
serde_json::from_slice(include_bytes!("../../data/cheats.json")).unwrap();
if let Some(cheat) = cheats.get(&game_crc) {
let cheat = cheat.clone();
weak.upgrade_in_event_loop(move |handle| {
let cheat_settings = ui::config::Cheats::new();
let game_cheats = cheat_settings.cheats.get(&game_crc).cloned();
handle.set_cheat_game_crc(game_crc.into());
let cheats_vec = slint::VecModel::default();
for item in cheat.iter() {
let mut cheat_enabled = false;
if let Some(game_cheat) = game_cheats.as_ref()
&& game_cheat.contains_key(item.0)
{
cheat_enabled = true;
}
let options_vec = slint::VecModel::default();
if let Some(options) = item.1.options.as_ref() {
for option in options.iter() {
let mut option_enabled = false;
if let Some(game_cheat) = game_cheats.as_ref()
&& game_cheat.contains_key(item.0)
&& let Some(opt) = game_cheat.get(item.0)
&& let Some(opt) = opt
&& opt == option.0
{
option_enabled = true;
}
options_vec.push((option_enabled, option.0.into()));
}
}
let options = slint::ModelRc::from(std::rc::Rc::new(options_vec));
cheats_vec.push((
item.0.clone().into(),
cheat_enabled,
item.1.clone().note.into(),
options,
));
}
#[allow(clippy::type_complexity)]
let cheats_model: std::rc::Rc<
slint::VecModel<(
slint::SharedString,
bool,
slint::SharedString,
slint::ModelRc<(bool, slint::SharedString)>,
)>,
> = std::rc::Rc::new(cheats_vec);
handle.set_cheats(slint::ModelRc::from(cheats_model));
})
.unwrap();
} else {
clear_cheats(&weak, false);
}
} else {
clear_cheats(&weak, true);
weak.upgrade_in_event_loop(move |_handle| {
let message_dialog = ErrorDialog::new().unwrap();
let weak_dialog = message_dialog.as_weak();
message_dialog.on_close_clicked(move || {
weak_dialog.unwrap().window().hide().unwrap();
});
message_dialog.set_text("Could not read ROM".into());
message_dialog.show().unwrap();
})
.unwrap();
}
});
});
let weak = app.as_weak();
app.on_cheats_clear_clicked(move || {
let mut cheats = ui::config::Cheats::new();
cheats.cheats.clear();
clear_cheats(&weak, true);
});
app.on_cheat_toggled(move |game_crc, cheat_name, option, enabled| {
let mut cheats = ui::config::Cheats::new();
let mut game_cheats = cheats
.cheats
.remove(&game_crc.to_string())
.unwrap_or_default();
if enabled {
let cheat_option = if option.is_empty() {
None
} else {
Some(option.into())
};
game_cheats.insert(cheat_name.into(), cheat_option);
} else {
game_cheats.remove(&cheat_name.to_string());
}
cheats.cheats.insert(game_crc.into(), game_cheats);
});
}
fn clear_cheats(weak: &slint::Weak<AppWindow>, clear_name: bool) {
weak.upgrade_in_event_loop(move |handle| {
let cheats_vec = slint::VecModel::default();
#[allow(clippy::type_complexity)]
let cheats_model: std::rc::Rc<
slint::VecModel<(
slint::SharedString,
bool,
slint::SharedString,
slint::ModelRc<(bool, slint::SharedString)>,
)>,
> = std::rc::Rc::new(cheats_vec);
handle.set_cheats(slint::ModelRc::from(cheats_model));
if clear_name {
handle.set_cheat_game_name("".into());
}
})
.unwrap();
}
+112 -10
View File
@@ -1,39 +1,132 @@
use crate::ui;
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct InputKeyButton {
pub enabled: bool,
pub id: i32,
}
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct InputControllerAxis {
pub enabled: bool,
pub id: i32,
pub axis: i16,
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct InputJoystickHat {
pub enabled: bool,
pub id: i32,
pub direction: u8,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct InputProfile {
pub keys: [(bool, i32); ui::input::PROFILE_SIZE],
pub controller_buttons: [(bool, i32); ui::input::PROFILE_SIZE],
pub controller_axis: [(bool, i32, i16); ui::input::PROFILE_SIZE],
pub joystick_buttons: [(bool, 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 keys: [InputKeyButton; ui::input::PROFILE_SIZE],
pub controller_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
pub controller_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
pub joystick_buttons: [InputKeyButton; ui::input::PROFILE_SIZE],
pub joystick_hat: [InputJoystickHat; ui::input::PROFILE_SIZE],
pub joystick_axis: [InputControllerAxis; ui::input::PROFILE_SIZE],
pub dinput: bool,
pub deadzone: i32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Input {
pub input_profiles: std::collections::HashMap<String, InputProfile>,
pub input_profiles: std::collections::BTreeMap<String, InputProfile>,
pub input_profile_binding: [String; 4],
pub controller_assignment: [Option<String>; 4],
pub controller_enabled: [bool; 4],
pub transfer_pak: [bool; 4],
pub emulate_vru: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Video {
pub upscale: bool,
pub upscale: u32,
pub integer_scaling: bool,
pub fullscreen: bool,
pub widescreen: bool,
pub crt: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Emulation {
pub overclock: bool,
pub disable_expansion_pak: bool,
pub usb: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cheats {
pub cheats:
std::collections::HashMap<String, std::collections::HashMap<String, Option<String>>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Config {
pub input: Input,
pub video: Video,
pub emulation: Emulation,
pub rom_dir: std::path::PathBuf,
}
impl Drop for Cheats {
fn drop(&mut self) {
write_cheats(self);
}
}
fn write_cheats(cheats: &Cheats) {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("cheats.json");
let f = std::fs::File::create(file_path).unwrap();
serde_json::to_writer_pretty(f, &cheats).unwrap();
}
impl Cheats {
pub fn new() -> Cheats {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("cheats.json");
let cheats_file = std::fs::read(file_path);
if let Ok(cheats_file) = cheats_file {
let result = serde_json::from_slice(cheats_file.as_ref());
if let Ok(result) = result {
return result;
}
}
Cheats {
cheats: std::collections::HashMap::new(),
}
}
}
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 mut input_profiles = std::collections::HashMap::new();
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let config_file = std::fs::read(file_path);
if let Ok(config_file) = config_file {
let result = serde_json::from_slice(config_file.as_ref());
if let Ok(result) = result {
return result;
}
}
let mut input_profiles = std::collections::BTreeMap::new();
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
Config {
input: Input {
@@ -46,13 +139,22 @@ impl Config {
controller_assignment: [None, None, None, None],
input_profiles,
controller_enabled: [true, false, false, false],
transfer_pak: [false, false, false, false],
emulate_vru: false,
},
video: Video {
upscale: false,
upscale: 1,
integer_scaling: false,
fullscreen: false,
widescreen: false,
crt: false,
},
emulation: Emulation {
overclock: false,
disable_expansion_pak: false,
usb: false,
},
rom_dir: std::path::PathBuf::new(),
}
}
}
+516 -479
View File
File diff suppressed because it is too large Load Diff
+98
View File
@@ -0,0 +1,98 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Page } from "page.slint";
export global AboutData {
in property <string> version;
in property <bool> has_update;
}
export component NewVersionButton {
callback newversion_button_clicked;
HorizontalBox {
alignment: center;
Button {
text: @tr("New version available! Click here to download");
primary: true;
clicked => {
newversion_button_clicked();
}
}
}
}
export component About inherits Page {
callback wiki_button_clicked;
callback discord_button_clicked;
callback newversion_button_clicked;
callback patreon_button_clicked;
callback github_sponsors_button_clicked;
callback source_code_button_clicked;
title: @tr("About");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: center;
Button {
text: @tr("Wiki");
clicked => {
wiki_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Discord Server");
clicked => {
discord_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Source Code");
clicked => {
source_code_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Donate (via Patreon)");
clicked => {
patreon_button_clicked();
}
}
Button {
text: @tr("Donate (via GitHub)");
clicked => {
github_sponsors_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Text {
text: AboutData.version;
}
}
if AboutData.has_update:NewVersionButton {
newversion_button_clicked => {
newversion_button_clicked();
}
}
Rectangle { }
}
}
+154
View File
@@ -0,0 +1,154 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { StandardButton } from "std-widgets.slint";
import { LocalGame, ControllerConfig, Netplay, Cheats, Settings, About } from "pages.slint";
import { SideBar } from "side_bar.slint";
import { SettingsData } from "settings_page.slint";
import { AboutData } from "about_page.slint";
import { ControllerData } from "controller_page.slint";
import { State } from "localgame_page.slint";
import { CheatsData } from "cheat_page.slint";
export { InputProfileDialog } from "input_profile.slint";
export { VruDialog } from "vru_dialog.slint";
export { NetplayCreate } from "netplay_create.slint";
export { NetplayJoin } from "netplay_join.slint";
export { NetplayWait } from "netplay_wait.slint";
export { CustomNetplayServer } from "netplay_page.slint";
export { DispatcherDialog } from "netplay_create.slint";
export component AppWindow inherits Window {
callback wiki_button_clicked;
callback discord_button_clicked;
callback newversion_button_clicked;
callback patreon_button_clicked;
callback github_sponsors_button_clicked;
callback source_code_button_clicked;
callback open_rom_button_clicked;
callback saves_folder_button_clicked;
callback input_profile_button_clicked;
callback create_session_button_clicked;
callback join_session_button_clicked;
callback netplay_discord_button_clicked;
callback netplay_feedback_button_clicked;
callback cheats_select_rom_clicked(string);
callback cheats_clear_clicked;
callback cheat_toggled(string, string, string, bool);
in property version <=> AboutData.version;
in property has_update <=> AboutData.has_update;
in-out property integer_scaling <=> SettingsData.integer_scaling;
in-out property fullscreen <=> SettingsData.fullscreen;
in-out property widescreen <=> SettingsData.widescreen;
in-out property apply_crt_shader <=> SettingsData.apply_crt_shader;
in-out property overclock_n64_cpu <=> SettingsData.overclock_n64_cpu;
in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak;
in-out property resolution <=> SettingsData.resolution;
in-out property emulate_usb <=> SettingsData.emulate_usb;
in-out property rom_dir <=> SettingsData.rom_dir;
in-out property emulate_vru <=> ControllerData.emulate_vru;
in-out property controller_enabled <=> ControllerData.controller_enabled;
in-out property controller_changed <=> ControllerData.controller_changed;
in-out property transferpak <=> ControllerData.transferpak;
in-out property selected_profile_binding <=> ControllerData.selected_profile_binding;
in-out property input_profiles <=> ControllerData.input_profiles;
in-out property controller_names <=> ControllerData.controller_names;
in-out property selected_controller <=> ControllerData.selected_controller;
in-out property game_running <=> State.game_running;
in-out property blank_profiles <=> ControllerData.blank_profiles;
in-out property cheat_game_name <=> CheatsData.cheat_game_name;
in-out property cheat_game_crc <=> CheatsData.cheat_game_crc;
in-out property cheats <=> CheatsData.cheats;
preferred-width: 700px;
preferred-height: 500px;
title: @tr("Gopher64");
icon: @image-url("../../../data/icon/gopher64.png");
HorizontalLayout {
side-bar := SideBar {
title: @tr("Gopher64");
model: [
@tr("Menu" => "Local Game"),
@tr("Menu" => "Controller Config"),
@tr("Menu" => "Netplay"),
@tr("Menu" => "Cheats"),
@tr("Menu" => "Settings"),
@tr("Menu" => "About")
];
}
if(side-bar.current-item == 0): LocalGame {
open_rom_button_clicked => {
open_rom_button_clicked();
}
saves_folder_button_clicked => {
saves_folder_button_clicked();
}
newversion_button_clicked => {
newversion_button_clicked();
}
}
if(side-bar.current-item == 1): ControllerConfig {
input_profile_button_clicked => {
input_profile_button_clicked();
}
}
if(side-bar.current-item == 2): Netplay {
create_session_button_clicked => {
create_session_button_clicked();
}
join_session_button_clicked => {
join_session_button_clicked();
}
netplay_discord_button_clicked => {
netplay_discord_button_clicked();
}
netplay_feedback_button_clicked => {
netplay_feedback_button_clicked();
}
}
if(side-bar.current-item == 3): Cheats {
cheats_select_rom_clicked(rom_dir) => {
cheats_select_rom_clicked(rom_dir);
}
cheats_clear_clicked => {
cheats_clear_clicked();
}
cheat_toggled(game_crc, cheat_name, option, enabled) => {
cheat_toggled(game_crc,cheat_name,option,enabled);
}
}
if(side-bar.current-item == 4): Settings { }
if(side-bar.current-item == 5): About {
wiki_button_clicked => {
wiki_button_clicked();
}
discord_button_clicked => {
discord_button_clicked();
}
newversion_button_clicked => {
newversion_button_clicked();
}
patreon_button_clicked => {
patreon_button_clicked();
}
github_sponsors_button_clicked => {
github_sponsors_button_clicked();
}
source_code_button_clicked => {
source_code_button_clicked();
}
}
}
}
export component ErrorDialog inherits Dialog {
in-out property <string> text;
title: @tr("Error");
Text {
text: text;
}
StandardButton {
kind: close;
}
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { ListView, HorizontalBox, VerticalBox, Button, GroupBox, CheckBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { SettingsData } from "settings_page.slint";
export global CheatsData {
in-out property <string> cheat_game_name;
in-out property <string> cheat_game_crc;
in-out property <[{cheat_name:string, notes:string, enabled:bool, options:[{option:string, enabled:bool}]}]> cheats;
}
export component Cheats inherits Page {
title: @tr("Cheats");
callback cheats_select_rom_clicked(string);
callback cheats_clear_clicked;
callback cheat_toggled(string, string, string, bool);
VerticalBox {
alignment: start;
HorizontalBox {
alignment: start;
if CheatsData.cheat_game_name == "": Button {
text: @tr("Select ROM");
clicked => {
cheats_select_rom_clicked(SettingsData.rom_dir);
}
}
if CheatsData.cheat_game_name != "": Button {
text: CheatsData.cheat_game_name;
clicked => {
cheats_select_rom_clicked(SettingsData.rom_dir);
}
}
Rectangle {
width: 20px;
}
Button {
text: @tr("Clear Cheats For All ROMs");
clicked => {
cheats_clear_clicked();
}
}
}
Rectangle { }
if CheatsData.cheats.length > 0:ListView {
preferred-height: 500px;
preferred-width: 520px;
for data in CheatsData.cheats: VerticalBox {
Text {
text: data.cheat_name;
font-size: 16px;
wrap: word-wrap;
}
if data.notes != "":Text {
text: data.notes;
wrap: word-wrap;
}
if data.options.length == 0: CheckBox {
text: @tr("Enabled");
checked: data.enabled;
toggled => {
data.enabled = self.checked;
cheat_toggled(CheatsData.cheat_game_crc,data.cheat_name,"",self.checked);
}
}
for item[index] in data.options: CheckBox {
text: item.option;
enabled: !data.enabled || (data.enabled && item.enabled);
checked: item.enabled;
toggled => {
item.enabled = self.checked;
data.enabled = self.checked;
cheat_toggled(CheatsData.cheat_game_crc,data.cheat_name,item.option,self.checked);
}
}
Rectangle { }
}
}
if CheatsData.cheats.length == 0 && CheatsData.cheat_game_name != "": Text {
text: @tr("No Cheats Available");
}
}
}
+125
View File
@@ -0,0 +1,125 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { CheckBox, ComboBox, Button, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { State } from "localgame_page.slint";
export global ControllerData {
in-out property <bool> emulate_vru;
in-out property <[bool]> controller_enabled;
in-out property <[bool]> transferpak;
in-out property <[int]> selected_profile_binding: [-1, -1, -1, -1];
in-out property <[string]> input_profiles;
in-out property <[string]> controller_names;
in-out property <[int]> selected_controller;
in-out property <[bool]> controller_changed;
in-out property <bool> blank_profiles;
}
export component ControllerConfig inherits Page {
callback input_profile_button_clicked;
title: @tr("Controller Configuration");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: start;
Button {
text: @tr("Configure Input Profile");
enabled: !State.game_running;
clicked => {
input_profile_button_clicked();
}
}
}
Rectangle { }
HorizontalBox {
GroupBox {
title: @tr("Port");
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: Text {
text: player + 1;
vertical-alignment: center;
}
}
}
GroupBox {
title: @tr("Enabled");
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: CheckBox {
checked: ControllerData.controller_enabled[player];
toggled => {
ControllerData.controller_enabled[player] = self.checked;
}
}
}
}
GroupBox {
title: @tr("Emulate VRU");
VerticalLayout {
alignment: space-between;
for valid in [false, false, false, true]: CheckBox {
enabled: valid;
checked: valid && ControllerData.emulate_vru;
toggled => {
ControllerData.emulate_vru = self.checked;
}
}
}
}
GroupBox {
title: @tr("Transfer Pak");
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: CheckBox {
checked: ControllerData.transferpak[player];
toggled => {
ControllerData.transferpak[player] = self.checked;
}
}
}
}
if !ControllerData.blank_profiles: GroupBox {
title: @tr("Profile");
horizontal-stretch: 0.25;
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: ComboBox {
model: ControllerData.input_profiles;
min-width: self.preferred-width > 150px ? self.preferred-width : 150px;
current-index: ControllerData.selected_profile_binding[player];
selected => {
ControllerData.selected_profile_binding[player] = self.current-index;
}
}
}
}
GroupBox {
title: @tr("Controller");
horizontal-stretch: 0.75;
VerticalLayout {
alignment: space-between;
for player in [0, 1, 2, 3]: ComboBox {
model: ControllerData.controller_names;
min-width: self.preferred-width > 300px ? self.preferred-width : 300px;
current-index: ControllerData.selected_controller[player];
selected => {
ControllerData.selected_controller[player] = self.current-index;
ControllerData.controller_changed[player] = true;
}
}
}
}
}
}
}
-878
View File
@@ -1,878 +0,0 @@
use crate::device;
use crate::ui::gui;
use crate::ui::gui::GopherEguiApp;
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();
tokio::spawn(async move {
if let Ok(response) =
reqwest::get("https://m64p.s3.amazonaws.com/servers.json").await
{
if let Ok(servers) = response
.json::<std::collections::HashMap<String, String>>()
.await
{
let _ = tx.send(servers).await;
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 task = rfd::AsyncFileDialog::new().pick_file();
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();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"Open ROM".to_string(),
vec![],
))
.await;
}
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!("CARGO_PKG_VERSION").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().clone()
{
// 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 = std::str::from_utf8(&rom_contents[0x20..0x20 + 0x14])
.unwrap()
.trim()
.replace('\0', "")
.to_string();
let _ = tx
.send((hash, game_name, file.file_name(), rom_contents))
.await;
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"Invalid ROM".to_string(),
vec![],
))
.await;
}
}
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
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!("CARGO_PKG_VERSION").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 task = rfd::AsyncFileDialog::new().pick_file();
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();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await
} else {
let _ = tx
.send((
"".to_string(),
"".to_string(),
"No ROM selected".to_string(),
vec![],
))
.await;
}
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;
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();
}
}
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();
};
});
}
+69
View File
@@ -0,0 +1,69 @@
import { StandardButton, HorizontalBox, VerticalBox, Button, LineEdit, CheckBox, Slider } from "std-widgets.slint";
export global InputProfileData {
in-out property <bool> dinput;
in-out property <string> profile_name;
in-out property <int> deadzone;
}
export component InputProfileDialog inherits Window {
title: @tr("Create Input Profile");
callback profile_creation_button_clicked;
in-out property dinput <=> InputProfileData.dinput;
in-out property profile_name <=> InputProfileData.profile_name;
in-out property deadzone <=> InputProfileData.deadzone;
VerticalBox {
HorizontalBox {
LineEdit {
placeholder-text: @tr("Enter profile name");
preferred-width: 300px;
edited(text) => {
InputProfileData.profile_name = text;
}
}
}
HorizontalBox {
alignment: center;
Text {
text: @tr("Deadzone Percentage");
vertical-alignment: center;
}
Slider {
min-width: 160px;
maximum: 99;
value: InputProfileData.deadzone;
changed(value) => {
InputProfileData.deadzone = value;
}
}
Text {
text: InputProfileData.deadzone + "%";
vertical-alignment: center;
}
}
HorizontalBox {
alignment: center;
CheckBox {
text: @tr("Use DirectInput");
checked: InputProfileData.dinput;
toggled => {
InputProfileData.dinput = self.checked;
}
}
}
HorizontalBox {
Button {
text: @tr("Start Profile Creation Wizard");
enabled: InputProfileData.profile_name != "" && InputProfileData.profile_name != "default";
clicked => {
profile_creation_button_clicked();
}
}
}
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { Button, HorizontalBox, VerticalBox } from "std-widgets.slint";
import { Page } from "page.slint";
import { AboutData, NewVersionButton } from "about_page.slint";
export global State {
in-out property <bool> game_running;
}
export component LocalGame inherits Page {
callback open_rom_button_clicked;
callback saves_folder_button_clicked;
callback newversion_button_clicked;
title: @tr("Local Game");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: center;
Button {
text: @tr("Open ROM");
enabled: !State.game_running;
clicked => {
open_rom_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Open Saves Folder");
clicked => {
saves_folder_button_clicked();
}
}
}
if AboutData.has_update:NewVersionButton {
newversion_button_clicked => {
newversion_button_clicked();
}
}
Rectangle { }
}
}
+130
View File
@@ -0,0 +1,130 @@
import { LineEdit, HorizontalBox, VerticalBox, Button, ComboBox } from "std-widgets.slint";
import { NetplayData } from "netplay_page.slint";
export component NetplayCreate inherits Window {
title: @tr("Netplay: Create Session");
in-out property server_names <=> NetplayData.server_names;
in-out property server_urls <=> NetplayData.server_urls;
in-out property custom_server_url <=> NetplayData.custom_server_url;
in-out property game_name <=> NetplayData.game_name;
in-out property game_hash <=> NetplayData.game_hash;
in-out property game_cheats <=> NetplayData.game_cheats;
in-out property pending_session <=> NetplayData.pending_session;
in-out property rom_path <=> NetplayData.rom_path;
in-out property peer_addr <=> NetplayData.peer_addr;
in-out property rom_dir <=> NetplayData.rom_dir;
callback get_custom_url();
callback select_rom(string);
callback create_session(string, string, string, string, string, string, string);
VerticalBox {
HorizontalBox {
VerticalBox {
Text {
text: @tr("Session Name:");
}
LineEdit {
placeholder_text: @tr("Enter session name");
edited(text) => {
NetplayData.session_name = text;
}
}
}
VerticalBox {
Text {
text: @tr("Password (Optional):");
}
LineEdit {
placeholder_text: @tr("Enter session password");
edited(text) => {
NetplayData.password = text;
}
}
}
}
VerticalBox {
Text {
text: @tr("Player Name:");
}
LineEdit {
placeholder_text: @tr("Enter player name");
edited(text) => {
NetplayData.player_name = text;
}
}
}
VerticalBox {
Text {
text: @tr("Server:");
}
HorizontalBox {
server := ComboBox {
model: NetplayData.server_names;
selected => {
if (self.current-index >= 0) {
if (self.current-value == "Custom") {
get_custom_url();
}
}
}
}
}
}
if NetplayData.game_name == "": Button {
text: @tr("Select ROM");
clicked => {
select_rom(NetplayData.rom_dir);
}
}
if NetplayData.game_name != "": Button {
text: NetplayData.game_name;
clicked => {
select_rom(NetplayData.rom_dir);
}
}
Rectangle { }
if NetplayData.game_cheats != "":Text {
text: @tr("This game has cheats enabled. Cheats will be synchronized with other players.");
}
Rectangle { }
Button {
property <string> server_url;
text: @tr("Create Session");
enabled: NetplayData.session_name != "" && NetplayData.player_name != "" && NetplayData.game_name != "" && !NetplayData.pending_session;
clicked => {
NetplayData.pending_session = true;
if (server.current-value == "Custom") {
server_url = "ws://" + NetplayData.custom_server_url;
} else {
server_url = NetplayData.server_urls[server.current-index];
}
create_session(
server_url,
NetplayData.session_name,
NetplayData.player_name,
NetplayData.game_name,
NetplayData.game_hash,
NetplayData.game_cheats,
NetplayData.password);
}
}
}
}
export component DispatcherDialog inherits Dialog {
title: @tr("Creating Server");
Text {
text: "Creating server, please wait...This may take about 30 seconds.";
}
}
+139
View File
@@ -0,0 +1,139 @@
import { VerticalBox, LineEdit, StandardTableView, HorizontalBox, ComboBox, Button } from "std-widgets.slint";
import { NetplayData } from "netplay_page.slint";
export component NetplayJoin inherits Window {
title: @tr("Netplay: Join Session");
in-out property server_names <=> NetplayData.server_names;
in-out property server_urls <=> NetplayData.server_urls;
in-out property custom_server_url <=> NetplayData.custom_server_url;
in-out property sessions <=> NetplayData.sessions;
in-out property current_session <=> NetplayData.current_session;
in-out property room_urls <=> NetplayData.room_urls;
in-out property room_ports <=> NetplayData.room_ports;
in-out property game_name <=> NetplayData.game_name;
in-out property game_hash <=> NetplayData.game_hash;
in-out property game_cheats <=> NetplayData.game_cheats;
in-out property peer_addr <=> NetplayData.peer_addr;
in-out property pending_session <=> NetplayData.pending_session;
in-out property pending_refresh <=> NetplayData.pending_refresh;
in-out property rom_path <=> NetplayData.rom_path;
in-out property rom_dir <=> NetplayData.rom_dir;
callback refresh_session();
callback select_rom(string);
callback join_session(string, string, string, string, int);
preferred-width: 700px;
VerticalBox {
HorizontalBox {
VerticalBox {
Text {
text: @tr("Player Name:");
}
LineEdit {
placeholder_text: @tr("Enter player name");
edited(text) => {
NetplayData.player_name = text;
}
}
}
VerticalBox {
Text {
text: @tr("Custom Server URL (optional):");
}
LineEdit {
placeholder-text: @tr("example.com:45000");
text: NetplayData.custom_server_url;
edited(text) => {
NetplayData.custom_server_url = text;
}
}
}
}
if NetplayData.pending_refresh: Text {
text: @tr("Refreshing session list, please wait...");
horizontal-alignment: center;
vertical-alignment: center;
}
if !NetplayData.pending_refresh && NetplayData.sessions.length > 0: Text {
text: @tr("Click on a session below to select it");
horizontal-alignment: center;
vertical-alignment: center;
}
if !NetplayData.pending_refresh && NetplayData.sessions.length == 0: Text {
text: @tr("No sessions available. Please refresh to check again.");
horizontal-alignment: center;
vertical-alignment: center;
}
StandardTableView {
columns: [
{ title: "Server" },
{ title: "Session Name" },
{ title: "Game Name" },
{ title: "Password" },
{ title: "Cheats" }
];
rows: NetplayData.sessions;
current-row: NetplayData.current_session;
current-row-changed(current-row) => {
NetplayData.current_session = current-row;
}
}
HorizontalBox {
Rectangle { }
Button {
enabled: !NetplayData.pending_refresh;
text: @tr("Refresh Session List");
clicked => {
NetplayData.pending_refresh = true;
NetplayData.current_session = -1;
NetplayData.sessions = [];
NetplayData.room_urls = [];
NetplayData.room_ports = [];
refresh_session();
}
}
}
HorizontalBox {
LineEdit {
placeholder-text: @tr("Password (if required)");
edited(text) => {
NetplayData.password = text;
}
}
if NetplayData.game_name == "": Button {
text: @tr("Select ROM");
clicked => {
select_rom(NetplayData.rom_dir);
}
}
if NetplayData.game_name != "": Button {
text: NetplayData.game_name;
clicked => {
select_rom(NetplayData.rom_dir);
}
}
Button {
text: @tr("Join Session");
enabled: NetplayData.player_name != "" && NetplayData.current_session >= 0 && NetplayData.game_name != "" && !NetplayData.pending_session;
clicked => {
NetplayData.pending_session = true;
join_session(
NetplayData.player_name,
NetplayData.game_hash,
NetplayData.password,
NetplayData.room_urls[NetplayData.current_session],
NetplayData.room_ports[NetplayData.current_session]);
}
}
}
}
}
+97
View File
@@ -0,0 +1,97 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { StandardButton, VerticalBox, HorizontalBox, Button, LineEdit } from "std-widgets.slint";
import { Page } from "page.slint";
export global NetplayData {
in-out property <[string]> server_names;
in-out property <[string]> server_urls;
in-out property <string> player_name;
in-out property <string> game_name;
in-out property <string> game_hash;
in-out property <string> game_cheats;
in-out property <string> rom_path;
in-out property <string> password;
in-out property <string> peer_addr;
in-out property <bool> pending_session;
in-out property <bool> pending_refresh;
in-out property <string> session_name;
in-out property <[[StandardListViewItem]]> sessions;
in-out property <[string]> room_urls;
in-out property <[int]> room_ports;
in-out property <int> current_session: -1;
in-out property <string> rom_dir;
in-out property <string> custom_server_url;
}
export component Netplay inherits Page {
callback create_session_button_clicked;
callback join_session_button_clicked;
callback netplay_discord_button_clicked;
callback netplay_feedback_button_clicked;
title: @tr("Netplay");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: center;
Button {
text: @tr("Create Session");
clicked => {
create_session_button_clicked();
}
}
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Join Session");
clicked => {
join_session_button_clicked();
}
}
}
Rectangle {
height: 20px;
}
HorizontalBox {
alignment: center;
Button {
text: @tr("Netplay Discord Channel");
clicked => {
netplay_discord_button_clicked();
}
}
Button {
text: @tr("Provide Feedback");
clicked => {
netplay_feedback_button_clicked();
}
}
}
}
}
export component CustomNetplayServer inherits Dialog {
in-out property custom_server_url <=> NetplayData.custom_server_url;
title: @tr("Custom Netplay Server");
preferred-width: 400px;
callback ok_clicked(string);
server_url := LineEdit {
text: NetplayData.custom_server_url;
placeholder-text: "example.com:45000";
}
StandardButton {
kind: ok;
clicked => {
NetplayData.custom_server_url = server_url.text;
ok_clicked(NetplayData.custom_server_url);
}
}
}
+110
View File
@@ -0,0 +1,110 @@
import { VerticalBox, HorizontalBox, TextEdit, ScrollView, LineEdit, Button, GridBox } from "std-widgets.slint";
import { NetplayData } from "netplay_page.slint";
export global NetplayWaitData {
in-out property <string> motd;
in-out property <[string]> players;
in-out property <int> port;
in-out property <bool> can_start;
in-out property <string> chat_text;
in-out property <string> ping;
}
export component NetplayWait inherits Window {
title: @tr("Netplay: Waiting Room");
preferred-width: 400px;
preferred-height: 400px;
in-out property players <=> NetplayWaitData.players;
in-out property session_name <=> NetplayData.session_name;
in-out property game_name <=> NetplayData.game_name;
in-out property rom_path <=> NetplayData.rom_path;
in-out property motd <=> NetplayWaitData.motd;
in-out property port <=> NetplayWaitData.port;
in-out property can_start <=> NetplayWaitData.can_start;
in-out property chat_text <=> NetplayWaitData.chat_text;
in-out property ping <=> NetplayWaitData.ping;
callback send_chat_message(string);
callback begin_game;
VerticalBox {
Text {
text: @tr("Session Name: ") + NetplayData.session_name;
font-size: 15px;
horizontal-alignment: center;
}
Text {
text: @tr("Game Name: ") + NetplayData.game_name;
font-size: 15px;
horizontal-alignment: center;
}
Text {
text: @tr("Ping: ") + NetplayWaitData.ping;
horizontal-alignment: center;
}
GridBox {
Row {
Text {
text: "Player 1: " + players[0];
}
Text {
text: "Player 2: " + players[1];
}
}
Row {
Text {
text: "Player 3: " + players[2];
}
Text {
text: "Player 4: " + players[3];
}
}
}
TextEdit {
read-only: true;
text: NetplayWaitData.chat_text;
}
HorizontalBox {
chat_message := LineEdit {
placeholder_text: @tr("Enter chat message");
key-released(event) => {
if (event.text == Key.Return && chat_message.text != "") {
send_chat_message(chat_message.text);
chat_message.text = "";
}
accept
}
}
Button {
text: @tr("Send Message");
enabled: chat_message.text != "";
clicked => {
send_chat_message(chat_message.text);
chat_message.text = "";
}
}
}
Text {
text: NetplayWaitData.motd;
wrap: word-wrap;
}
Button {
text: @tr("Start Game");
enabled: NetplayWaitData.can_start;
clicked => {
NetplayWaitData.can_start = false;
begin_game();
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { Switch, GridBox, ListView, ScrollView, HorizontalBox, VerticalBox, Palette } from "std-widgets.slint";
export component Page inherits VerticalBox {
in property <string> title: "title";
accessible-role: tab-panel;
accessible-label: root.title;
HorizontalBox {
Text {
font-size: 20px;
text <=> root.title;
}
// Spacer
Rectangle { }
}
@children
}
+6
View File
@@ -0,0 +1,6 @@
export { LocalGame } from "localgame_page.slint";
export { ControllerConfig } from "controller_page.slint";
export { Netplay } from "netplay_page.slint";
export { Cheats } from "cheat_page.slint";
export { Settings } from "settings_page.slint";
export { About } from "about_page.slint";
+121
View File
@@ -0,0 +1,121 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { ComboBox, HorizontalBox, VerticalBox, CheckBox } from "std-widgets.slint";
import { Page } from "page.slint";
export global SettingsData {
in-out property <bool> integer_scaling;
in-out property <bool> fullscreen;
in-out property <bool> widescreen;
in-out property <bool> apply_crt_shader;
in-out property <bool> overclock_n64_cpu;
in-out property <bool> disable_expansion_pak;
in-out property <int> resolution;
in-out property <bool> emulate_usb;
in-out property <string> rom_dir;
}
export component Settings inherits Page {
title: @tr("Settings");
VerticalBox {
alignment: start;
HorizontalBox {
alignment: start;
Text {
text: @tr("Resolution:");
vertical-alignment: center;
}
ComboBox {
model: ["1x", "2x", "4x", "8x"];
current-index: SettingsData.resolution;
selected => {
SettingsData.resolution = self.current-index;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Integer Scaling");
checked: SettingsData.integer_scaling;
toggled => {
SettingsData.integer_scaling = self.checked;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Fullscreen (Esc closes game)");
checked: SettingsData.fullscreen;
toggled => {
SettingsData.fullscreen = self.checked;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Widescreen (strech)");
checked: SettingsData.widescreen;
toggled => {
SettingsData.widescreen = self.checked;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Apply CRT shader");
checked: SettingsData.apply_crt_shader;
toggled => {
SettingsData.apply_crt_shader = self.checked;
}
}
}
Rectangle {
height: 20px;
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Overclock N64 CPU (may cause bugs)");
checked: SettingsData.overclock_n64_cpu;
toggled => {
SettingsData.overclock_n64_cpu = self.checked;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Disable Expansion Pak");
checked: SettingsData.disable_expansion_pak;
toggled => {
SettingsData.disable_expansion_pak = self.checked;
}
}
}
HorizontalBox {
alignment: start;
CheckBox {
text: @tr("Emulate UNFLoader (reload emulator after changing)");
checked: SettingsData.emulate_usb;
toggled => {
SettingsData.emulate_usb = self.checked;
}
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: MIT
import { HorizontalBox, VerticalBox, Palette } from "std-widgets.slint";
component SideBarItem inherits Rectangle {
in property <int> tab-index;
in property <bool> selected;
in property <bool> has-focus;
in-out property <string> text <=> label.text;
callback clicked <=> touch.clicked;
min-height: l.preferred-height;
accessible-role: tab;
accessible-label: root.text;
accessible-item-index: root.tab-index;
accessible-item-selectable: true;
accessible-item-selected: root.selected;
accessible-action-default => {
self.clicked();
}
states [
pressed when touch.pressed: {
state.opacity: 0.8;
}
hover when touch.has-hover: {
state.opacity: 0.6;
}
selected when root.selected: {
state.opacity: 1;
}
focused when root.has-focus: {
state.opacity: 0.8;
}
]
state := Rectangle {
opacity: 0;
background: Palette.background;
animate opacity { duration: 150ms; }
}
l := HorizontalBox {
y: (parent.height - self.height) / 2;
spacing: 0px;
label := Text {
vertical-alignment: center;
accessible-role: none;
}
}
touch := TouchArea {
width: 100%;
height: 100%;
}
}
export component SideBar inherits Rectangle {
in property <[string]> model: [];
in property <string> title <=> label.text;
out property <int> current-item: 0;
out property <int> current-focused: fs.has-focus ? fs.focused-tab : -1; // The currently focused tab
width: 180px;
forward-focus: fs;
Rectangle {
background: Palette.background.darker(0.2);
}
VerticalBox {
padding-left: 0px;
padding-right: 0px;
alignment: start;
label := Text {
font-size: 16px;
horizontal-alignment: center;
}
navigation := VerticalLayout {
alignment: start;
vertical-stretch: 0;
accessible-role: tab-list;
accessible-delegate-focus: root.current-focused >= 0 ? root.current-focused : root.current-item;
accessible-label: root.title;
accessible-item-count: root.model.length;
fs := FocusScope {
key-pressed(event) => {
if (event.text == "\n") {
root.current-item = root.current-focused;
return accept;
}
if (event.text == Key.UpArrow) {
self.focused-tab = Math.max(self.focused-tab - 1, 0);
return accept;
}
if (event.text == Key.DownArrow) {
self.focused-tab = Math.min(self.focused-tab + 1, root.model.length - 1);
return accept;
}
return reject;
}
key-released(event) => {
if (event.text == " ") {
root.current-item = root.current-focused;
return accept;
}
return reject;
}
property <int> focused-tab: 0;
x: 0;
width: 0; // Do not react on clicks
}
for item[index] in root.model: SideBarItem {
clicked => {
root.current-item = index;
}
tab-index: index;
has-focus: index == root.current-focused;
text: item;
selected: index == root.current-item;
}
}
VerticalLayout {
bottom := VerticalBox {
padding-top: 0px;
padding-bottom: 0px;
@children
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More