Compare commits

..

259 Commits

Author SHA1 Message Date
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
Logan McNaughton 6820da889c add icon for windows (#179) 2025-01-25 19:00:38 +01:00
Logan McNaughton 6292459f8b install proper SDL Dependencies (#178)
* install proper SDL Dependencies

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

* more

* more

* more

* more

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

* more

* more

* more

* source

* more

* destory

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* fix

* fix

* more

* more

* more

* more

* more

* more

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

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

* Set TOS value on packets

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

* use wrapping add

* non blocking

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* message

* more

* close

* fixes

* redirect rumble

* more

* fix

* more

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

* more

* more

* more

* more

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

* more

* more

* more

* more

* more

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

* more

* more

* more

* wait room

* more

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

* more

* auth

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

* more

* more

* more

* more

* tokio

* more

* more

* more

* video

* functions

* more

* use rusttls

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

* more

* Update main.rs

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

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

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

* more

* more

* more

* more

* more

* more

* more

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

* more

* more

* more

* more

* more

* more

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

* more

* more

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

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

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

* more

* fix
2025-01-14 09:43:04 +01:00
Logan McNaughton 344f41cf47 Add minimum rust version (#138) 2025-01-13 22:56:33 +01:00
Logan McNaughton 9978e28cc3 Bump to 0.1.18 (#137) 2025-01-13 22:30:34 +01:00
Logan McNaughton d3c26fb359 Remove loops in RSP code (#136)
* Remove loops in RSP cose

* more

* more

* more

* more

* more
2025-01-13 22:21:35 +01:00
Logan McNaughton cd106345c9 use weave deinterlacing (#135) 2025-01-13 14:58:35 +01:00
Logan McNaughton f75636f7e5 Cleanup unsafe functions (#134)
* cleanup unsafe blocks

* comment out flush mode stuff

* remove unsafe from input
2025-01-13 10:16:27 +01:00
Logan McNaughton 97e4921b6a Don't transmute values in RSP code (#132)
* remove transmute

* fix

* more

* more

* fix
2025-01-13 09:13:14 +01:00
dependabot[bot] 21300ab7db Update dirs requirement from 5.0 to 6.0 (#133)
---
updated-dependencies:
- dependency-name: dirs
  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-01-13 06:49:13 +01:00
Logan McNaughton 8fd7688aa7 remove un-needed var 2025-01-12 22:17:47 +01:00
Logan McNaughton a013e72fda set aarch64 linker from env variable (#131) 2025-01-12 20:09:15 +01:00
Logan McNaughton f2812b0434 Bump to 0.1.17 (#130) 2025-01-12 18:31:10 +01:00
Logan McNaughton 5f84c18b39 Build aarch64 for Linux (#129)
* linux aarch

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2025-01-12 18:20:42 +01:00
Logan McNaughton 9058cd1715 aarch64 support (#128)
* First step for aarch64

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* test with cpp

* more

* more

* more

* more

* more

* more

* more

* more
2025-01-12 09:20:06 +01:00
Logan McNaughton 5d00eccb19 Use bindgen for FFI interface (#127)
* Use bindgen for FFI interface

* use allow list
2025-01-11 18:18:01 +01:00
Logan McNaughton e821626f2b Bump to 0.1.16 (#126) 2025-01-11 17:06:41 +01:00
Logan McNaughton 63b8b79dd7 More RSP cleanup 2025-01-11 15:02:22 +01:00
Logan McNaughton 90c9bfbf09 More RSP Cleanup (#125) 2025-01-11 14:46:13 +01:00
Logan McNaughton 9a3a56d0c6 RSP cleanup (#124) 2025-01-11 14:34:16 +01:00
Logan McNaughton e71d5a1b2d add build flags for aarch64 (#123) 2025-01-10 11:16:09 +01:00
Logan McNaughton a731b870da add portable mode (#122)
* add portable mode

* readme

* more

* more

* more
2025-01-10 09:26:40 +01:00
Logan McNaughton c54356e6f4 Fix VRU demo encoding (#120) 2025-01-09 21:10:03 +01:00
Logan McNaughton b2d2089d95 Remove un-needed entry in Cargo.toml (#119) 2025-01-09 18:39:39 +01:00
Logan McNaughton a47801b9cb Minor changes to boot latest LibDragon ROMs (#117) 2025-01-09 16:11:48 +01:00
Logan McNaughton 9a76bcd303 Bump to 0.1.15 (#116) 2025-01-09 10:38:34 +01:00
Logan McNaughton ba6fe1bc05 VRU support (#115)
* VRU support

* clean

* more

* more

* more

* more

* more

* more

* more

* add mappings

* more

* more

* more

* more

* VRU window

* more

* more

* more

* more

* more

* change fony

* more

* clippy

* clip

* demo

* demo

* if

* fix

* if

* if
2025-01-09 10:31:21 +01:00
Logan McNaughton e02add9676 Remove pub modifier for consts (#114) 2025-01-07 19:54:15 +01:00
Logan McNaughton 24b464fea4 Add option for high-res graphics (#111) 2025-01-07 13:38:57 +01:00
Logan McNaughton 89673df459 Add multi controller option (#110) 2025-01-07 12:34:36 +01:00
Logan McNaughton 774bb672fd Fix cart ROM save (#109) 2025-01-07 12:04:37 +01:00
Logan McNaughton f5d4160a5b Bump to 0.1.14 (#108) 2025-01-07 09:17:46 +01:00
Logan McNaughton 913ec771bb new speed limiter code (#107)
* new speed limiter code

* add version
2025-01-07 09:11:59 +01:00
Logan McNaughton c3dee292a2 build update (#106) 2025-01-06 19:11:27 +01:00
Logan McNaughton 508ae5bc17 build updates (#105)
* build updates

* more

* more

* more

* more
2025-01-06 19:08:47 +01:00
Logan McNaughton 376fc85660 Fix typo (#104) 2025-01-06 18:15:52 +01:00
Logan McNaughton 31bad0416d Code cleanup (#103) 2025-01-06 10:08:58 +01:00
Logan McNaughton e103118407 Use github action to set up rust (#102) 2025-01-01 16:46:54 +01:00
Logan McNaughton 12e37b0b29 code formatting (#100) 2024-12-31 22:21:55 +01:00
Logan McNaughton 415c3eca40 Clippy fixes (#99) 2024-12-31 22:15:39 +01:00
Logan McNaughton 2c60f6bf81 Bumpto 0.1.13 (#98) 2024-12-29 12:15:01 +01:00
Logan McNaughton 5409b0f5d4 use spin sleep for more accurate VI timing (#97) 2024-12-29 12:06:59 +01:00
Logan McNaughton f9de4110ce Bump to 0.1.12 (#96) 2024-12-23 12:09:18 +01:00
Logan McNaughton 0eadc38917 fix out-of-bound access in sram (#95) 2024-12-23 11:20:09 +01:00
Logan McNaughton a40e795036 deal with out-of-bounds reads for RDRAM->RSP DMA (#94) 2024-12-23 10:15:30 +01:00
dependabot[bot] b70c943a43 Update eframe requirement from 0.29 to 0.30 (#93)
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.29.0...0.30.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>
2024-12-17 06:52:15 +01:00
dependabot[bot] c962589478 Update governor requirement from 0.7 to 0.8 (#92)
Updates the requirements on [governor](https://github.com/boinkor-net/governor) to permit the latest version.
- [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.7.0...v0.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-11 06:46:17 +01:00
Logan McNaughton 6dec154351 Bump to 0.1.11 (#91) 2024-11-10 23:01:50 +01:00
Logan McNaughton ca33797973 Update parallel-rdp (#90) 2024-11-10 22:53:53 +01:00
dependabot[bot] 8d331e1aed Update governor requirement from 0.6 to 0.7 (#88)
Updates the requirements on [governor](https://github.com/boinkor-net/governor) to permit the latest version.
- [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.6.0...v0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 05:07:08 +02:00
Logan McNaughton 5e7f562d0d update eframe to 0.29 (#87) 2024-10-18 21:22:51 +02:00
dependabot[bot] 14e3fe2a55 Update rfd requirement from 0.14 to 0.15 (#86)
Updates the requirements on [rfd](https://github.com/PolyMeilex/rfd) to permit the latest version.
- [Release notes](https://github.com/PolyMeilex/rfd/releases)
- [Changelog](https://github.com/PolyMeilex/rfd/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PolyMeilex/rfd/compare/0.14.0...0.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-18 21:14:29 +02:00
Logan McNaughton b1c6dd2910 Add unsafe keyword for extern C (#83) 2024-10-17 20:35:39 +02:00
Logan McNaughton e87c4736ee Update parallel-rdp (#80) 2024-10-07 14:35:12 +02:00
Logan McNaughton e9e1e000c2 Bump to 0.1.10 (#79) 2024-09-02 20:07:27 +02:00
Logan McNaughton bc83dd26ee Update parallel-rdp (#78) 2024-09-02 19:57:47 +02:00
Logan McNaughton ae97990308 Add x86_64 build flag for parallel-rdp (#77)
* Add x86_64 build flag for parallel-rdp

* Fix flag

* Update build.rs
2024-08-28 12:35:52 +02:00
Logan McNaughton e02ac09703 Fix build target for optimizations (#76) 2024-08-25 23:01:52 +02:00
Logan McNaughton 4cbdae2fce Bump version to 0.1.9 (#75) 2024-08-25 17:58:42 +02:00
87 changed files with 26655 additions and 3050 deletions
+4 -1
View File
@@ -1,2 +1,5 @@
[build]
[target.'cfg(target_arch = "x86_64")']
rustflags = ["-C", "target-cpu=x86-64-v3"]
[target.'cfg(target_arch = "aarch64")']
rustflags = ["-C", "target-cpu=cortex-a76"]
+66 -27
View File
@@ -11,61 +11,100 @@ env:
jobs:
build-linux:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
target: x86_64-unknown-linux-gnu
arch: linux-x86_64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
arch: linux-aarch64
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install sdl2 headers
run: |
sudo apt -y install libsdl2-dev
- name: Update rust
run: |
rustup update
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: ${{ matrix.target }}-1.0
- name: Build
run: |
cargo build --release -v
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
- name: Upload file
uses: actions/upload-artifact@v4
with:
name: gopher64-linux
path: target/release/gopher64
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
build-windows:
runs-on: windows-latest
runs-on: windows-2025
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-pc-windows-msvc
arch: windows-x86_64
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Update rust
run: |
rustup update
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Build
run: |
cargo build --release -v
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
- name: Upload file
uses: actions/upload-artifact@v4
with:
name: gopher64-windows
path: target/release/gopher64.exe
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}.exe
build-macos:
runs-on: macos-13
runs-on: macos-14
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin
arch: mac-x86_64
- target: aarch64-apple-darwin
arch: mac-aarch64
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install sdl2 headers
run: |
brew install sdl2
- name: Update rust
run: |
rustup update
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
- name: Build
run: |
cargo build --release -v
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
- name: Upload file
uses: actions/upload-artifact@v4
with:
name: gopher64-mac
path: target/release/gopher64
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
+17 -7
View File
@@ -16,13 +16,23 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install sdl2 headers
run: |
sudo apt -y install libsdl2-dev
- name: Update rust
run: |
rustup update
rustup component add clippy
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
components: clippy, rustfmt
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README/linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev
version: lint-1.0
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
run: |
cargo clippy -- -Dwarnings
-4
View File
@@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
-5
View File
@@ -1,5 +0,0 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
}
Generated
+5370
View File
File diff suppressed because it is too large Load Diff
+27 -17
View File
@@ -1,35 +1,45 @@
[package]
name = "gopher64"
version = "0.1.8"
edition = "2021"
version = "1.0.13"
edition = "2024"
rust-version = "1.85"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dirs = "5.0"
zip = "2.1"
governor = "0.6"
dirs = "6.0"
zip = "2.2"
governor = "0.8"
sevenz-rust = "0.6"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
eframe = "0.28"
rfd = "0.14"
serde-big-array = "0.5"
eframe = { version = "0.31", default-features = false, features = ["wayland", "x11", "glow"] }
sha2 = "0.10"
rusttype = "0.9"
futures = "0.3"
sdl2-sys = "0.37"
clap = { version = "4.4", features = ["derive"] }
[dependencies.sdl2]
version = "0.37"
default-features = false
features = ["static-link","bundled"]
sdl3-sys = { version = "0.4", features = ["build-from-source-static"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.43", features = ["rt-multi-thread", "macros"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
regex = "1.11"
tungstenite = "0.26"
socket2 = "0.5"
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"
cc = { version = "1.0", features = ["parallel"] }
bindgen = "0.71"
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
lto = true
codegen-units = 1
strip = true
panic = "abort"
+39 -39
View File
@@ -1,29 +1,40 @@
# gopher64
## download
https://github.com/gopher64/gopher64/releases
## wiki
https://github.com/gopher64/gopher64/wiki
## discord
https://discord.gg/9RGXq8W8JQ
## controls
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).
You can create you own mappings by running `./gopher64 --configure-input-profile my_profile`. You then bind that profile to a port: `./gopher64 --bind-input-profile my_profile --port 1`
In order to use a controller (for example, an Xbox controller), run `./gopher64 --list-controllers` to get a list of attached controllers, and then assign it by doing `./gopher64 --assign-controller <controller_number> --port 1`
## netplay
Gopher64 supports netplay (online play with others). It has a few public netplay servers. If you are interested in running a public netplay server, please let me know (open an issue or discussion, or contact me on Discord). You can also run the server (https://github.com/simple64/simple64-netplay-server) yourself on a LAN.
## portable mode
If you would like to keep all the game data in the same folder as executable, you just need to create a file called "portable.txt" in the same directory as the executable.
## flatpak
If you want to run the flatpak from the command line, you need to add the `--filesystem=host:ro` option, for example:
```
Usage: gopher64 [OPTIONS] [GAME]
Arguments:
[GAME]
Options:
-f, --fullscreen
-c, --configure-input-profile <PROFILE_NAME>
Create a new input profile (keyboard/gamepad mappings).
-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
-h, --help
Print help
-V, --version
Print version
flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
```
## goals
1. Performance. I want to be able to use this emulator on my laptop.
@@ -32,19 +43,12 @@ Options:
## 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`
## 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`
1. Linux only: install the SDL3 dependencies: https://wiki.libsdl.org/SDL3/README/linux
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
@@ -53,7 +57,3 @@ I am very open to contributions! Please reach out to me via a GitHub issue, or v
## 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
https://github.com/gopher64/gopher64/releases
+116 -9
View File
@@ -1,7 +1,12 @@
fn main() {
println!("cargo::rerun-if-changed=parallel-rdp");
println!("cargo::rerun-if-changed=src/compat");
let mut simd_build = cc::Build::new();
let mut build = cc::Build::new();
build
.cpp(true)
.warnings(false)
.std("c++17")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/rdp_device.cpp")
@@ -47,22 +52,124 @@ fn main() {
.include("parallel-rdp/parallel-rdp-standalone/vulkan")
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
.include("parallel-rdp/parallel-rdp-standalone/util")
.includes(std::env::var("DEP_SDL2_INCLUDE"));
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
.join("include"),
);
#[cfg(target_os = "windows")]
{
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");
winres::WindowsResource::new()
.set_icon("data/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")
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
build
.flag("-Wno-missing-field-initializers")
.flag("-Wno-unused-parameter");
}
build.compile("parallel-rdp");
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let parallel_bindings = bindgen::Builder::default()
.header("parallel-rdp/interface.hpp")
.allowlist_function("rdp_init")
.allowlist_function("rdp_close")
.allowlist_function("rdp_set_vi_register")
.allowlist_function("rdp_update_screen")
.allowlist_function("rdp_process_commands")
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.allowlist_function("rdp_check_framebuffers")
.allowlist_function("rdp_full_sync")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
parallel_bindings
.write_to_file(out_path.join("parallel_bindings.rs"))
.expect("Couldn't write bindings!");
if arch == "aarch64" {
let simd_bindings = bindgen::Builder::default()
.header("src/compat/sse2neon/v1.8.0/sse2neon.h")
.allowlist_function("_mm_setzero_si128")
.allowlist_function("_mm_set_epi8")
.allowlist_function("_mm_movemask_epi8")
.allowlist_function("_mm_shuffle_epi8")
.allowlist_function("_mm_packs_epi16")
.allowlist_function("_mm_set_epi16")
.allowlist_function("_mm_cmpeq_epi8")
.allowlist_function("_mm_and_si128")
.allowlist_function("_mm_set1_epi8")
.allowlist_function("_mm_mullo_epi16")
.allowlist_function("_mm_cmpeq_epi16")
.allowlist_function("_mm_add_epi16")
.allowlist_function("_mm_slli_epi16")
.allowlist_function("_mm_mulhi_epi16")
.allowlist_function("_mm_srai_epi16")
.allowlist_function("_mm_andnot_si128")
.allowlist_function("_mm_or_si128")
.allowlist_function("_mm_mulhi_epu16")
.allowlist_function("_mm_sub_epi16")
.allowlist_function("_mm_unpacklo_epi16")
.allowlist_function("_mm_unpackhi_epi16")
.allowlist_function("_mm_packs_epi32")
.allowlist_function("_mm_adds_epu16")
.allowlist_function("_mm_cmpgt_epi16")
.allowlist_function("_mm_blendv_epi8")
.allowlist_function("_mm_min_epi16")
.allowlist_function("_mm_max_epi16")
.allowlist_function("_mm_subs_epi16")
.allowlist_function("_mm_adds_epi16")
.allowlist_function("_mm_xor_si128")
.allowlist_function("_mm_cmplt_epi16")
.allowlist_function("_mm_subs_epu16")
.allowlist_function("_mm_set1_epi32")
.blocklist_type("__m128i")
.blocklist_type("int64x2_t")
.wrap_static_fns(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
simd_bindings
.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");
}
let git_output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.unwrap();
let git_hash = String::from_utf8(git_output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={}", git_hash);
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
}
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+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
Binary file not shown.
+316 -171
View File
@@ -3,6 +3,8 @@
#include "rdp_device.hpp"
#include "interface.hpp"
#include "spirv.hpp"
#include "spirv_crt.hpp"
#include <SDL3/SDL_vulkan.h>
using namespace Vulkan;
@@ -50,20 +52,41 @@ enum vi_registers
VI_REGS_COUNT
};
static bool fullscreen;
static void *rdram;
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 uint8_t emu_running;
static uint64_t rdp_sync_signal;
static uint32_t vi_registers[14];
static CALL_BACK callback;
static GFX_INFO gfx_info;
static uint32_t region;
static bool crop_letterbox;
static const uint32_t *fragment_spirv;
static size_t fragment_size;
static uint64_t last_frame_counter;
static uint64_t frame_counter;
typedef struct
{
uint32_t depthbuffer_address;
uint32_t framebuffer_address;
uint32_t framebuffer_pixel_size;
uint32_t framebuffer_width;
uint32_t framebuffer_height;
uint32_t framebuffer_size;
uint32_t depthbuffer_size;
uint8_t depthbuffer_enabled;
} FrameBufferInfo;
typedef struct
{
float SourceSize[4];
float OutputSize[4];
} Push;
static std::vector<bool> rdram_dirty;
static uint64_t sync_signal;
static FrameBufferInfo frame_buffer_info;
static const unsigned cmd_len_lut[64] = {
1,
@@ -132,77 +155,45 @@ static const unsigned cmd_len_lut[64] = {
1,
};
enum
bool sdl_event_filter(void *userdata, SDL_Event *event)
{
MB_RDRAM_DRAM_ALIGNMENT_REQUIREMENT = 64 * 1024
};
void vk_init(void *mem_base, uint32_t rdram_size, uint8_t _fullscreen)
{
fullscreen = _fullscreen != 0;
rdram = mem_base;
bool window_vsync = 0;
wsi = new WSI;
SDL_WSIPlatform *wsi_platform = new SDL_WSIPlatform;
wsi_platform->set_window(window);
wsi->set_platform(wsi_platform);
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader(nullptr))
if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED)
{
vk_close();
callback.emu_running = false;
}
if (!wsi->init_simple(1, handles))
else if (event->type == SDL_EVENT_WINDOW_RESIZED && callback.emu_running)
{
vk_close();
wsi_platform->do_resize();
}
RDP::CommandProcessorFlags flags = 0;
processor = new RDP::CommandProcessor(wsi->get_device(), rdram, 0, rdram_size, rdram_size / 2, flags);
if (!processor->device_is_supported())
else if (event->type == SDL_EVENT_KEY_DOWN && !event->key.repeat)
{
delete processor;
delete wsi;
processor = nullptr;
vk_close();
}
wsi->begin_frame();
emu_running = 1;
last_frame_counter = 0;
frame_counter = 0;
}
void vk_close()
{
delete processor;
delete wsi;
}
int sdl_event_filter(void *userdata, SDL_Event *event)
{
if (event->type == SDL_WINDOWEVENT)
{
SDL_WSIPlatform *platform = (SDL_WSIPlatform *)&wsi->get_platform();
switch (event->window.event)
switch (event->key.scancode)
{
case SDL_WINDOWEVENT_CLOSE:
emu_running = 0;
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_WINDOWEVENT_RESIZED:
platform->do_resize();
case SDL_SCANCODE_F:
if (event->key.mod & SDL_KMOD_ALT)
{
callback.enable_speedlimiter = !callback.enable_speedlimiter;
}
break;
default:
break;
}
}
else if (fullscreen && event->type == SDL_KEYDOWN)
{
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_ESCAPE:
emu_running = 0;
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;
default:
break;
@@ -212,58 +203,190 @@ int sdl_event_filter(void *userdata, SDL_Event *event)
return 0;
}
void set_sdl_window(void *_window)
void rdp_new_processor(GFX_INFO _gfx_info)
{
window = (SDL_Window *)_window;
SDL_SetEventFilter(sdl_event_filter, nullptr);
memset(&frame_buffer_info, 0, sizeof(FrameBufferInfo));
sync_signal = 0;
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
gfx_info = _gfx_info;
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;
}
processor = new RDP::CommandProcessor(wsi->get_device(), gfx_info.RDRAM, 0, gfx_info.RDRAM_SIZE, gfx_info.RDRAM_SIZE / 2, flags);
}
static void calculate_viewport(float *x, float *y, float *width, float *height)
void rdp_init(void *_window, GFX_INFO _gfx_info)
{
bool window_widescreen = false;
int32_t display_width = (window_widescreen ? 854 : 640);
int32_t display_height = 480;
window = (SDL_Window *)_window;
bool result = SDL_AddEventWatch(sdl_event_filter, nullptr);
if (!result)
{
printf("Could not add event watch.\n");
return;
}
gfx_info = _gfx_info;
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;
wsi_platform->set_window(window);
wsi->set_platform(wsi_platform);
wsi->set_present_mode(window_vsync ? PresentMode::SyncToVBlank : PresentMode::UnlockedMaybeTear);
wsi->set_backbuffer_srgb(false);
Context::SystemHandles handles = {};
if (!::Vulkan::Context::init_loader((PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr()))
{
rdp_close();
}
if (!wsi->init_simple(1, handles))
{
rdp_close();
}
rdp_new_processor(gfx_info);
if (!processor->device_is_supported())
{
delete processor;
delete wsi;
processor = nullptr;
rdp_close();
}
wsi->begin_frame();
callback.emu_running = true;
callback.enable_speedlimiter = true;
crop_letterbox = false;
}
void rdp_close()
{
wsi->end_frame();
if (processor)
{
delete processor;
processor = nullptr;
}
if (wsi)
{
delete wsi;
wsi = nullptr;
}
if (wsi_platform)
{
delete wsi_platform;
wsi_platform = nullptr;
}
}
static void calculate_viewport(float *x, float *y, float *width, float *height, uint32_t display_height)
{
uint32_t display_width = gfx_info.widescreen ? display_height * 16 / 9 : display_height * 4 / 3;
int w, h;
SDL_GetWindowSize(window, &w, &h);
*width = w;
*height = h;
*x = 0;
*y = 0;
int32_t hw = display_height * *width;
int32_t wh = display_width * *height;
if (gfx_info.integer_scaling)
{
// Integer scaling path
int scale_x = w / display_width;
int scale_y = h / display_height;
int scale = (scale_x < scale_y) ? scale_x : scale_y;
if (scale < 1)
scale = 1;
// add letterboxes or pillarboxes if the window has a different aspect ratio
// than the current display mode
if (hw > wh)
{
int32_t w_max = wh / display_height;
*x += (*width - w_max) / 2;
*width = w_max;
// Calculate scaled dimensions
int scaled_width = display_width * scale;
int scaled_height = display_height * scale;
*width = scaled_width;
*height = scaled_height;
// Center the viewport
int integer_x = (w - *width) / 2.0f;
int integer_y = (h - *height) / 2.0f;
*x = integer_x;
*y = integer_y;
}
else if (hw < wh)
else
{
int32_t h_max = hw / display_width;
*y += (*height - h_max) / 2;
*height = h_max;
// Regular scaling path - maintain aspect ratio
float scale_x = w / (float)display_width;
float scale_y = h / (float)display_height;
float scale = (scale_x < scale_y) ? scale_x : scale_y;
*width = display_width * scale;
*height = display_height * scale;
// Center the viewport
*x = (w - *width) / 2.0f;
*y = (h - *height) / 2.0f;
}
}
static void render_frame(Vulkan::Device &device)
{
RDP::ScanoutOptions options = {};
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);
@@ -273,9 +396,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.
@@ -286,6 +406,19 @@ 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)
{
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::LinearClamp);
cmd->set_viewport(vp);
// The vertices are constants in the shader.
@@ -301,63 +434,64 @@ static void render_frame(Vulkan::Device &device)
void rdp_set_vi_register(uint32_t reg, uint32_t value)
{
processor->set_vi_register(RDP::VIRegister(reg), value);
vi_registers[reg] = value;
}
uint8_t rdp_update_screen()
void rdp_update_screen()
{
auto &device = wsi->get_device();
render_frame(device);
wsi->end_frame();
wsi->begin_frame();
frame_counter++;
return emu_running;
}
static uint32_t viCalculateHorizonalWidth(uint32_t hstart, uint32_t xscale, uint32_t width)
CALL_BACK rdp_check_callback()
{
if (xscale == 0)
return 320;
CALL_BACK return_value = callback;
callback.save_state = false;
callback.load_state = 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)
{
if (sync_signal && rdram_dirty[address >> 3])
{
delta = width;
processor->wait_for_timeline(sync_signal);
rdram_dirty.assign(gfx_info.RDRAM_SIZE / 8, false);
sync_signal = 0;
}
return (delta * scale) / 0x400;
}
static uint32_t viCalculateVerticalHeight(uint32_t vstart, uint32_t yscale)
void rdp_full_sync()
{
if (yscale == 0)
return 240;
uint32_t start = ((vstart & 0x03FF0000) >> 16) & 0x3FF;
uint32_t end = (vstart & 0x3FF);
uint32_t delta;
if (end > start)
delta = end - start;
else
delta = start - end;
uint32_t scale = (yscale & 0xFFF);
return (delta * scale) / 0x800;
processor->wait_for_timeline(processor->signal_timeline());
}
uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
void calculate_buffer_size()
{
switch (frame_buffer_info.framebuffer_pixel_size)
{
case 0:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height / 2) >> 3;
break;
case 1:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height) >> 3;
break;
case 2:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
break;
case 3:
frame_buffer_info.framebuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 4) >> 3;
break;
}
frame_buffer_info.depthbuffer_size = (frame_buffer_info.framebuffer_width * frame_buffer_info.framebuffer_height * 2) >> 3;
}
uint64_t rdp_process_commands()
{
uint64_t interrupt_timer = 0;
const uint32_t DP_CURRENT = dpc_regs[DPC_CURRENT_REG] & 0x00FFFFF8;
const uint32_t DP_END = dpc_regs[DPC_END_REG] & 0x00FFFFF8;
const uint32_t DP_CURRENT = *gfx_info.DPC_CURRENT_REG & 0x00FFFFF8;
const uint32_t DP_END = *gfx_info.DPC_END_REG & 0x00FFFFF8;
int length = DP_END - DP_CURRENT;
if (length <= 0)
@@ -367,16 +501,14 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
if ((cmd_ptr + length) & ~(0x0003FFFF >> 3))
return interrupt_timer;
dpc_regs[DPC_STATUS_REG] |= DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK;
uint32_t offset = DP_CURRENT;
if (dpc_regs[DPC_STATUS_REG] & DP_STATUS_XBUS_DMA)
if (*gfx_info.DPC_STATUS_REG & DP_STATUS_XBUS_DMA)
{
do
{
offset &= 0xFF8;
cmd_data[2 * cmd_ptr + 0] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(SP_DMEM + offset));
cmd_data[2 * cmd_ptr + 1] = SDL_SwapBE32(*reinterpret_cast<const uint32_t *>(SP_DMEM + offset + 4));
cmd_data[2 * cmd_ptr + 0] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset));
cmd_data[2 * cmd_ptr + 1] = SDL_Swap32BE(*reinterpret_cast<const uint32_t *>(gfx_info.DMEM + offset + 4));
offset += sizeof(uint64_t);
cmd_ptr++;
} while (--length > 0);
@@ -392,8 +524,8 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
do
{
offset &= 0xFFFFF8;
cmd_data[2 * cmd_ptr + 0] = *reinterpret_cast<const uint32_t *>((uint8_t *)rdram + offset);
cmd_data[2 * cmd_ptr + 1] = *reinterpret_cast<const uint32_t *>((uint8_t *)rdram + offset + 4);
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);
offset += sizeof(uint64_t);
cmd_ptr++;
} while (--length > 0);
@@ -403,43 +535,65 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
while (cmd_cur - cmd_ptr < 0)
{
uint32_t w1 = cmd_data[2 * cmd_cur];
uint32_t w2 = cmd_data[2 * cmd_cur + 1];
uint32_t command = (w1 >> 24) & 63;
int cmd_length = cmd_len_lut[command];
if (cmd_ptr - cmd_cur - cmd_length < 0)
{
dpc_regs[DPC_START_REG] = dpc_regs[DPC_CURRENT_REG] = dpc_regs[DPC_END_REG];
*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]);
if (RDP::Op(command) == RDP::Op::SyncFull)
if ((RDP::Op(command) >= RDP::Op::FillTriangle && RDP::Op(command) <= RDP::Op::ShadeTextureZBufferTriangle) ||
RDP::Op(command) == RDP::Op::TextureRectangle ||
RDP::Op(command) == RDP::Op::TextureRectangleFlip ||
RDP::Op(command) == RDP::Op::FillRectangle)
{
if (frame_counter != last_frame_counter) // Only sync once per frame
if (!rdram_dirty[frame_buffer_info.framebuffer_address])
{
rdp_sync_signal = processor->signal_timeline();
last_frame_counter = frame_counter;
}
else
{
rdp_sync_signal = 0;
std::fill_n(rdram_dirty.begin() + frame_buffer_info.framebuffer_address, frame_buffer_info.framebuffer_size, true);
}
uint32_t width = viCalculateHorizonalWidth(vi_registers[VI_H_START_REG], vi_registers[VI_X_SCALE_REG], vi_registers[VI_WIDTH_REG]);
if (width == 0)
if (frame_buffer_info.depthbuffer_enabled && !rdram_dirty[frame_buffer_info.depthbuffer_address])
{
width = 320;
std::fill_n(rdram_dirty.begin() + frame_buffer_info.depthbuffer_address, frame_buffer_info.depthbuffer_size, true);
}
uint32_t height = viCalculateVerticalHeight(vi_registers[VI_V_START_REG], vi_registers[VI_Y_SCALE_REG]);
if (height == 0)
{
height = 240;
}
interrupt_timer = width * height * 4;
dpc_regs[DPC_STATUS_REG] &= ~(DP_STATUS_PIPE_BUSY | DP_STATUS_START_GCLK);
}
else if (RDP::Op(command) == RDP::Op::SetOtherModes)
{
frame_buffer_info.depthbuffer_enabled = (w2 >> 5) & 1;
}
else if (RDP::Op(command) == RDP::Op::SetColorImage)
{
frame_buffer_info.framebuffer_address = (w2 & 0x00FFFFFF) >> 3;
frame_buffer_info.framebuffer_pixel_size = (w1 >> 19) & 0x3;
frame_buffer_info.framebuffer_width = (w1 & 0x3FF) + 1;
calculate_buffer_size();
}
else if (RDP::Op(command) == RDP::Op::SetMaskImage)
{
frame_buffer_info.depthbuffer_address = (w2 & 0x00FFFFFF) >> 3;
}
else if (RDP::Op(command) == RDP::Op::SetScissor)
{
uint32_t upper_left_x = ((w1 >> 12) & 0xFFF) >> 2;
uint32_t upper_left_y = (w1 & 0xFFF) >> 2;
uint32_t lower_right_x = ((w2 >> 12) & 0xFFF) >> 2;
uint32_t lower_right_y = (w2 & 0xFFF) >> 2;
region = (lower_right_x - upper_left_x) * (lower_right_y - upper_left_y);
frame_buffer_info.framebuffer_height = lower_right_y;
calculate_buffer_size();
}
else if (RDP::Op(command) == RDP::Op::SyncFull)
{
sync_signal = processor->signal_timeline();
interrupt_timer = region;
if (interrupt_timer == 0)
interrupt_timer = 5000;
}
cmd_cur += cmd_length;
@@ -447,16 +601,7 @@ uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM)
cmd_ptr = 0;
cmd_cur = 0;
dpc_regs[DPC_CURRENT_REG] = dpc_regs[DPC_END_REG];
dpc_regs[DPC_STATUS_REG] |= DP_STATUS_CBUF_READY;
*gfx_info.DPC_CURRENT_REG = *gfx_info.DPC_END_REG;
return interrupt_timer;
}
void full_sync()
{
if (rdp_sync_signal)
{
processor->wait_for_timeline(rdp_sync_signal);
}
}
+36 -7
View File
@@ -1,17 +1,46 @@
#pragma once
#ifdef __cplusplus
#include <cstdint>
extern "C"
{
#endif
void vk_init(void *mem_base, uint32_t rdram_size, uint8_t fullscreen);
void vk_close();
void set_sdl_window(void *_window);
void rdp_set_vi_register(uint32_t reg, uint32_t value);
uint8_t rdp_update_screen();
uint64_t rdp_process_commands(uint32_t *dpc_regs, uint8_t *SP_DMEM);
void full_sync();
typedef struct
{
uint8_t *RDRAM;
uint8_t *DMEM;
uint32_t RDRAM_SIZE;
uint32_t *DPC_CURRENT_REG;
uint32_t *DPC_START_REG;
uint32_t *DPC_END_REG;
uint32_t *DPC_STATUS_REG;
bool PAL;
bool widescreen;
bool fullscreen;
bool integer_scaling;
uint32_t upscale;
bool crt;
} GFX_INFO;
typedef struct
{
bool emu_running;
bool save_state;
bool load_state;
bool enable_speedlimiter;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info);
void rdp_close();
void rdp_set_vi_register(uint32_t reg, uint32_t value);
void rdp_update_screen();
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address);
void rdp_full_sync();
#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};
+13 -9
View File
@@ -1,11 +1,11 @@
#include "wsi_platform.hpp"
#include <SDL_vulkan.h>
#include <SDL3/SDL_vulkan.h>
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
{
VkSurfaceKHR surface = nullptr;
SDL_bool result = SDL_Vulkan_CreateSurface(window, instance, &surface);
if (result != SDL_TRUE)
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
if (result != true)
{
printf("Error creating surface\n");
}
@@ -14,18 +14,23 @@ VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevi
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
{
SDL_Vulkan_DestroySurface(instance, surface, NULL);
}
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
{
unsigned int extensionCount = 0;
SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, nullptr);
std::vector<const char *> extensionNames(extensionCount);
SDL_bool result = SDL_Vulkan_GetInstanceExtensions(window, &extensionCount, extensionNames.data());
if (result != SDL_TRUE)
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (extensions == NULL)
{
printf("Error creating surface\n");
printf("Error getting instance extensions\n");
}
std::vector<const char *> extensionNames;
for (int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}
return extensionNames;
}
@@ -56,7 +61,6 @@ void SDL_WSIPlatform::poll_input()
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
{
SDL_PumpEvents();
}
void SDL_WSIPlatform::set_window(SDL_Window *_window)
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "wsi.hpp"
#include <SDL.h>
#include <SDL3/SDL.h>
class SDL_WSIPlatform : public Vulkan::WSIPlatform
{
+197
View File
@@ -0,0 +1,197 @@
#include "src/compat/sse2neon/v1.8.0/sse2neon.h"
// Static wrappers
__m128i _mm_srli_epi16__extern(__m128i __a, int __imm8) { return _mm_srli_epi16(__a, __imm8); }
__m128i _mm_insert_epi8__extern(__m128i __a, int __i, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_insert_epi8(__a, __i, 0);
case 1:
return _mm_insert_epi8(__a, __i, 1);
case 2:
return _mm_insert_epi8(__a, __i, 2);
case 3:
return _mm_insert_epi8(__a, __i, 3);
case 4:
return _mm_insert_epi8(__a, __i, 4);
case 5:
return _mm_insert_epi8(__a, __i, 5);
case 6:
return _mm_insert_epi8(__a, __i, 6);
case 7:
return _mm_insert_epi8(__a, __i, 7);
case 8:
return _mm_insert_epi8(__a, __i, 8);
case 9:
return _mm_insert_epi8(__a, __i, 9);
case 10:
return _mm_insert_epi8(__a, __i, 10);
case 11:
return _mm_insert_epi8(__a, __i, 11);
case 12:
return _mm_insert_epi8(__a, __i, 12);
case 13:
return _mm_insert_epi8(__a, __i, 13);
case 14:
return _mm_insert_epi8(__a, __i, 14);
case 15:
return _mm_insert_epi8(__a, __i, 15);
default:
abort();
}
}
int _mm_extract_epi8__extern(__m128i __a, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_extract_epi8(__a, 0);
case 1:
return _mm_extract_epi8(__a, 1);
case 2:
return _mm_extract_epi8(__a, 2);
case 3:
return _mm_extract_epi8(__a, 3);
case 4:
return _mm_extract_epi8(__a, 4);
case 5:
return _mm_extract_epi8(__a, 5);
case 6:
return _mm_extract_epi8(__a, 6);
case 7:
return _mm_extract_epi8(__a, 7);
case 8:
return _mm_extract_epi8(__a, 8);
case 9:
return _mm_extract_epi8(__a, 9);
case 10:
return _mm_extract_epi8(__a, 10);
case 11:
return _mm_extract_epi8(__a, 11);
case 12:
return _mm_extract_epi8(__a, 12);
case 13:
return _mm_extract_epi8(__a, 13);
case 14:
return _mm_extract_epi8(__a, 14);
case 15:
return _mm_extract_epi8(__a, 15);
default:
abort();
}
}
__m128i _mm_insert_epi16__extern(__m128i __a, int __i, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_insert_epi16(__a, __i, 0);
case 1:
return _mm_insert_epi16(__a, __i, 1);
case 2:
return _mm_insert_epi16(__a, __i, 2);
case 3:
return _mm_insert_epi16(__a, __i, 3);
case 4:
return _mm_insert_epi16(__a, __i, 4);
case 5:
return _mm_insert_epi16(__a, __i, 5);
case 6:
return _mm_insert_epi16(__a, __i, 6);
case 7:
return _mm_insert_epi16(__a, __i, 7);
default:
abort();
}
}
int _mm_extract_epi16__extern(__m128i __a, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_extract_epi16(__a, 0);
case 1:
return _mm_extract_epi16(__a, 1);
case 2:
return _mm_extract_epi16(__a, 2);
case 3:
return _mm_extract_epi16(__a, 3);
case 4:
return _mm_extract_epi16(__a, 4);
case 5:
return _mm_extract_epi16(__a, 5);
case 6:
return _mm_extract_epi16(__a, 6);
case 7:
return _mm_extract_epi16(__a, 7);
default:
abort();
}
}
__m128i _mm_insert_epi32__extern(__m128i __a, int __i, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_insert_epi32(__a, __i, 0);
case 1:
return _mm_insert_epi32(__a, __i, 1);
case 2:
return _mm_insert_epi32(__a, __i, 2);
case 3:
return _mm_insert_epi32(__a, __i, 3);
default:
abort();
}
}
int _mm_extract_epi32__extern(__m128i __a, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_extract_epi32(__a, 0);
case 1:
return _mm_extract_epi32(__a, 1);
case 2:
return _mm_extract_epi32(__a, 2);
case 3:
return _mm_extract_epi32(__a, 3);
default:
abort();
}
}
__m128i _mm_insert_epi64__extern(__m128i __a, int64_t __i, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_insert_epi64(__a, __i, 0);
case 1:
return _mm_insert_epi64(__a, __i, 1);
default:
abort();
}
}
int64_t _mm_extract_epi64__extern(__m128i __a, const int __imm8)
{
switch (__imm8)
{
case 0:
return _mm_extract_epi64(__a, 0);
case 1:
return _mm_extract_epi64(__a, 1);
default:
abort();
}
}
+47
View File
@@ -0,0 +1,47 @@
unsafe extern "C" {
#[link_name = "_mm_srli_epi16__extern"]
fn _mm_srli_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> __m128i;
#[link_name = "_mm_insert_epi8__extern"]
fn _mm_insert_epi8(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi8__extern"]
fn _mm_extract_epi8(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi16__extern"]
fn _mm_insert_epi16(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi16__extern"]
fn _mm_extract_epi16(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi32__extern"]
fn _mm_insert_epi32(
__a: __m128i,
__i: ::std::os::raw::c_int,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi32__extern"]
fn _mm_extract_epi32(__a: __m128i, __imm8: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
#[link_name = "_mm_insert_epi64__extern"]
fn _mm_insert_epi64(
__a: __m128i,
__i: ::std::os::raw::c_longlong,
__imm8: ::std::os::raw::c_int,
) -> __m128i;
#[link_name = "_mm_extract_epi64__extern"]
fn _mm_extract_epi64(
__a: __m128i,
__imm8: ::std::os::raw::c_int,
) -> ::std::os::raw::c_longlong;
}
File diff suppressed because it is too large Load Diff
+164 -57
View File
@@ -1,11 +1,23 @@
use crate::ui;
use std::fs;
use std::io::Read;
#![allow(non_camel_case_types)]
#![allow(improper_ctypes)]
#[cfg(target_arch = "aarch64")]
#[derive(Copy, Clone)]
#[allow(dead_code)]
pub struct __m128i(std::arch::aarch64::int64x2_t);
#[cfg(target_arch = "aarch64")]
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use crate::{netplay, ui};
use std::{collections::HashMap, fs, io::Read};
pub mod ai;
pub mod cache;
pub mod cart;
pub mod cart_rom;
pub mod controller;
pub mod cop0;
pub mod cop1;
@@ -17,7 +29,6 @@ pub mod exceptions;
pub mod fpu_instructions;
pub mod is_viewer;
pub mod memory;
pub mod mempak;
pub mod mi;
pub mod pi;
pub mod pif;
@@ -29,36 +40,63 @@ 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(file_path: &std::path::Path, device: &mut Device, fullscreen: bool) {
let rom_contents = get_rom_contents(file_path);
pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, fullscreen: bool, overclock: bool) {
device.ui.video.fullscreen = fullscreen;
device.cpu.overclock = overclock;
cart_rom::init(device, rom_contents); // cart needs to come before rdram
init_rng(device);
cart::rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
let (rdram_ptr, rdram_size) = rdram::init(device);
rdram::init(device);
ui::audio::init(&mut device.ui, 33600);
ui::video::init(&mut device.ui, rdram_ptr, rdram_size, fullscreen);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::init(device);
ui::input::init(&mut device.ui);
mi::init(device);
pif::init(device);
if device.ui.config.input.emulate_vru {
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);
ui::storage::load_saves(&mut device.ui);
cart_rom::load_rom_save(device);
ui::storage::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
cpu::run(device);
ui::input::close(&mut device.ui);
ui::video::close(&device.ui);
ui::audio::close(&mut device.ui);
ui::storage::write_saves(device);
}
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 device.netplay.is_some() {
let netplay = device.netplay.as_mut().unwrap();
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>) -> Vec<u8> {
@@ -83,13 +121,14 @@ fn swap_rom(contents: Vec<u8>) -> Vec<u8> {
}
return data;
} else {
panic!("unknown rom format")
let data: Vec<u8> = vec![];
data
}
}
fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
pub fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
let mut contents = vec![];
if file_path.extension().unwrap().to_ascii_lowercase() == "zip" {
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
for i in 0..archive.len() {
@@ -106,7 +145,7 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
break;
}
}
} else if file_path.extension().unwrap().to_ascii_lowercase() == "7z" {
} 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();
@@ -137,23 +176,37 @@ fn get_rom_contents(file_path: &std::path::Path) -> Vec<u8> {
swap_rom(contents)
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Device {
#[serde(skip)]
pub netplay: Option<netplay::Netplay>,
#[serde(skip, default = "ui::Ui::default")]
pub ui: ui::Ui,
byte_swap: usize,
cpu: cpu::Cpu,
pif: pif::Pif,
cart: cart_rom::Cart,
memory: memory::Memory,
rsp: rsp_interface::Rsp,
rdp: rdp::Rdp,
pub save_state: bool,
pub load_state: bool,
pub cpu: cpu::Cpu,
pub pif: pif::Pif,
pub cart: cart::Cart,
pub memory: memory::Memory,
pub rsp: rsp_interface::Rsp,
pub rdp: rdp::Rdp,
pub rdram: rdram::Rdram,
mi: mi::Mi,
pi: pi::Pi,
vi: vi::Vi,
ai: ai::Ai,
si: si::Si,
ri: ri::Ri,
flashram: sram::Flashram,
pub mi: mi::Mi,
pub pi: pi::Pi,
pub vi: vi::Vi,
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 fn zero_m128i() -> __m128i {
unsafe { _mm_setzero_si128() }
}
impl Device {
@@ -166,13 +219,18 @@ impl Device {
byte_swap = 3;
}
Device {
netplay: None,
ui: ui::Ui::new(),
byte_swap,
save_state: false,
load_state: false,
cpu: cpu::Cpu {
cop0: cop0::Cop0 {
regs: [0; cop0::COP0_REGS_COUNT as usize],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
tlb_lut_w: vec![
@@ -214,7 +272,8 @@ impl Device {
cop1: cop1::Cop1 {
fcr0: 0,
fcr31: 0,
flush_mode: 0,
//#[cfg(target_arch = "x86_64")]
//flush_mode: 0,
fgr32: [[0; 4]; 32],
fgr64: [[0; 8]; 32],
instrs: [cop1::reserved; 32],
@@ -237,19 +296,19 @@ impl Device {
pc: 0xBFC00000,
pc_phys: 0,
llbit: false,
overclock: false,
lo: 0,
hi: 0,
running: 0,
running: false,
instrs: [cop0::reserved; 64],
special_instrs: [cop0::reserved; 64],
regimm_instrs: [cop0::reserved; 32],
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],
@@ -261,17 +320,32 @@ impl Device {
rx_buf: None,
process: None,
pak_handler: None,
change_pak: controller::PakType::None,
}; 5],
},
cart: cart_rom::Cart {
cart: cart::Cart {
rom: Vec::new(),
is_viewer_buffer: [0; 0xFFFF],
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],
},
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],
@@ -292,7 +366,11 @@ impl Device {
words: [0; 4],
}; 512],
},
rdram: rdram::Rdram { mem: vec![] },
rdram: rdram::Rdram {
mem: vec![],
size: 0x800000,
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
},
rsp: rsp_interface::Rsp {
cpu: rsp_cpu::Cpu {
instructions: [rsp_cpu::Instructions {
@@ -307,25 +385,26 @@ impl Device {
pc: 0,
},
broken: false,
running: false,
halted: false,
sync_point: false,
cycle_counter: 0,
gpr: [0; 32],
vpr: [0; 32],
vpr: unsafe { [_mm_setzero_si128(); 32] },
reciprocals: [0; 512],
inverse_square_roots: [0; 512],
divdp: false,
divin: 0,
divout: 0,
shuffle: unsafe { [std::arch::x86_64::_mm_setzero_si128(); 16] },
vcol: unsafe { std::arch::x86_64::_mm_setzero_si128() },
vcoh: unsafe { std::arch::x86_64::_mm_setzero_si128() },
vccl: unsafe { std::arch::x86_64::_mm_setzero_si128() },
vcch: unsafe { std::arch::x86_64::_mm_setzero_si128() },
vce: unsafe { std::arch::x86_64::_mm_setzero_si128() },
accl: unsafe { std::arch::x86_64::_mm_setzero_si128() },
accm: unsafe { std::arch::x86_64::_mm_setzero_si128() },
acch: unsafe { std::arch::x86_64::_mm_setzero_si128() },
shuffle: unsafe { [_mm_setzero_si128(); 16] },
vcol: unsafe { _mm_setzero_si128() },
vcoh: unsafe { _mm_setzero_si128() },
vccl: unsafe { _mm_setzero_si128() },
vcch: unsafe { _mm_setzero_si128() },
vce: unsafe { _mm_setzero_si128() },
accl: unsafe { _mm_setzero_si128() },
accm: unsafe { _mm_setzero_si128() },
acch: unsafe { _mm_setzero_si128() },
special_instrs: [rsp_su_instructions::reserved; 64],
regimm_instrs: [rsp_su_instructions::reserved; 32],
cop0_instrs: [rsp_su_instructions::reserved; 32],
@@ -338,6 +417,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,
@@ -349,6 +430,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],
@@ -359,6 +441,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,
@@ -372,6 +455,7 @@ impl Device {
},
ri: ri::Ri {
regs: [0; ri::RI_REGS_COUNT as usize],
ram_init: false,
},
vi: vi::Vi {
regs: [0; vi::VI_REGS_COUNT as usize],
@@ -379,15 +463,38 @@ 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 {
vru_window: controller::vru::VruWindow {
window_notifier: None,
word_receiver: None,
gui_ctx: None,
},
vru: controller::vru::Vru {
status: 0,
erase_page: 0,
page_buf: [0xff; 128],
silicon_id: [sram::FLASHRAM_TYPE_ID, sram::MX29L1100_ID],
mode: sram::FlashramMode::ReadArray,
voice_state: 0,
load_offset: 0,
voice_init: 0,
word_buffer: [0; 40],
words: Vec::new(),
talking: false,
word_mappings: HashMap::new(),
},
rng: set_rng(),
transferpaks: [
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
controller::transferpak::TransferPak::default(),
],
}
}
}
+28 -25
View File
@@ -1,37 +1,38 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
pub const AI_DRAM_ADDR_REG: u32 = 0;
pub const AI_LEN_REG: u32 = 1;
//pub const AI_CONTROL_REG: u32 = 2;
pub const AI_STATUS_REG: u32 = 3;
pub const AI_DACRATE_REG: u32 = 4;
//pub const AI_BITRATE_REG: u32 = 5;
const AI_DRAM_ADDR_REG: u32 = 0;
const AI_LEN_REG: u32 = 1;
// const AI_CONTROL_REG: u32 = 2;
const AI_STATUS_REG: u32 = 3;
const AI_DACRATE_REG: u32 = 4;
// const AI_BITRATE_REG: u32 = 5;
pub const AI_REGS_COUNT: u32 = 6;
pub const AI_STATUS_BUSY: u32 = 0x40000000;
pub const AI_STATUS_FULL: u32 = 0x80000000;
const AI_STATUS_BUSY: u32 = 0x40000000;
const AI_STATUS_FULL: u32 = 0x80000000;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ai {
pub regs: [u32; AI_REGS_COUNT as usize],
pub fifo: [AiDma; 2],
pub last_read: u64,
pub delayed_carry: bool,
pub freq: u64,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct AiDma {
pub address: u64,
pub length: u64,
pub duration: u64,
}
pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
if device.ai.fifo[0].duration == 0 {
return 0;
}
let next_ai_event = device::events::get_event(device, device::events::EventType::AI);
let next_ai_event = device::events::get_event(device, device::events::EVENT_TYPE_AI);
if next_ai_event.is_none() {
return 0;
}
@@ -43,7 +44,7 @@ pub fn get_remaining_dma_length(device: &mut device::Device) -> u64 {
dma_length & !7
}
pub fn get_dma_duration(device: &device::Device) -> u64 {
fn get_dma_duration(device: &device::Device) -> u64 {
let samples_per_sec = device.vi.clock / (1 + device.ai.regs[AI_DACRATE_REG as usize]) as u64;
let bytes_per_sample = 4; /* XXX: assume 16bit stereo - should depends on bitrate instead */
let length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
@@ -51,7 +52,7 @@ pub fn get_dma_duration(device: &device::Device) -> u64 {
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
}
pub fn do_dma(device: &mut device::Device) {
fn do_dma(device: &mut device::Device) {
device.ai.last_read = device.ai.fifo[0].length;
if device.ai.delayed_carry {
@@ -64,14 +65,13 @@ pub fn do_dma(device: &mut device::Device) {
/* schedule end of dma event */
device::events::create_event(
device,
device::events::EventType::AI,
device.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);
}
pub fn fifo_push(device: &mut device::Device) {
fn fifo_push(device: &mut device::Device) {
let duration = get_dma_duration(device);
if (device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_BUSY) != 0 {
@@ -91,7 +91,7 @@ pub fn fifo_push(device: &mut device::Device) {
}
}
pub fn fifo_pop(device: &mut device::Device) {
fn fifo_pop(device: &mut device::Device) {
if device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_FULL != 0 {
device.ai.fifo[0].address = device.ai.fifo[1].address;
device.ai.fifo[0].length = device.ai.fifo[1].length;
@@ -111,7 +111,7 @@ pub fn read_regs(
_access_size: device::memory::AccessSize,
) -> u32 {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
let return_value = match reg as u32 {
AI_LEN_REG => {
let value = get_remaining_dma_length(device);
if value < device.ai.last_read {
@@ -128,7 +128,9 @@ pub fn read_regs(
value as u32
}
_ => device.ai.regs[reg as usize],
}
};
device::cop0::add_cycles(device, 20);
return_value
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -143,8 +145,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
AI_STATUS_REG => device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_AI),
AI_DACRATE_REG => {
if device.ai.regs[reg as usize] != value & mask {
let frequency = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::init(&mut device.ui, frequency)
device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::close(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq)
}
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
}
+5 -4
View File
@@ -1,15 +1,16 @@
use crate::device;
use crate::{device, savestates};
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct ICache {
pub valid: bool,
pub tag: u32,
pub index: u16,
pub words: [u32; 8],
#[serde(skip, default = "savestates::default_instructions")]
pub instruction: [fn(&mut device::Device, u32); 8],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct DCache {
pub valid: bool,
pub dirty: bool,
@@ -205,7 +206,7 @@ pub fn dcache_writeback(device: &mut device::Device, line_index: usize) {
);
}
pub fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
fn dcache_fill(device: &mut device::Device, line_index: usize, phys_address: u64) {
device::cop0::add_cycles(device, 7);
device.memory.dcache[line_index].valid = true;
+66 -27
View File
@@ -1,32 +1,60 @@
use chrono::Datelike;
use chrono::Timelike;
use crate::device;
use crate::ui;
use crate::{device, ui};
pub const JCMD_STATUS: u8 = 0x00;
pub const JCMD_EEPROM_READ: u8 = 0x04;
pub const JCMD_EEPROM_WRITE: u8 = 0x05;
pub const JCMD_AF_RTC_STATUS: u8 = 0x06;
pub const JCMD_AF_RTC_READ: u8 = 0x07;
pub const JCMD_AF_RTC_WRITE: u8 = 0x08;
pub const JCMD_RESET: u8 = 0xff;
pub mod rom;
pub mod sc64;
pub mod sram;
pub const JDT_AF_RTC: u16 = 0x1000; /* RTC */
pub const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
pub const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
pub const EEPROM_BLOCK_SIZE: usize = 8;
const JCMD_STATUS: u8 = 0x00;
const JCMD_EEPROM_READ: u8 = 0x04;
const JCMD_EEPROM_WRITE: u8 = 0x05;
const JCMD_AF_RTC_STATUS: u8 = 0x06;
const JCMD_AF_RTC_READ: u8 = 0x07;
const JCMD_AF_RTC_WRITE: u8 = 0x08;
const JCMD_RESET: u8 = 0xff;
const JDT_AF_RTC: u16 = 0x1000; /* RTC */
const JDT_EEPROM_4K: u16 = 0x8000; /* 4k EEPROM */
const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
const EEPROM_BLOCK_SIZE: usize = 8;
pub const EEPROM_MAX_SIZE: usize = 0x800;
#[derive(serde::Serialize, serde::Deserialize)]
pub enum CicType {
CicNus6101,
CicNus6102,
CicNus6103,
CicNus6105,
CicNus6106,
CicNus5167,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct AfRtc {
pub control: u16,
}
pub fn byte2bcd(mut n: u32) -> u8 {
fn byte2bcd(mut n: u32) -> u8 {
n %= 100;
(((n / 10) << 4) | (n % 10)) as u8
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
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 +63,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)
{
@@ -96,35 +126,44 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
pub 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)
if device.ui.storage.saves.eeprom.data.len() < EEPROM_MAX_SIZE {
device
.ui
.storage
.saves
.eeprom
.data
.resize(EEPROM_MAX_SIZE, 0xFF)
}
}
pub fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
fn eeprom_read_block(device: &mut device::Device, block: usize, offset: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
format_eeprom(device);
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],
);
}
pub fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn eeprom_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
let address = device.pif.ram[block] as usize * EEPROM_BLOCK_SIZE;
format_eeprom(device);
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
}
pub fn time2data(device: &mut device::Device, offset: usize) {
let now: chrono::DateTime<chrono::Local> = chrono::Local::now();
fn time2data(device: &mut device::Device, offset: usize) {
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());
@@ -136,7 +175,7 @@ pub fn time2data(device: &mut device::Device, offset: usize) {
device.pif.ram[offset + 7] = byte2bcd((now.year() as u32 - 1900) / 100);
}
pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.pif.ram[offset] = device.cart.rtc.control as u8;
@@ -155,11 +194,11 @@ pub fn af_rtc_read_block(device: &mut device::Device, block: usize, offset: usiz
}
}
}
pub fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
fn af_rtc_write_block(device: &mut device::Device, block: usize, offset: usize, status: usize) {
match device.pif.ram[block] {
0 => {
device.cart.rtc.control =
(device.pif.ram[offset + 1] as u16) << 8 | device.pif.ram[offset] as u16;
((device.pif.ram[offset + 1] as u16) << 8) | device.pif.ram[offset] as u16;
device.pif.ram[status] = 0x00;
}
1 => {
+198
View File
@@ -0,0 +1,198 @@
use crate::device;
use crate::netplay;
use sha2::{Digest, Sha256};
const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &mut 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: &mut 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
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH 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
&& device.cart.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH 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[(dram_addr + i) as usize ^ device.byte_swap],
);
}
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[i as usize ^ device.byte_swap] = *value;
} else {
device.rdram.mem[i as usize ^ device.byte_swap] =
*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 device.netplay.is_some() {
let netplay = device.netplay.as_mut().unwrap();
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.sc64.cfg[device::cart::sc64::SC64_BOOTLOADER_SWITCH as usize] = 0;
}
_ => {
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())
}
+344
View File
@@ -0,0 +1,344 @@
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>,
}
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 {} address {:#x}", reg, address),
}
}
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
SC64_KEY_REG => {
device::memory::masked_write_32(&mut device.cart.sc64.regs[reg as usize], value, mask);
if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0x4F434B5F {
device.cart.sc64.regs_locked = false;
} else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
device.cart.sc64.regs_locked = true;
}
}
SC64_DATA0_REG | SC64_DATA1_REG => {
if !device.cart.sc64.regs_locked {
device::memory::masked_write_32(
&mut device.cart.sc64.regs[reg as usize],
value,
mask,
);
}
}
SC64_SCR_REG => {
if !device.cart.sc64.regs_locked {
match char::from_u32(value & mask).unwrap() {
'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 {
if offset + i < device.ui.storage.saves.sdcard.data.len() {
let data = u32::from_be_bytes(
device.ui.storage.saves.sdcard.data
[(offset + i)..(offset + i + 4)]
.try_into()
.unwrap(),
);
device::memory::data_write(
device,
address + i as u64,
data,
0xFFFFFFFF,
false,
);
} else {
panic!("sd card read out of bounds")
}
i += 4;
}
}
'S' => {
format_sdcard(device);
// write sd card
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let offset = (device.cart.sc64.sector * 512) as usize;
let length =
(device.cart.sc64.regs[SC64_DATA1_REG as usize] * 512) as usize;
let mut i = 0;
while i < length {
if offset + i < device.ui.storage.saves.sdcard.data.len() {
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[(offset + i)..(offset + i + 4)]
.copy_from_slice(&data);
} else {
panic!("sd card write out of bounds")
}
i += 4;
}
device.ui.storage.saves.sdcard.written = true;
}
'U' | 'u' => {} // USB status, ignored
'M' | 'm' => {} // USB read/write, ignored
'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 && i < device.rdram.size {
buffer[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
pub fn dma_write(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
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 && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] = buffer[j as usize];
i += 1;
j += 1;
}
device::pi::calculate_cycles(device, 1, length)
}
+108 -85
View File
@@ -1,15 +1,14 @@
use crate::device;
use crate::ui;
use crate::{device, ui};
pub const SRAM_MASK: usize = 0xFFFF;
const SRAM_MASK: usize = 0xFFFF;
pub const SRAM_SIZE: usize = 0x8000;
pub const FLASHRAM_SIZE: usize = 0x20000;
pub const FLASHRAM_TYPE_ID: u32 = 0x11118001;
pub const MX29L1100_ID: u32 = 0x00c2001e;
pub const MX29L0000_ID: u32 = 0x00c20000;
pub const MX29L0001_ID: u32 = 0x00c20001;
const MX29L0000_ID: u32 = 0x00c20000;
const MX29L0001_ID: u32 = 0x00c20001;
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FlashramMode {
ReadArray,
ReadSiliconId,
@@ -19,43 +18,52 @@ pub enum FlashramMode {
PageProgram,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Flashram {
pub status: u32,
pub mode: FlashramMode,
pub erase_page: u16,
#[serde(with = "serde_big_array::BigArray")]
pub page_buf: [u8; 128],
pub silicon_id: [u32; 2],
}
pub fn format_sram(device: &mut device::Device) {
if device.ui.saves.sram.0.len() < SRAM_SIZE {
device.ui.saves.sram.0.resize(SRAM_SIZE, 0xFF)
fn format_sram(device: &mut device::Device) {
if device.ui.storage.saves.sram.data.len() < SRAM_SIZE {
device.ui.storage.saves.sram.data.resize(SRAM_SIZE, 0xFF)
}
}
pub 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)
fn format_flash(device: &mut device::Device) {
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
device
.ui
.storage
.saves
.flash
.data
.resize(FLASHRAM_SIZE, 0xFF)
}
}
pub fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
fn read_mem_sram(device: &mut device::Device, address: u64) -> u32 {
let masked_address = address as usize & SRAM_MASK;
format_sram(device);
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(),
)
}
pub fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
fn read_mem_flash(device: &device::Device, address: u64) -> u32 {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* read Status register */
device.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.flashram.mode == FlashramMode::ReadArray {
device.cart.flashram.status
} else if (address & 0x1ffff) == 0x0000 && device.cart.flashram.mode == FlashramMode::ReadArray
{
/* flashram MMIO read are not supported except for the "dummy" read @0x0000 done before DMA.
* returns a "dummy" value. */
return 0;
@@ -73,33 +81,39 @@ 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)
}
}
pub fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let masked_address = address as usize & SRAM_MASK;
format_sram(device);
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
}
pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.flashram.mode == FlashramMode::Status {
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if (address & 0x1ffff) == 0x00000 && device.cart.flashram.mode == FlashramMode::Status {
/* clear/set Status register */
device.flashram.status = (value & mask) & 0xff;
device.cart.flashram.status = (value & mask) & 0xff;
} else if (address & 0x1ffff) == 0x10000 {
/* set command */
format_flash(device);
@@ -111,7 +125,12 @@ pub fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, ma
}
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,20 +139,10 @@ 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);
}
pub fn dma_read_sram(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
length: u32,
) {
fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr: u32, length: u32) {
dram_addr &= device::rdram::RDRAM_MASK as u32;
cart_addr &= SRAM_MASK as u32;
let mut i = dram_addr;
@@ -141,25 +150,26 @@ pub fn dma_read_sram(
format_sram(device);
while i < dram_addr + length {
device.ui.saves.sram.0[j as usize] = device.rdram.mem[i as usize ^ device.byte_swap];
while i < dram_addr + length && i < device.rdram.size {
device.ui.storage.saves.sram.data[j as usize] =
device.rdram.mem[i as usize ^ device.byte_swap];
i += 1;
j += 1;
}
device.ui.saves.sram.1 = true
device.ui.storage.saves.sram.written = true
}
pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u32, length: u32) {
format_flash(device);
if (cart_addr & 0x1ffff) == 0x00000
&& length == 128
&& device.flashram.mode == FlashramMode::PageProgram
&& device.cart.flashram.mode == FlashramMode::PageProgram
{
/* load page buf using DMA */
for i in 0..length {
device.flashram.page_buf[i as usize] =
device.cart.flashram.page_buf[i as usize] =
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap];
}
} else {
@@ -170,7 +180,12 @@ pub fn dma_read_flash(device: &mut device::Device, cart_addr: u32, dram_addr: u3
// 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)
@@ -178,7 +193,7 @@ pub fn dma_read(device: &mut device::Device, cart_addr: u32, dram_addr: u32, len
device::pi::calculate_cycles(device, 2, length)
}
pub fn dma_write_sram(
fn dma_write_sram(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
@@ -191,14 +206,15 @@ pub fn dma_write_sram(
format_sram(device);
while i < dram_addr + length {
device.rdram.mem[i as usize ^ device.byte_swap] = device.ui.saves.sram.0[j as usize];
while i < dram_addr + length && i < device.rdram.size {
device.rdram.mem[i as usize ^ device.byte_swap] =
device.ui.storage.saves.sram.data[j as usize];
i += 1;
j += 1;
}
}
pub fn dma_write_flash(
fn dma_write_flash(
device: &mut device::Device,
mut cart_addr: u32,
mut dram_addr: u32,
@@ -208,20 +224,22 @@ pub fn dma_write_flash(
if (cart_addr & 0x1ffff) == 0x00000
&& length == 8
&& device.flashram.mode == FlashramMode::ReadSiliconId
&& device.cart.flashram.mode == FlashramMode::ReadSiliconId
{
/* read Silicon ID using DMA */
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[0].to_ne_bytes());
.copy_from_slice(&device.cart.flashram.silicon_id[0].to_ne_bytes());
dram_addr += 4;
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.copy_from_slice(&device.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000 && device.flashram.mode == FlashramMode::ReadArray {
.copy_from_slice(&device.cart.flashram.silicon_id[1].to_ne_bytes());
} else if (cart_addr & 0x1ffff) < 0x10000
&& device.cart.flashram.mode == FlashramMode::ReadArray
{
format_flash(device);
/* adjust flashram address before starting DMA. */
if device.flashram.silicon_id[1] == MX29L1100_ID
|| device.flashram.silicon_id[1] == MX29L0000_ID
|| device.flashram.silicon_id[1] == MX29L0001_ID
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
|| device.cart.flashram.silicon_id[1] == MX29L0001_ID
{
/* "old" flash needs special address adjusting */
cart_addr = (cart_addr & 0xffff) * 2;
@@ -233,7 +251,7 @@ pub 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.ui.storage.saves.flash.data[(cart_addr + i) as usize];
}
} else {
/* other accesses are not implemented */
@@ -243,7 +261,12 @@ pub 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)
@@ -251,81 +274,81 @@ pub fn dma_write(device: &mut device::Device, cart_addr: u32, dram_addr: u32, le
device::pi::calculate_cycles(device, 2, length)
}
pub fn flashram_command(device: &mut device::Device, command: u32) {
fn flashram_command(device: &mut device::Device, command: u32) {
match command & 0xff000000 {
0x3c000000 => {
/* set chip erase mode */
device.flashram.mode = FlashramMode::ChipErase;
device.cart.flashram.mode = FlashramMode::ChipErase;
}
0x4b000000 => {
/* set sector erase mode, set erase sector */
device.flashram.mode = FlashramMode::SectorErase;
device.flashram.erase_page = command as u16;
device.cart.flashram.mode = FlashramMode::SectorErase;
device.cart.flashram.erase_page = command as u16;
}
0x78000000 => {
/* set erase busy flag */
device.flashram.status |= 0x02;
device.cart.flashram.status |= 0x02;
/* do chip/sector erase */
if device.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.flashram.erase_page & 0xff80) as usize * 128;
if device.cart.flashram.mode == FlashramMode::SectorErase {
let offset: usize = (device.cart.flashram.erase_page & 0xff80) as usize * 128;
for i in 0..128 * 128 {
device.ui.saves.flash.0[offset + i] = 0xFF;
device.ui.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,
}
pub 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()
}
}
pub 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
}
}
}
pub 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)
}
}
}
fn calculate_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:X}", hasher.finalize())
}
+113 -30
View File
@@ -1,31 +1,41 @@
use crate::device;
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;
pub const JCMD_CONTROLLER_READ: u8 = 0x01;
pub const JCMD_PAK_READ: u8 = 0x02;
pub const JCMD_PAK_WRITE: u8 = 0x03;
//pub const JCMD_VRU_READ: u8 = 0x09;
//pub const JCMD_VRU_WRITE: u8 = 0x0A;
//pub const JCMD_VRU_READ_STATUS: u8 = 0x0B;
//pub const JCMD_VRU_WRITE_CONFIG: u8 = 0x0C;
//pub const JCMD_VRU_WRITE_INIT: u8 = 0x0D;
const JCMD_PAK_READ: u8 = 0x02;
const JCMD_PAK_WRITE: u8 = 0x03;
pub const JCMD_RESET: u8 = 0xff;
//pub const JDT_NONE: u16 = 0x0000;
pub const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates */
//pub const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */
pub const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */
//pub const JDT_VRU: u16 = 0x0100; /* VRU */
pub const PAK_CHUNK_SIZE: usize = 0x20;
pub const CONT_STATUS_PAK_NOT_PRESENT: u8 = 0;
pub const CONT_STATUS_PAK_PRESENT: u8 = 1;
pub const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
//const JDT_NONE: u16 = 0x0000;
const JDT_JOY_ABS_COUNTERS: u16 = 0x0001; /* joystick with absolute coordinates */
//const JDT_JOY_REL_COUNTERS: u16 = 0x0002; /* joystick with relative coordinates (= mouse) */
const JDT_JOY_PORT: u16 = 0x0004; /* has port for external paks */
const PAK_CHUNK_SIZE: usize = 0x20;
const CONT_STATUS_PAK_PRESENT: u8 = 1;
const CONT_STATUS_PAK_NOT_PRESENT: u8 = 2;
const CONT_FLAVOR: u16 = JDT_JOY_ABS_COUNTERS | JDT_JOY_PORT;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum PakType {
None = 0,
MemPak = 1,
RumblePak = 2,
TransferPak = 3,
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PakHandler {
#[serde(skip, default = "savestates::default_pak_handler")]
pub read: fn(&mut device::Device, usize, u16, usize, usize),
#[serde(skip, default = "savestates::default_pak_handler")]
pub write: fn(&mut device::Device, usize, u16, usize, usize),
pub pak_type: PakType,
}
pub fn process(device: &mut device::Device, channel: usize) {
@@ -46,8 +56,31 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = ui::input::get(&mut device.ui, channel);
device.pif.ram[offset..offset + 4].copy_from_slice(&input.to_ne_bytes());
let input = if device.netplay.is_none() {
ui::input::get(&mut device.ui, channel)
} else {
if device.netplay.as_ref().unwrap().player_number as usize == channel {
let local_input = ui::input::get(&mut device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
}
netplay::get_input(device, channel)
};
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
if input.pak_change_pressed {
// pak change button pressed
if device::events::get_event(device, device::events::EVENT_TYPE_PAK).is_none() {
device.pif.channels[channel].change_pak =
device.pif.channels[channel].pak_handler.unwrap().pak_type;
device.pif.channels[channel].pak_handler = None;
device::events::create_event(
device,
device::events::EVENT_TYPE_PAK,
device.cpu.clock_rate, // 1 second
)
}
}
}
JCMD_PAK_READ => pak_read_block(
device,
@@ -67,7 +100,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
}
pub fn pak_read_block(
fn pak_read_block(
device: &mut device::Device,
addr_acrc: usize,
data: usize,
@@ -75,18 +108,18 @@ pub fn pak_read_block(
channel: usize,
) {
let address =
(device.pif.ram[addr_acrc] as u16) << 8 | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
((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);
device.pif.ram[dcrc] = pak_data_crc(device, data, PAK_CHUNK_SIZE)
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
} else {
device.pif.ram[dcrc] = !pak_data_crc(device, data, PAK_CHUNK_SIZE)
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
}
}
pub fn pak_write_block(
fn pak_write_block(
device: &mut device::Device,
addr_acrc: usize,
data: usize,
@@ -94,18 +127,18 @@ pub fn pak_write_block(
channel: usize,
) {
let address =
(device.pif.ram[addr_acrc] as u16) << 8 | (device.pif.ram[addr_acrc + 1] & 0xe0) as u16;
((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);
device.pif.ram[dcrc] = pak_data_crc(device, data, PAK_CHUNK_SIZE)
device.pif.ram[dcrc] = data_crc(device, data, PAK_CHUNK_SIZE)
} else {
device.pif.ram[dcrc] = !pak_data_crc(device, data, PAK_CHUNK_SIZE)
device.pif.ram[dcrc] = !data_crc(device, data, PAK_CHUNK_SIZE)
}
}
pub fn pak_data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
fn data_crc(device: &device::Device, data_offset: usize, size: usize) -> u8 {
let mut i = 0;
let mut crc = 0;
@@ -124,3 +157,53 @@ pub fn pak_data_crc(device: &device::Device, data_offset: usize, size: usize) ->
}
crc
}
pub fn pak_switch_event(device: &mut device::Device) {
for (i, channel) in device.pif.channels.iter_mut().enumerate() {
if channel.change_pak != PakType::None {
//stop rumble if it is on
if device.netplay.is_none() {
device::ui::input::set_rumble(&mut device.ui, i, 0);
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
device::ui::input::set_rumble(&mut device.ui, 0, 0);
}
let 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: new_pak_type,
});
} else if new_pak_type == PakType::RumblePak {
channel.pak_handler = Some(device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: new_pak_type,
});
} 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::audio::play_pak_switch(&mut device.ui, new_pak_type);
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: &mut [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: &mut 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: &mut [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 {:x}", address);
}
}
fn read_mbc3(
pif_ram: &mut [u8],
cart: &mut 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 {:x}", address);
}
}
fn write_mbc5(
pif_ram: &mut [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 {:x}", address);
} 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 {:x}", address);
}
}
fn read_mbc5(
pif_ram: &mut [u8],
cart: &mut 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 {:x}", address);
}
}
pub fn read(
pif_ram: &mut [u8],
cart: &mut 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: &mut [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 {:x}", data),
}
}
@@ -1,11 +1,17 @@
use crate::device;
pub const MEMPAK_SIZE: usize = 0x8000;
pub const MPK_PAGE_SIZE: usize = 256;
const MPK_PAGE_SIZE: usize = 256;
pub 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);
fn format_mempak(device: &mut device::Device) {
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 @@ pub 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
}
}
+24
View File
@@ -0,0 +1,24 @@
use crate::device;
pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: usize, size: usize) {
let value: u8 = if (0x8000..0x9000).contains(&address) {
0x80
} else {
0x00
};
for i in 0..size {
device.pif.ram[data + i] = value;
}
}
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if address == 0xc000 {
let rumble = device.pif.ram[data + size - 1];
if device.netplay.is_none() {
device::ui::input::set_rumble(&mut device.ui, channel, rumble);
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
device::ui::input::set_rumble(&mut device.ui, 0, rumble);
}
}
}
+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,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
);
}
_ => {
panic!("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;
}
_ => panic!("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(
&mut device.pif.ram,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
);
}
_ => {
panic!("unknown transfer pak write {:x}", address >> 12);
}
}
}
File diff suppressed because it is too large Load Diff
+89 -87
View File
@@ -1,37 +1,37 @@
use crate::device;
use crate::{device, savestates};
pub const COP0_INDEX_REG: u32 = 0;
pub const COP0_RANDOM_REG: u32 = 1;
const COP0_RANDOM_REG: u32 = 1;
pub const COP0_ENTRYLO0_REG: u32 = 2;
pub const COP0_ENTRYLO1_REG: u32 = 3;
pub const COP0_CONTEXT_REG: u32 = 4;
pub const COP0_PAGEMASK_REG: u32 = 5;
pub const COP0_WIRED_REG: u32 = 6;
//pub const COP0_UNUSED_7: u32 = 7;
const COP0_WIRED_REG: u32 = 6;
//const COP0_UNUSED_7: u32 = 7;
pub const COP0_BADVADDR_REG: u32 = 8;
pub const COP0_COUNT_REG: u32 = 9;
pub const COP0_ENTRYHI_REG: u32 = 10;
pub const COP0_COMPARE_REG: u32 = 11;
const COP0_COMPARE_REG: u32 = 11;
pub const COP0_STATUS_REG: u32 = 12;
pub const COP0_CAUSE_REG: u32 = 13;
pub const COP0_EPC_REG: u32 = 14;
pub const COP0_PREVID_REG: u32 = 15;
pub const COP0_CONFIG_REG: u32 = 16;
const COP0_PREVID_REG: u32 = 15;
const COP0_CONFIG_REG: u32 = 16;
pub const COP0_LLADDR_REG: u32 = 17;
//pub const COP0_WATCHLO_REG: u32 = 18;
//pub const COP0_WATCHHI_REG: u32 = 19;
//const COP0_WATCHLO_REG: u32 = 18;
//const COP0_WATCHHI_REG: u32 = 19;
pub const COP0_XCONTEXT_REG: u32 = 20;
//pub const COP0_UNUSED_21: u32 = 21;
//pub const COP0_UNUSED_22: u32 = 22;
//pub const COP0_UNUSED_23: u32 = 23;
//pub const COP0_UNUSED_24: u32 = 24;
//pub const COP0_UNUSED_25: u32 = 25;
//pub const COP0_PARITYERR_REG: u32 = 26;
//pub const COP0_CACHEERR_REG: u32 = 27;
//const COP0_UNUSED_21: u32 = 21;
//const COP0_UNUSED_22: u32 = 22;
//const COP0_UNUSED_23: u32 = 23;
//const COP0_UNUSED_24: u32 = 24;
//const COP0_UNUSED_25: u32 = 25;
//const COP0_PARITYERR_REG: u32 = 26;
//const COP0_CACHEERR_REG: u32 = 27;
pub const COP0_TAGLO_REG: u32 = 28;
//pub const COP0_TAGHI_REG: u32 = 29;
pub const COP0_ERROREPC_REG: u32 = 30;
//pub const COP0_UNUSED_31: u32 = 31;
//const COP0_TAGHI_REG: u32 = 29;
const COP0_ERROREPC_REG: u32 = 30;
//const COP0_UNUSED_31: u32 = 31;
pub const COP0_REGS_COUNT: u32 = 32;
pub const COP0_STATUS_IE: u64 = 1 << 0;
@@ -42,12 +42,12 @@ pub const COP0_STATUS_FR: u64 = 1 << 26;
pub const COP0_STATUS_CU1: u64 = 1 << 29;
pub const COP0_STATUS_CU2: u64 = 1 << 30;
//pub const COP0_CAUSE_EXCCODE_INTR: u64 = 0 << 2;
//const COP0_CAUSE_EXCCODE_INTR: u64 = 0 << 2;
pub const COP0_CAUSE_EXCCODE_MOD: u64 = 1 << 2;
pub const COP0_CAUSE_EXCCODE_TLBL: u64 = 2 << 2;
pub const COP0_CAUSE_EXCCODE_TLBS: u64 = 3 << 2;
//pub const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2;
//pub const COP0_CAUSE_EXCCODE_ADES: u64 = 5 << 2;
//const COP0_CAUSE_EXCCODE_ADEL: u64 = 4 << 2;
//const COP0_CAUSE_EXCCODE_ADES: u64 = 5 << 2;
pub const COP0_CAUSE_EXCCODE_SYS: u64 = 8 << 2;
pub const COP0_CAUSE_EXCCODE_BP: u64 = 9 << 2;
pub const COP0_CAUSE_EXCCODE_RI: u64 = 10 << 2;
@@ -62,7 +62,7 @@ pub const COP0_CAUSE_CE1: u64 = 1 << 28;
pub const COP0_CAUSE_CE2: u64 = 1 << 29;
pub const COP0_CAUSE_IP_MASK: u64 = 0b00000000000000001111111100000000;
pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
//pub const COP0_CAUSE_EXCCODE_MASK: u64 = 0x1F << 2;
pub const COP0_CONTEXT_BADVPN2_MASK: u64 = 0b00000000011111111111111111110000;
pub const COP0_XCONTEXT_BADVPN2_MASK: u64 = 0b01111111111111111111111111110000;
pub const COP0_XCONTEXT_REGION_MASK: u64 = 0b110000000000000000000000000000000;
@@ -88,29 +88,34 @@ const COP0_XCONTEXT_REG_MASK: u64 =
const COP0_PARITYERR_REG_MASK: u64 = 0b00000000000000000000000011111111;
const COP0_TAGLO_REG_MASK: u64 = 0b00001111111111111111111111000000;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop0 {
pub reg_latch: u64,
pub regs: [u64; COP0_REGS_COUNT as usize],
pub reg_write_masks: [u64; COP0_REGS_COUNT as usize],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
#[serde(skip, default = "savestates::default_instructions")]
pub instrs2: [fn(&mut device::Device, u32); 32],
pub tlb_lut_r: Vec<device::tlb::TlbLut>,
pub tlb_lut_w: Vec<device::tlb::TlbLut>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
pub fn mfc0(device: &mut device::Device, opcode: u32) {
fn mfc0(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device::cpu_instructions::se32(
(get_control_registers(device, device::cpu_instructions::rd(opcode))) as u32 as i32,
)
}
pub fn dmfc0(device: &mut device::Device, opcode: u32) {
fn dmfc0(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
get_control_registers(device, device::cpu_instructions::rd(opcode))
}
pub fn mtc0(device: &mut device::Device, opcode: u32) {
fn mtc0(device: &mut device::Device, opcode: u32) {
device::cop0::set_control_registers(
device,
device::cpu_instructions::rd(opcode),
@@ -120,7 +125,7 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
)
}
pub fn dmtc0(device: &mut device::Device, opcode: u32) {
fn dmtc0(device: &mut device::Device, opcode: u32) {
device::cop0::set_control_registers(
device,
device::cpu_instructions::rd(opcode),
@@ -128,24 +133,24 @@ pub fn dmtc0(device: &mut device::Device, opcode: u32) {
)
}
pub fn tlbr(device: &mut device::Device, _opcode: u32) {
fn tlbr(device: &mut device::Device, _opcode: u32) {
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
}
pub fn tlbwi(device: &mut device::Device, _opcode: u32) {
fn tlbwi(device: &mut device::Device, _opcode: u32) {
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
}
pub fn tlbwr(device: &mut device::Device, _opcode: u32) {
fn tlbwr(device: &mut device::Device, _opcode: u32) {
let random = set_random_register(device);
device::tlb::write(device, random)
}
pub fn tlbp(device: &mut device::Device, _opcode: u32) {
fn tlbp(device: &mut device::Device, _opcode: u32) {
device::tlb::probe(device)
}
pub fn eret(device: &mut device::Device, _opcode: u32) {
fn eret(device: &mut device::Device, _opcode: u32) {
if device.cpu.cop0.regs[COP0_STATUS_REG as usize] & COP0_STATUS_ERL != 0 {
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG as usize];
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_ERL
@@ -158,7 +163,7 @@ pub fn eret(device: &mut device::Device, _opcode: u32) {
device::exceptions::check_pending_interrupts(device)
}
pub fn execute_cp0(device: &mut device::Device, opcode: u32) {
fn execute_cp0(device: &mut device::Device, opcode: u32) {
device.cpu.cop0.instrs2[(opcode & 0x3F) as usize](device, opcode)
}
@@ -174,7 +179,7 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
device::exceptions::reserved_exception(device, 0);
}
pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
match index {
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
COP0_RANDOM_REG => set_random_register(device),
@@ -183,7 +188,7 @@ pub fn get_control_registers(device: &device::Device, index: u32) -> u64 {
}
}
pub fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
device.cpu.cop0.reg_latch = data;
match index {
COP0_COUNT_REG => {
@@ -209,11 +214,11 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
device::events::create_event(
device,
device::events::EventType::Compare,
device.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 {
@@ -231,19 +236,16 @@ pub fn set_control_registers(device: &mut device::Device, index: u32, mut data:
}
pub fn compare_event(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] &= !COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[COP0_CAUSE_REG as usize] |= COP0_CAUSE_IP7;
device::events::create_event(
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);
}
pub fn set_random_register(device: &device::Device) -> u64 {
fn set_random_register(device: &device::Device) -> u64 {
if device.cpu.cop0.regs[COP0_WIRED_REG as usize] > 31 {
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize]) & 0x3F
} else {
@@ -257,42 +259,7 @@ pub fn add_cycles(device: &mut device::Device, cycles: u64) {
device.cpu.cop0.regs[COP0_COUNT_REG as usize] += cycles // COUNT_REG is shifted right 1 bit when read by MFC0
}
pub fn init(device: &mut device::Device) {
device.cpu.cop0.reg_write_masks = [
COP0_INDEX_REG_MASK,
0, // Random, read only
COP0_ENTRYLO_REG_MASK,
COP0_ENTRYLO_REG_MASK,
COP0_CONTEXT_REG_MASK,
COP0_PAGEMASK_REG_MASK,
COP0_WIRED_REG_MASK,
0, // 7
0, // BadVAddr, read only
u32::MAX as u64, // count
COP0_ENTRYHI_REG_MASK,
u32::MAX as u64, // compare
COP0_STATUS_REG_MASK,
COP0_CAUSE_REG_MASK,
u64::MAX, // EPC
0, // previd, read only
COP0_CONFIG_REG_MASK,
COP0_LLADDR_REG_MASK,
COP0_WATCHLO_REG_MASK,
COP0_WATCHHI_REG_MASK,
COP0_XCONTEXT_REG_MASK,
0, // 21
0, // 22
0, // 23
0, // 24
0, // 25
COP0_PARITYERR_REG_MASK,
0, // cache error
COP0_TAGLO_REG_MASK,
0, // taghi
u64::MAX, // ErrorPC
0, // 31
];
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop0.instrs = [
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
@@ -362,6 +329,46 @@ pub fn init(device: &mut device::Device) {
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
}
pub fn init(device: &mut device::Device) {
device.cpu.cop0.reg_write_masks = [
COP0_INDEX_REG_MASK,
0, // Random, read only
COP0_ENTRYLO_REG_MASK,
COP0_ENTRYLO_REG_MASK,
COP0_CONTEXT_REG_MASK,
COP0_PAGEMASK_REG_MASK,
COP0_WIRED_REG_MASK,
0, // 7
0, // BadVAddr, read only
u32::MAX as u64, // count
COP0_ENTRYHI_REG_MASK,
u32::MAX as u64, // compare
COP0_STATUS_REG_MASK,
COP0_CAUSE_REG_MASK,
u64::MAX, // EPC
0, // previd, read only
COP0_CONFIG_REG_MASK,
COP0_LLADDR_REG_MASK,
COP0_WATCHLO_REG_MASK,
COP0_WATCHHI_REG_MASK,
COP0_XCONTEXT_REG_MASK,
0, // 21
0, // 22
0, // 23
0, // 24
0, // 25
COP0_PARITYERR_REG_MASK,
0, // cache error
COP0_TAGLO_REG_MASK,
0, // taghi
u64::MAX, // ErrorPC
0, // 31
];
map_instructions(device);
// taken from VR4300 manual
device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 0b00000000000000000000000000011111;
device.cpu.cop0.regs[COP0_CONFIG_REG as usize] = 0b01110000000001101110010001100000;
@@ -372,10 +379,5 @@ pub fn init(device: &mut device::Device) {
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
device::events::create_event(
device,
device::events::EventType::Compare,
u32::MAX as u64,
compare_event,
)
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
}
+63 -46
View File
@@ -1,39 +1,48 @@
use crate::device;
use crate::{device, savestates};
//pub const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
//pub const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
//pub const FCR31_FLAG_OVERFLOW_BIT: u32 = 1 << 4;
//pub const FCR31_FLAG_DIVBYZERO_BIT: u32 = 1 << 5;
//pub const FCR31_FLAG_INVALID_BIT: u32 = 1 << 6;
//pub const FCR31_ENABLE_INEXACT_BIT: u32 = 1 << 7;
//pub const FCR31_ENABLE_UNDERFLOW_BIT: u32 = 1 << 8;
//pub const FCR31_ENABLE_OVERFLOW_BIT: u32 = 1 << 9;
//pub const FCR31_ENABLE_DIVBYZERO_BIT: u32 = 1 << 10;
//pub const FCR31_ENABLE_INVALID_BIT: u32 = 1 << 11;
//pub const FCR31_CAUSE_INEXACT_BIT: u32 = 1 << 12;
//pub const FCR31_CAUSE_UNDERFLOW_BIT: u32 = 1 << 13;
//pub const FCR31_CAUSE_OVERFLOW_BIT: u32 = 1 << 14;
//pub const FCR31_CAUSE_DIVBYZERO_BIT: u32 = 1 << 15;
//pub const FCR31_CAUSE_INVALID_BIT: u32 = 1 << 16;
pub const FCR31_CAUSE_UNIMP_BIT: u32 = 1 << 17;
//const FCR31_FLAG_INEXACT_BIT: u32 = 1 << 2;
//const FCR31_FLAG_UNDERFLOW_BIT: u32 = 1 << 3;
//const FCR31_FLAG_OVERFLOW_BIT: u32 = 1 << 4;
//const FCR31_FLAG_DIVBYZERO_BIT: u32 = 1 << 5;
//const FCR31_FLAG_INVALID_BIT: u32 = 1 << 6;
//const FCR31_ENABLE_INEXACT_BIT: u32 = 1 << 7;
//const FCR31_ENABLE_UNDERFLOW_BIT: u32 = 1 << 8;
//const FCR31_ENABLE_OVERFLOW_BIT: u32 = 1 << 9;
//const FCR31_ENABLE_DIVBYZERO_BIT: u32 = 1 << 10;
//const FCR31_ENABLE_INVALID_BIT: u32 = 1 << 11;
//const FCR31_CAUSE_INEXACT_BIT: u32 = 1 << 12;
//const FCR31_CAUSE_UNDERFLOW_BIT: u32 = 1 << 13;
//const FCR31_CAUSE_OVERFLOW_BIT: u32 = 1 << 14;
//const FCR31_CAUSE_DIVBYZERO_BIT: u32 = 1 << 15;
//const FCR31_CAUSE_INVALID_BIT: u32 = 1 << 16;
const FCR31_CAUSE_UNIMP_BIT: u32 = 1 << 17;
pub const FCR31_CMP_BIT: u32 = 1 << 23;
pub const FCR31_FS_BIT: u32 = 1 << 24;
//#[cfg(target_arch = "x86_64")]
//const FCR31_FS_BIT: u32 = 1 << 24;
pub const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
pub const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
pub const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
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,
pub flush_mode: u32,
//#[cfg(target_arch = "x86_64")]
//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],
}
@@ -171,7 +180,7 @@ pub fn sdc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn mfc1(device: &mut device::Device, opcode: u32) {
fn mfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -183,7 +192,7 @@ pub fn mfc1(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32(u32::from_ne_bytes(value.to_ne_bytes()) as i32);
}
pub fn dmfc1(device: &mut device::Device, opcode: u32) {
fn dmfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -195,7 +204,7 @@ pub fn dmfc1(device: &mut device::Device, opcode: u32) {
u64::from_ne_bytes(value.to_ne_bytes());
}
pub fn cfc1(device: &mut device::Device, opcode: u32) {
fn cfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -207,7 +216,7 @@ pub fn cfc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dcfc1(device: &mut device::Device, opcode: u32) {
fn dcfc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -219,7 +228,7 @@ pub fn dcfc1(device: &mut device::Device, opcode: u32) {
device::exceptions::floating_point_exception(device)
}
pub fn mtc1(device: &mut device::Device, opcode: u32) {
fn mtc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -237,7 +246,7 @@ pub fn mtc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dmtc1(device: &mut device::Device, opcode: u32) {
fn dmtc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -250,7 +259,7 @@ pub fn dmtc1(device: &mut device::Device, opcode: u32) {
set_fpr_double(device, device::fpu_instructions::fs(opcode) as usize, value)
}
pub fn ctc1(device: &mut device::Device, opcode: u32) {
fn ctc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -264,7 +273,7 @@ pub fn ctc1(device: &mut device::Device, opcode: u32) {
)
}
pub fn dctc1(device: &mut device::Device, opcode: u32) {
fn dctc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -276,7 +285,7 @@ pub fn dctc1(device: &mut device::Device, opcode: u32) {
device::exceptions::floating_point_exception(device)
}
pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -286,7 +295,7 @@ pub fn execute_cop1_b(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.b_instrs[((opcode >> 16) & 0x3) as usize](device, opcode)
}
pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -296,7 +305,7 @@ pub fn execute_cop1_s(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.s_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -306,7 +315,7 @@ pub fn execute_cop1_d(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.d_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -316,7 +325,7 @@ pub fn execute_cop1_l(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.l_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
@@ -326,7 +335,7 @@ pub fn execute_cop1_w(device: &mut device::Device, opcode: u32) {
device.cpu.cop1.w_instrs[(opcode & 0x3F) as usize](device, opcode)
}
pub fn unusable(device: &mut device::Device, _opcode: u32) {
fn unusable(device: &mut device::Device, _opcode: u32) {
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE1)
}
@@ -340,7 +349,7 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE1);
}
pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
match index {
0 => device.cpu.cop1.fcr0,
31 => device.cpu.cop1.fcr31,
@@ -350,7 +359,7 @@ pub fn get_control_registers_fpu(device: &device::Device, index: u32) -> u32 {
}
}
pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32) {
match index {
0 => { // read only
}
@@ -359,15 +368,16 @@ pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data:
device.cpu.cop1.fcr31 = data & FCR31_WRITE_MASK;
// the Cause bits are ANDed with the Enable bits to check for exceptions
// "Unimplemented Operation" has no Enable bit and always causes an exception
if (device.cpu.cop1.fcr31 & FCR31_CAUSE_MASK) >> 5
if ((device.cpu.cop1.fcr31 & FCR31_CAUSE_MASK) >> 5)
& (device.cpu.cop1.fcr31 & FCR31_ENABLE_MASK)
!= 0
|| device.cpu.cop1.fcr31 & FCR31_CAUSE_UNIMP_BIT != 0
{
device::exceptions::floating_point_exception(device)
}
unsafe {
/*
#[cfg(target_arch = "x86_64")]
{
let flush_mode;
if (device.cpu.cop1.fcr31 & 2) != 0 {
if (device.cpu.cop1.fcr31 & FCR31_FS_BIT) != 0 {
@@ -380,10 +390,13 @@ pub fn set_control_registers_fpu(device: &mut device::Device, index: u32, data:
}
if flush_mode != device.cpu.cop1.flush_mode {
#[allow(deprecated)]
std::arch::x86_64::_MM_SET_FLUSH_ZERO_MODE(flush_mode);
unsafe {
std::arch::x86_64::_MM_SET_FLUSH_ZERO_MODE(flush_mode)
};
device.cpu.cop1.flush_mode = flush_mode;
}
}
*/
}
_ => {
panic!("unknown FCR register")
@@ -442,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
@@ -757,6 +767,13 @@ pub fn init(device: &mut device::Device) {
]
}
pub fn init(device: &mut device::Device) {
set_fgr_registers(device, 0);
device.cpu.cop1.fcr0 = 0b101000000000;
map_instructions(device);
}
pub fn set_fgr_registers(device: &mut device::Device, status_reg: u64) {
// this method doesn't account for undocumented behaviour (accessing odd numbered registers in half mode)
if (status_reg & device::cop0::COP0_STATUS_FR) == 0 {
+15 -9
View File
@@ -1,11 +1,13 @@
use crate::device;
use crate::{device, savestates};
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cop2 {
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
pub reg_latch: u64,
}
pub fn mfc2(device: &mut device::Device, opcode: u32) {
fn mfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -15,7 +17,7 @@ pub fn mfc2(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
}
pub fn dmfc2(device: &mut device::Device, opcode: u32) {
fn dmfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -24,7 +26,7 @@ pub fn dmfc2(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device.cpu.cop2.reg_latch
}
pub fn cfc2(device: &mut device::Device, opcode: u32) {
fn cfc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -34,7 +36,7 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
device::cpu_instructions::se32((device.cpu.cop2.reg_latch) as u32 as i32)
}
pub fn mtc2(device: &mut device::Device, opcode: u32) {
fn mtc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -43,7 +45,7 @@ pub fn mtc2(device: &mut device::Device, opcode: u32) {
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
pub fn dmtc2(device: &mut device::Device, opcode: u32) {
fn dmtc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -52,7 +54,7 @@ pub fn dmtc2(device: &mut device::Device, opcode: u32) {
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
pub fn ctc2(device: &mut device::Device, opcode: u32) {
fn ctc2(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
@@ -72,11 +74,11 @@ pub fn reserved(device: &mut device::Device, opcode: u32) {
device::exceptions::reserved_exception(device, device::cop0::COP0_CAUSE_CE2);
}
pub fn unusable(device: &mut device::Device, _opcode: u32) {
fn unusable(device: &mut device::Device, _opcode: u32) {
device::exceptions::cop_unusable_exception(device, device::cop0::COP0_CAUSE_CE2)
}
pub fn init(device: &mut device::Device) {
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop2.instrs = [
device::cop2::mfc2, // 0
device::cop2::dmfc2, // 1
@@ -112,3 +114,7 @@ pub fn init(device: &mut device::Device) {
device::cop2::reserved, // 31
]
}
pub fn init(device: &mut device::Device) {
map_instructions(device);
}
+25 -9
View File
@@ -1,6 +1,6 @@
use crate::device;
use crate::{device, savestates};
#[derive(PartialEq)]
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
pub enum State {
Step,
Take,
@@ -11,10 +11,13 @@ pub enum State {
Exception,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: State,
pub pc: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Cpu {
pub cop0: device::cop0::Cop0,
pub cop1: device::cop1::Cop1,
@@ -25,13 +28,17 @@ pub struct Cpu {
pub pc_phys: u64,
pub lo: u64,
pub hi: u64,
pub running: u8,
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,17 @@ 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 {
125000000
};
map_instructions(device);
device::cop0::init(device);
device::cop1::init(device);
device::cop2::init(device);
@@ -231,8 +247,8 @@ pub fn in_delay_slot_taken(device: &device::Device) -> bool {
}
pub fn run(device: &mut device::Device) {
device.cpu.running = 1;
while device.cpu.running == 1 {
device.cpu.running = true;
while device.cpu.running {
device.cpu.gpr[0] = 0; // gpr 0 is read only
let (cached, err);
(device.cpu.pc_phys, cached, err) = device::memory::translate_address(
+18 -20
View File
@@ -12,7 +12,7 @@ pub fn rt(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn sa(opcode: u32) -> u32 {
fn sa(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
@@ -28,23 +28,23 @@ pub fn se16(value: i16) -> u64 {
value as i64 as u64
}
pub fn se8(value: i8) -> u64 {
fn se8(value: i8) -> u64 {
value as i64 as u64
}
pub fn bshift<T: Into<u64>>(address: T) -> u64 {
fn bshift<T: Into<u64>>(address: T) -> u64 {
((address.into() & 3) ^ 3) << 3
}
pub fn hshift<T: Into<u64>>(address: T) -> u64 {
fn hshift<T: Into<u64>>(address: T) -> u64 {
((address.into() & 2) ^ 2) << 3
}
pub fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
fn bits_below_mask<T: Into<u64>>(x: T) -> u64 {
(1 << x.into()) - 1
}
pub fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
fn bits_above_mask<T: Into<u64>>(x: T) -> u64 {
!bits_below_mask(x)
}
@@ -60,8 +60,8 @@ pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
}
}
pub fn check_absolute_idle_loop(device: &mut device::Device, opcode: u32) {
if (opcode & 0x3FFFFFF) as u64 == (device.cpu.pc_phys & 0x0FFFFFFF) >> 2
fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
if device.cpu.pc == target
&& device.memory.fast_read[(device.cpu.pc_phys >> 16) as usize](
device,
device.cpu.pc_phys + 4,
@@ -76,10 +76,10 @@ pub fn j(device: &mut device::Device, opcode: u32) {
if device::cpu::in_delay_slot_taken(device) {
return;
}
check_absolute_idle_loop(device, opcode);
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
check_absolute_idle_loop(device, target);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc =
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
device.cpu.branch_state.pc = target
}
pub fn jal(device: &mut device::Device, opcode: u32) {
@@ -89,10 +89,10 @@ pub fn jal(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[31] = device.cpu.pc + 8
}
if !device::cpu::in_delay_slot_taken(device) {
check_absolute_idle_loop(device, opcode);
let target = (device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64;
check_absolute_idle_loop(device, target);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc =
(device.cpu.pc + 4) & 0xFFFFFFFFF0000000 | ((opcode & 0x3FFFFFF) << 2) as u64
device.cpu.branch_state.pc = target
} else if !device::cpu::in_delay_slot(device) {
device.cpu.branch_state.state = device::cpu::State::NotTaken;
}
@@ -346,12 +346,12 @@ pub fn lwl(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[rt(opcode) as usize] = se32(
((device.cpu.gpr[rt(opcode) as usize] as u32) & mask
| device::memory::data_read(
| (device::memory::data_read(
device,
phys_address,
device::memory::AccessSize::Word,
cached,
) << shift) as i32,
) << shift)) as i32,
)
}
@@ -431,12 +431,12 @@ pub fn lwr(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[rt(opcode) as usize] = se32(
((device.cpu.gpr[rt(opcode) as usize] as u32) & (mask as u32)
| device::memory::data_read(
| (device::memory::data_read(
device,
phys_address,
device::memory::AccessSize::Word,
cached,
) >> shift) as i32,
) >> shift)) as i32,
)
}
@@ -966,7 +966,6 @@ pub fn srav(device: &mut device::Device, opcode: u32) {
pub fn jr(device: &mut device::Device, opcode: u32) {
if !device::cpu::in_delay_slot_taken(device) {
check_relative_idle_loop(device, opcode);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
} else if !device::cpu::in_delay_slot(device) {
@@ -978,7 +977,6 @@ pub fn jalr(device: &mut device::Device, opcode: u32) {
let in_delay_slot_taken = device::cpu::in_delay_slot_taken(device);
if !in_delay_slot_taken {
check_relative_idle_loop(device, opcode);
device.cpu.branch_state.state = device::cpu::State::Take;
device.cpu.branch_state.pc = device.cpu.gpr[rs(opcode) as usize];
} else if !device::cpu::in_delay_slot(device) {
+51 -31
View File
@@ -1,61 +1,81 @@
use crate::device;
#[derive(PartialEq, Copy, Clone)]
pub enum EventType {
AI,
SI,
VI,
PI,
DP,
SP,
InterruptCheck,
SPDma,
Compare,
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: &mut device::Device, name: usize) -> Option<&mut Event> {
if device.cpu.events[name].enabled {
return Some(&mut device.cpu.events[name]);
}
None
}
pub fn remove_event(device: &mut device::Device, name: EventType) {
device.cpu.events[name as usize].enabled = false;
pub fn remove_event(device: &mut device::Device, name: usize) {
device.cpu.events[name].enabled = false;
}
fn get_event_handler(name: usize) -> fn(&mut device::Device) {
match name {
EVENT_TYPE_AI => device::ai::dma_event,
EVENT_TYPE_SI => device::si::dma_event,
EVENT_TYPE_VI => device::vi::vertical_interrupt_event,
EVENT_TYPE_PI => device::pi::dma_event,
EVENT_TYPE_DP => device::rdp::rdp_interrupt_event,
EVENT_TYPE_SP => device::rsp_interface::rsp_event,
EVENT_TYPE_INT => device::exceptions::interrupt_exception,
EVENT_TYPE_SPDMA => device::rsp_interface::fifo_pop,
EVENT_TYPE_COMPARE => device::cop0::compare_event,
EVENT_TYPE_VRU => device::controller::vru::vru_talking_event,
EVENT_TYPE_PAK => device::controller::pak_switch_event,
_ => dummy_event,
}
}
pub fn trigger_event(device: &mut device::Device) {
let next_event_name = device.cpu.next_event;
device.cpu.events[next_event_name].enabled = false;
let handler = device.cpu.events[next_event_name].handler;
let handler = get_event_handler(next_event_name);
device.cpu.cop0.is_event = true;
handler(device);
device.cpu.cop0.is_event = false;
set_next_event(device);
}
pub fn set_next_event(device: &mut device::Device) {
fn set_next_event(device: &mut device::Device) {
device.cpu.next_event_count = u64::MAX;
for (pos, i) in device.cpu.events.iter().enumerate() {
if i.enabled && i.count < device.cpu.next_event_count {
+28 -10
View File
@@ -1,15 +1,6 @@
use crate::device;
pub fn check_pending_interrupts(device: &mut device::Device) {
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& (device::cop0::COP0_STATUS_IE
| device::cop0::COP0_STATUS_EXL
@@ -20,6 +11,33 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.mi.regs[device::mi::MI_INTR_REG as usize]
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
!= 0
{
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
} else if device.cpu.cop0.pending_compare_interrupt {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
device.cpu.cop0.pending_compare_interrupt = false;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
// interrupt disabled, or no pending interrupts
return;
}
if device.cpu.cop0.is_event {
interrupt_exception(device);
} else {
device::events::create_event(device, device::events::EVENT_TYPE_INT, 0);
}
}
pub fn interrupt_exception(device: &mut device::Device) {
exception_general(device, 0x180);
}
@@ -133,7 +151,7 @@ pub fn tlb_miss_exception(
exception_general(device, vector_offset)
}
pub fn exception_general(device: &mut device::Device, vector_offset: u32) {
fn exception_general(device: &mut device::Device, vector_offset: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
== 0
{
+1 -1
View File
@@ -8,7 +8,7 @@ pub fn ft(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn fd(opcode: u32) -> u32 {
fn fd(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
+5 -4
View File
@@ -1,6 +1,6 @@
use crate::device;
pub const IS_VIEWER_MASK: usize = 0xFFFF;
const IS_VIEWER_MASK: usize = 0xFFFF;
pub fn read_mem(
device: &mut device::Device,
@@ -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 data.is_ok() {
print!("{}", data.unwrap());
}
} else {
let mut data = u32::from_be_bytes(
device.cart.is_viewer_buffer[masked_address..masked_address + 4]
+44 -30
View File
@@ -1,24 +1,26 @@
use crate::device;
use crate::{device, savestates};
pub const MM_RDRAM_DRAM: usize = 0x00000000;
pub const MM_RDRAM_REGS: usize = 0x03f00000;
pub const MM_RSP_MEM: usize = 0x04000000;
pub const MM_RSP_REGS: usize = 0x04040000;
pub const MM_RSP_REGS_PC: usize = 0x04080000;
pub const MM_DPC_REGS: usize = 0x04100000;
pub const MM_DPS_REGS: usize = 0x04200000;
pub const MM_MI_REGS: usize = 0x04300000;
pub const MM_VI_REGS: usize = 0x04400000;
pub const MM_AI_REGS: usize = 0x04500000;
pub const MM_PI_REGS: usize = 0x04600000;
pub const MM_RI_REGS: usize = 0x04700000;
pub const MM_SI_REGS: usize = 0x04800000;
const MM_RDRAM_DRAM: usize = 0x00000000;
const MM_RDRAM_REGS: usize = 0x03f00000;
const MM_RSP_MEM: usize = 0x04000000;
const MM_RSP_REGS: usize = 0x04040000;
const MM_RSP_REGS_PC: usize = 0x04080000;
const MM_DPC_REGS: usize = 0x04100000;
const MM_DPS_REGS: usize = 0x04200000;
const MM_MI_REGS: usize = 0x04300000;
const MM_VI_REGS: usize = 0x04400000;
const MM_AI_REGS: usize = 0x04500000;
const MM_PI_REGS: usize = 0x04600000;
const MM_RI_REGS: usize = 0x04700000;
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;
pub const MM_PIF_MEM: usize = 0x1fc00000;
//pub const MM_DOM1_ADDR3: usize = 0x1fd00000;
pub const MM_IS_VIEWER: usize = 0x13ff0000;
//const MM_DOM1_ADDR3: usize = 0x1fd00000;
const MM_IS_VIEWER: usize = 0x13ff0000;
pub const MM_SC64_BUFFER: usize = 0x1ffe0000;
const MM_SC64_REGS: usize = 0x1fff0000;
#[derive(PartialEq)]
pub enum AccessType {
@@ -26,20 +28,27 @@ pub enum AccessType {
Read,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub enum AccessSize {
// None = 0,
#[default]
None = 0,
Word = 4,
Dword = 8,
Dcache = 16,
Icache = 32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Memory {
#[serde(skip, default = "savestates::default_memory_read")]
pub fast_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
#[serde(skip, default = "savestates::default_memory_read")]
pub memory_map_read: [fn(&mut device::Device, u64, AccessSize) -> u32; 0x2000],
#[serde(skip, default = "savestates::default_memory_write")]
pub memory_map_write: [fn(&mut device::Device, u64, u32, u32); 0x2000],
#[serde(with = "serde_big_array::BigArray")]
pub icache: [device::cache::ICache; 512],
#[serde(with = "serde_big_array::BigArray")]
pub dcache: [device::cache::DCache; 512],
}
@@ -56,13 +65,10 @@ pub fn translate_address(
address: u64,
access_type: AccessType,
) -> (u64, bool, bool) {
let mut cached = false;
if (address & 0xc0000000) != 0x80000000 {
return device::tlb::get_physical_address(device, address, access_type);
} else if address & 0x20000000 == 0 {
cached = true;
}
(address & 0x1FFFFFFF, cached, false)
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
}
pub fn data_read(
@@ -145,19 +151,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)
}
+30 -56
View File
@@ -1,7 +1,7 @@
use crate::device;
pub const MI_INIT_MODE_REG: u32 = 0;
pub const MI_VERSION_REG: u32 = 1;
const MI_INIT_MODE_REG: u32 = 0;
const MI_VERSION_REG: u32 = 1;
pub const MI_INTR_REG: u32 = 2;
pub const MI_INTR_MASK_REG: u32 = 3;
pub const MI_REGS_COUNT: u32 = 4;
@@ -15,35 +15,36 @@ pub const MI_INTR_PI: u32 = 1 << 4;
pub const MI_INTR_DP: u32 = 1 << 5;
/* write */
pub const MI_CLR_SP: u32 = 1 << 0;
pub const MI_SET_SP: u32 = 1 << 1;
pub const MI_CLR_SI: u32 = 1 << 2;
pub const MI_SET_SI: u32 = 1 << 3;
pub const MI_CLR_AI: u32 = 1 << 4;
pub const MI_SET_AI: u32 = 1 << 5;
pub const MI_CLR_VI: u32 = 1 << 6;
pub const MI_SET_VI: u32 = 1 << 7;
pub const MI_CLR_PI: u32 = 1 << 8;
pub const MI_SET_PI: u32 = 1 << 9;
pub const MI_CLR_DP: u32 = 1 << 10;
pub const MI_SET_DP: u32 = 1 << 11;
const MI_CLR_SP: u32 = 1 << 0;
const MI_SET_SP: u32 = 1 << 1;
const MI_CLR_SI: u32 = 1 << 2;
const MI_SET_SI: u32 = 1 << 3;
const MI_CLR_AI: u32 = 1 << 4;
const MI_SET_AI: u32 = 1 << 5;
const MI_CLR_VI: u32 = 1 << 6;
const MI_SET_VI: u32 = 1 << 7;
const MI_CLR_PI: u32 = 1 << 8;
const MI_SET_PI: u32 = 1 << 9;
const MI_CLR_DP: u32 = 1 << 10;
const MI_SET_DP: u32 = 1 << 11;
/* mode read */
pub const MI_INIT_MODE: u32 = 1 << 7;
pub const MI_EBUS_MODE: u32 = 1 << 8;
pub const MI_RDRAM_MODE: u32 = 1 << 9;
const MI_INIT_MODE: u32 = 1 << 7;
const MI_EBUS_MODE: u32 = 1 << 8;
const MI_RDRAM_MODE: u32 = 1 << 9;
/* mode write */
pub const MI_CLR_INIT: u32 = 1 << 7;
pub const MI_SET_INIT: u32 = 1 << 8;
pub const MI_CLR_EBUS: u32 = 1 << 9;
pub const MI_SET_EBUS: u32 = 1 << 10;
pub const MI_CLR_DP_INTR: u32 = 1 << 11;
pub const MI_CLR_RDRAM: u32 = 1 << 12;
pub const MI_SET_RDRAM: u32 = 1 << 13;
const MI_CLR_INIT: u32 = 1 << 7;
const MI_SET_INIT: u32 = 1 << 8;
const MI_CLR_EBUS: u32 = 1 << 9;
const MI_SET_EBUS: u32 = 1 << 10;
const MI_CLR_DP_INTR: u32 = 1 << 11;
const MI_CLR_RDRAM: u32 = 1 << 12;
const MI_SET_RDRAM: u32 = 1 << 13;
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Mi {
pub regs: [u32; MI_REGS_COUNT as usize],
}
@@ -53,6 +54,7 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.mi.regs[((address & 0xFFFF) >> 2) as usize]
}
@@ -64,18 +66,14 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => device::memory::masked_write_32(&mut device.mi.regs[reg as usize], value, mask),
}
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
} else {
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn update_init_mode(device: &mut device::Device, w: u32) {
fn update_init_mode(device: &mut device::Device, w: u32) {
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_LENGTH_MASK;
device.mi.regs[MI_INIT_MODE_REG as usize] |= w & MI_INIT_LENGTH_MASK;
@@ -103,7 +101,7 @@ pub fn update_init_mode(device: &mut device::Device, w: u32) {
}
}
pub fn update_intr_mask(device: &mut device::Device, w: u32) {
fn update_intr_mask(device: &mut device::Device, w: u32) {
if w & MI_CLR_SP != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SP
}
@@ -149,37 +147,13 @@ pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
pub fn schedule_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
if device.mi.regs[MI_INTR_REG as usize] & device.mi.regs[MI_INTR_MASK_REG as usize] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |= device::cop0::COP0_CAUSE_IP2;
}
device::events::create_event(
device,
device::events::EventType::InterruptCheck,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize],
device::exceptions::check_pending_interrupts,
)
}
pub fn init(device: &mut device::Device) {
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
}
+85 -47
View File
@@ -1,35 +1,38 @@
use crate::device;
use crate::{device, ui};
use rand_chacha::rand_core::RngCore;
pub const PI_DRAM_ADDR_REG: u32 = 0;
pub const PI_CART_ADDR_REG: u32 = 1;
pub const PI_RD_LEN_REG: u32 = 2;
pub const PI_WR_LEN_REG: u32 = 3;
const PI_DRAM_ADDR_REG: u32 = 0;
const PI_CART_ADDR_REG: u32 = 1;
const PI_RD_LEN_REG: u32 = 2;
const PI_WR_LEN_REG: u32 = 3;
pub const PI_STATUS_REG: u32 = 4;
pub const PI_BSD_DOM1_LAT_REG: u32 = 5;
pub const PI_BSD_DOM1_PWD_REG: u32 = 6;
pub const PI_BSD_DOM1_PGS_REG: u32 = 7;
pub const PI_BSD_DOM1_RLS_REG: u32 = 8;
pub const PI_BSD_DOM2_LAT_REG: u32 = 9;
pub const PI_BSD_DOM2_PWD_REG: u32 = 10;
pub const PI_BSD_DOM2_PGS_REG: u32 = 11;
pub const PI_BSD_DOM2_RLS_REG: u32 = 12;
pub const PI_REGS_COUNT: u32 = 13;
const PI_BSD_DOM1_LAT_REG: u32 = 5;
const PI_BSD_DOM1_PWD_REG: u32 = 6;
const PI_BSD_DOM1_PGS_REG: u32 = 7;
const PI_BSD_DOM1_RLS_REG: u32 = 8;
const PI_BSD_DOM2_LAT_REG: u32 = 9;
const PI_BSD_DOM2_PWD_REG: u32 = 10;
const PI_BSD_DOM2_PGS_REG: u32 = 11;
const PI_BSD_DOM2_RLS_REG: u32 = 12;
//const UNKNOWN_REG: u32 = 13; //LibDragon
pub const PI_REGS_COUNT: u32 = 14;
/* PI_STATUS - read */
pub const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
pub const PI_STATUS_IO_BUSY: u32 = 1 << 1;
//pub const PI_STATUS_ERROR: u32 = 1 << 2;
pub const PI_STATUS_INTERRUPT: u32 = 1 << 3;
//const PI_STATUS_ERROR: u32 = 1 << 2;
const PI_STATUS_INTERRUPT: u32 = 1 << 3;
/* PI_STATUS - write */
pub const PI_STATUS_RESET: u32 = 1 << 0;
pub const PI_STATUS_CLR_INTR: u32 = 1 << 1;
const PI_STATUS_RESET: u32 = 1 << 0;
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pi {
pub regs: [u32; PI_REGS_COUNT as usize],
}
pub struct PiHandler {
struct PiHandler {
read: fn(&mut device::Device, u32, u32, u32) -> u64,
write: fn(&mut device::Device, u32, u32, u32) -> u64,
}
@@ -39,6 +42,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,
@@ -48,7 +52,7 @@ pub fn read_regs(
}
}
pub fn dma_read(device: &mut device::Device) {
fn dma_read(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
@@ -62,12 +66,7 @@ pub 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] =
@@ -78,12 +77,13 @@ pub fn dma_read(device: &mut device::Device) {
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
}
pub fn dma_write(device: &mut device::Device) {
fn dma_write(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let cart_addr = device.pi.regs[PI_CART_ADDR_REG as usize] & !1;
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG as usize] & 0xFFFFFE;
let mut length = (device.pi.regs[PI_WR_LEN_REG as usize] & 0xFFFFFF) + 1;
ui::video::check_framebuffers(dram_addr);
/* PI seems to treat the first 128 bytes differently, see https://n64brew.dev/wiki/Peripheral_Interface#Unaligned_DMA_transfer */
if length >= 0x7f && (length & 1) != 0 {
@@ -95,12 +95,7 @@ pub fn dma_write(device: &mut device::Device) {
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] =
@@ -111,19 +106,29 @@ pub fn dma_write(device: &mut device::Device) {
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
}
pub fn get_handler(address: u32) -> PiHandler {
fn get_handler(address: u32) -> PiHandler {
let mut handler = PiHandler {
read: device::cart_rom::dma_read,
write: device::cart_rom::dma_write,
read: device::cart::rom::dma_read,
write: device::cart::rom::dma_write,
};
if address >= device::memory::MM_CART_ROM as u32 {
handler.read = device::cart_rom::dma_read;
handler.write = device::cart_rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32 {
handler.read = device::sram::dma_read;
handler.write = device::sram::dma_write;
if address >= device::memory::MM_SC64_BUFFER as u32
&& address < (device::memory::MM_SC64_BUFFER + 0x2000) as u32
{
handler.read = device::cart::sc64::dma_read;
handler.write = device::cart::sc64::dma_write;
} else if address >= device::memory::MM_CART_ROM as u32
&& address < device::memory::MM_PIF_MEM as u32
{
handler.read = device::cart::rom::dma_read;
handler.write = device::cart::rom::dma_write;
} else if address >= device::memory::MM_DOM2_ADDR2 as u32
&& address < device::memory::MM_CART_ROM as u32
{
handler.read = device::cart::sram::dma_read;
handler.write = device::cart::sram::dma_write;
} else {
panic!("unknown pi handler")
handler.read = unknown_dma_read;
handler.write = unknown_dma_write;
}
handler
}
@@ -148,15 +153,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;
@@ -179,6 +193,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
}
@@ -188,3 +203,26 @@ pub fn dma_event(device: &mut device::Device) {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
fn unknown_dma_read(
device: &mut device::Device,
mut _cart_addr: u32,
mut _dram_addr: u32,
length: u32,
) -> u64 {
device::pi::calculate_cycles(device, 1, length)
}
fn unknown_dma_write(
device: &mut device::Device,
mut _cart_addr: u32,
mut dram_addr: u32,
length: u32,
) -> u64 {
dram_addr &= device::rdram::RDRAM_MASK as u32;
for i in 0..length {
device.rdram.mem[(dram_addr + i) as usize ^ device.byte_swap] = 0;
}
device::pi::calculate_cycles(device, 1, length)
}
+58 -20
View File
@@ -1,20 +1,25 @@
mod rom;
use crate::device;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Pif {
#[serde(with = "serde_big_array::BigArray")]
pub rom: [u8; 1984],
#[serde(with = "serde_big_array::BigArray")]
pub ram: [u8; 64],
pub channels: [PifChannel; 5],
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct PifChannel {
pub tx: Option<usize>,
pub tx_buf: Option<usize>,
pub rx: Option<usize>,
pub rx_buf: Option<usize>,
#[serde(skip)]
pub process: Option<fn(&mut device::Device, usize)>,
pub pak_handler: Option<device::controller::PakHandler>,
pub change_pak: device::controller::PakType,
}
pub const PIF_RAM_SIZE: usize = 64;
@@ -50,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(
@@ -62,17 +67,12 @@ 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
}
pub fn process_channel(device: &mut device::Device, channel: usize) -> usize {
fn process_channel(device: &mut device::Device, channel: usize) -> usize {
/* don't process channel if it has been disabled */
if device.pif.channels[channel].tx.is_none() {
return 0;
@@ -99,17 +99,18 @@ pub fn update_pif_ram(device: &mut device::Device) -> u64 {
for k in 0..PIF_CHANNELS_COUNT {
active_channels += process_channel(device, k)
}
(24000 + (active_channels * 28000)) as u64
(24000 + (active_channels * 30000)) as u64
}
pub fn disable_pif_channel(channel: &mut PifChannel) {
fn disable_pif_channel(channel: &mut PifChannel) {
channel.tx = None;
channel.rx = None;
channel.tx_buf = None;
channel.rx_buf = None;
}
pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize) -> usize {
let tx = device.pif.ram[buf] & 0x3f;
let rx = device.pif.ram[buf + 1] & 0x3f;
@@ -123,7 +124,7 @@ pub fn setup_pif_channel(device: &mut device::Device, channel: usize, buf: usize
(2 + tx + rx) as usize
}
pub fn setup_channels_format(device: &mut device::Device) {
fn setup_channels_format(device: &mut device::Device) {
let mut i: usize = 0;
let mut k: usize = 0;
while i < PIF_RAM_SIZE && k < PIF_CHANNELS_COUNT {
@@ -215,6 +216,22 @@ pub fn process_ram(device: &mut device::Device) {
device.pif.ram[0x3F] &= !clrmask
}
pub fn connect_pif_channels(device: &mut device::Device) {
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].process = Some(device::controller::process);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].process = Some(device::controller::vru::process);
}
device.pif.channels[4].process = Some(device::cart::process)
}
pub fn init(device: &mut device::Device) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
@@ -225,16 +242,37 @@ pub fn init(device: &mut device::Device) {
device.pif.ram[0x27] = device.cart.cic_seed;
let mempak_handler = device::controller::PakHandler {
read: device::mempak::read,
write: device::mempak::write,
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
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[0].pak_handler = Some(mempak_handler);
device.pif.channels[0].process = Some(device::controller::process);
device.pif.channels[4].process = Some(device::cart::process)
connect_pif_channels(device);
for i in 0..4 {
if device.netplay.is_none() {
if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(mempak_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);
}
}
if device.ui.config.input.emulate_vru {
device.pif.channels[3].pak_handler = None;
}
}
pub fn process_cic_challenge(device: &mut device::Device) {
fn process_cic_challenge(device: &mut device::Device) {
let mut challenge: [u8; 30] = [0; 30];
let mut response: [u8; 30] = [0; 30];
@@ -255,7 +293,7 @@ pub fn process_cic_challenge(device: &mut device::Device) {
}
}
pub fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
fn n64_cic_nus_6105(chl: [u8; 30], rsp: &mut [u8; 30], len: usize) {
let lut0: [u8; 0x10] = [
0x4, 0x7, 0xA, 0x7, 0xE, 0x5, 0xE, 0x1, 0xC, 0xF, 0x8, 0xF, 0x6, 0x3, 0x6, 0x9,
];
+77 -42
View File
@@ -1,50 +1,51 @@
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;
pub const DPC_CURRENT_REG: u32 = 2;
pub const DPC_STATUS_REG: u32 = 3;
pub const DPC_CLOCK_REG: u32 = 4;
pub const DPC_BUFBUSY_REG: u32 = 5;
pub const DPC_PIPEBUSY_REG: u32 = 6;
pub const DPC_TMEM_REG: u32 = 7;
const DPC_CLOCK_REG: u32 = 4;
const DPC_BUFBUSY_REG: u32 = 5;
const DPC_PIPEBUSY_REG: u32 = 6;
const DPC_TMEM_REG: u32 = 7;
pub const DPC_REGS_COUNT: u32 = 8;
//pub const DPS_TBIST_REG: u32 = 0;
//pub const DPS_TEST_MODE_REG: u32 = 1;
//pub const DPS_BUFTEST_ADDR_REG: u32 = 2;
//pub const DPS_BUFTEST_DATA_REG: u32 = 3;
//const DPS_TBIST_REG: u32 = 0;
//const DPS_TEST_MODE_REG: u32 = 1;
//const DPS_BUFTEST_ADDR_REG: u32 = 2;
//const DPS_BUFTEST_DATA_REG: u32 = 3;
pub const DPS_REGS_COUNT: u32 = 4;
/* DPC status - read */
pub const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
pub const DPC_STATUS_FREEZE: u32 = 1 << 1;
pub const DPC_STATUS_FLUSH: u32 = 1 << 2;
pub const DPC_STATUS_START_GCLK: u32 = 1 << 3;
pub const DPC_STATUS_TMEM_BUSY: u32 = 1 << 4;
pub const DPC_STATUS_PIPE_BUSY: u32 = 1 << 5;
pub const DPC_STATUS_CMD_BUSY: u32 = 1 << 6;
pub const DPC_STATUS_CBUF_READY: u32 = 1 << 7;
//pub const DPC_STATUS_DMA_BUSY: u32 = 1 << 8;
//pub const DPC_STATUS_END_VALID: u32 = 1 << 9;
pub const DPC_STATUS_START_VALID: u32 = 1 << 10;
const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
const DPC_STATUS_FREEZE: u32 = 1 << 1;
const DPC_STATUS_FLUSH: u32 = 1 << 2;
const DPC_STATUS_START_GCLK: u32 = 1 << 3;
const DPC_STATUS_TMEM_BUSY: u32 = 1 << 4;
const DPC_STATUS_PIPE_BUSY: u32 = 1 << 5;
const DPC_STATUS_CMD_BUSY: u32 = 1 << 6;
const DPC_STATUS_CBUF_READY: u32 = 1 << 7;
//const DPC_STATUS_DMA_BUSY: u32 = 1 << 8;
//const DPC_STATUS_END_VALID: u32 = 1 << 9;
const DPC_STATUS_START_VALID: u32 = 1 << 10;
/* DPC status - write */
pub const DPC_CLR_XBUS_DMEM_DMA: u32 = 1 << 0;
pub const DPC_SET_XBUS_DMEM_DMA: u32 = 1 << 1;
pub const DPC_CLR_FREEZE: u32 = 1 << 2;
pub const DPC_SET_FREEZE: u32 = 1 << 3;
pub const DPC_CLR_FLUSH: u32 = 1 << 4;
pub const DPC_SET_FLUSH: u32 = 1 << 5;
pub const DPC_CLR_TMEM_CTR: u32 = 1 << 6;
pub const DPC_CLR_PIPE_CTR: u32 = 1 << 7;
pub const DPC_CLR_CMD_CTR: u32 = 1 << 8;
pub const DPC_CLR_CLOCK_CTR: u32 = 1 << 9;
const DPC_CLR_XBUS_DMEM_DMA: u32 = 1 << 0;
const DPC_SET_XBUS_DMEM_DMA: u32 = 1 << 1;
const DPC_CLR_FREEZE: u32 = 1 << 2;
const DPC_SET_FREEZE: u32 = 1 << 3;
const DPC_CLR_FLUSH: u32 = 1 << 4;
const DPC_SET_FLUSH: u32 = 1 << 5;
const DPC_CLR_TMEM_CTR: u32 = 1 << 6;
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) {
@@ -91,15 +114,23 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
}
}
pub fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list(&mut device.rdp.regs_dpc, &mut device.rsp.mem);
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)
}
}
@@ -108,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]
}
@@ -119,7 +151,7 @@ pub fn write_regs_dps(device: &mut device::Device, address: u64, value: u32, mas
)
}
pub fn update_dpc_status(device: &mut device::Device, w: u32) {
fn update_dpc_status(device: &mut device::Device, w: u32) {
/* clear / set xbus_dmem_dma */
if w & DPC_CLR_XBUS_DMEM_DMA != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_XBUS_DMEM_DMA
@@ -170,9 +202,12 @@ pub 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;
}
pub fn rdp_interrupt_event(device: &mut device::Device) {
ui::video::rdp_full_sync();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
}
+51 -18
View File
@@ -1,12 +1,26 @@
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;
//const RDRAM_DELAY_REG: u32 = 2;
const RDRAM_MODE_REG: u32 = 3;
//const RDRAM_REF_INTERVAL_REG: u32 = 4;
//const RDRAM_REF_ROW_REG: u32 = 5;
//const RDRAM_RAS_INTERVAL_REG: u32 = 6;
//const RDRAM_MIN_INTERVAL_REG: u32 = 7;
//const RDRAM_ADDR_SELECT_REG: u32 = 8;
//const RDRAM_DEVICE_MANUF_REG: u32 = 9;
pub const RDRAM_REGS_COUNT: u32 = 10;
pub const RDRAM_MASK: usize = 0xFFFFFF;
pub const RDRAM_SIZE: usize = 0x800000; // 8MB
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rdram {
pub mem: Vec<u8>,
pub size: u32,
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
}
pub fn read_mem_fast(
@@ -32,7 +46,8 @@ 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 < RDRAM_SIZE as u64 {
if address < device.rdram.size as u64 {
ui::video::check_framebuffers(masked_address as u32);
u32::from_ne_bytes(
device.rdram.mem[masked_address..masked_address + 4]
.try_into()
@@ -44,7 +59,8 @@ pub fn read_mem(
}
pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u32) {
if address < RDRAM_SIZE as u64 {
if address < device.rdram.size as u64 {
ui::video::check_framebuffers(address as u32);
let mut data = u32::from_ne_bytes(
device.rdram.mem[address as usize..(address + 4) as usize]
.try_into()
@@ -57,30 +73,47 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
}
pub fn read_regs(
_device: &mut device::Device,
_address: u64,
device: &mut device::Device,
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
panic!("rdram read reg");
device::cop0::add_cycles(device, 20);
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
match reg as u32 {
RDRAM_MODE_REG => device.pi.regs[reg as usize] ^ 0xc0c0c0c0,
0x80 => 0x00000000, //Row, needed for libdragon
_ => device.rdram.regs[chip_id as usize][reg as usize],
}
}
pub fn write_regs(_device: &mut device::Device, _address: u64, _value: u32, _mask: u32) {
panic!("rdram write reg");
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
device::memory::masked_write_32(
&mut device.rdram.regs[chip_id as usize][reg as usize],
value,
mask,
)
}
pub fn init(device: &mut device::Device) -> (*mut u8, usize) {
pub fn init(device: &mut device::Device) {
let alignment = 64 * 1024;
let layout = Layout::from_size_align(RDRAM_SIZE, alignment).expect("Invalid layout");
let layout =
Layout::from_size_align(device.rdram.size as usize, alignment).expect("Invalid layout");
let ptr = unsafe { alloc_zeroed(layout) };
device.rdram.mem = unsafe { Vec::from_raw_parts(ptr, RDRAM_SIZE, RDRAM_SIZE) };
device.rdram.mem =
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
// hack, skip RDRAM initialization
let data: u32 = RDRAM_SIZE as u32;
device.rdram.mem[device.cart.rdram_size_offset..device.cart.rdram_size_offset + 4]
.copy_from_slice(&data.to_ne_bytes());
(ptr, RDRAM_SIZE)
device.rdram.mem[0x318..0x318 + 4].copy_from_slice(&device.rdram.size.to_ne_bytes());
// hack, skip RDRAM initialization
device.rdram.mem[0x3f0..0x3f0 + 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;
}
pub fn rdram_calculate_cycles(length: u64) -> u64 {
31 + (length / 3)// https://hcs64.com/dma.html, https://github.com/rasky/n64-systembench
31 + (length / 3) // https://hcs64.com/dma.html, https://github.com/rasky/n64-systembench
}
+26 -15
View File
@@ -1,17 +1,19 @@
use crate::device;
//pub const RI_MODE_REG: u32 = 0;
//pub const RI_CONFIG_REG: u32 = 1;
//pub const RI_CURRENT_LOAD_REG: u32 = 2;
pub const RI_MODE_REG: u32 = 0;
pub const RI_CONFIG_REG: u32 = 1;
//const RI_CURRENT_LOAD_REG: u32 = 2;
pub const RI_SELECT_REG: u32 = 3;
//pub const RI_REFRESH_REG: u32 = 4;
//pub const RI_LATENCY_REG: u32 = 5;
//pub const RI_ERROR_REG: u32 = 6;
//pub const RI_WERROR_REG: u32 = 7;
pub const RI_REFRESH_REG: u32 = 4;
//const RI_LATENCY_REG: u32 = 5;
//const RI_ERROR_REG: u32 = 6;
//const RI_WERROR_REG: u32 = 7;
pub const RI_REGS_COUNT: u32 = 8;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Ri {
pub regs: [u32; RI_REGS_COUNT as usize],
pub ram_init: bool,
}
pub fn read_regs(
@@ -19,16 +21,25 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
if ((address & 0xFFFF) >> 2) as u32 == RI_SELECT_REG {
return 0x14; // hack, skip RDRAM initialization
device::cop0::add_cycles(device, 20);
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);
}
+199 -132
View File
@@ -1,56 +1,116 @@
use crate::device;
#[cfg(target_arch = "aarch64")]
use device::__m128i;
#[cfg(target_arch = "aarch64")]
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
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],
pub vpr: [u128; 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],
pub vcol: std::arch::x86_64::__m128i,
pub vcoh: std::arch::x86_64::__m128i,
pub vccl: std::arch::x86_64::__m128i,
pub vcch: std::arch::x86_64::__m128i,
pub vce: std::arch::x86_64::__m128i,
pub accl: std::arch::x86_64::__m128i,
pub accm: std::arch::x86_64::__m128i,
pub acch: std::arch::x86_64::__m128i,
#[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],
}
@@ -66,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
@@ -127,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
}
@@ -160,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
@@ -284,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 = [
@@ -592,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);
}
+113 -79
View File
@@ -1,73 +1,74 @@
use crate::device;
use crate::ui;
pub const SP_MEM_ADDR_REG: u32 = 0;
pub const SP_DRAM_ADDR_REG: u32 = 1;
pub const SP_RD_LEN_REG: u32 = 2;
pub const SP_WR_LEN_REG: u32 = 3;
const SP_MEM_ADDR_REG: u32 = 0;
const SP_DRAM_ADDR_REG: u32 = 1;
const SP_RD_LEN_REG: u32 = 2;
const SP_WR_LEN_REG: u32 = 3;
pub const SP_STATUS_REG: u32 = 4;
pub const SP_DMA_FULL_REG: u32 = 5;
pub const SP_DMA_BUSY_REG: u32 = 6;
pub const SP_SEMAPHORE_REG: u32 = 7;
const SP_DMA_FULL_REG: u32 = 5;
const SP_DMA_BUSY_REG: u32 = 6;
const SP_SEMAPHORE_REG: u32 = 7;
pub const SP_REGS_COUNT: u32 = 8;
pub const SP_PC_REG: u32 = 0;
//pub const SP_IBIST_REG: u32 = 1;
//const SP_IBIST_REG: u32 = 1;
pub const SP_REGS2_COUNT: u32 = 2;
/* SP_STATUS - read */
pub const SP_STATUS_HALT: u32 = 1 << 0;
pub const SP_STATUS_BROKE: u32 = 1 << 1;
pub const SP_STATUS_DMA_BUSY: u32 = 1 << 2;
pub const SP_STATUS_DMA_FULL: u32 = 1 << 3;
//pub const SP_STATUS_IO_FULL: u32 = 1 << 4;
pub const SP_STATUS_SSTEP: u32 = 1 << 5;
pub const SP_STATUS_INTR_BREAK: u32 = 1 << 6;
pub const SP_STATUS_SIG0: u32 = 1 << 7;
pub const SP_STATUS_SIG1: u32 = 1 << 8;
pub const SP_STATUS_SIG2: u32 = 1 << 9;
pub const SP_STATUS_SIG3: u32 = 1 << 10;
pub const SP_STATUS_SIG4: u32 = 1 << 11;
pub const SP_STATUS_SIG5: u32 = 1 << 12;
pub const SP_STATUS_SIG6: u32 = 1 << 13;
pub const SP_STATUS_SIG7: u32 = 1 << 14;
const SP_STATUS_BROKE: u32 = 1 << 1;
const SP_STATUS_DMA_BUSY: u32 = 1 << 2;
const SP_STATUS_DMA_FULL: u32 = 1 << 3;
//const SP_STATUS_IO_FULL: u32 = 1 << 4;
const SP_STATUS_SSTEP: u32 = 1 << 5;
const SP_STATUS_INTR_BREAK: u32 = 1 << 6;
const SP_STATUS_SIG0: u32 = 1 << 7;
const SP_STATUS_SIG1: u32 = 1 << 8;
const SP_STATUS_SIG2: u32 = 1 << 9;
const SP_STATUS_SIG3: u32 = 1 << 10;
const SP_STATUS_SIG4: u32 = 1 << 11;
const SP_STATUS_SIG5: u32 = 1 << 12;
const SP_STATUS_SIG6: u32 = 1 << 13;
const SP_STATUS_SIG7: u32 = 1 << 14;
/* SP_STATUS - write */
pub const SP_CLR_HALT: u32 = 1 << 0;
const SP_CLR_HALT: u32 = 1 << 0;
pub const SP_SET_HALT: u32 = 1 << 1;
pub const SP_CLR_BROKE: u32 = 1 << 2;
pub const SP_CLR_INTR: u32 = 1 << 3;
pub const SP_SET_INTR: u32 = 1 << 4;
pub const SP_CLR_SSTEP: u32 = 1 << 5;
pub const SP_SET_SSTEP: u32 = 1 << 6;
pub const SP_CLR_INTR_BREAK: u32 = 1 << 7;
pub const SP_SET_INTR_BREAK: u32 = 1 << 8;
pub const SP_CLR_SIG0: u32 = 1 << 9;
pub const SP_SET_SIG0: u32 = 1 << 10;
pub const SP_CLR_SIG1: u32 = 1 << 11;
pub const SP_SET_SIG1: u32 = 1 << 12;
pub const SP_CLR_SIG2: u32 = 1 << 13;
pub const SP_SET_SIG2: u32 = 1 << 14;
pub const SP_CLR_SIG3: u32 = 1 << 15;
pub const SP_SET_SIG3: u32 = 1 << 16;
pub const SP_CLR_SIG4: u32 = 1 << 17;
pub const SP_SET_SIG4: u32 = 1 << 18;
pub const SP_CLR_SIG5: u32 = 1 << 19;
pub const SP_SET_SIG5: u32 = 1 << 20;
pub const SP_CLR_SIG6: u32 = 1 << 21;
pub const SP_SET_SIG6: u32 = 1 << 22;
pub const SP_CLR_SIG7: u32 = 1 << 23;
pub const SP_SET_SIG7: u32 = 1 << 24;
const SP_CLR_BROKE: u32 = 1 << 2;
const SP_CLR_INTR: u32 = 1 << 3;
const SP_SET_INTR: u32 = 1 << 4;
const SP_CLR_SSTEP: u32 = 1 << 5;
const SP_SET_SSTEP: u32 = 1 << 6;
const SP_CLR_INTR_BREAK: u32 = 1 << 7;
const SP_SET_INTR_BREAK: u32 = 1 << 8;
const SP_CLR_SIG0: u32 = 1 << 9;
const SP_SET_SIG0: u32 = 1 << 10;
const SP_CLR_SIG1: u32 = 1 << 11;
const SP_SET_SIG1: u32 = 1 << 12;
const SP_CLR_SIG2: u32 = 1 << 13;
const SP_SET_SIG2: u32 = 1 << 14;
const SP_CLR_SIG3: u32 = 1 << 15;
const SP_SET_SIG3: u32 = 1 << 16;
const SP_CLR_SIG4: u32 = 1 << 17;
const SP_SET_SIG4: u32 = 1 << 18;
const SP_CLR_SIG5: u32 = 1 << 19;
const SP_SET_SIG5: u32 = 1 << 20;
const SP_CLR_SIG6: u32 = 1 << 21;
const SP_SET_SIG6: u32 = 1 << 22;
const SP_CLR_SIG7: u32 = 1 << 23;
const SP_SET_SIG7: u32 = 1 << 24;
pub const RSP_MEM_MASK: usize = 0x1FFF;
const RSP_MEM_MASK: usize = 0x1FFF;
#[derive(PartialEq, Copy, Clone)]
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct RspDma {
pub dir: DmaDir,
pub length: u32,
@@ -75,12 +76,16 @@ pub struct RspDma {
pub dramaddr: u32,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Rsp {
pub cpu: device::rsp_cpu::Cpu,
pub regs: [u32; SP_REGS_COUNT as usize],
pub regs2: [u32; SP_REGS2_COUNT as usize],
#[serde(with = "serde_big_array::BigArray")]
pub mem: [u8; 0x2000],
pub fifo: [RspDma; 2],
pub last_status_value: u32,
pub run_after_dma: bool,
}
pub fn read_mem_fast(
@@ -99,8 +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]
@@ -129,7 +136,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, _mask: u
// SH/SB are broken: They overwrite the whole 32 bit, filling everything that isn't written with zeroes
}
pub fn do_dma(device: &mut device::Device, dma: RspDma) {
fn do_dma(device: &mut device::Device, dma: RspDma) {
let l = dma.length;
let length = ((l & 0xfff) | 7) + 1;
@@ -140,6 +147,7 @@ pub 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);
if dma.dir == DmaDir::Read {
let mut j = 0;
while j < count {
@@ -165,11 +173,14 @@ pub fn do_dma(device: &mut device::Device, dma: RspDma) {
while j < count {
let mut i = 0;
while i < length {
let data = u32::from_ne_bytes(
device.rdram.mem[dram_addr as usize..dram_addr as usize + 4]
.try_into()
.unwrap(),
);
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(),
);
}
if offset != 0 {
// imem being updated
device.rsp.cpu.instructions[((mem_addr & 0xFFF) / 4) as usize].func =
@@ -194,15 +205,12 @@ pub 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,
);
}
pub fn fifo_push(device: &mut device::Device, dir: DmaDir) {
fn fifo_push(device: &mut device::Device, dir: DmaDir) {
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
panic!("RSP DMA already full")
}
@@ -246,7 +254,11 @@ pub fn fifo_pop(device: &mut device::Device) {
do_dma(device, device.rsp.fifo[0])
} else {
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 0;
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_BUSY;
if device.rsp.run_after_dma {
device.rsp.run_after_dma = false;
do_task(device);
}
}
}
@@ -255,10 +267,34 @@ 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_DMA_BUSY_REG | SP_DMA_FULL_REG => {
if device.rsp.regs[reg as usize] != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.regs[reg as usize]
}
SP_STATUS_REG => {
let value = device.rsp.regs[reg as usize]
& !(SP_STATUS_HALT | SP_STATUS_BROKE | SP_STATUS_INTR_BREAK);
if value == device.rsp.last_status_value && value != 0 {
device.rsp.cpu.sync_point = true;
}
if value & (SP_STATUS_DMA_BUSY | SP_STATUS_DMA_FULL) != 0 {
device.rsp.cpu.sync_point = true;
}
device.rsp.last_status_value = value;
device.rsp.regs[reg as usize]
}
SP_SEMAPHORE_REG => {
let value = device.rsp.regs[reg as usize];
if value == 1 {
device.rsp.cpu.sync_point = true;
}
device.rsp.regs[reg as usize] = 1;
value
}
@@ -281,9 +317,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),
}
}
@@ -292,6 +326,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]
}
@@ -309,7 +344,7 @@ pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask:
}
}
pub fn update_sp_status(device: &mut device::Device, w: u32) {
fn update_sp_status(device: &mut device::Device, w: u32) {
let was_halted = device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT != 0;
/* clear / set halt */
@@ -317,7 +352,7 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT
}
if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
device::events::remove_event(device, device::events::EventType::SP);
device::events::remove_event(device, device::events::EVENT_TYPE_SP);
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT
}
@@ -332,7 +367,7 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
}
/* set SP interrupt */
if (w & SP_SET_INTR) != 0 && (w & SP_CLR_INTR) == 0 {
device::mi::schedule_rcp_interrupt(device, device::mi::MI_INTR_SP);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP);
}
/* clear / set single step */
@@ -418,20 +453,20 @@ pub fn update_sp_status(device: &mut device::Device, w: u32) {
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
device.rsp.cpu.broken = false;
device.rsp.cpu.halted = false;
device.rsp.cpu.sync_point = false;
do_task(device);
}
}
pub fn do_task(device: &mut device::Device) {
let timer = device::rsp_cpu::run(device);
fn do_task(device: &mut device::Device) {
device.rsp.cpu.sync_point = false;
device.rsp.last_status_value = 0;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
device.rsp.run_after_dma = true
} else {
let timer = device::rsp_cpu::run(device);
device::events::create_event(
device,
device::events::EventType::SP,
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + timer,
rsp_event,
)
device::events::create_event(device, device::events::EVENT_TYPE_SP, timer)
}
}
pub fn rsp_event(device: &mut device::Device) {
@@ -447,7 +482,6 @@ pub fn rsp_event(device: &mut device::Device) {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
return;
}
device.rsp.cpu.sync_point = false;
do_task(device)
}
+368 -173
View File
@@ -1,66 +1,181 @@
#[cfg(target_arch = "aarch64")]
use device::__m128i;
#[cfg(target_arch = "aarch64")]
include!(concat!(env!("OUT_DIR"), "/simd_bindings.rs"));
#[cfg(target_arch = "aarch64")]
include!("../compat/aarch64.rs");
use crate::device;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
pub fn rd(opcode: u32) -> u32 {
fn rd(opcode: u32) -> u32 {
(opcode >> 11) & 0x1F
}
pub fn rs(opcode: u32) -> u32 {
fn rs(opcode: u32) -> u32 {
(opcode >> 21) & 0x1F
}
pub fn rt(opcode: u32) -> u32 {
fn rt(opcode: u32) -> u32 {
(opcode >> 16) & 0x1F
}
pub fn sa(opcode: u32) -> u32 {
fn sa(opcode: u32) -> u32 {
(opcode >> 6) & 0x1F
}
pub fn imm(opcode: u32) -> u16 {
fn imm(opcode: u32) -> u16 {
opcode as u16
}
pub fn se16(value: i16) -> u32 {
fn se16(value: i16) -> u32 {
value as i32 as u32
}
pub fn voffset(opcode: u32) -> u8 {
fn voffset(opcode: u32) -> u8 {
(opcode & 0x7F) as u8
}
pub fn velement(opcode: u32) -> u8 {
fn velement(opcode: u32) -> u8 {
((opcode >> 7) & 0xF) as u8
}
pub fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
fn sign_extend_7bit_offset(offset: u8, shift_amount: u32) -> u32 {
let soffset = (((offset << 1) & 0x80) | offset) as i8;
(((soffset) as i32) as u32) << shift_amount
}
pub fn modify_vpr_byte(vpr: &mut u128, value: u8, element: u8) {
let pos = 15 - (element & 15);
let mask = 0xFF << (pos * 8);
*vpr &= !mask;
*vpr |= (value as u128) << (pos * 8);
fn modify_vpr8(vpr: &mut __m128i, element: u8, value: u8) {
unsafe {
*vpr = match element & 15 {
0 => _mm_insert_epi8(*vpr, value as i32, 15),
1 => _mm_insert_epi8(*vpr, value as i32, 14),
2 => _mm_insert_epi8(*vpr, value as i32, 13),
3 => _mm_insert_epi8(*vpr, value as i32, 12),
4 => _mm_insert_epi8(*vpr, value as i32, 11),
5 => _mm_insert_epi8(*vpr, value as i32, 10),
6 => _mm_insert_epi8(*vpr, value as i32, 9),
7 => _mm_insert_epi8(*vpr, value as i32, 8),
8 => _mm_insert_epi8(*vpr, value as i32, 7),
9 => _mm_insert_epi8(*vpr, value as i32, 6),
10 => _mm_insert_epi8(*vpr, value as i32, 5),
11 => _mm_insert_epi8(*vpr, value as i32, 4),
12 => _mm_insert_epi8(*vpr, value as i32, 3),
13 => _mm_insert_epi8(*vpr, value as i32, 2),
14 => _mm_insert_epi8(*vpr, value as i32, 1),
15 => _mm_insert_epi8(*vpr, value as i32, 0),
_ => unreachable!(),
};
}
}
pub fn get_vpr_byte(vpr: u128, element: u8) -> u8 {
let pos = 15 - (element & 15);
(vpr >> (pos * 8)) as u8
fn get_vpr8(vpr: __m128i, element: u8) -> u8 {
unsafe {
match element & 15 {
0 => _mm_extract_epi8(vpr, 15) as u8,
1 => _mm_extract_epi8(vpr, 14) as u8,
2 => _mm_extract_epi8(vpr, 13) as u8,
3 => _mm_extract_epi8(vpr, 12) as u8,
4 => _mm_extract_epi8(vpr, 11) as u8,
5 => _mm_extract_epi8(vpr, 10) as u8,
6 => _mm_extract_epi8(vpr, 9) as u8,
7 => _mm_extract_epi8(vpr, 8) as u8,
8 => _mm_extract_epi8(vpr, 7) as u8,
9 => _mm_extract_epi8(vpr, 6) as u8,
10 => _mm_extract_epi8(vpr, 5) as u8,
11 => _mm_extract_epi8(vpr, 4) as u8,
12 => _mm_extract_epi8(vpr, 3) as u8,
13 => _mm_extract_epi8(vpr, 2) as u8,
14 => _mm_extract_epi8(vpr, 1) as u8,
15 => _mm_extract_epi8(vpr, 0) as u8,
_ => unreachable!(),
}
}
}
pub fn modify_vpr_element(vpr: &mut u128, value: u16, element: u8) {
let pos = 7 - (element & 7);
let mask = 0xFFFF << (pos * 16);
*vpr &= !mask;
*vpr |= (value as u128) << (pos * 16);
pub fn modify_vpr16(vpr: &mut __m128i, element: u8, value: u16) {
unsafe {
*vpr = match element & 7 {
0 => _mm_insert_epi16(*vpr, value as i32, 7),
1 => _mm_insert_epi16(*vpr, value as i32, 6),
2 => _mm_insert_epi16(*vpr, value as i32, 5),
3 => _mm_insert_epi16(*vpr, value as i32, 4),
4 => _mm_insert_epi16(*vpr, value as i32, 3),
5 => _mm_insert_epi16(*vpr, value as i32, 2),
6 => _mm_insert_epi16(*vpr, value as i32, 1),
7 => _mm_insert_epi16(*vpr, value as i32, 0),
_ => unreachable!(),
};
}
}
pub fn get_vpr_element(vpr: u128, element: u8) -> u16 {
let pos = 7 - (element & 7);
(vpr >> (pos * 16)) as u16
pub fn get_vpr16(vpr: __m128i, element: u8) -> u16 {
unsafe {
match element & 7 {
0 => _mm_extract_epi16(vpr, 7) as u16,
1 => _mm_extract_epi16(vpr, 6) as u16,
2 => _mm_extract_epi16(vpr, 5) as u16,
3 => _mm_extract_epi16(vpr, 4) as u16,
4 => _mm_extract_epi16(vpr, 3) as u16,
5 => _mm_extract_epi16(vpr, 2) as u16,
6 => _mm_extract_epi16(vpr, 1) as u16,
7 => _mm_extract_epi16(vpr, 0) as u16,
_ => unreachable!(),
}
}
}
fn modify_vpr32(vpr: &mut __m128i, element: u8, value: u32) {
unsafe {
*vpr = match element & 3 {
0 => _mm_insert_epi32(*vpr, value as i32, 3),
1 => _mm_insert_epi32(*vpr, value as i32, 2),
2 => _mm_insert_epi32(*vpr, value as i32, 1),
3 => _mm_insert_epi32(*vpr, value as i32, 0),
_ => unreachable!(),
};
}
}
fn get_vpr32(vpr: __m128i, element: u8) -> u32 {
unsafe {
match element & 3 {
0 => _mm_extract_epi32(vpr, 3) as u32,
1 => _mm_extract_epi32(vpr, 2) as u32,
2 => _mm_extract_epi32(vpr, 1) as u32,
3 => _mm_extract_epi32(vpr, 0) as u32,
_ => unreachable!(),
}
}
}
fn modify_vpr64(vpr: &mut __m128i, element: u8, value: u64) {
unsafe {
*vpr = match element & 1 {
0 => _mm_insert_epi64(*vpr, value as i64, 1),
1 => _mm_insert_epi64(*vpr, value as i64, 0),
_ => unreachable!(),
};
}
}
fn get_vpr64(vpr: __m128i, element: u8) -> u64 {
unsafe {
match element & 1 {
0 => _mm_extract_epi64(vpr, 1) as u64,
1 => _mm_extract_epi64(vpr, 0) as u64,
_ => unreachable!(),
}
}
}
fn modify_vpr128(vpr: &mut __m128i, value: u128) {
unsafe { *vpr = std::mem::transmute::<u128, __m128i>(value) }
}
fn get_vpr128(vpr: __m128i) -> u128 {
unsafe { std::mem::transmute::<__m128i, u128>(vpr) }
}
pub fn j(device: &mut device::Device, opcode: u32) {
@@ -208,7 +323,7 @@ pub fn lw(device: &mut device::Device, opcode: u32) {
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
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)
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
}
pub fn lbu(device: &mut device::Device, opcode: u32) {
@@ -240,7 +355,7 @@ pub fn lwu(device: &mut device::Device, opcode: u32) {
w[3] = device.rsp.mem[(address as usize + 3) & 0xFFF];
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)
((w[0] as u32) << 24) | ((w[1] as u32) << 16) | ((w[2] as u32) << 8) | (w[3] as u32)
}
pub fn sb(device: &mut device::Device, opcode: u32) {
@@ -437,6 +552,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,
@@ -450,8 +566,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) {
@@ -477,16 +591,16 @@ 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) {
let hi = get_vpr_byte(device.rsp.cpu.vpr[rd(opcode) as usize], velement(opcode));
let lo = get_vpr_byte(
let hi = get_vpr8(device.rsp.cpu.vpr[rd(opcode) as usize], velement(opcode));
let lo = get_vpr8(
device.rsp.cpu.vpr[rd(opcode) as usize],
velement(opcode) + 1,
);
device.rsp.cpu.gpr[rt(opcode) as usize] = ((hi as u16) << 8 | (lo as u16)) as i16 as i32 as u32
device.rsp.cpu.gpr[rt(opcode) as usize] =
(((hi as u16) << 8) | (lo as u16)) as i16 as i32 as u32
}
pub fn cfc2(device: &mut device::Device, opcode: u32) {
@@ -507,7 +621,7 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
lo = &mut device.rsp.cpu.vce;
}
_ => {
panic!("unknown ctc2")
panic!("unknown cfc2")
}
}
@@ -519,16 +633,16 @@ pub fn cfc2(device: &mut device::Device, opcode: u32) {
}
pub fn mtc2(device: &mut device::Device, opcode: u32) {
modify_vpr_byte(
modify_vpr8(
&mut device.rsp.cpu.vpr[rd(opcode) as usize],
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8,
velement(opcode),
(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as u8,
);
if velement(opcode) != 15 {
modify_vpr_byte(
modify_vpr8(
&mut device.rsp.cpu.vpr[rd(opcode) as usize],
device.rsp.cpu.gpr[rt(opcode) as usize] as u8,
velement(opcode) + 1,
device.rsp.cpu.gpr[rt(opcode) as usize] as u8,
);
}
}
@@ -573,7 +687,7 @@ pub fn ctc2(device: &mut device::Device, opcode: u32) {
),
_mm_setzero_si128(),
);
*hi = std::arch::x86_64::_mm_cmpeq_epi8(
*hi = _mm_cmpeq_epi8(
_mm_and_si128(
_mm_set1_epi8(!(device.rsp.cpu.gpr[rt(opcode) as usize] >> 8) as i8),
mask,
@@ -588,10 +702,10 @@ pub fn lbv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 0));
let element = velement(opcode);
modify_vpr_byte(
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
}
@@ -600,15 +714,25 @@ pub fn lsv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 1));
let mut element = velement(opcode);
let end = std::cmp::min(element + 2, 16);
while element < end {
modify_vpr_byte(
if element % 2 == 0 {
modify_vpr16(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
element / 2,
((device.rsp.mem[(address & 0xFFF) as usize] as u16) << 8)
| (device.rsp.mem[((address + 1) & 0xFFF) as usize] as u16),
);
address += 1;
element += 1;
} else {
let end = std::cmp::min(element + 2, 16);
while element < end {
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
element += 1;
}
}
}
@@ -617,15 +741,28 @@ pub fn llv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 2));
let mut element = velement(opcode);
let end = std::cmp::min(element + 4, 16);
while element < end {
modify_vpr_byte(
if element % 4 == 0 {
let mut value = 0;
for i in 0..4 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u32) << (8 * (3 - i));
}
modify_vpr32(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
element / 4,
value,
);
address += 1;
element += 1;
} else {
let end = std::cmp::min(element + 4, 16);
while element < end {
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
element += 1;
}
}
}
@@ -634,15 +771,28 @@ pub fn ldv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 3));
let mut element = velement(opcode);
let end = std::cmp::min(element + 8, 16);
while element < end {
modify_vpr_byte(
if element % 8 == 0 {
let mut value = 0;
for i in 0..8 {
value |= (device.rsp.mem[((address + i) & 0xFFF) as usize] as u64) << (8 * (7 - i));
}
modify_vpr64(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
element / 8,
value,
);
address += 1;
element += 1;
} else {
let end = std::cmp::min(element + 8, 16);
while element < end {
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
element += 1;
}
}
}
@@ -651,15 +801,24 @@ pub fn lqv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
let mut element = velement(opcode);
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
while element < end {
modify_vpr_byte(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
);
address += 1;
element += 1;
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));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
let end = std::cmp::min(16 + element - ((address & 15) as u8), 16);
while element < end {
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
element += 1;
}
}
}
@@ -669,14 +828,22 @@ 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;
while element < 16 {
modify_vpr_byte(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
element,
);
address += 1;
element += 1;
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));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
} else {
while element < 16 {
modify_vpr8(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
element,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
element += 1;
}
}
}
@@ -686,17 +853,15 @@ pub fn lpv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
let mut offset: u8 = 0;
while offset < 8 {
modify_vpr_element(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset)) & 15) as u32))
& 0xFFF) as usize] as u16)
<< 8,
offset,
);
offset += 1;
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 8) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn luv(device: &mut device::Device, opcode: u32) {
@@ -705,17 +870,15 @@ pub fn luv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
let mut offset: u8 = 0;
while offset < 8 {
modify_vpr_element(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset)) & 15) as u32))
& 0xFFF) as usize] as u16)
<< 7,
offset,
);
offset += 1;
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 7) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn lhv(device: &mut device::Device, opcode: u32) {
@@ -724,17 +887,15 @@ pub fn lhv(device: &mut device::Device, opcode: u32) {
let index = ((address & 7) as u8).wrapping_sub(velement(opcode));
address &= !7;
let mut offset: u8 = 0;
while offset < 8 {
modify_vpr_element(
&mut device.rsp.cpu.vpr[rt(opcode) as usize],
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset * 2)) & 15) as u32))
& 0xFFF) as usize] as u16)
<< 7,
offset,
);
offset += 1;
let mut value = 0;
for i in 0..8 {
value |= (((device.rsp.mem
[((address.wrapping_add(((index.wrapping_add(i * 2)) & 15) as u32)) & 0xFFF) as usize]
as u16)
<< 7) as u128)
<< (16 * (7 - i));
}
modify_vpr128(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value);
}
pub fn lfv(device: &mut device::Device, opcode: u32) {
@@ -745,30 +906,30 @@ pub fn lfv(device: &mut device::Device, opcode: u32) {
address &= !7;
let start = velement(opcode);
let end = std::cmp::min(start + 8, 16);
let mut tmp: u128 = 0;
let mut tmp: __m128i = unsafe { _mm_setzero_si128() };
let mut offset: u8 = 0;
while offset < 4 {
modify_vpr_element(
modify_vpr16(
&mut tmp,
offset,
(device.rsp.mem[((address.wrapping_add(((index.wrapping_add(offset * 4)) & 15) as u32))
& 0xFFF) as usize] as u16)
<< 7,
offset,
);
modify_vpr_element(
modify_vpr16(
&mut tmp,
offset + 4,
(device.rsp.mem[((address
.wrapping_add(((index.wrapping_add(offset * 4).wrapping_add(8)) & 15) as u32))
& 0xFFF) as usize] as u16)
<< 7,
offset + 4,
);
offset += 1;
}
offset = start;
while offset < end {
let value = get_vpr_byte(tmp, offset);
modify_vpr_byte(&mut device.rsp.cpu.vpr[rt(opcode) as usize], value, offset);
let value = get_vpr8(tmp, offset);
modify_vpr8(&mut device.rsp.cpu.vpr[rt(opcode) as usize], offset, value);
offset += 1;
}
}
@@ -783,28 +944,26 @@ pub fn ltv(device: &mut device::Device, opcode: u32) {
address = begin + (((velement(opcode)) as u32 + (address & 8)) & 15);
let vtbase = rt(opcode) & !7;
let mut vtoff = (velement(opcode)) as u32 >> 1;
let mut i = 0;
while i < 8 {
modify_vpr_byte(
for i in 0..8 {
modify_vpr8(
&mut device.rsp.cpu.vpr[(vtbase + vtoff) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
i * 2,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
if address == begin + 16 {
address = begin
}
modify_vpr_byte(
modify_vpr8(
&mut device.rsp.cpu.vpr[(vtbase + vtoff) as usize],
device.rsp.mem[(address & 0xFFF) as usize],
i * 2 + 1,
device.rsp.mem[(address & 0xFFF) as usize],
);
address += 1;
if address == begin + 16 {
address = begin
}
vtoff = (vtoff + 1) & 7;
i += 1;
}
}
@@ -813,7 +972,7 @@ pub fn sbv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 0));
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], velement(opcode))
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], velement(opcode))
}
pub fn ssv(device: &mut device::Device, opcode: u32) {
@@ -821,12 +980,20 @@ pub fn ssv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 1));
let mut element = velement(opcode);
let end = element + 2;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
if element % 2 == 0 {
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 2].copy_from_slice(
&get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element / 2).to_be_bytes(),
);
} else {
let end = element + 2;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
}
}
}
@@ -835,12 +1002,20 @@ pub fn slv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 2));
let mut element = velement(opcode);
let end = element + 4;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
if element % 4 == 0 {
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 4].copy_from_slice(
&get_vpr32(device.rsp.cpu.vpr[rt(opcode) as usize], element / 4).to_be_bytes(),
);
} else {
let end = element + 4;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
}
}
}
@@ -849,12 +1024,20 @@ pub fn sdv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 3));
let mut element = velement(opcode);
let end = element + 8;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
if element % 8 == 0 {
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 8].copy_from_slice(
&get_vpr64(device.rsp.cpu.vpr[rt(opcode) as usize], element / 8).to_be_bytes(),
);
} else {
let end = element + 8;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
}
}
}
@@ -863,12 +1046,19 @@ pub fn sqv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
let mut element = velement(opcode);
let end = element + (16 - (address & 15)) as u8;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
if element == 0 && address % 16 == 0 {
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 16]
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
} else {
let end = element + (16 - (address & 15)) as u8;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
address += 1;
element += 1;
}
}
}
@@ -880,11 +1070,18 @@ pub fn srv(device: &mut device::Device, opcode: u32) {
let end = element + (address & 15) as u8;
let base = (16 - (address & 15)) as u8;
address &= !15;
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element + base);
address += 1;
element += 1;
if element == 0 && base == 0 {
let start = (address & 0xFFF) as usize;
device.rsp.mem[start..start + 16]
.copy_from_slice(&get_vpr128(device.rsp.cpu.vpr[rt(opcode) as usize]).to_be_bytes());
} else {
while element < end {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element + base);
address += 1;
element += 1;
}
}
}
@@ -897,10 +1094,10 @@ pub fn spv(device: &mut device::Device, opcode: u32) {
while element < end {
if (element & 15) < 8 {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
} else {
device.rsp.mem[(address & 0xFFF) as usize] =
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
}
address += 1;
element += 1;
@@ -916,10 +1113,10 @@ pub fn suv(device: &mut device::Device, opcode: u32) {
while element < end {
if (element & 15) < 8 {
device.rsp.mem[(address & 0xFFF) as usize] =
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
} else {
device.rsp.mem[(address & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], (element & 7) << 1);
}
address += 1;
element += 1;
@@ -933,13 +1130,11 @@ pub fn shv(device: &mut device::Device, opcode: u32) {
let element = velement(opcode);
let index = (address & 7) as u8;
address &= !7;
let mut offset = 0;
while offset < 8 {
for offset in 0..8 {
let byte_val = element + offset * 2;
let value = get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val) << 1
| get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val + 1) >> 7;
let value = (get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val) << 1)
| (get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], byte_val + 1) >> 7);
device.rsp.mem[((address + ((index + offset * 2) & 15) as u32) & 0xFFF) as usize] = value;
offset += 1;
}
}
@@ -948,8 +1143,7 @@ pub fn sfv(device: &mut device::Device, opcode: u32) {
.wrapping_add(sign_extend_7bit_offset(voffset(opcode), 4));
let base = address & 7;
address &= !7;
let element = velement(opcode);
let elements = match element {
let elements = match velement(opcode) {
0 | 15 => [0, 1, 2, 3],
1 => [6, 7, 4, 5],
4 => [1, 2, 3, 0],
@@ -966,12 +1160,10 @@ pub fn sfv(device: &mut device::Device, opcode: u32) {
}
};
let mut offset = 0;
let mut i = 0;
while i < 4 {
for element in elements {
device.rsp.mem[((address + ((base + offset) & 15)) & 0xFFF) as usize] =
(get_vpr_element(device.rsp.cpu.vpr[rt(opcode) as usize], elements[i]) >> 7) as u8;
(get_vpr16(device.rsp.cpu.vpr[rt(opcode) as usize], element) >> 7) as u8;
offset += 4;
i += 1;
}
}
@@ -985,7 +1177,7 @@ pub fn swv(device: &mut device::Device, opcode: u32) {
address &= !7;
while element < end {
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[rt(opcode) as usize], element);
get_vpr8(device.rsp.cpu.vpr[rt(opcode) as usize], element);
base += 1;
element += 1;
}
@@ -1002,17 +1194,20 @@ pub fn stv(device: &mut device::Device, opcode: u32) {
let mut offset = start;
while offset < end {
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[offset as usize], element);
get_vpr8(device.rsp.cpu.vpr[offset as usize], element);
base = base.wrapping_add(1);
element += 1;
device.rsp.mem[((address + (base & 15)) & 0xFFF) as usize] =
get_vpr_byte(device.rsp.cpu.vpr[offset as usize], element);
get_vpr8(device.rsp.cpu.vpr[offset as usize], element);
base = base.wrapping_add(1);
element += 1;
offset += 1;
}
}
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
+26 -24
View File
@@ -1,26 +1,29 @@
use rand_chacha::rand_core::RngCore;
use crate::device;
pub const SI_DRAM_ADDR_REG: u32 = 0;
pub const SI_PIF_ADDR_RD64B_REG: u32 = 1;
//pub const SI_R2_REG: u32 = 2;
//pub const SI_R3_REG: u32 = 3;
pub const SI_PIF_ADDR_WR64B_REG: u32 = 4;
//pub const SI_R5_REG: u32 = 5;
const SI_DRAM_ADDR_REG: u32 = 0;
const SI_PIF_ADDR_RD64B_REG: u32 = 1;
//const SI_R2_REG: u32 = 2;
//const SI_R3_REG: u32 = 3;
const SI_PIF_ADDR_WR64B_REG: u32 = 4;
//const SI_R5_REG: u32 = 5;
pub const SI_STATUS_REG: u32 = 6;
pub const SI_REGS_COUNT: u32 = 7;
pub const SI_STATUS_DMA_BUSY: u32 = 1 << 0;
pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
//pub const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
pub const SI_STATUS_INTERRUPT: u32 = 1 << 12;
//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,37 +34,36 @@ pub fn read_regs(
address: u64,
_access_size: device::memory::AccessSize,
) -> u32 {
device::cop0::add_cycles(device, 20);
device.si.regs[((address & 0xFFFF) >> 2) as usize]
}
pub fn dma_read(device: &mut device::Device) {
fn 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;
let duration = device::pif::update_pif_ram(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)
}
pub fn dma_write(device: &mut device::Device) {
fn dma_write(device: &mut device::Device) {
device.si.dma_dir = DmaDir::Write;
copy_pif_rdram(device);
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] + 5200, //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) {
@@ -78,7 +80,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
//rdram is in native endian format, and pif memory is in big endian format
pub fn copy_pif_rdram(device: &mut device::Device) {
fn copy_pif_rdram(device: &mut device::Device) {
let dram_addr = device.si.regs[SI_DRAM_ADDR_REG as usize] as usize & device::rdram::RDRAM_MASK;
if device.si.dma_dir == DmaDir::Write {
let mut i = 0;
+18 -11
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,
@@ -38,8 +38,8 @@ pub fn read(device: &mut device::Device, index: u64) {
device.cpu.cop0.tlb_entries[index as usize].mask << 13;
device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] =
(device.cpu.cop0.tlb_entries[index as usize].region as u64) << 62
| device.cpu.cop0.tlb_entries[index as usize].vpn2 << 13
((device.cpu.cop0.tlb_entries[index as usize].region as u64) << 62)
| (device.cpu.cop0.tlb_entries[index as usize].vpn2 << 13)
| (device.cpu.cop0.tlb_entries[index as usize].asid) as u64;
device.cpu.cop0.regs[device::cop0::COP0_ENTRYLO0_REG as usize] =
@@ -136,7 +136,8 @@ pub fn probe(device: &mut device::Device) {
if e.region != (device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] >> 62) as u8 {
continue;
}
if e.g == 0 && e.asid != device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] as u8 {
if e.g == 0 && e.asid != device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize] as u8
{
continue;
}
device.cpu.cop0.regs[device::cop0::COP0_INDEX_REG as usize] = pos as u64;
@@ -144,7 +145,7 @@ pub fn probe(device: &mut device::Device) {
}
}
pub fn tlb_unmap(device: &mut device::Device, index: u64) {
fn tlb_unmap(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 {
@@ -182,11 +183,14 @@ pub fn tlb_unmap(device: &mut device::Device, index: u64) {
}
}
pub fn tlb_map(device: &mut device::Device, index: u64) {
fn tlb_map(device: &mut device::Device, index: u64) {
let e = &mut device.cpu.cop0.tlb_entries[index as usize];
if e.v_even != 0 && e.start_even < e.end_even
&& !(e.start_even >= 0x80000000 && e.end_even < 0xC0000000) && e.phys_even < 0x20000000 {
if e.v_even != 0
&& e.start_even < e.end_even
&& !(e.start_even >= 0x80000000 && e.end_even < 0xC0000000)
&& e.phys_even < 0x20000000
{
let mut i = e.start_even;
while i < e.end_even {
device.cpu.cop0.tlb_lut_r[(i >> 12) as usize].address =
@@ -205,8 +209,11 @@ pub fn tlb_map(device: &mut device::Device, index: u64) {
}
}
if e.v_odd != 0 && e.start_odd < e.end_odd
&& !(e.start_odd >= 0x80000000 && e.end_odd < 0xC0000000) && e.phys_odd < 0x20000000 {
if e.v_odd != 0
&& e.start_odd < e.end_odd
&& !(e.start_odd >= 0x80000000 && e.end_odd < 0xC0000000)
&& e.phys_odd < 0x20000000
{
let mut i = e.start_odd;
while i < e.end_odd {
device.cpu.cop0.tlb_lut_r[(i >> 12) as usize].address =
+101 -59
View File
@@ -1,64 +1,77 @@
use crate::device;
use crate::ui;
use crate::{device, netplay, ui};
use governor::clock::Clock;
pub const VI_STATUS_REG: u32 = 0;
//pub const VI_ORIGIN_REG: u32 = 1;
//pub const VI_WIDTH_REG: u32 = 2;
//pub const VI_V_INTR_REG: u32 = 3;
pub const VI_CURRENT_REG: u32 = 4;
//pub const VI_BURST_REG: u32 = 5;
pub const VI_V_SYNC_REG: u32 = 6;
pub const VI_H_SYNC_REG: u32 = 7;
//pub const VI_LEAP_REG: u32 = 8;
//pub const VI_H_START_REG: u32 = 9;
//pub const VI_V_START_REG: u32 = 10;
//pub 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_STATUS_REG: u32 = 0;
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;
//const VI_H_START_REG: u32 = 9;
//const VI_V_START_REG: u32 = 10;
//const VI_V_BURST_REG: u32 = 11;
//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;
pub fn set_expected_refresh_rate(device: &mut device::Device) {
let expected_refresh_rate = device.vi.clock as f64
/ (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as f64
/ ((device.vi.regs[VI_H_SYNC_REG as usize] & 0xFFF) + 1) as f64
* 2.0;
device.vi.frame_time = 1.0 / expected_refresh_rate;
device.vi.delay = (device.cpu.clock_rate as f64 / expected_refresh_rate) as u64;
device.vi.count_per_scanline =
device.vi.delay / (device.vi.regs[VI_V_SYNC_REG as usize] + 1) as u64;
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
1.0 / expected_refresh_rate,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota))
create_limiter(device);
}
pub fn set_vertical_interrupt(device: &mut device::Device) {
if device::events::get_event(device, device::events::EventType::VI).is_none() {
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,
)
fn create_limiter(device: &mut device::Device) {
let quota = governor::Quota::with_period(std::time::Duration::from_secs_f64(
device.vi.frame_time * device.vi.limit_freq as f64,
))
.unwrap();
device.vi.limiter = Some(governor::RateLimiter::direct(quota));
let _ = device.vi.limiter.as_ref().unwrap().check();
//println!("new limit freq: {}", device.vi.limit_freq);
}
fn set_vertical_interrupt(device: &mut device::Device) {
if device::events::get_event(device, device::events::EVENT_TYPE_VI).is_none() {
device::events::create_event(device, device::events::EVENT_TYPE_VI, device.vi.delay)
}
}
pub fn set_current_line(device: &mut device::Device) {
fn set_current_line(device: &mut device::Device) {
let delay = device.vi.delay;
let next_vi = device::events::get_event(device, device::events::EventType::VI);
let next_vi = device::events::get_event(device, device::events::EVENT_TYPE_VI);
if next_vi.is_some() {
device.vi.regs[VI_CURRENT_REG as usize] = ((delay
- (next_vi.unwrap().count
@@ -85,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]
}
@@ -97,44 +111,59 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
set_vertical_interrupt(device);
set_expected_refresh_rate(device);
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
}
}
VI_H_SYNC_REG => {
if device.vi.regs[reg as usize] != value & mask {
device::memory::masked_write_32(&mut device.vi.regs[reg as usize], value, mask);
set_expected_refresh_rate(device);
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
}
}
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);
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
}
}
ui::video::set_register(reg as u32, device.vi.regs[reg as usize])
}
pub fn vertical_interrupt_event(device: &mut device::Device) {
device.cpu.running = ui::video::update_screen();
ui::video::check_callback(device);
if device.netplay.is_some() {
netplay::send_sync_check(device);
device.vi.enable_speed_limiter = !device.netplay.as_ref().unwrap().fast_forward;
}
device.vi.vi_counter += 1;
if device.vi.vi_counter % device.vi.limit_freq == 0 && device.vi.enable_speed_limiter {
speed_limiter(device);
}
ui::video::update_screen();
/*
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;
}
*/
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,
)
}
@@ -144,27 +173,40 @@ pub fn init(device: &mut device::Device) {
} else {
device.vi.clock = 48681812
}
/*
std::thread::spawn(move || {
let mut last_count = 0;
loop {
unsafe {
println!("{:?}", FRAME_COUNTER - last_count);
last_count = FRAME_COUNTER;
}
std::thread::sleep(std::time::Duration::from_secs(1));
}
});
*/
}
pub fn speed_limiter(device: &device::Device) {
fn speed_limiter(device: &mut device::Device) {
let result = device.vi.limiter.as_ref().unwrap().check();
if result.is_err() {
let outcome = result.unwrap_err();
let dur = outcome.wait_time_from(governor::clock::DefaultClock::default().now());
std::thread::sleep(dur);
spin_sleep::sleep(dur);
if dur < device.vi.min_wait_time {
device.vi.min_wait_time = dur;
}
device.vi.limiter.as_ref().unwrap().check().unwrap();
let _ = device.vi.limiter.as_ref().unwrap().check();
} else {
device.vi.min_wait_time = std::time::Duration::from_secs(0);
}
if std::time::Instant::now()
.duration_since(device.vi.limit_freq_check)
.as_secs_f64()
> 1.0
{
if device.vi.min_wait_time.as_secs_f64() == 0.0 && device.vi.limit_freq < MAX_LIMIT_FREQ {
device.vi.limit_freq += 1;
create_limiter(device);
} else if device.vi.min_wait_time.as_secs_f64() > device.vi.frame_time
&& device.vi.limit_freq > 1
{
device.vi.limit_freq -= 1;
create_limiter(device);
}
//println!("limit freq: {}", device.vi.limit_freq);
device.vi.min_wait_time = std::time::Duration::from_secs(1);
device.vi.limit_freq_check = std::time::Instant::now();
}
}
+88 -20
View File
@@ -1,8 +1,14 @@
#![deny(warnings)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod device;
mod netplay;
mod savestates;
mod ui;
use clap::Parser;
use ui::gui;
/// N64 emulator
#[derive(Parser, Debug)]
@@ -18,6 +24,12 @@ struct Args {
help = "Create a new input profile (keyboard/gamepad mappings)."
)]
configure_input_profile: Option<String>,
#[arg(
short,
long,
help = "Use DirectInput when configuring a new input profile."
)]
use_dinput: bool,
#[arg(
short,
long,
@@ -37,7 +49,7 @@ struct Args {
value_name = "CONTROLLER_NUMBER",
help = "Must also specify --port. Used to assign a controller listed in --list-controllers to a port"
)]
assign_controller: Option<u32>,
assign_controller: Option<i32>,
#[arg(
short,
long,
@@ -53,22 +65,42 @@ struct Args {
clear_input_bindings: bool,
}
fn main() {
let config_dir = dirs::config_dir().unwrap().join("gopher64");
let cache_dir = dirs::cache_dir().unwrap().join("gopher64");
#[tokio::main]
async fn main() {
let dirs = ui::get_dirs();
let _ = std::fs::create_dir_all(config_dir.clone());
let _ = std::fs::create_dir_all(cache_dir.clone());
let _ = std::fs::remove_file(cache_dir.clone().join("game_running"));
let mut result = std::fs::create_dir_all(dirs.config_dir);
if result.is_err() {
panic!("could not create config dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(dirs.cache_dir.clone());
if result.is_err() {
panic!("could not create cache dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(dirs.data_dir.join("saves"));
if result.is_err() {
panic!("could not create save dir: {}", result.err().unwrap())
}
result = std::fs::create_dir_all(dirs.data_dir.join("states"));
if result.is_err() {
panic!("could not create state dir: {}", result.err().unwrap())
}
let running_file = dirs.cache_dir.join("game_running");
if running_file.exists() {
result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
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();
if args_count > 1 && args.game.is_none() {
let mut ui = ui::Ui::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut device.ui);
ui::input::clear_bindings(&mut ui);
return;
}
if args.port.is_some() {
@@ -79,7 +111,10 @@ fn main() {
}
}
if args.list_controllers {
ui::input::list_controllers(&device.ui);
let controllers = gui::get_controller_names(&ui);
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {}: {}", i, controller);
}
return;
}
if args.assign_controller.is_some() {
@@ -88,7 +123,7 @@ fn main() {
return;
}
ui::input::assign_controller(
&mut device.ui,
&mut ui,
args.assign_controller.unwrap(),
args.port.unwrap(),
);
@@ -100,7 +135,7 @@ fn main() {
return;
}
ui::input::bind_input_profile(
&mut device.ui,
&mut ui,
args.bind_input_profile.unwrap(),
args.port.unwrap(),
);
@@ -108,25 +143,58 @@ fn main() {
}
if args.configure_input_profile.is_some() {
ui::input::configure_input_profile(
&mut device.ui,
&mut ui,
args.configure_input_profile.unwrap(),
args.use_dinput,
);
return;
}
if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
device::run_game(file_path, &mut device, args.fullscreen);
} else if args.game.is_some() {
let file_path = std::path::Path::new(args.game.as_ref().unwrap());
let rom_contents = device::get_rom_contents(file_path);
if rom_contents.is_empty() {
println!("Could not read rom file");
return;
}
let handle = std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let mut device = device::Device::new();
let overclock = device.ui.config.emulation.overclock;
device::run_game(&mut device, rom_contents, args.fullscreen, overclock);
})
.unwrap();
handle.join().unwrap();
} else {
let options = eframe::NativeOptions {
viewport: eframe::egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]),
viewport: eframe::egui::ViewportBuilder::default()
.with_inner_size([854.0, 480.0])
.with_icon(
eframe::icon_data::from_png_bytes(include_bytes!("../data/gopher64.png"))
.unwrap(),
),
..Default::default()
};
let controllers_paths;
let controller_names;
{
let game_ui = ui::Ui::new();
controllers_paths = gui::get_controller_paths(&game_ui);
controller_names = gui::get_controller_names(&game_ui);
}
eframe::run_native(
"gopher64",
options,
Box::new(|_cc| Ok(Box::new(ui::gui::GopherEguiApp::new()))),
Box::new(|cc| {
Ok(Box::new(ui::gui::GopherEguiApp::new(
cc,
controllers_paths,
controller_names,
)))
}),
)
.unwrap();
}
+357
View File
@@ -0,0 +1,357 @@
use crate::{device, ui};
use std::io::{Read, Write};
//UDP packet formats
const UDP_SEND_KEY_INFO: u8 = 0;
const UDP_RECEIVE_KEY_INFO: u8 = 1;
const UDP_REQUEST_KEY_INFO: u8 = 2;
const UDP_RECEIVE_KEY_INFO_GRATUITOUS: u8 = 3;
const UDP_SYNC_DATA: u8 = 4;
//TCP packet formats
const TCP_SEND_SAVE: u8 = 1;
//const TCP_RECEIVE_SAVE: u8 = 2;
//const TCP_SEND_SETTINGS: u8 = 3;
//const TCP_RECEIVE_SETTINGS: u8 = 4;
const TCP_REGISTER_PLAYER: u8 = 5;
const TCP_GET_REGISTRATION: u8 = 6;
const TCP_DISCONNECT_NOTICE: u8 = 7;
const TCP_RECEIVE_SAVE_WITH_SIZE: u8 = 8;
const 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;
pub struct Netplay {
udp_socket: std::net::UdpSocket,
tcp_stream: std::net::TcpStream,
pub player_number: u8,
pub player_data: [PlayerData; 4],
vi_counter: u32,
status: u8,
buffer_target: u8,
pub fast_forward: bool,
}
pub struct PlayerData {
lag: u8,
count: u32,
pub reg_id: u32,
input_events: std::collections::HashMap<u32, InputEvent>,
}
struct InputEvent {
input: u32,
plugin: u8,
}
pub const NETPLAY_ERROR_DESYNC: u32 = 0;
pub const NETPLAY_ERROR_LOST_CONNECTION: u32 = 1;
pub const NETPLAY_ERROR_PLAYER_DISCONNECTED: u32 = 2;
pub const NETPLAY_ERROR_PLAYER_1_DISCONNECTED: u32 = 3;
pub const NETPLAY_ERROR_PLAYER_2_DISCONNECTED: u32 = 4;
pub const NETPLAY_ERROR_PLAYER_3_DISCONNECTED: u32 = 5;
pub const NETPLAY_ERROR_PLAYER_4_DISCONNECTED: u32 = 6;
pub fn send_rtc(netplay: &mut Netplay, rtc: i64) {
let mut request: Vec<u8> = [TCP_SEND_RTC].to_vec();
let size: u32 = 8;
request.extend_from_slice(&size.to_be_bytes());
request.extend_from_slice(&rtc.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
}
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) {
let mut request: Vec<u8> = [TCP_SEND_SAVE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
request.extend_from_slice(&(size as u32).to_be_bytes());
if size > 0 {
request.extend(save_data.to_owned());
}
netplay.tcp_stream.write_all(&request).unwrap();
}
pub fn receive_save(netplay: &mut Netplay, save_type: &str, save_data: &mut Vec<u8>) {
let mut request: Vec<u8> = [TCP_RECEIVE_SAVE_WITH_SIZE].to_vec();
request.extend_from_slice(save_type.as_bytes());
request.push(0); // null terminate string
netplay.tcp_stream.write_all(&request).unwrap();
let mut size: [u8; 4] = [0; 4];
netplay.tcp_stream.read_exact(&mut size).unwrap();
let mut response: Vec<u8> = vec![0; u32::from_be_bytes(size) as usize];
netplay.tcp_stream.read_exact(&mut response).unwrap();
*save_data = response;
}
pub fn send_sync_check(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
if netplay.vi_counter % 600 == 0 {
let mut request: Vec<u8> = [UDP_SYNC_DATA].to_vec();
request.extend_from_slice(&(netplay.vi_counter).to_be_bytes());
for i in 0..device::cop0::COP0_REGS_COUNT as usize {
request.extend_from_slice(&(device.cpu.cop0.regs[i] as u32).to_be_bytes());
}
netplay.udp_socket.send(&request).unwrap();
}
netplay.vi_counter = netplay.vi_counter.wrapping_add(1);
}
pub fn send_input(netplay: &Netplay, input: 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.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) -> 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, &mut 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);
request_timer = std::time::Instant::now() + std::time::Duration::from_millis(5);
}
input = netplay.player_data[channel]
.input_events
.remove(&netplay.player_data[channel].count);
if std::time::Instant::now() > timeout {
ui::audio::play_netplay_audio(&mut device.ui, NETPLAY_ERROR_LOST_CONNECTION);
input = Some(InputEvent {
input: 0,
plugin: 0,
});
device.cpu.running = false;
}
}
netplay.fast_forward = netplay.player_data[channel].lag > 0
&& netplay.player_data[channel].input_events.len() as u8 > netplay.buffer_target;
netplay.player_data[channel].count = netplay.player_data[channel].count.wrapping_add(1);
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) {
let mut request: Vec<u8> = [UDP_REQUEST_KEY_INFO].to_vec();
request.push(channel as u8); //The player we need input for
request.extend_from_slice(
&(netplay.player_data[netplay.player_number as usize].reg_id).to_be_bytes(),
);
request.extend_from_slice(&(netplay.player_data[channel].count).to_be_bytes());
request.push(0); //spectator mode
request.push(netplay.player_data[channel].input_events.len() as u8);
netplay.udp_socket.send(&request).unwrap();
}
fn process_incoming(netplay: &mut Netplay, ui: &mut ui::Ui) {
let mut buf: [u8; 1024] = [0; 1024];
while let Ok(_incoming) = netplay.udp_socket.recv(&mut buf) {
match buf[0] {
UDP_RECEIVE_KEY_INFO | UDP_RECEIVE_KEY_INFO_GRATUITOUS => {
let player = buf[1] as usize;
//current_status is a status update from the server
//it will let us know if another player has disconnected, or the games have desynced
let current_status = buf[2];
if buf[0] == UDP_RECEIVE_KEY_INFO {
netplay.player_data[player].lag = buf[3];
}
if current_status != netplay.status {
if ((current_status & 0x1) ^ (netplay.status & 0x1)) != 0 {
ui::audio::play_netplay_audio(ui, NETPLAY_ERROR_DESYNC);
}
for dis in 1..5 {
if ((current_status & (0x1 << dis)) ^ (netplay.status & (0x1 << dis))) != 0
{
ui::audio::play_netplay_audio(
ui,
NETPLAY_ERROR_PLAYER_DISCONNECTED + dis,
);
}
}
netplay.status = current_status;
}
let mut buffer_offset = 5;
for _i in 0..buf[4] {
let count = u32::from_be_bytes(
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
);
buffer_offset += 4;
if (count.wrapping_sub(netplay.player_data[player].count)) > (u32::MAX / 2) {
//event doesn't need to be recorded
buffer_offset += 5;
continue;
}
let input = u32::from_be_bytes(
buf[buffer_offset..buffer_offset + 4].try_into().unwrap(),
);
buffer_offset += 4;
let plugin = buf[buffer_offset];
buffer_offset += 1;
let input_event = InputEvent { input, plugin };
netplay.player_data[player]
.input_events
.insert(count, input_event);
}
}
_ => {
panic!("unknown UDP packet")
}
}
}
}
pub fn init(
mut peer_addr: std::net::SocketAddr,
session: ui::gui::gui_netplay::NetplayRoom,
player_number: u8,
) -> Netplay {
peer_addr.set_port(session.port.unwrap() as u16);
let udp_socket;
if peer_addr.is_ipv4() {
udp_socket = std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address");
socket2::SockRef::from(&udp_socket)
.set_tos(CS4 << 2)
.unwrap();
} else {
udp_socket = std::net::UdpSocket::bind((std::net::Ipv6Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address");
#[cfg(not(target_os = "windows"))]
socket2::SockRef::from(&udp_socket)
.set_tclass_v6(CS4 << 2)
.unwrap();
};
udp_socket.connect(peer_addr).unwrap();
udp_socket.set_nonblocking(true).unwrap();
let mut stream = std::net::TcpStream::connect(peer_addr).unwrap();
let regid = (player_number + 1) as u32;
let mut request: [u8; 8] = [
TCP_REGISTER_PLAYER,
player_number,
0, //plugin/pak
0, //rawdata
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
0, //regid (u32)
];
request[4..8].copy_from_slice(&regid.to_be_bytes());
stream.write_all(&request).unwrap();
let mut response: [u8; 2] = [0, 0];
stream.read_exact(&mut response).unwrap();
if response[0] != 1 {
panic!("Failed to register player");
}
let buffer_target = response[1];
let request: [u8; 1] = [TCP_GET_REGISTRATION];
stream.write_all(&request).unwrap();
let mut response: [u8; 24] = [0; 24];
stream.read_exact(&mut response).unwrap();
let mut reg_id: [u32; 4] = [0; 4];
for i in 0..4 {
// reg_id of 0 means no player connected
reg_id[i] = u32::from_be_bytes(response[(i * 6)..(i * 6) + 4].try_into().unwrap());
}
Netplay {
udp_socket,
tcp_stream: stream,
player_number,
vi_counter: 0,
status: 0,
buffer_target,
fast_forward: false,
player_data: [
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[0],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[1],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[2],
input_events: std::collections::HashMap::new(),
},
PlayerData {
lag: 0,
count: 0,
reg_id: reg_id[3],
input_events: std::collections::HashMap::new(),
},
],
}
}
pub fn close(device: &mut device::Device) {
let netplay = device.netplay.as_mut().unwrap();
let regid = (netplay.player_number + 1) as u32;
let mut request: [u8; 5] = [TCP_DISCONNECT_NOTICE, 0, 0, 0, 0];
request[1..5].copy_from_slice(&regid.to_be_bytes());
netplay.tcp_stream.write_all(&request).unwrap();
netplay.tcp_stream.flush().unwrap();
netplay
.tcp_stream
.shutdown(std::net::Shutdown::Both)
.unwrap();
}
+226
View File
@@ -0,0 +1,226 @@
use crate::{device, ui};
#[cfg(target_arch = "aarch64")]
use device::__m128i;
use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
use serde::ser::{Serialize, SerializeSeq, Serializer};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
struct M128iArrayVisitor<const N: usize>;
impl<'de, const N: usize> Visitor<'de> for M128iArrayVisitor<N> {
type Value = [__m128i; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(&format!("an array of {} 128-bit integers", N))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut arr: [__m128i; N] = [device::zero_m128i(); N];
for (index, item) in arr.iter_mut().enumerate().take(N) {
match seq.next_element::<u128>()? {
Some(value) => *item = unsafe { std::mem::transmute::<u128, __m128i>(value) },
None => return Err(serde::de::Error::invalid_length(index, &self)),
}
}
Ok(arr)
}
}
pub fn deserialize_m128i_array<'de, D, const N: usize>(
deserializer: D,
) -> Result<[__m128i; N], D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(M128iArrayVisitor)
}
pub fn serialize_m128i<S>(data: &__m128i, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes: u128 = unsafe { std::mem::transmute(*data) };
bytes.serialize(serializer)
}
pub fn deserialize_m128i<'de, D>(deserializer: D) -> Result<__m128i, D::Error>
where
D: Deserializer<'de>,
{
let bytes = u128::deserialize(deserializer)?;
Ok(unsafe { std::mem::transmute::<u128, __m128i>(bytes) })
}
pub fn serialize_m128i_array<S>(value: &[__m128i], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(value.len()))?;
for item in value {
let bytes: u128 = unsafe { std::mem::transmute(*item) };
seq.serialize_element(&bytes)?;
}
seq.end()
}
pub fn create_savestate(device: &device::Device) {
ui::video::full_sync();
let data: &[(&[u8], &str)] = &[
(&postcard::to_stdvec(device).unwrap(), "device"),
(
&postcard::to_stdvec(&device.ui.storage.saves).unwrap(),
"saves",
),
];
let compressed_file = ui::storage::compress_file(data);
std::fs::write(
device.ui.storage.paths.savestate_file_path.clone(),
compressed_file,
)
.unwrap();
}
pub fn load_savestate(device: &mut device::Device) {
let savestate = std::fs::read(&mut device.ui.storage.paths.savestate_file_path);
if savestate.is_ok() {
let device_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "device");
let save_bytes = ui::storage::decompress_file(savestate.as_ref().unwrap(), "saves");
let state: device::Device = postcard::from_bytes(&device_bytes).unwrap();
device.ui.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;
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 {
device.memory.icache[line_index].instruction[0] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[0]);
device.memory.icache[line_index].instruction[1] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[1]);
device.memory.icache[line_index].instruction[2] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[2]);
device.memory.icache[line_index].instruction[3] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[3]);
device.memory.icache[line_index].instruction[4] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[4]);
device.memory.icache[line_index].instruction[5] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[5]);
device.memory.icache[line_index].instruction[6] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[6]);
device.memory.icache[line_index].instruction[7] =
device::cpu::decode_opcode(device, device.memory.icache[line_index].words[7]);
}
device::pif::connect_pif_channels(device);
for i in 0..4 {
if device.pif.channels[i].pak_handler.is_some() {
if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::RumblePak
{
let rumblepak_handler = device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: device::controller::PakType::RumblePak,
};
device.pif.channels[i].pak_handler = Some(rumblepak_handler);
} else if device.pif.channels[i].pak_handler.unwrap().pak_type
== device::controller::PakType::MemPak
{
let mempak_handler = device::controller::PakHandler {
read: device::controller::mempak::read,
write: device::controller::mempak::write,
pak_type: device::controller::PakType::MemPak,
};
device.pif.channels[i].pak_handler = Some(mempak_handler);
} 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(&mut device.ui);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::load_state(device);
}
}
pub fn default_pak_handler() -> fn(&mut device::Device, usize, u16, usize, usize) {
device::controller::mempak::read
}
pub fn default_instruction() -> fn(&mut device::Device, u32) {
device::cop0::reserved
}
pub fn default_instructions<const N: usize>() -> [fn(&mut device::Device, u32); N]
where
[fn(&mut device::Device, u32); N]: Sized,
{
[device::cop0::reserved; N]
}
pub fn default_memory_read()
-> [fn(&mut device::Device, u64, device::memory::AccessSize) -> u32; 0x2000] {
[device::rdram::read_mem; 0x2000]
}
pub fn default_memory_write() -> [fn(&mut device::Device, u64, u32, u32); 0x2000] {
[device::rdram::write_mem; 0x2000]
}
+182 -75
View File
@@ -4,104 +4,211 @@ pub mod gui;
pub mod input;
pub mod storage;
pub mod video;
pub mod vru;
pub struct Ui {
pub struct Dirs {
pub config_dir: std::path::PathBuf,
pub cache_dir: std::path::PathBuf,
pub data_dir: std::path::PathBuf,
}
pub struct Audio {
pub audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub event_audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
pub audio_device: u32,
pub event_audio: audio::EventAudio,
}
pub struct Input {
pub num_joysticks: i32,
pub joysticks: *mut 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 sdl_context: Option<sdl2::Sdl>,
pub video_subsystem: Option<sdl2::VideoSubsystem>,
pub audio_subsystem: Option<sdl2::AudioSubsystem>,
pub joystick_subsystem: Option<sdl2::JoystickSubsystem>,
#[allow(dead_code)]
pub controller_subsystem: Option<sdl2::GameControllerSubsystem>,
pub window: Option<sdl2::video::Window>,
pub audio_device: Option<sdl2::audio::AudioQueue<i16>>,
}
pub struct Video {
pub window: *mut sdl3_sys::video::SDL_Window,
pub fullscreen: bool,
}
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,
}
impl Drop for Ui {
fn drop(&mut self) {
storage::write_saves(self);
write_config(self);
if self.with_sdl {
unsafe {
sdl3_sys::stdinc::SDL_free(self.input.joysticks as *mut std::ffi::c_void);
sdl3_sys::init::SDL_Quit();
}
}
}
}
fn write_config(ui: &Ui) {
let f = std::fs::File::create(ui.config_file_path.clone()).unwrap();
serde_json::to_writer_pretty(f, &ui.config).unwrap();
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
unsafe {
let init = sdl3_sys::init::SDL_WasInit(0);
if init & flag == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
let err = std::ffi::CStr::from_ptr(sdl3_sys::error::SDL_GetError())
.to_str()
.unwrap();
panic!("Could not initialize SDL subsystem: {}, {}", flag, err);
}
}
}
pub fn get_dirs() -> Dirs {
let exe_path = std::env::current_exe().unwrap();
let portable_dir = exe_path.parent();
let portable = portable_dir.unwrap().join("portable.txt").exists();
let config_dir;
let cache_dir;
let data_dir;
if portable {
config_dir = portable_dir.unwrap().join("portable_data").join("config");
cache_dir = portable_dir.unwrap().join("portable_data").join("cache");
data_dir = portable_dir.unwrap().join("portable_data").join("data");
} else {
config_dir = dirs::config_dir().unwrap().join("gopher64");
cache_dir = dirs::cache_dir().unwrap().join("gopher64");
data_dir = dirs::data_dir().unwrap().join("gopher64");
};
Dirs {
config_dir,
cache_dir,
data_dir,
}
}
impl Ui {
pub fn new() -> Ui {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let audio_subsystem = sdl_context.audio().unwrap();
let joystick_subsystem = sdl_context.joystick().unwrap();
let controller_subsystem = sdl_context.game_controller().unwrap();
let config_file_path = dirs::config_dir()
.unwrap()
.join("gopher64")
.join("config.json");
let config_file = std::fs::read(config_file_path.clone());
let mut config_map = config::Config::new();
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if result.is_ok() {
config_map = result.unwrap();
}
}
fn construct_ui(num_joysticks: i32, joysticks: *mut u32, with_sdl: bool) -> Ui {
let dirs = get_dirs();
Ui {
controllers: [
input::Controllers {
game_controller: None,
joystick: None,
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(),
num_joysticks,
joysticks,
},
storage: Storage {
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(),
},
input::Controllers {
game_controller: None,
joystick: None,
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,
},
},
input::Controllers {
game_controller: None,
joystick: None,
},
input::Controllers {
game_controller: None,
joystick: None,
},
],
config_file_path,
config: config_map,
save_type: vec![],
},
config: config::Config::new(),
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(),
audio: Audio {
event_audio: audio::EventAudio {
mempak: include_bytes!("../data/mempak.wav").to_vec(),
rumblepak: include_bytes!("../data/rumblepak.wav").to_vec(),
transferpak: include_bytes!("../data/transferpak.wav").to_vec(),
netplay_desync: include_bytes!("../data/netplay_desync.wav").to_vec(),
netplay_lost_connection: include_bytes!("../data/netplay_lost_connection.wav")
.to_vec(),
netplay_disconnected: [
include_bytes!("../data/netplay_p1_disconnected.wav").to_vec(),
include_bytes!("../data/netplay_p2_disconnected.wav").to_vec(),
include_bytes!("../data/netplay_p3_disconnected.wav").to_vec(),
include_bytes!("../data/netplay_p4_disconnected.wav").to_vec(),
],
},
audio_stream: std::ptr::null_mut(),
event_audio_stream: std::ptr::null_mut(),
audio_device: 0,
},
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),
video: Video {
window: std::ptr::null_mut(),
fullscreen: false,
},
sdl_context: Some(sdl_context),
video_subsystem: Some(video_subsystem),
audio_subsystem: Some(audio_subsystem),
joystick_subsystem: Some(joystick_subsystem),
controller_subsystem: Some(controller_subsystem),
window: None,
audio_device: None,
dirs,
with_sdl,
}
}
pub fn default() -> Ui {
Self::construct_ui(0, std::ptr::null_mut(), false)
}
pub fn new() -> Ui {
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
let mut num_joysticks = 0;
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
if joysticks.is_null() {
panic!("Could not get joystick list");
}
Self::construct_ui(num_joysticks, joysticks, true)
}
}
+165 -22
View File
@@ -1,47 +1,190 @@
use crate::device;
use crate::netplay;
use crate::ui;
pub struct EventAudio {
pub mempak: Vec<u8>,
pub rumblepak: Vec<u8>,
pub transferpak: Vec<u8>,
pub netplay_desync: Vec<u8>,
pub netplay_lost_connection: Vec<u8>,
pub netplay_disconnected: [Vec<u8>; 4],
}
pub fn init(ui: &mut ui::Ui, frequency: u64) {
let desired_spec = sdl2::audio::AudioSpecDesired {
freq: Some(frequency as i32),
channels: Some(2),
samples: None,
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: frequency as i32,
channels: 2,
};
ui.audio_device = Some(
ui.audio_subsystem
.as_mut()
.unwrap()
.open_queue::<i16, _>(None, &desired_spec)
.unwrap(),
);
ui.audio_device.as_mut().unwrap().resume();
let device_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: 48000,
channels: 2,
};
ui.audio.audio_device = unsafe {
sdl3_sys::audio::SDL_OpenAudioDevice(
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&device_audio_spec,
)
};
if ui.audio.audio_device == 0 {
panic!("Could not open audio device");
}
let mut dst = Default::default();
if !unsafe {
sdl3_sys::audio::SDL_GetAudioDeviceFormat(
ui.audio.audio_device,
&mut dst,
std::ptr::null_mut(),
)
} {
panic!("Could not get audio device format");
}
ui.audio.audio_stream =
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&game_audio_spec, &dst) };
if ui.audio.audio_stream.is_null() {
return;
}
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.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.audio.event_audio_stream =
unsafe { sdl3_sys::audio::SDL_CreateAudioStream(&wav_audio_spec, &dst) };
if !unsafe {
sdl3_sys::audio::SDL_BindAudioStream(ui.audio.audio_device, ui.audio.event_audio_stream)
} {
panic!("Could not bind audio stream");
}
}
pub fn close(ui: &mut ui::Ui) {
unsafe {
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.audio.event_audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.event_audio_stream);
ui.audio.event_audio_stream = std::ptr::null_mut();
}
sdl3_sys::audio::SDL_CloseAudioDevice(ui.audio.audio_device);
ui.audio.audio_device = 0;
}
}
pub fn play_netplay_audio(ui: &mut ui::Ui, error: u32) {
if ui.audio.event_audio_stream.is_null() {
return;
}
let sound = match error {
netplay::NETPLAY_ERROR_DESYNC => &ui.audio.event_audio.netplay_desync,
netplay::NETPLAY_ERROR_LOST_CONNECTION => &ui.audio.event_audio.netplay_lost_connection,
netplay::NETPLAY_ERROR_PLAYER_1_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[0]
}
netplay::NETPLAY_ERROR_PLAYER_2_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[1]
}
netplay::NETPLAY_ERROR_PLAYER_3_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[2]
}
netplay::NETPLAY_ERROR_PLAYER_4_DISCONNECTED => {
&ui.audio.event_audio.netplay_disconnected[3]
}
_ => panic!("Invalid netplay error"),
};
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
ui.audio.event_audio_stream,
sound.as_ptr() as *const std::ffi::c_void,
sound.len() as i32,
)
} {
panic!("Could not play audio");
}
}
pub fn play_pak_switch(ui: &mut ui::Ui, pak: device::controller::PakType) {
if ui.audio.event_audio_stream.is_null() {
return;
}
let sound = match pak {
device::controller::PakType::RumblePak => &ui.audio.event_audio.rumblepak,
device::controller::PakType::MemPak => &ui.audio.event_audio.mempak,
device::controller::PakType::TransferPak => &ui.audio.event_audio.transferpak,
_ => panic!("Invalid pak type"),
};
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
ui.audio.event_audio_stream,
sound.as_ptr() as *const std::ffi::c_void,
sound.len() as i32,
)
} {
panic!("Could not play audio");
}
}
pub fn play_audio(device: &mut device::Device, dram_addr: usize, length: u64) {
let audio_device = device.ui.audio_device.as_mut().unwrap();
if device.ui.audio.audio_stream.is_null() {
return;
}
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
let mut i = 0;
while i < length as usize / 2 {
// 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;
| ((device.rdram.mem[dram_addr + (i * 2) + 3] 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;
| ((device.rdram.mem[dram_addr + (i * 2) + 1] as i16) << 8);
i += 2;
}
let audio_queued = audio_device.size() as f64;
let acceptable_latency = (audio_device.spec().freq as f64 * 0.2) * 4.0;
let min_latency = (audio_device.spec().freq as f64 * 0.02) * 4.0;
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.audio_stream) } as f64;
let acceptable_latency = (device.ai.freq as f64 * 0.2) * 4.0;
let min_latency = (device.ai.freq as f64 * 0.02) * 4.0;
if audio_queued < min_latency {
let silence_buffer: Vec<i16> = vec![0; ((min_latency - audio_queued) * 2.0) as usize & !1];
audio_device.queue_audio(&silence_buffer).unwrap();
let silence_buffer: Vec<u8> = vec![0; min_latency as usize & !3];
if !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio.audio_stream,
silence_buffer.as_ptr() as *const std::ffi::c_void,
silence_buffer.len() as i32,
)
} {
panic!("Could not play audio");
}
}
if audio_queued < acceptable_latency {
audio_device.queue_audio(&primary_buffer).unwrap();
if audio_queued < acceptable_latency
&& !unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
}
{
panic!("Could not play audio");
}
}
+80 -6
View File
@@ -1,13 +1,34 @@
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); 18],
pub controller_buttons: [(bool, i32); 14],
pub controller_axis: [(bool, i32, i16); 18],
pub joystick_buttons: [(bool, u32); 14],
pub joystick_hat: [(bool, u32, i8); 14],
pub joystick_axis: [(bool, u32, i16); 18],
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,
}
#[derive(serde::Serialize, serde::Deserialize)]
@@ -15,14 +36,56 @@ pub struct Input {
pub input_profiles: std::collections::HashMap<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: 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,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Config {
pub input: Input,
pub video: Video,
pub emulation: Emulation,
}
impl Drop for Config {
fn drop(&mut self) {
write_config(self);
}
}
fn write_config(config: &Config) {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let f = std::fs::File::create(file_path).unwrap();
serde_json::to_writer_pretty(f, &config).unwrap();
}
impl Config {
pub fn new() -> Config {
let dirs = ui::get_dirs();
let file_path = dirs.config_dir.join("config.json");
let config_file = std::fs::read(file_path);
if config_file.is_ok() {
let result = serde_json::from_slice(config_file.unwrap().as_ref());
if let Ok(result) = result {
return result;
}
}
let mut input_profiles = std::collections::HashMap::new();
input_profiles.insert("default".to_string(), ui::input::get_default_profile());
Config {
@@ -35,7 +98,18 @@ 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: 1,
integer_scaling: false,
fullscreen: false,
widescreen: false,
crt: false,
},
emulation: Emulation { overclock: false },
}
}
}
+585 -115
View File
@@ -1,56 +1,110 @@
use crate::device;
use crate::ui;
use crate::{device, netplay, ui};
use eframe::egui;
pub mod gui_netplay;
pub struct GopherEguiApp {
dirs: ui::Dirs,
configure_profile: bool,
profile_name: String,
controllers: Vec<String>,
controller_names: Vec<String>,
selected_controller: [i32; 4],
selected_profile: [String; 4],
input_profiles: Vec<String>,
controller_enabled: [bool; 4],
transfer_pak: [bool; 4],
controller_paths: Vec<String>,
upscale: u32,
integer_scaling: bool,
fullscreen: bool,
widescreen: bool,
crt: bool,
overclock: bool,
emulate_vru: bool,
dinput: bool,
show_vru_dialog: bool,
vru_window_receiver: Option<tokio::sync::mpsc::Receiver<Vec<String>>>,
vru_word_notifier: Option<tokio::sync::mpsc::Sender<String>>,
vru_word_list: Vec<String>,
latest_version: Option<semver::Version>,
update_receiver: Option<tokio::sync::mpsc::Receiver<GithubData>>,
netplay: gui_netplay::GuiNetplay,
}
fn get_input_profiles(game_ui: &ui::Ui) -> Vec<String> {
#[derive(serde::Deserialize)]
struct GithubData {
tag_name: String,
}
struct SaveConfig {
selected_controller: [i32; 4],
selected_profile: [String; 4],
controller_enabled: [bool; 4],
transfer_pak: [bool; 4],
upscale: u32,
integer_scaling: bool,
fullscreen: bool,
widescreen: bool,
crt: bool,
emulate_vru: bool,
overclock: bool,
}
fn get_input_profiles(config: &ui::config::Config) -> Vec<String> {
let mut profiles = vec![];
for key in game_ui.config.input.input_profiles.keys() {
profiles.push((*key).clone())
for key in config.input.input_profiles.keys() {
profiles.push(key.clone())
}
profiles
}
fn get_controllers(game_ui: &ui::Ui) -> Vec<String> {
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
let mut controllers: Vec<String> = vec![];
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
for i in 0..num_joysticks {
controllers.push(joystick_subsystem.name_for_index(i).unwrap());
for offset in 0..game_ui.input.num_joysticks as isize {
let name = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickNameForID(
*(game_ui.input.joysticks.offset(offset)),
))
};
controllers.push(name.to_string_lossy().to_string());
}
controllers
}
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<String> {
let mut controller_paths: Vec<String> = vec![];
for offset in 0..game_ui.input.num_joysticks as isize {
let path = unsafe {
std::ffi::CStr::from_ptr(sdl3_sys::joystick::SDL_GetJoystickPathForID(
*(game_ui.input.joysticks.offset(offset)),
))
.to_string_lossy()
.to_string()
};
controller_paths.push(path);
}
controller_paths
}
impl GopherEguiApp {
pub fn new() -> GopherEguiApp {
let game_ui = ui::Ui::new();
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
let num_joysticks = joystick_subsystem.num_joysticks().unwrap();
let mut guids: Vec<String> = vec![];
for i in 0..num_joysticks {
guids.push(joystick_subsystem.device_guid(i).unwrap().to_string());
}
pub fn new(
cc: &eframe::CreationContext<'_>,
controller_paths: Vec<String>,
controller_names: Vec<String>,
) -> GopherEguiApp {
add_fonts(&cc.egui_ctx);
let config = ui::config::Config::new();
let mut selected_controller = [-1, -1, -1, -1];
for (pos, item) in game_ui
.config
.input
.controller_assignment
.iter()
.enumerate()
{
for (pos, item) in config.input.controller_assignment.iter().enumerate() {
if item.is_some() {
for (guid_pos, guid) in guids.iter().enumerate() {
if item.as_deref().unwrap() == *guid {
selected_controller[pos] = guid_pos as i32;
for (path_pos, path) in controller_paths.iter().enumerate() {
if item.as_deref().unwrap() == *path {
selected_controller[pos] = path_pos as i32;
break;
}
}
@@ -59,127 +113,464 @@ impl GopherEguiApp {
GopherEguiApp {
configure_profile: false,
profile_name: "".to_string(),
selected_profile: game_ui.config.input.input_profile_binding.clone(),
selected_profile: config.input.input_profile_binding.clone(),
selected_controller,
controllers: get_controllers(&game_ui),
input_profiles: get_input_profiles(&game_ui),
controller_names,
input_profiles: get_input_profiles(&config),
controller_enabled: config.input.controller_enabled,
transfer_pak: config.input.transfer_pak,
upscale: config.video.upscale,
integer_scaling: config.video.integer_scaling,
fullscreen: config.video.fullscreen,
widescreen: config.video.widescreen,
crt: config.video.crt,
emulate_vru: config.input.emulate_vru,
overclock: config.emulation.overclock,
show_vru_dialog: false,
dinput: false,
controller_paths,
vru_window_receiver: None,
vru_word_notifier: None,
latest_version: None,
update_receiver: None,
vru_word_list: Vec::new(),
netplay: Default::default(),
dirs: ui::get_dirs(),
}
}
}
fn save_config(game_ui: &mut ui::Ui, selected_controller: [i32; 4], selected_profile: [String; 4]) {
let joystick_subsystem = game_ui.joystick_subsystem.as_ref().unwrap();
for (pos, item) in selected_controller.iter().enumerate() {
fn save_config(
config: &mut ui::config::Config,
controller_paths: Vec<String>,
save_config_items: SaveConfig,
) {
for (pos, item) in save_config_items.selected_controller.iter().enumerate() {
if *item != -1 {
game_ui.config.input.controller_assignment[pos] = Some(
joystick_subsystem
.device_guid(*item as u32)
.unwrap()
.to_string(),
);
config.input.controller_assignment[pos] =
Some(controller_paths[*item as usize].clone());
} else {
game_ui.config.input.controller_assignment[pos] = None
config.input.controller_assignment[pos] = None
}
}
game_ui.config.input.input_profile_binding = selected_profile;
config.input.input_profile_binding = save_config_items.selected_profile;
config.input.controller_enabled = save_config_items.controller_enabled;
config.input.transfer_pak = save_config_items.transfer_pak;
config.video.upscale = save_config_items.upscale;
config.video.integer_scaling = save_config_items.integer_scaling;
config.video.fullscreen = save_config_items.fullscreen;
config.video.widescreen = save_config_items.widescreen;
config.video.crt = save_config_items.crt;
config.input.emulate_vru = save_config_items.emulate_vru;
config.emulation.overclock = save_config_items.overclock;
}
impl Drop for GopherEguiApp {
fn drop(&mut self) {
let mut game_ui = ui::Ui::new();
let save_config_items = SaveConfig {
selected_controller: self.selected_controller,
selected_profile: self.selected_profile.clone(),
controller_enabled: self.controller_enabled,
transfer_pak: self.transfer_pak,
upscale: self.upscale,
integer_scaling: self.integer_scaling,
fullscreen: self.fullscreen,
widescreen: self.widescreen,
crt: self.crt,
emulate_vru: self.emulate_vru,
overclock: self.overclock,
};
let mut config = ui::config::Config::new();
save_config(
&mut game_ui,
self.selected_controller,
self.selected_profile.clone(),
&mut config,
self.controller_paths.clone(),
save_config_items,
);
}
}
fn configure_profile(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name:");
ui.text_edit_singleline(&mut app.profile_name)
.labelled_by(name_label.id);
});
ui.checkbox(&mut app.dinput, "Use DirectInput");
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = app.profile_name.clone();
let dinput = app.dinput;
std::thread::spawn(move || {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name, dinput);
});
app.configure_profile = false;
if !app.profile_name.is_empty()
&& !app.input_profiles.contains(&app.profile_name)
{
app.input_profiles.push(app.profile_name.clone())
}
};
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
app.configure_profile = false
};
})
});
});
}
fn show_vru_dialog(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("What would you like to say?");
egui::Grid::new("vru_words").show(ui, |ui| {
for (i, v) in app.vru_word_list.iter().enumerate() {
if i % 5 == 0 {
ui.end_row();
}
if ui.button((*v).to_string()).clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(v.clone())
.unwrap();
app.show_vru_dialog = false;
}
}
});
ui.add_space(16.0);
if ui.button("Close without saying anything").clicked() {
app.vru_word_notifier
.as_ref()
.unwrap()
.try_send(String::from(""))
.unwrap();
app.show_vru_dialog = false;
};
});
}
fn get_latest_version(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.update_receiver.is_none() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.update_receiver = Some(rx);
let gui_ctx = ctx.clone();
let client = reqwest::Client::builder()
.user_agent(env!("CARGO_PKG_NAME"))
.build()
.unwrap();
let task = client
.get("https://api.github.com/repos/gopher64/gopher64/releases/latest")
.send();
tokio::spawn(async move {
let response = task.await;
if let Ok(response) = response {
let data: Result<GithubData, reqwest::Error> = response.json().await;
if data.is_ok() {
tx.send(data.unwrap()).await.unwrap();
} else {
tx.send(GithubData {
tag_name: format!("v{}", env!("CARGO_PKG_VERSION")),
})
.await
.unwrap();
}
gui_ctx.request_repaint();
}
});
} else if app.latest_version.is_none() {
let result = app.update_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
let tag = &result.unwrap().tag_name[1..];
app.latest_version = Some(semver::Version::parse(tag).unwrap());
} else {
ctx.request_repaint();
}
}
}
pub fn open_rom(app: &mut GopherEguiApp, ctx: &egui::Context, enable_overclock: bool) {
let netplay;
let selected_controller = app.selected_controller;
let selected_profile = app.selected_profile.clone();
let controller_enabled = app.controller_enabled;
let transfer_pak = app.transfer_pak;
let upscale = app.upscale;
let integer_scaling = app.integer_scaling;
let fullscreen = app.fullscreen;
let widescreen = app.widescreen;
let crt = app.crt;
let emulate_vru = app.emulate_vru;
let overclock = app.overclock;
let peer_addr;
let session;
let player_number;
let cache_dir = app.dirs.cache_dir.clone();
let controller_paths = app.controller_paths.clone();
if app.netplay.player_name.is_empty() {
netplay = false;
peer_addr = None;
session = None;
player_number = None;
} else {
netplay = true;
peer_addr = app.netplay.peer_addr;
session = app.netplay.waiting_session.clone();
player_number = Some(app.netplay.player_number);
}
let (vru_window_notifier, vru_window_receiver): (
tokio::sync::mpsc::Sender<Vec<String>>,
tokio::sync::mpsc::Receiver<Vec<String>>,
) = tokio::sync::mpsc::channel(1);
let (vru_word_notifier, vru_word_receiver): (
tokio::sync::mpsc::Sender<String>,
tokio::sync::mpsc::Receiver<String>,
) = tokio::sync::mpsc::channel(1);
if emulate_vru && !netplay {
app.vru_window_receiver = Some(vru_window_receiver);
app.vru_word_notifier = Some(vru_word_notifier);
} else {
app.vru_window_receiver = None;
app.vru_word_notifier = None;
}
let rom_contents = app.netplay.rom_contents.clone();
let gui_ctx = ctx.clone();
let mut select_rom = None;
let mut select_gb_rom = [None, None, None, None];
let mut select_gb_ram = [None, None, None, None];
if !netplay {
select_rom = Some(
rfd::AsyncFileDialog::new()
.set_title("Select ROM")
.pick_file(),
);
for i in 0..4 {
if transfer_pak[i] {
select_gb_rom[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB ROM P{}", i + 1))
.pick_file(),
);
select_gb_ram[i] = Some(
rfd::AsyncFileDialog::new()
.set_title(format!("GB RAM P{}", i + 1))
.pick_file(),
);
}
}
}
tokio::spawn(async move {
let file = if !netplay {
select_rom.unwrap().await
} else {
None
};
let mut gb_rom_path = [None, None, None, None];
let mut gb_ram_path = [None, None, None, None];
if !netplay {
for i in 0..4 {
if transfer_pak[i] {
gb_rom_path[i] = select_gb_rom[i].as_mut().unwrap().await;
gb_ram_path[i] = select_gb_ram[i].as_mut().unwrap().await;
}
}
}
std::thread::Builder::new()
.name("n64".to_string())
.stack_size(env!("N64_STACK_SIZE").parse().unwrap())
.spawn(move || {
let save_config_items = SaveConfig {
selected_controller,
selected_profile,
controller_enabled,
transfer_pak,
upscale,
integer_scaling,
fullscreen,
widescreen,
crt,
emulate_vru,
overclock,
};
if file.is_some() || netplay {
let running_file = cache_dir.join("game_running");
if running_file.exists() {
println!("Game already running");
return;
}
let result = std::fs::File::create(running_file.clone());
if result.is_err() {
panic!("could not create running file: {}", result.err().unwrap())
}
let mut device = device::Device::new();
save_config(&mut device.ui.config, controller_paths, save_config_items);
if netplay {
device.netplay = Some(netplay::init(
peer_addr.unwrap(),
session.unwrap(),
player_number.unwrap(),
));
device::run_game(&mut device, rom_contents, fullscreen, enable_overclock);
netplay::close(&mut device);
} else {
for i in 0..4 {
if gb_rom_path[i].is_some() && gb_ram_path[i].is_some() {
device.transferpaks[i].cart.rom =
std::fs::read(gb_rom_path[i].as_ref().unwrap().path()).unwrap();
device.transferpaks[i].cart.ram =
std::fs::read(gb_ram_path[i].as_ref().unwrap().path()).unwrap();
}
}
if emulate_vru {
device.vru_window.window_notifier = Some(vru_window_notifier);
device.vru_window.word_receiver = Some(vru_word_receiver);
device.vru_window.gui_ctx = Some(gui_ctx);
}
let rom_contents = device::get_rom_contents(file.unwrap().path());
if rom_contents.is_empty() {
println!("Could not read rom file");
} else {
device::run_game(
&mut device,
rom_contents,
fullscreen,
enable_overclock,
);
}
}
let result = std::fs::remove_file(running_file);
if result.is_err() {
panic!("could not remove running file: {}", result.err().unwrap())
}
}
})
.unwrap();
});
}
impl eframe::App for GopherEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
if self.show_vru_dialog {
show_vru_dialog(self, ctx);
return;
}
if self.netplay.create {
gui_netplay::netplay_create(self, ctx);
}
if self.netplay.join {
gui_netplay::netplay_join(self, ctx);
}
if self.netplay.wait {
gui_netplay::netplay_wait(self, ctx);
}
if !self.netplay.error.is_empty() {
gui_netplay::netplay_error(self, ctx, self.netplay.error.clone());
}
if self.configure_profile {
egui::Window::new("Configure Input Profile")
// .open(&mut self.configure_profile)
.show(ctx, |ui| {
ui.horizontal(|ui| {
let name_label = ui.label("Profile Name: ");
ui.text_edit_singleline(&mut self.profile_name)
.labelled_by(name_label.id);
});
ui.horizontal(|ui| {
if ui.button("Configure Profile").clicked() {
let profile_name = self.profile_name.clone();
execute(async {
let mut game_ui = ui::Ui::new();
ui::input::configure_input_profile(&mut game_ui, profile_name);
});
self.configure_profile = false;
if !self.profile_name.is_empty()
&& !self.input_profiles.contains(&self.profile_name)
{
self.input_profiles.push(self.profile_name.clone())
}
};
if ui.button("Close").clicked() {
self.configure_profile = false
};
});
});
configure_profile(self, ctx);
}
egui::CentralPanel::default().show(ctx, |ui| {
if self.configure_profile {
ui.disable()
}
egui::Grid::new("button_grid")
.min_col_width(200.0)
.show(ui, |ui| {
if ui.button("Open ROM").clicked() {
open_rom(self, ctx, self.overclock);
}
if ui.button("Netplay: Create Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.create = true;
}
if ui.button("Open ROM").clicked() {
// Spawn dialog on main thread
let task = rfd::AsyncFileDialog::new().pick_file();
let selected_controller = self.selected_controller;
let selected_profile = self.selected_profile.clone();
execute(async move {
let file = task.await;
if ui.button("Open Saves Folder").clicked() {
let command = if cfg!(target_os = "windows") {
"explorer"
} else if cfg!(target_os = "linux") {
"xdg-open"
} else {
panic!("Unsupported platform");
};
let _ = std::process::Command::new(command)
.arg(self.dirs.data_dir.join("saves"))
.spawn();
}
if let Some(file) = file {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
let _ = std::fs::File::create(running_file.clone());
let mut device = device::Device::new();
save_config(&mut device.ui, selected_controller, selected_profile);
device::run_game(std::path::Path::new(file.path()), &mut device, false);
let _ = std::fs::remove_file(running_file.clone());
ui.end_row();
if ui.button("Configure Input Profile").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.configure_profile = true;
}
if ui.button("Netplay: Join Session").clicked()
&& !self.dirs.cache_dir.join("game_running").exists()
{
self.netplay.join = true;
}
});
}
if ui.button("Configure Input Profile").clicked() {
let running_file = dirs::cache_dir()
.unwrap()
.join("gopher64")
.join("game_running");
if running_file.exists() {
return;
}
self.configure_profile = true;
}
ui.add_space(32.0);
ui.add_space(16.0);
ui.label("Controller Config:");
egui::Grid::new("some_unique_id").show(ui, |ui| {
egui::Grid::new("controller_config").show(ui, |ui| {
ui.label("Port");
ui.label("Enabled");
ui.label("Emulate VRU");
ui.label("Transfer Pak");
ui.label("Profile");
ui.label("Controller");
ui.end_row();
for i in 0..4 {
ui.label(format!("{}", i + 1));
ui.centered_and_justified(|ui| {
ui.checkbox(&mut self.controller_enabled[i], "");
});
let mut vru = false;
ui.centered_and_justified(|ui| {
if i < 3 {
ui.add_enabled(false, egui::Checkbox::new(&mut vru, ""));
} else {
ui.add_enabled(true, egui::Checkbox::new(&mut self.emulate_vru, ""));
}
});
egui::ComboBox::from_id_source(format!("profile-combo-{}", i))
ui.centered_and_justified(|ui| {
ui.checkbox(&mut self.transfer_pak[i], "");
});
egui::ComboBox::from_id_salt(format!("profile-combo-{}", i))
.selected_text(self.selected_profile[i].clone())
.show_ui(ui, |ui| {
for j in 0..self.input_profiles.len() {
@@ -194,9 +585,9 @@ impl eframe::App for GopherEguiApp {
let controller_text = if self.selected_controller[i] == -1 {
"None".to_string()
} else {
self.controllers[self.selected_controller[i] as usize].clone()
self.controller_names[self.selected_controller[i] as usize].clone()
};
egui::ComboBox::from_id_source(format!("controller-combo-{}", i))
egui::ComboBox::from_id_salt(format!("controller-combo-{}", i))
.selected_text(controller_text)
.show_ui(ui, |ui| {
ui.selectable_value(
@@ -204,21 +595,100 @@ impl eframe::App for GopherEguiApp {
-1,
"None".to_string(),
);
for j in 0..self.controllers.len() {
for j in 0..self.controller_names.len() {
ui.selectable_value(
&mut self.selected_controller[i],
j as i32,
self.controllers[j].clone(),
self.controller_names[j].clone(),
);
}
});
ui.end_row();
}
});
ui.add_space(16.0);
let upscale_values = [1, 2, 4];
let mut slider_value = match self.upscale {
1 => 0,
2 => 1,
4 => 2,
_ => 0,
};
let display_text = format!("{}x Resolution", upscale_values[slider_value]);
if ui
.add(
egui::Slider::new(&mut slider_value, 0..=2)
.show_value(false)
.text(display_text),
)
.changed()
{
self.upscale = upscale_values[slider_value];
};
ui.checkbox(&mut self.integer_scaling, "Integer Scaling");
ui.checkbox(&mut self.fullscreen, "Fullscreen (Esc closes game)");
ui.checkbox(&mut self.widescreen, "Widescreen (stretch)");
ui.checkbox(&mut self.crt, "Apply CRT shader");
ui.add_space(16.0);
ui.checkbox(&mut self.overclock, "Overclock N64 CPU (may cause bugs)");
ui.add_space(16.0);
ui.hyperlink_to("Wiki", "https://github.com/gopher64/gopher64/wiki");
ui.hyperlink_to("Discord Server", "https://discord.gg/9RGXq8W8JQ");
ui.add_space(16.0);
ui.label(format!("Version: {}", env!("CARGO_PKG_VERSION")));
if self.latest_version.is_some() {
let current_version = semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
if current_version < *self.latest_version.as_ref().unwrap() {
ui.hyperlink_to(
"New version available!",
"https://github.com/gopher64/gopher64/releases/latest",
);
}
}
});
if self.emulate_vru && self.vru_window_receiver.is_some() {
let result = self.vru_window_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
self.show_vru_dialog = true;
self.vru_word_list = result.unwrap();
}
}
get_latest_version(self, ctx);
}
}
fn execute<F: std::future::Future<Output = ()> + Send + 'static>(f: F) {
std::thread::spawn(move || futures::executor::block_on(f));
fn add_fonts(ctx: &egui::Context) {
ctx.add_font(eframe::epaint::text::FontInsert::new(
"regular_font",
egui::FontData::from_static(include_bytes!("../../data/Roboto-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Highest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Highest,
},
],
));
ctx.add_font(eframe::epaint::text::FontInsert::new(
"japanese_font",
egui::FontData::from_static(include_bytes!("../../data/NotoSansJP-Regular.ttf")),
vec![
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Proportional,
priority: egui::epaint::text::FontPriority::Lowest,
},
eframe::epaint::text::InsertFontFamily {
family: egui::FontFamily::Monospace,
priority: egui::epaint::text::FontPriority::Lowest,
},
],
));
}
+942
View File
@@ -0,0 +1,942 @@
use crate::device;
use crate::ui::gui::GopherEguiApp;
use crate::ui::{self, gui};
use eframe::egui;
use sha2::{Digest, Sha256};
const NETPLAY_VERSION: i32 = 17;
const EMU_NAME: &str = "gopher64";
#[derive(Default)]
pub struct GameInfo {
pub md5: String,
pub game_name: String,
pub rom_label: String,
pub rom_contents: Vec<u8>,
}
#[derive(PartialEq, Default)]
pub struct NetplayServer {
pub name: String,
pub ip: String,
}
#[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<NetplayServer>,
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: NetplayServer,
pub socket_waiting: bool,
pub game_info: GameInfo,
pub servers: Vec<NetplayServer>,
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>,
features: Option<std::collections::HashMap<String, String>>,
buffer_target: Option<i32>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct NetplayMessage {
#[serde(rename = "type")]
message_type: String,
player_name: Option<String>,
client_sha: Option<String>,
netplay_version: Option<i32>,
emulator: Option<String>,
room: Option<NetplayRoom>,
accept: Option<i32>,
message: Option<String>,
auth: Option<String>,
player_names: Option<[String; 4]>,
#[serde(rename = "authTime")]
auth_time: Option<String>,
rooms: Option<Vec<NetplayRoom>>,
}
fn get_servers(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.servers.is_empty() {
if app.netplay.broadcast_socket.is_none() {
app.netplay.broadcast_socket = Some(
std::net::UdpSocket::bind((std::net::Ipv4Addr::UNSPECIFIED, 0))
.expect("couldn't bind to address"),
);
let socket = app.netplay.broadcast_socket.as_ref().unwrap();
socket
.set_broadcast(true)
.expect("set_broadcast call failed");
socket
.set_nonblocking(true)
.expect("could not set up socket");
let data: [u8; 1] = [1];
socket
.send_to(&data, (std::net::Ipv4Addr::BROADCAST, 45000))
.expect("couldn't send data");
app.netplay.broadcast_timer =
Some(std::time::Instant::now() + std::time::Duration::from_secs(5));
ctx.request_repaint();
}
if app.netplay.server_receiver.is_none() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.server_receiver = Some(rx);
let gui_ctx = ctx.clone();
let task = reqwest::get("https://m64p.s3.amazonaws.com/servers.json");
tokio::spawn(async move {
let response = task.await;
if let Ok(response) = response {
tx.send(response.json().await.unwrap()).await.unwrap();
gui_ctx.request_repaint();
}
});
}
}
if app.netplay.broadcast_timer.is_some()
&& std::time::Instant::now() > app.netplay.broadcast_timer.unwrap()
{
app.netplay.broadcast_timer = None;
}
if app.netplay.broadcast_socket.is_some() && app.netplay.broadcast_timer.is_some() {
let mut buffer = [0; 1024];
let result = app
.netplay
.broadcast_socket
.as_ref()
.unwrap()
.recv_from(&mut buffer);
if result.is_ok() {
let (amt, _src) = result.unwrap();
let data: std::collections::HashMap<String, String> =
serde_json::from_slice(&buffer[..amt]).unwrap();
for server in data.iter() {
let (server_name, server_ip) = server;
app.netplay.servers.push(NetplayServer {
name: server_name.to_string(),
ip: server_ip.to_string(),
});
app.netplay.server = NetplayServer {
name: server.0.clone(),
ip: 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() {
for server in result.unwrap().iter() {
app.netplay.servers.push(NetplayServer {
name: server.0.clone(),
ip: server.1.clone(),
});
}
app.netplay.server_receiver = None;
if app.netplay.server.name.is_empty() {
let first_server = app.netplay.servers.first().unwrap();
app.netplay.server = NetplayServer {
name: first_server.name.clone(),
ip: first_server.ip.clone(),
};
}
}
ctx.request_repaint();
}
}
pub fn netplay_create(app: &mut GopherEguiApp, ctx: &egui::Context) {
egui::Window::new("Create Netplay Session").show(ctx, |ui| {
egui::Grid::new("netplay_create_grid").show(ui, |ui| {
let profile_name_label = ui.label("Session Name:");
let mut size = ui.spacing().interact_size;
size.x = 200.0;
ui.add_sized(size, |ui: &mut egui::Ui| {
ui.text_edit_singleline(&mut app.netplay.session_name)
.labelled_by(profile_name_label.id)
});
ui.end_row();
let password_label = ui.label("Password (Optional):");
ui.text_edit_singleline(&mut app.netplay.password)
.labelled_by(password_label.id);
ui.end_row();
ui.label("ROM");
if app.netplay.create_rom_label.is_empty() {
app.netplay.create_rom_label = "Open ROM".to_string();
}
if ui.button(&app.netplay.create_rom_label).clicked() {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.create_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new()
.set_title("Select ROM")
.pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await;
} else {
tx.send(GameInfo {
md5: "".to_string(),
game_name: "".to_string(),
rom_label: "Open ROM".to_string(),
rom_contents: vec![],
})
.await
.unwrap();
}
gui_ctx.request_repaint();
});
}
ui.end_row();
let player_name_label = ui.label("Player Name:");
ui.text_edit_singleline(&mut app.netplay.player_name)
.labelled_by(player_name_label.id);
ui.end_row();
ui.label("Server:");
get_servers(app, ctx);
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
if !data.md5.is_empty() {
app.netplay.game_info = data;
app.netplay.rom_contents = app.netplay.game_info.rom_contents.clone();
app.netplay.create_rom_label = app.netplay.game_info.rom_label.clone();
} else {
app.netplay.create_rom_label = data.rom_label;
}
}
ctx.request_repaint();
}
egui::ComboBox::from_id_salt("server-combobox")
.selected_text(app.netplay.server.name.to_string())
.show_ui(ui, |ui| {
for server in app.netplay.servers.iter() {
ui.selectable_value(
&mut app.netplay.server,
NetplayServer {
name: server.name.clone(),
ip: server.ip.clone(),
},
server.name.clone(),
);
}
});
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.md5.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.game_name.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 mut features = std::collections::HashMap::new();
features.insert("overclock".to_string(), app.overclock.to_string());
let netplay_message = NetplayMessage {
message_type: "request_create_room".to_string(),
player_name: Some(app.netplay.player_name.clone()),
client_sha: Some(env!("GIT_HASH").to_string()),
netplay_version: Some(NETPLAY_VERSION),
emulator: Some(EMU_NAME.to_string()),
accept: None,
message: None,
rooms: None,
auth_time: Some(now_utc),
player_names: None,
auth: Some(format!("{:x}", hasher.finalize())),
room: Some(NetplayRoom {
room_name: Some(app.netplay.session_name.clone()),
password: Some(app.netplay.password.clone()),
game_name: Some(game_name),
md5: Some(app.netplay.game_info.md5.clone()),
protected: None,
port: None,
features: Some(features),
buffer_target: Some(2),
}),
};
let (mut socket, _response) =
tungstenite::connect(&app.netplay.server.ip).expect("Can't connect");
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&netplay_message).unwrap(),
)))
.unwrap();
match socket.get_mut() {
tungstenite::stream::MaybeTlsStream::Plain(stream) => {
app.netplay.peer_addr = Some(stream.peer_addr().unwrap());
stream.set_nonblocking(true)
}
_ => unimplemented!(),
}
.expect("could not set socket to non-blocking");
app.netplay.socket_waiting = true;
app.netplay.socket = Some(socket);
}
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
})
});
});
}
fn get_sessions(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.have_sessions.is_some()
&& app.netplay.server != *app.netplay.have_sessions.as_ref().unwrap()
{
// User has changed the server
app.netplay.have_sessions = None;
app.netplay.socket = None;
}
if app.netplay.socket.is_none() {
let (mut sock, _response) =
tungstenite::connect(&app.netplay.server.ip).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(NetplayServer {
name: app.netplay.server.name.clone(),
ip: app.netplay.server.ip.clone(),
});
app.netplay.socket_waiting = true;
ctx.request_repaint();
}
}
async fn parse_rom_file(file: rfd::FileHandle, tx: tokio::sync::mpsc::Sender<GameInfo>) {
let rom_contents = device::get_rom_contents(file.path());
if !rom_contents.is_empty() {
let hash = device::cart::rom::calculate_hash(&rom_contents);
let game_name = ui::storage::get_game_name(&rom_contents);
tx.send(GameInfo {
md5: hash,
game_name,
rom_label: file.file_name(),
rom_contents,
})
.await
.unwrap();
} else {
tx.send(GameInfo {
md5: "".to_string(),
game_name: "".to_string(),
rom_label: "Invalid ROM".to_string(),
rom_contents: vec![],
})
.await
.unwrap();
}
}
pub fn netplay_join(app: &mut GopherEguiApp, ctx: &egui::Context) {
if app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
let data = socket.read();
if data.is_ok() {
let message: NetplayMessage =
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
if message.accept.unwrap() == 0 {
if message.message_type == "reply_get_rooms" {
if message.rooms.is_some() {
app.netplay.sessions = message.rooms.unwrap();
}
} else if message.message_type == "reply_join_room" {
app.netplay.join = false;
app.netplay.wait = true;
app.netplay.waiting_session = Some(message.room.unwrap());
}
app.netplay.socket_waiting = false;
} else {
app.netplay.error = message.message.unwrap();
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
}
}
ctx.request_repaint();
}
if app.netplay.game_info_receiver.is_some() {
let result = app.netplay.game_info_receiver.as_mut().unwrap().try_recv();
if result.is_ok() {
app.netplay.game_info_receiver = None;
let data = result.unwrap();
if !data.md5.is_empty() {
app.netplay.game_info = data;
app.netplay.rom_contents = app.netplay.game_info.rom_contents.clone();
let netplay_message = NetplayMessage {
message_type: "request_join_room".to_string(),
player_name: Some(app.netplay.player_name.clone()),
client_sha: Some(env!("GIT_HASH").to_string()),
netplay_version: None,
emulator: None,
accept: None,
message: None,
rooms: None,
auth_time: None,
player_names: None,
auth: None,
room: Some(NetplayRoom {
room_name: None,
password: Some(app.netplay.password.clone()),
game_name: None,
md5: Some(app.netplay.game_info.md5.clone()),
protected: None,
port: app.netplay.selected_session.as_ref().unwrap().port,
features: None,
buffer_target: None,
}),
};
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.rom_label;
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.name.to_string())
.show_ui(ui, |ui| {
for server in app.netplay.servers.iter() {
ui.selectable_value(
&mut app.netplay.server,
NetplayServer {
name: server.name.clone(),
ip: server.ip.clone(),
},
server.name.clone(),
);
}
});
});
});
if !app.netplay.server.name.is_empty() {
get_sessions(app, ctx);
}
ui.add_space(16.0);
if app.netplay.sessions.is_empty() {
ui.label("No sessions available");
} else {
egui::Grid::new("netplay_join_grid").show(ui, |ui| {
ui.label(egui::RichText::new("Session Name (click to select)").underline());
ui.label(egui::RichText::new("Game Name").underline());
ui.label(egui::RichText::new("Password Protected").underline());
ui.end_row();
for room in app.netplay.sessions.iter() {
ui.selectable_value(
&mut app.netplay.selected_session,
Some(room.clone()),
room.room_name.as_ref().unwrap(),
);
ui.label(room.game_name.as_ref().unwrap());
ui.label(room.protected.unwrap_or(false).to_string());
ui.end_row();
}
});
}
ui.add_space(16.0);
ui.horizontal(|ui| {
let mut size = ui.spacing().interact_size;
size.x = 130.0;
ui.add_sized(
size,
egui::TextEdit::singleline(&mut app.netplay.password)
.hint_text("Password (if required)"),
);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
if app.netplay.join_rom_label.is_empty() {
app.netplay.join_rom_label = "Join Session (Open ROM)".to_string();
}
if ui.button(&app.netplay.join_rom_label).clicked() {
if app.netplay.player_name.is_empty() {
app.netplay.error = "Player Name cannot be empty".to_string();
} else if app.netplay.selected_session.is_none() {
app.netplay.error = "No session selected".to_string();
} else if app
.netplay
.selected_session
.as_ref()
.unwrap()
.protected
.unwrap()
&& app.netplay.password.is_empty()
{
app.netplay.error = "Session requires a password".to_string();
} else {
let (tx, rx) = tokio::sync::mpsc::channel(1);
app.netplay.game_info_receiver = Some(rx);
let gui_ctx = ctx.clone();
app.netplay.join_rom_label = "Inspecting ROM".to_string();
let task = rfd::AsyncFileDialog::new()
.set_title("Select ROM")
.pick_file();
tokio::spawn(async move {
let file = task.await;
if let Some(file) = file {
parse_rom_file(file, tx).await;
} else {
tx.send(GameInfo {
md5: "".to_string(),
game_name: "".to_string(),
rom_label: "No ROM selected".to_string(),
rom_contents: vec![],
})
.await
.unwrap();
}
gui_ctx.request_repaint();
});
}
};
});
});
});
}
pub fn netplay_wait(app: &mut GopherEguiApp, ctx: &egui::Context) {
let overclock = app
.netplay
.waiting_session
.as_ref()
.unwrap()
.features
.as_ref()
.unwrap()
.get("overclock")
.unwrap();
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,
features: None,
buffer_target: None,
}),
};
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,
features: None,
buffer_target: None,
}),
};
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,
features: None,
buffer_target: None,
}),
};
app.netplay.chat_message.clear();
let socket = app.netplay.socket.as_mut().unwrap();
socket
.send(tungstenite::Message::Binary(tungstenite::Bytes::from(
serde_json::to_vec(&send_chat).unwrap(),
)))
.unwrap();
app.netplay.send_chat = false;
}
if app.netplay.socket_waiting {
let socket = app.netplay.socket.as_mut().unwrap();
let data = socket.read();
if data.is_ok() {
let message: NetplayMessage =
serde_json::from_slice(&data.unwrap().into_data()).unwrap();
if message.accept.unwrap() == 0 {
if message.message_type == "reply_motd" {
let re = regex::Regex::new(r"<[^>]*>").unwrap();
app.netplay.motd = re
.replace_all(message.message.unwrap().as_str(), "")
.into_owned();
} else if message.message_type == "reply_players" {
app.netplay.player_names = message.player_names.unwrap();
} else if message.message_type == "reply_chat_message" {
app.netplay.chat_log.push_str(&message.message.unwrap());
app.netplay.chat_log.push('\n');
} else if message.message_type == "reply_begin_game" {
let mut player = 0;
for (i, name) in app.netplay.player_names.iter().enumerate() {
if *name == app.netplay.player_name {
player = i;
break;
}
}
app.netplay.player_number = player as u8;
if message.accept.unwrap() == 0 {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
gui::open_rom(app, ctx, *overclock == "true");
app.netplay = Default::default();
return;
} else {
app.netplay.error = message.message.unwrap();
}
}
} else {
app.netplay.error = message.message.unwrap();
}
}
ctx.request_repaint();
}
egui::Window::new("Pending Netplay Session").show(ctx, |ui| {
egui::Grid::new("netplay_wait_grid_1").show(ui, |ui| {
ui.label("Session Name:");
let room_name = app
.netplay
.waiting_session
.as_ref()
.unwrap()
.room_name
.as_ref()
.unwrap();
let game_name = app
.netplay
.waiting_session
.as_ref()
.unwrap()
.game_name
.as_ref()
.unwrap();
ui.label(room_name);
ui.end_row();
ui.label("Game Name:");
ui.label(game_name);
ui.end_row();
for i in 0..4 {
ui.label(format!("Player {}:", i + 1));
ui.label(app.netplay.player_names[i].clone());
ui.end_row();
}
});
egui::ScrollArea::vertical()
.max_height(100.0)
.stick_to_bottom(true)
.show(ui, |ui| {
ui.add(
egui::TextEdit::multiline(&mut app.netplay.chat_log)
.interactive(false)
.desired_width(ui.available_width()),
);
});
ui.horizontal(|ui| {
let mut size = ui.spacing().interact_size;
size.x = 200.0;
ui.add_sized(
size,
egui::TextEdit::singleline(&mut app.netplay.chat_message)
.hint_text("Enter chat message here"),
);
if ui
.add_enabled(!app.netplay.send_chat, egui::Button::new("Send Message"))
.clicked()
&& !app.netplay.chat_message.is_empty()
{
app.netplay.send_chat = true;
}
});
ui.add_space(16.0);
ui.label(app.netplay.motd.clone());
ui.add_space(16.0);
ui.horizontal(|ui| {
let button_enabled = app.netplay.player_name == app.netplay.player_names[0]
&& !app.netplay.pending_begin;
if ui
.add_enabled(button_enabled, egui::Button::new("Start Session"))
.clicked()
{
app.netplay.begin_game = true;
app.netplay.pending_begin = true;
}
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Close").clicked() {
if let Some(socket) = app.netplay.socket.as_mut() {
socket.close(None).unwrap();
loop {
match socket.read() {
Err(tungstenite::Error::ConnectionClosed) => break,
_ => continue,
};
}
}
app.netplay = Default::default();
};
});
});
});
}
pub fn netplay_error(app: &mut GopherEguiApp, ctx: &egui::Context, error: String) {
egui::Window::new("Netplay Error").show(ctx, |ui| {
ui.label(error);
if ui.button("Close").clicked() {
app.netplay.error = "".to_string();
};
});
}
+725 -334
View File
File diff suppressed because it is too large Load Diff
+315 -75
View File
@@ -1,4 +1,6 @@
use crate::ui;
use crate::{device, netplay, ui};
use std::io::Read;
use std::io::Write;
#[derive(PartialEq)]
pub enum SaveTypes {
@@ -7,6 +9,7 @@ pub enum SaveTypes {
Sram,
Flash,
Mempak,
Sdcard,
Romsave,
}
@@ -15,20 +18,51 @@ pub struct Paths {
pub sra_file_path: std::path::PathBuf,
pub fla_file_path: std::path::PathBuf,
pub pak_file_path: std::path::PathBuf,
pub sdcard_file_path: std::path::PathBuf,
pub romsave_file_path: std::path::PathBuf,
pub savestate_file_path: std::path::PathBuf,
}
// the bool indicates whether the save has been written to
// if that is the case, it will be flushed to the disk when the program closes
pub struct Saves {
pub eeprom: (Vec<u8>, bool),
pub sram: (Vec<u8>, bool),
pub flash: (Vec<u8>, bool),
pub mempak: (Vec<u8>, bool),
pub romsave: (serde_json::Map<String, serde_json::Value>, bool),
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Save {
pub data: Vec<u8>,
pub written: bool,
}
pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct RomSave {
pub data: std::collections::HashMap<u32, u8>,
pub written: bool,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Saves {
pub eeprom: Save,
pub sram: Save,
pub flash: Save,
pub mempak: Save,
pub sdcard: Save,
pub romsave: RomSave,
pub write_to_disk: bool,
}
fn get_save_type(rom: &[u8], game_id: &str) -> Vec<SaveTypes> {
let header_type = std::str::from_utf8(rom[0x3C..0x3E].try_into().unwrap());
if header_type.is_ok() && header_type.unwrap() == "ED" {
let save_type = rom[0x3F] >> 4;
match save_type {
0 => return vec![],
1 => return vec![SaveTypes::Eeprom4k],
2 => return vec![SaveTypes::Eeprom16k],
3 => return vec![SaveTypes::Sram],
4 => panic!("Unsupported save type: {}", save_type),
5 => return vec![SaveTypes::Flash],
6 => panic!("Unsupported save type: {}", save_type),
_ => panic!("Unknown save type: {}", save_type),
}
}
match game_id {
"NB7" | // Banjo-Tooie [Banjo to Kazooie no Daiboken 2 (J)]
"NGT" | // City Tour GrandPrix - Zen Nihon GT Senshuken
@@ -86,108 +120,314 @@ pub fn get_save_type(game_id: &str) -> Vec<SaveTypes> {
}
}
pub fn init(ui: &mut ui::Ui) {
let id = ui.game_id.as_str();
ui.save_type = get_save_type(id);
let base_path = dirs::data_dir().unwrap().join("gopher64").join("saves");
let result = std::fs::create_dir_all(base_path.clone());
if result.is_err() {
panic!("could not create save dir")
pub fn get_game_name(rom: &[u8]) -> String {
let mut game_name = "".to_owned();
let header_value = std::str::from_utf8(&rom[0x20..0x34]);
if header_value.is_ok() {
let re = regex::Regex::new(r"[^a-zA-Z0-9_ -]").unwrap();
game_name = re
.replace_all(header_value.unwrap(), "")
.trim()
.replace('\0', "");
}
game_name
}
ui.paths.eep_file_path.clone_from(&base_path);
ui.paths
pub fn init(ui: &mut ui::Ui, rom: &[u8]) {
ui.storage.save_type = get_save_type(rom, &ui.game_id);
let saves_path = ui.dirs.data_dir.join("saves");
let states_path = ui.dirs.data_dir.join("states");
let game_name = get_game_name(rom);
let prefix = if game_name.is_empty() {
&ui.game_id
} else {
&game_name
};
ui.storage.paths.eep_file_path.clone_from(&saves_path);
ui.storage
.paths
.eep_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".eep");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".eep");
ui.paths.sra_file_path.clone_from(&base_path);
ui.paths
ui.storage.paths.sra_file_path.clone_from(&saves_path);
ui.storage
.paths
.sra_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".sra");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".sra");
ui.paths.fla_file_path.clone_from(&base_path);
ui.paths
ui.storage.paths.fla_file_path.clone_from(&saves_path);
ui.storage
.paths
.fla_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".fla");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".fla");
ui.paths.pak_file_path.clone_from(&base_path);
ui.paths
ui.storage.paths.pak_file_path.clone_from(&saves_path);
ui.storage
.paths
.pak_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".mpk");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".mpk");
ui.paths.romsave_file_path.clone_from(&base_path);
ui.paths
ui.storage.paths.sdcard_file_path.clone_from(&saves_path);
ui.storage
.paths
.sdcard_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".img");
ui.storage.paths.romsave_file_path.clone_from(&saves_path);
ui.storage
.paths
.romsave_file_path
.push(ui.game_id.to_owned() + "-" + &ui.game_hash + ".romsave");
.push(prefix.to_owned() + "-" + &ui.game_hash + ".romsave");
ui.storage
.paths
.savestate_file_path
.clone_from(&states_path);
ui.storage
.paths
.savestate_file_path
.push(prefix.to_owned() + "-" + &ui.game_hash + ".state");
}
pub fn load_saves(ui: &mut ui::Ui) {
let eep = std::fs::read(&mut ui.paths.eep_file_path);
if eep.is_ok() {
ui.saves.eeprom.0 = eep.unwrap();
pub fn load_saves(ui: &mut ui::Ui, netplay: &mut Option<netplay::Netplay>) {
if netplay.is_none() || netplay.as_ref().unwrap().player_number == 0 {
let eep = std::fs::read(&mut ui.storage.paths.eep_file_path);
if eep.is_ok() {
ui.storage.saves.eeprom.data = eep.unwrap();
}
let sra = std::fs::read(&mut ui.storage.paths.sra_file_path);
if sra.is_ok() {
ui.storage.saves.sram.data = sra.unwrap();
}
let fla = std::fs::read(&mut ui.storage.paths.fla_file_path);
if fla.is_ok() {
ui.storage.saves.flash.data = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.storage.paths.pak_file_path);
if mempak.is_ok() {
ui.storage.saves.mempak.data = mempak.unwrap();
}
let sdcard = std::fs::read(&mut ui.storage.paths.sdcard_file_path);
if sdcard.is_ok() {
ui.storage.saves.sdcard.data = sdcard.unwrap();
}
let romsave = std::fs::read(&mut ui.storage.paths.romsave_file_path);
if romsave.is_ok() {
ui.storage.saves.romsave.data =
postcard::from_bytes(romsave.unwrap().as_ref()).unwrap();
}
}
let sra = std::fs::read(&mut ui.paths.sra_file_path);
if sra.is_ok() {
ui.saves.sram.0 = sra.unwrap();
}
let fla = std::fs::read(&mut ui.paths.fla_file_path);
if fla.is_ok() {
ui.saves.flash.0 = fla.unwrap();
}
let mempak = std::fs::read(&mut ui.paths.pak_file_path);
if mempak.is_ok() {
ui.saves.mempak.0 = mempak.unwrap();
}
let romsave = std::fs::read(&mut ui.paths.romsave_file_path);
if romsave.is_ok() {
ui.saves.romsave.0 = serde_json::from_slice(romsave.unwrap().as_ref()).unwrap();
if netplay.is_some() {
if netplay.as_ref().unwrap().player_number == 0 {
netplay::send_save(
netplay.as_mut().unwrap(),
"eep",
&ui.storage.saves.eeprom.data,
ui.storage.saves.eeprom.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"sra",
&ui.storage.saves.sram.data,
ui.storage.saves.sram.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"fla",
&ui.storage.saves.flash.data,
ui.storage.saves.flash.data.len(),
);
netplay::send_save(
netplay.as_mut().unwrap(),
"mpk",
&ui.storage.saves.mempak.data,
ui.storage.saves.mempak.data.len(),
);
let mut compressed_sd = Vec::new();
if !ui.storage.saves.sdcard.data.is_empty() {
compressed_sd = compress_file(&[(&ui.storage.saves.sdcard.data, "save")]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"img",
&compressed_sd,
compressed_sd.len(),
);
let mut compressed_romsave = Vec::new();
if !ui.storage.saves.romsave.data.is_empty() {
compressed_romsave = compress_file(&[(
&postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap(),
"save",
)]);
}
netplay::send_save(
netplay.as_mut().unwrap(),
"rom",
&compressed_romsave,
compressed_romsave.len(),
);
} else {
netplay::receive_save(
netplay.as_mut().unwrap(),
"eep",
&mut ui.storage.saves.eeprom.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"sra",
&mut ui.storage.saves.sram.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"fla",
&mut ui.storage.saves.flash.data,
);
netplay::receive_save(
netplay.as_mut().unwrap(),
"mpk",
&mut ui.storage.saves.mempak.data,
);
let mut compressed_sd = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "img", &mut compressed_sd);
if !compressed_sd.is_empty() {
ui.storage.saves.sdcard.data = decompress_file(&compressed_sd, "save");
}
let mut compressed_romsave = Vec::new();
netplay::receive_save(netplay.as_mut().unwrap(), "rom", &mut compressed_romsave);
if !compressed_romsave.is_empty() {
let romsave_bytes = decompress_file(&compressed_romsave, "save");
ui.storage.saves.romsave.data = postcard::from_bytes(&romsave_bytes).unwrap();
}
}
}
}
pub fn write_rom_save(ui: &ui::Ui) {
let f = std::fs::File::create(ui.paths.romsave_file_path.clone()).unwrap();
serde_json::to_writer(f, &ui.saves.romsave).unwrap();
pub fn decompress_file(input: &[u8], name: &str) -> Vec<u8> {
let mut decompressed_file = Vec::new();
{
let mut reader = zip::ZipArchive::new(std::io::Cursor::new(input)).unwrap();
let mut file = reader.by_name(name).unwrap();
file.read_to_end(&mut decompressed_file).unwrap();
}
decompressed_file
}
pub fn write_saves(ui: &ui::Ui) {
if ui.saves.eeprom.1 {
write_save(ui, SaveTypes::Eeprom16k)
pub fn compress_file(data: &[(&[u8], &str)]) -> Vec<u8> {
let mut compressed_file = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut compressed_file));
for item in data {
writer
.start_file(
item.1,
zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Zstd),
)
.unwrap();
writer.write_all(item.0).unwrap();
}
}
if ui.saves.sram.1 {
write_save(ui, SaveTypes::Sram)
compressed_file
}
fn write_rom_save(ui: &ui::Ui) {
let data = postcard::to_stdvec(&ui.storage.saves.romsave.data).unwrap();
std::fs::write(ui.storage.paths.romsave_file_path.clone(), data).unwrap();
}
fn writeback_sdcard(device: &mut device::Device) {
let length;
let save_data: &[u8];
if device.ui.storage.saves.eeprom.written {
if device
.ui
.storage
.save_type
.contains(&ui::storage::SaveTypes::Eeprom4k)
{
length = 1;
} else {
length = 4;
}
save_data = device.ui.storage.saves.eeprom.data.as_ref();
} else if device.ui.storage.saves.sram.written {
length = device.ui.storage.saves.sram.data.len() / 512;
save_data = device.ui.storage.saves.sram.data.as_ref();
} else if device.ui.storage.saves.flash.written {
length = device.ui.storage.saves.flash.data.len() / 512;
save_data = device.ui.storage.saves.flash.data.as_ref();
} else {
return;
}
if ui.saves.flash.1 {
write_save(ui, SaveTypes::Flash)
for i in 0..length {
let offset = device.cart.sc64.writeback_sector[i] as usize * 512;
device.ui.storage.saves.sdcard.data[offset..offset + 512]
.copy_from_slice(&save_data[i * 512..(i + 1) * 512]);
}
if ui.saves.mempak.1 {
write_save(ui, SaveTypes::Mempak)
}
if ui.saves.romsave.1 {
write_save(ui, SaveTypes::Romsave)
device.ui.storage.saves.sdcard.written = true;
}
pub fn write_saves(device: &mut device::Device) {
if device.netplay.is_none() || device.netplay.as_ref().unwrap().player_number == 0 {
if device.ui.storage.saves.write_to_disk {
if device.ui.storage.saves.eeprom.written {
write_save(&device.ui, SaveTypes::Eeprom16k)
}
if device.ui.storage.saves.sram.written {
write_save(&device.ui, SaveTypes::Sram)
}
if device.ui.storage.saves.flash.written {
write_save(&device.ui, SaveTypes::Flash)
}
if device.ui.storage.saves.romsave.written {
write_save(&device.ui, SaveTypes::Romsave)
}
} else {
writeback_sdcard(device)
}
if device.ui.storage.saves.mempak.written {
write_save(&device.ui, SaveTypes::Mempak)
}
if device.ui.storage.saves.sdcard.written {
write_save(&device.ui, SaveTypes::Sdcard)
}
}
}
pub fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
fn write_save(ui: &ui::Ui, save_type: SaveTypes) {
let path: &std::path::Path;
let data: &Vec<u8>;
match save_type {
SaveTypes::Eeprom4k | SaveTypes::Eeprom16k => {
path = ui.paths.eep_file_path.as_ref();
data = ui.saves.eeprom.0.as_ref();
path = ui.storage.paths.eep_file_path.as_ref();
data = ui.storage.saves.eeprom.data.as_ref();
}
SaveTypes::Sram => {
path = ui.paths.sra_file_path.as_ref();
data = ui.saves.sram.0.as_ref();
path = ui.storage.paths.sra_file_path.as_ref();
data = ui.storage.saves.sram.data.as_ref();
}
SaveTypes::Flash => {
path = ui.paths.fla_file_path.as_ref();
data = ui.saves.flash.0.as_ref();
path = ui.storage.paths.fla_file_path.as_ref();
data = ui.storage.saves.flash.data.as_ref();
}
SaveTypes::Mempak => {
path = ui.paths.pak_file_path.as_ref();
data = ui.saves.mempak.0.as_ref();
path = ui.storage.paths.pak_file_path.as_ref();
data = ui.storage.saves.mempak.data.as_ref();
}
SaveTypes::Sdcard => {
path = ui.storage.paths.sdcard_file_path.as_ref();
data = ui.storage.saves.sdcard.data.as_ref();
}
SaveTypes::Romsave => {
write_rom_save(ui);
+133 -46
View File
@@ -1,69 +1,148 @@
use crate::ui;
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/parallel_bindings.rs"));
use crate::{device, ui};
extern "C" {
pub fn vk_init(rdram_ptr: usize, rdram_size: u32, fullscreen: u8);
pub fn set_sdl_window(window: usize);
pub fn rdp_update_screen() -> u8;
pub fn rdp_set_vi_register(reg: u32, value: u32);
pub fn rdp_process_commands(dpc_regs: &mut [u32; 8], SP_DMEM: &mut [u8; 8192]) -> u64;
pub fn full_sync();
}
pub fn init(device: &mut device::Device) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
pub fn init(ui: &mut ui::Ui, rdram_ptr: *mut u8, rdram_size: usize, fullscreen: bool) {
let mut builder = ui
.video_subsystem
.as_ref()
.unwrap()
.window("gopher64", 640, 480);
builder.position_centered().vulkan();
if fullscreen {
builder.fullscreen_desktop();
let title = std::ffi::CString::new("gopher64").unwrap();
let mut flags = sdl3_sys::video::SDL_WINDOW_VULKAN
| sdl3_sys::video::SDL_WINDOW_RESIZABLE
| sdl3_sys::video::SDL_WINDOW_INPUT_FOCUS;
if device.ui.video.fullscreen {
flags |= sdl3_sys::video::SDL_WINDOW_FULLSCREEN;
}
let window_width;
let window_height;
if device.cart.pal {
window_width = if device.ui.config.video.widescreen {
1024
} else {
768
};
window_height = 576;
} else {
builder.resizable();
window_width = if device.ui.config.video.widescreen {
852
} else {
640
};
window_height = 480;
}
ui.window = Some(builder.build().unwrap());
device.ui.video.window = unsafe {
sdl3_sys::video::SDL_CreateWindow(title.as_ptr(), window_width, window_height, flags)
};
if device.ui.video.window.is_null() {
panic!("Could not create window");
}
if !unsafe { sdl3_sys::video::SDL_ShowWindow(device.ui.video.window) } {
panic!("Could not show window");
}
unsafe { sdl3_sys::everything::SDL_HideCursor() };
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
DMEM: device.rsp.mem.as_mut_ptr(),
RDRAM_SIZE: device.rdram.size,
DPC_CURRENT_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_CURRENT_REG as usize],
DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize],
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
PAL: device.cart.pal,
widescreen: device.ui.config.video.widescreen,
fullscreen: device.ui.video.fullscreen,
integer_scaling: device.ui.config.video.integer_scaling,
upscale: device.ui.config.video.upscale,
crt: device.ui.config.video.crt,
};
unsafe { rdp_init(device.ui.video.window as *mut std::ffi::c_void, gfx_info) }
}
pub fn close(ui: &ui::Ui) {
unsafe {
set_sdl_window(ui.window.as_mut().unwrap().raw() as usize);
vk_init(rdram_ptr as usize, rdram_size as u32, fullscreen as u8)
rdp_close();
sdl3_sys::video::SDL_DestroyWindow(ui.video.window);
}
}
pub fn update_screen() -> u8 {
// when the window is closed, running is set to 0
pub fn update_screen() {
// when the window is closed, running is set to false
unsafe { rdp_update_screen() }
}
pub fn check_framebuffers(address: u32) {
unsafe { rdp_check_framebuffers(address) }
}
pub fn full_sync() {
unsafe { rdp_full_sync() }
}
pub fn load_state(device: &mut device::Device) {
let gfx_info = GFX_INFO {
RDRAM: device.rdram.mem.as_mut_ptr(),
DMEM: device.rsp.mem.as_mut_ptr(),
RDRAM_SIZE: device.rdram.size,
DPC_CURRENT_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_CURRENT_REG as usize],
DPC_START_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_START_REG as usize],
DPC_END_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_END_REG as usize],
DPC_STATUS_REG: &mut device.rdp.regs_dpc[device::rdp::DPC_STATUS_REG as usize],
PAL: device.cart.pal,
widescreen: device.ui.config.video.widescreen,
fullscreen: device.ui.video.fullscreen,
integer_scaling: device.ui.config.video.integer_scaling,
upscale: device.ui.config.video.upscale,
crt: device.ui.config.video.crt,
};
unsafe {
rdp_new_processor(gfx_info);
}
}
pub fn check_callback(device: &mut device::Device) {
let callback = unsafe { rdp_check_callback() };
device.cpu.running = callback.emu_running;
if device.netplay.is_none() {
if callback.save_state {
device.save_state = true;
} else if callback.load_state {
device.load_state = true;
}
device.vi.enable_speed_limiter = callback.enable_speedlimiter;
}
}
pub fn set_register(reg: u32, value: u32) {
unsafe {
rdp_set_vi_register(reg, value);
}
}
pub fn process_rdp_list(dpc_regs: &mut [u32; 8], sp_dmem: &mut [u8; 8192]) -> u64 {
unsafe { rdp_process_commands(dpc_regs, sp_dmem) }
pub fn process_rdp_list() -> u64 {
unsafe { rdp_process_commands() }
}
pub fn rdp_full_sync() {
unsafe {
full_sync();
}
}
pub fn draw_text(
text: &str,
canvas: &mut sdl2::render::Canvas<sdl2::video::Window>,
font: &rusttype::Font,
) {
pub fn draw_text(text: &str, renderer: *mut sdl3_sys::render::SDL_Renderer, font: &rusttype::Font) {
let text_size = 32;
let scale = rusttype::Scale::uniform(text_size as f32);
let v_metrics = font.v_metrics(scale);
let offset = rusttype::point(10.0, 10.0 + v_metrics.ascent);
// Clear the canvas
canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0));
canvas.clear();
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
0,
0,
0,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderClear(renderer);
};
for glyph in font.layout(text, scale, offset) {
if let Some(bb) = glyph.pixel_bounding_box() {
@@ -71,15 +150,23 @@ pub fn draw_text(
let x = x as i32 + bb.min.x;
let y = y as i32 + bb.min.y + (240 - text_size);
if v > 0.5 {
canvas.set_draw_color(sdl2::pixels::Color::RGB(255, 255, 255));
canvas
.draw_point(sdl2::rect::Point::new(x, y))
.expect("Error drawing pixel");
unsafe {
sdl3_sys::render::SDL_SetRenderDrawColor(
renderer,
255,
255,
255,
sdl3_sys::pixels::SDL_ALPHA_OPAQUE,
);
sdl3_sys::render::SDL_RenderPoint(renderer, x as f32, y as f32);
};
}
});
}
}
// Present the canvas
canvas.present();
if !unsafe { sdl3_sys::render::SDL_RenderPresent(renderer) } {
panic!("Could not present renderer");
}
}
+28
View File
@@ -0,0 +1,28 @@
use eframe::egui;
use crate::ui;
pub fn prompt_for_match(
words: &[String],
window_notifier: &tokio::sync::mpsc::Sender<Vec<String>>,
word_index_receiver: &mut tokio::sync::mpsc::Receiver<String>,
gui_ctx: &egui::Context,
) -> u16 {
let mut dedup_words = words.to_owned();
dedup_words.sort();
dedup_words.dedup();
window_notifier.try_send(dedup_words).unwrap();
gui_ctx.request_repaint(); // this is so the window pops up right away
let mut result = word_index_receiver.try_recv();
while result.is_err() {
result = word_index_receiver.try_recv();
std::thread::sleep(std::time::Duration::from_secs_f64(1.0 / 60.0));
ui::video::update_screen(); // so the OS doesn't complain about the game window being frozen
}
for (i, v) in words.iter().enumerate() {
if *v == *result.as_ref().unwrap() {
return i as u16;
}
}
0x7FFF
}