Compare commits

..

468 Commits

Author SHA1 Message Date
Logan McNaughton e1ad3aac06 use lockstep mode 2026-07-07 09:43:47 +02:00
PolygonPrincess e8a7abffe3 Add cli flag for widescreen (#1152)
* Add cli flag for widescreen and enable config fallback

* Fix else statement brace spacing and trailing whitespace

* Fix trailing whitespace within args.widescreen else statement
2026-07-06 12:54:51 +00:00
dependabot[bot] f7ad6a4b0d Bump rust-toolchain from 1.96.0 to 1.96.1 (#1151)
Bumps [rust-toolchain](https://github.com/rust-lang/rust) from 1.96.0 to 1.96.1.
- [Release notes](https://github.com/rust-lang/rust/releases)
- [Changelog](https://github.com/rust-lang/rust/blob/main/RELEASES.md)
- [Commits](https://github.com/rust-lang/rust/compare/1.96.0...1.96.1)

---
updated-dependencies:
- dependency-name: rust-toolchain
  dependency-version: 1.96.1
  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>
2026-07-06 04:34:17 +00:00
Logan McNaughton 1bfb50de8a re-arrange input profiles (#1150) 2026-07-05 10:43:23 +00:00
Logan McNaughton 3bc7c96191 move input polling (#1145)
* move input polling

* more

* more

* more
2026-07-02 08:04:51 +00:00
Logan McNaughton 032bf1acf3 Update contribution guidelines (#1143) 2026-07-01 07:58:17 +00:00
Logan McNaughton 8bc765c1a3 match RA branding softcore->casual (#1142) 2026-07-01 07:34:48 +00:00
Logan McNaughton da061240af normalize no-intro names (#1139) 2026-06-30 10:32:43 +00:00
Logan McNaughton 407a1b967c Re-arrange settings (#1138)
* update setting locations

* tra
2026-06-30 09:53:03 +00:00
999sian ad756cd6bd settings: add a dark/light theme toggle (#1132)
* settings: add dark/light theme toggle

* i18n: translate theme selector strings

* settings: align Resolution and Theme dropdowns in a GridBox
2026-06-30 09:17:12 +00:00
Logan McNaughton 6e11bc18d6 translation tweaks (#1137) 2026-06-30 05:24:48 +00:00
Logan McNaughton c8eee9f727 don't copy from db (#1136) 2026-06-30 05:23:38 +00:00
Logan McNaughton 01a53ce1c0 use no-intro names in netplay/cheats (#1133)
* use no-intro names in netplay/cheats

* more

* more
2026-06-29 19:37:33 +00:00
Logan McNaughton 0eddf31f92 update contributing guidelines (#1130) 2026-06-29 09:53:00 +00:00
Logan McNaughton e257ba717b update ggrs (#1127) 2026-06-27 08:19:14 +00:00
Logan McNaughton 7a797165fd Fix translation for 'Open Saves Folder' in Portuguese (#1125) 2026-06-26 07:23:50 +00:00
Logan McNaughton a659413090 bump ver (#1124) 2026-06-25 13:09:55 +00:00
Logan McNaughton 08e7ec71d9 clippy fix (#1123)
* clippy fix

* more
2026-06-25 13:09:18 +00:00
Logan McNaughton c97f14980b show proper number of players (#1121) 2026-06-25 12:28:09 +00:00
Logan McNaughton 5d090f496b update ggrs (#1119) 2026-06-25 06:41:33 +00:00
Logan McNaughton d6b46037b4 update netplay connection (#1118) 2026-06-24 20:44:38 +00:00
Logan McNaughton 5b5acfdd25 update slint (#1117)
* update slint

* more
2026-06-24 15:04:14 +00:00
Logan McNaughton 926338ba62 fix desync issue (#1116)
* fix desync issue

* more

* more

* more

* more
2026-06-24 12:52:56 +00:00
Logan McNaughton cc420013f9 add sleep while waiting for state (#1115)
* add sleep while waiting for state

* more

* more
2026-06-23 16:37:14 +00:00
Logan McNaughton ba0a21feca register tx early (#1114) 2026-06-23 14:52:27 +00:00
Logan McNaughton 70b9d20c64 fix android build (#1113)
* fix android build

* more

* more
2026-06-23 13:57:52 +00:00
Logan McNaughton dee2b85daa use tokio mutex (#1112)
* use tokio mutex

* more
2026-06-23 10:27:15 +00:00
Logan McNaughton 119e9044a8 cargo update (#1111) 2026-06-23 05:35:05 +00:00
dependabot[bot] e6a260bd6a Bump actions/checkout from 6 to 7 (#1108)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-22 05:00:57 +00:00
Logan McNaughton a23ede433b bump version (#1107) 2026-06-21 19:13:16 +00:00
Logan McNaughton 346b0a139d Clarify netplay support (#1106)
Updated netplay section to clarify support for P2P connections and added a note to read the netplay guide.
2026-06-21 15:12:19 +00:00
Logan McNaughton f5cda2901f clear join sessions list (#1105) 2026-06-21 14:22:21 +00:00
Logan McNaughton 2dda5c8706 handle matchbox panics (#1104) 2026-06-21 08:51:32 +00:00
Logan McNaughton d6aa2ddce3 send disconnect notice (#1103)
* send disconnect notice

* more

* more

* more

* more
2026-06-20 16:40:56 +00:00
Logan McNaughton 6f9801a185 cargo update (#1102) 2026-06-20 11:20:11 +00:00
Logan McNaughton c82edae338 show message more frequently (#1101)
* show message more frequently

* more
2026-06-20 07:58:25 +00:00
Logan McNaughton 57327c1f73 improve netplay startup (#1100) 2026-06-19 21:06:05 +00:00
Logan McNaughton e3a22bf7d0 tweak connection times (#1099)
* tweak connection times

* more

* more
2026-06-19 07:26:39 +00:00
Logan McNaughton 3718d914fd try to restore input data from old config (#1097) 2026-06-17 06:58:44 +00:00
Logan McNaughton 97a8b63c9c fix disconnect (#1096) 2026-06-16 16:46:57 +00:00
Logan McNaughton d6d0b66078 bump version (#1095) 2026-06-16 16:41:13 +00:00
Logan McNaughton b173598320 reset timeout (#1094) 2026-06-16 14:45:04 +00:00
Logan McNaughton f211eede3b retry socket creation (#1093)
* retry sockey creation

* more

* more

* more

* more
2026-06-16 14:22:53 +00:00
Logan McNaughton b123ed9fa3 input delay cannot be less than 1 (#1092) 2026-06-16 14:02:02 +00:00
Logan McNaughton 2b0d8e5423 skip save states (#1091)
* skip save states

* more
2026-06-16 13:34:18 +00:00
Logan McNaughton 49b8024fbe Revert translation changes (#1090)
* revert translation changes

* more
2026-06-16 13:05:58 +00:00
Logan McNaughton be987e1434 don't spam errors (#1089) 2026-06-16 12:58:16 +00:00
Logan McNaughton 27e2b89fcf re-arrange netplay setup (#1088)
* re-arrange netplay setup

* more

* more

* more

* more

* more

* more

* more

* more
2026-06-16 12:48:31 +00:00
Logan McNaughton c261a746a3 enable rollback (#1087)
* enable rollback

* more
2026-06-16 10:43:50 +00:00
Logan McNaughton c290e60af1 put tokio runtime in dedicated thread (#1086)
* put tokio runtime in dedicated thread

* more

* more

* more

* more
2026-06-16 09:33:27 +00:00
Logan McNaughton 19142a30c6 use unbounded channel (#1084)
* use unbounded channel

* more
2026-06-15 17:44:20 +00:00
Logan McNaughton 2ca317d742 put rec back (#1083) 2026-06-15 15:56:53 +00:00
Logan McNaughton 45963aeea5 Fix ping recommendation (#1082)
* don't block tokio

* more

* more

* more

* more

* more
2026-06-15 15:29:51 +00:00
Logan McNaughton 4929f2cc51 remove highest ping (#1081) 2026-06-15 13:02:29 +00:00
Logan McNaughton 90e51f5f09 setup callbacks at start (#1080)
* setup callbacks at start

* more

* more
2026-06-15 09:44:33 +00:00
Logan McNaughton 9b7aeccd48 close window on user request (#1079)
* close window on user request

* more

* more

* more
2026-06-15 06:53:54 +00:00
Logan McNaughton 10d55bd25c move netplay init (#1078)
* move netplay init

* more
2026-06-14 20:36:54 +00:00
Logan McNaughton 16224cd5a6 use spinbox for latency (#1077)
* use spinbox for latency

* more

* more
2026-06-14 18:28:36 +00:00
Logan McNaughton d62d4c852a Increase timeout duration from 2 to 3 seconds (#1075) 2026-06-14 10:50:35 +00:00
Logan McNaughton 6d6349c79d Increase timeout duration from 2 to 3 seconds (#1074) 2026-06-14 08:27:11 +00:00
Logan McNaughton 9d2eec1732 bump version (#1073) 2026-06-14 06:53:05 +00:00
Logan McNaughton d6da1656b8 clean up ICE config (#1072) 2026-06-13 17:29:36 +00:00
Logan McNaughton ae7bac65d5 use custom ICE server config (#1071)
* use custom ICE server config

* more

* more

* more

* more

* more

* more
2026-06-13 16:28:33 +00:00
Logan McNaughton 99c4d6dff8 average top 3 values (#1070) 2026-06-13 13:07:10 +00:00
Logan McNaughton 74600b4b25 pump sdl events before callbacks (#1069) 2026-06-13 13:00:12 +00:00
Logan McNaughton 6cae2ed3ac dont use usize in netplay messages (#1068) 2026-06-13 09:55:12 +00:00
Logan McNaughton 611b56186d update recommendation formula (#1067)
* update recommendation formula

* more
2026-06-13 09:14:43 +00:00
Logan McNaughton dfa673e6e9 add note about input delay (#1066)
* add note about input delay

* more
2026-06-13 08:00:29 +00:00
Logan McNaughton be74e3358a Use mpsc instead of mutex (#1065)
* use mpsc

* more

* more

* more
2026-06-12 14:16:06 +00:00
Logan McNaughton 4cc85d595f pass channel to ggrs (#1064)
* pass channel to ggrs

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-06-12 13:34:21 +00:00
Logan McNaughton b2d7a12033 wait for mesh (#1063)
* wait for mesh

* more

* more
2026-06-12 07:02:53 +00:00
Logan McNaughton 65cffbba0c cap recommendation (#1062) 2026-06-12 05:36:56 +00:00
Logan McNaughton 5f8e3b9760 run netplay_close on window close (#1061)
* run netplay_close on window close

* more
2026-06-11 19:16:06 +00:00
Logan McNaughton 397a15349c handle close messages (#1060) 2026-06-11 12:53:00 +00:00
Logan McNaughton 235a4af0a9 check number of peers (#1059) 2026-06-11 07:38:05 +00:00
Logan McNaughton b171d48f87 print errors (#1058) 2026-06-11 05:36:00 +00:00
Logan McNaughton bfeba94e20 show error (#1057) 2026-06-11 05:29:28 +00:00
Logan McNaughton aa0bde0999 use tls-vendored (#1056) 2026-06-11 05:16:13 +00:00
Logan McNaughton 72e4a6b94a early rollback code (#1054)
* some rollback test

* more
2026-06-10 15:09:07 +00:00
Logan McNaughton 88f00f5638 clean up if statement (#1053) 2026-06-10 12:07:10 +00:00
Logan McNaughton db21800d9f move rewind check (#1051)
* move rewind check

* more
2026-06-10 10:54:39 +00:00
Logan McNaughton 4a63f469bf update VI on V_START (#1050)
* update VI on V_START

* more

* more

* more

* more
2026-06-10 08:55:51 +00:00
Logan McNaughton 810852997a improve ping loop (#1049) 2026-06-09 20:27:12 +00:00
Logan McNaughton 02d53beeac bump ver (#1048) 2026-06-09 17:11:49 +00:00
Logan McNaughton d589d8bac2 improve loop management (#1047)
* improve loop management

* more
2026-06-09 16:56:56 +00:00
Logan McNaughton 0deb1d11fc fix some netplay state (#1046) 2026-06-09 16:09:11 +00:00
Logan McNaughton 6cc6153abb improve wait room (#1045) 2026-06-09 14:47:16 +00:00
Logan McNaughton af163dac62 Fixed input delay (#1044)
* disable rollback

* more

* more

* more

* more

* more

* more
2026-06-09 14:10:49 +00:00
Logan McNaughton 8c4be03ab7 Revert "move netplay poll (#1042)" (#1043)
This reverts commit fce57f8ee2.
2026-06-09 05:21:36 +00:00
Logan McNaughton fce57f8ee2 move netplay poll (#1042) 2026-06-08 19:19:12 +00:00
Logan McNaughton d48423da77 cargo update (#1040)
* cargo update

* add cmake for mac

* more
2026-06-08 14:28:51 +00:00
Logan McNaughton f691578698 use idle (#1039) 2026-06-08 13:23:22 +00:00
Logan McNaughton 42a4a46dd9 Fix formatting for device information in bug report (#1038)
Updated formatting for CPU and GPU fields in bug report template.
2026-06-08 08:53:20 +00:00
Logan McNaughton e1d715bc38 state updates (#1036)
* state updates

* more
2026-06-08 08:40:58 +00:00
Logan McNaughton 4285290452 update issue template (#1037) 2026-06-08 07:32:20 +00:00
Logan McNaughton d79834eb50 add to rdp state (#1035)
* add to rdp state

* more

* more
2026-06-07 20:31:26 +00:00
Logan McNaughton 1244706b6e update issue template (#1034) 2026-06-07 18:43:58 +00:00
Logan McNaughton a1a7699516 use check_framebuffers (#1031) 2026-06-07 15:10:49 +00:00
Logan McNaughton 577741907f move speed limiter out of save state (#1030) 2026-06-07 09:55:18 +00:00
Logan McNaughton ba35935d2e Update issue templates (#1029) 2026-06-07 08:44:52 +00:00
Logan McNaughton a7df8d837f don't count FPS during rollback (#1027) 2026-06-07 07:28:09 +00:00
Logan McNaughton 820c577f5f use tokio::spawn (#1026) 2026-06-07 06:21:16 +00:00
Logan McNaughton 8b3a1a40f2 improve tokio setup (#1024)
* improve tokio setup

* more

* more

* Revert "more"

This reverts commit e0e1db4482.

* more

* more
2026-06-06 20:44:30 +00:00
Logan McNaughton 21238c8e41 cleanup if statement (#1023) 2026-06-06 19:20:53 +00:00
Logan McNaughton 73f145a35c Increase ping sample size from 10 to 12 (#1022) 2026-06-06 13:57:53 +00:00
Logan McNaughton 3184305c41 increase disconnect timeout (#1020) 2026-06-06 12:42:13 +00:00
Logan McNaughton 06dc40fd3e change netplay auth (#1019) 2026-06-06 12:22:23 +00:00
Logan McNaughton c7ac333cb7 fix ping calc (#1018) 2026-06-06 09:30:36 +00:00
Logan McNaughton 1d6e415422 use wss (#1017)
* use wss

* more
2026-06-06 09:06:40 +00:00
Logan McNaughton c89c5d1cc3 increase disconnect timeout (#1016)
* increase disconnect timeout

* more
2026-06-06 06:22:52 +00:00
Logan McNaughton 19dc966abd use different rooms for lobby and game (#1009) 2026-06-05 21:35:59 +00:00
Logan McNaughton 077c1f439c is_system_pal (#1007) 2026-06-05 17:21:29 +00:00
Logan McNaughton 9140138843 Don't render frame during roll forward (#1006)
* no update check on android

* more

* more

* more

* more

* more
2026-06-05 15:37:56 +00:00
Logan McNaughton 618e3a32f2 netplay error checking (#1005)
* netplay error checking

* more

* more

* more

* more

* more

* more

* more

* more
2026-06-05 12:16:01 +00:00
Logan McNaughton 681f8670f5 GGRS netplay (#1004)
* update netplay

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-06-05 07:43:31 +00:00
Logan McNaughton 0a44124c20 process savestates on swap buffer (#1003) 2026-06-01 20:30:31 +00:00
Logan McNaughton eb64a9ad43 switch idle command (#1002)
* switch idle command

* more
2026-06-01 11:43:39 +00:00
Logan McNaughton 5a5ff40efc set_rdram should not be needed (#1001)
* set_rdram should not be needed

* more
2026-06-01 09:07:37 +00:00
Connor Welsh cd6726d451 set application ID (#998) 2026-05-31 21:05:26 +00:00
Logan McNaughton cbc973a559 idle RDP before load state (#1000)
* idle RDP before load state

* more
2026-05-31 20:25:28 +00:00
Logan McNaughton d968983ceb Savestate tweaks (#999)
* move savestate processing

* fix stack overflow

* more

* more
2026-05-31 19:29:28 +00:00
Logan McNaughton 5175d50aa8 save video state before cloning device (#997) 2026-05-31 17:49:07 +00:00
Logan McNaughton e25818b451 keep static device intact (#996) 2026-05-31 16:22:50 +00:00
Logan McNaughton 4453e66bcf Improve state load time (#995)
* load state speed improvements

* more

* more

* more
2026-05-31 14:40:40 +00:00
Logan McNaughton 19432f33ba use hashmap for lut (#994) 2026-05-31 07:45:02 +00:00
Logan McNaughton 5c7170268e Don't store luts in savestate (#993)
* use hashmap for tlb

* more

* more

* more

* more

* more
2026-05-31 06:37:11 +00:00
Logan McNaughton 79d2c023e6 change message length (#992) 2026-05-30 19:16:15 +00:00
Logan McNaughton 864175fae6 Rewind (#991)
* rewind

* more
2026-05-30 19:00:33 +00:00
Logan McNaughton 7fc2412c9c move state to new thread (#990)
* move state to new thread

* more

* more
2026-05-30 12:26:38 +00:00
Logan McNaughton 3fdbe9a4f7 implement default for Ui (#989)
* implement default for Ui

* more
2026-05-30 11:36:32 +00:00
Logan McNaughton 399b5d65e5 Fix MBC3 latch (#987)
* Fix MBC3 latch

* more
2026-05-30 07:43:22 +00:00
Logan McNaughton 24e5a3fac4 move joystick update (#985) 2026-05-29 14:51:27 +00:00
Logan McNaughton 31420b6777 move state compression into thread (#984) 2026-05-29 13:01:45 +00:00
Logan McNaughton 7379484932 add load state func for audio (#983)
* add load state func for audio

* more

* more
2026-05-29 08:06:46 +00:00
Logan McNaughton d36ce10ab3 remove nested listview for cheats (#982) 2026-05-29 05:57:57 +00:00
Logan McNaughton 57807f09c8 update NDK (#981)
* update NDK

* Add ANDROID_NDK_ROOT environment variable
2026-05-29 05:42:41 +00:00
Logan McNaughton 0745d75c2f Add Android APK download link to README (#980) 2026-05-28 20:29:10 +00:00
Logan McNaughton a78f47ecfe fix download folder (#979) 2026-05-28 19:53:59 +00:00
Logan McNaughton 1df705c4f9 fix keystore path (#978) 2026-05-28 19:17:07 +00:00
Logan McNaughton f7ed2c5273 move itch upload (#977) 2026-05-28 18:33:11 +00:00
Logan McNaughton 8bd4c936f8 bump rust (#976) 2026-05-28 18:09:08 +00:00
Logan McNaughton 34e55d9cb5 fix small speed limiter bug (#975)
* fix small speed limiter bug

* more
2026-05-28 13:54:42 +00:00
Logan McNaughton 8a8596a66b clippy fixes (#974) 2026-05-28 13:25:23 +00:00
Logan McNaughton 40ab4ceb2b fix name of apk (#973) 2026-05-28 13:10:39 +00:00
Logan McNaughton a8dbbba6e3 update sessions async (#972)
* update sessions async

* more
2026-05-28 12:58:26 +00:00
Logan McNaughton 5b0cc0130e minSdk to 33 for android (#971) 2026-05-28 11:24:49 +00:00
Logan McNaughton bb32a0a8ba update android release settings (#970)
* update android release settings

* more

* more

* more

* more
2026-05-28 11:04:52 +00:00
Logan McNaughton e23894c2e6 make usb handle same as discord (#969) 2026-05-28 10:04:44 +00:00
Logan McNaughton e0efa80f44 Don't reload RDP processor (#968)
* change rdram pointer

* more

* more

* more

* more

* more

* more

* more
2026-05-28 09:56:35 +00:00
Logan McNaughton cb734f8df1 fix overflow in load state (#967)
* fix overflow in load state

* more
2026-05-28 09:21:58 +00:00
Logan McNaughton 3197d415e0 timeout handles (#966)
* timeout handles

* more
2026-05-28 09:12:40 +00:00
Logan McNaughton 9dfc42c723 use setSustainedPerformanceMode (#965)
* use setSustainedPerformanceMode

* more
2026-05-27 18:45:43 +00:00
Logan McNaughton 14b973ec5f remove event watch on close (#964) 2026-05-27 16:24:25 +00:00
Logan McNaughton 81a3cea9c7 fix ra init on android (#963)
* fix ra init on android

* more

* more
2026-05-27 13:39:37 +00:00
Logan McNaughton dc6138076f use const for unknown name (#962) 2026-05-27 12:23:43 +00:00
Logan McNaughton a9f7285c0f auto set version (#961) 2026-05-27 12:11:02 +00:00
Logan McNaughton 7106a461b5 Update android manifest (#960)
* Update android manifest

* more
2026-05-27 11:17:27 +00:00
Logan McNaughton 69e3ebcc26 match controllers on android (#959)
* match controllers on android

* more

* more
2026-05-27 10:03:36 +00:00
Logan McNaughton f80f0f5f02 handle pause on android (#958) 2026-05-27 08:11:48 +00:00
Logan McNaughton a447bc8104 pass all args (#957) 2026-05-26 20:43:21 +00:00
Logan McNaughton 32f54f157b Run rom android (#956)
* run rom android

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-26 18:09:57 +00:00
Logan McNaughton 9c9f1a3000 connect to native code (#955)
* connect to native code

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-26 13:03:00 +00:00
Logan McNaughton 9d7b13d4c9 include sdl libs for android (#954) 2026-05-26 07:39:51 +00:00
Logan McNaughton 60cbf59ddf add intent for input profile (#952)
* add intent for input profile

* more

* more
2026-05-25 19:35:04 +00:00
Logan McNaughton 6f00e630e0 fix order in activity result (#951)
* fix order in activity result

* more
2026-05-25 18:37:12 +00:00
Logan McNaughton 7599f71c17 add sdl for android 2026-05-25 19:59:24 +02:00
Logan McNaughton 411cb0efa7 remove dialog windows (#948) 2026-05-25 09:31:33 +00:00
Logan McNaughton 00ec554783 wait a little longer for broadcast response (#947) 2026-05-23 18:57:44 +00:00
Logan McNaughton d25d3a460a save settings on android (#946)
* save settings on android

* more

* more
2026-05-23 13:44:14 +00:00
Logan McNaughton 8eec90c35c remove focusscope (#945) 2026-05-23 09:36:33 +00:00
Logan McNaughton a596e01ef2 clean up controller detection (#943) 2026-05-23 08:19:44 +00:00
Logan McNaughton 43ebc37f87 fix RA on android (#942) 2026-05-23 07:31:39 +00:00
Logan McNaughton 6223f10fae improve jni paths (#941) 2026-05-23 07:04:23 +00:00
Logan McNaughton c16e927288 use native-tls (#940)
* use native-tls

* Add linker argument for aarch64 macOS target

* Update linker settings in config.toml

Remove linker configuration for macOS and consolidate for Windows.

* Revert "Update linker settings in config.toml"

This reverts commit 33c8da07bf.

* more
2026-05-23 05:34:01 +00:00
Logan McNaughton 6ef33204e8 remove duplicate code (#938) 2026-05-22 17:49:15 +00:00
Logan McNaughton 3f9d62248a rom reading on android (#937) 2026-05-22 14:42:08 +00:00
Logan McNaughton 18fbd8e56c Remove AndroidX usage declaration (#936)
Removed AndroidX usage declaration from properties.
2026-05-22 13:50:50 +00:00
Logan McNaughton e5c74d05aa Improve some dialogs (#935)
* improve dialogs

* more
2026-05-22 10:43:37 +00:00
Logan McNaughton 82c22dc18e check file exists on android (#934)
* check file exists on android

* more

* more

* more
2026-05-22 07:43:20 +00:00
Logan McNaughton 66a74b5693 keep file permissions (#933) 2026-05-22 06:05:59 +00:00
Logan McNaughton 367b8bdb5c Handle intent results (#932)
* Handle intent results

* more

* more

* more

* more
2026-05-21 20:06:55 +00:00
Logan McNaughton b865c88d99 Use Gradle (#931)
* remove cargo-apk

* add gradle

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* Revert "more"

This reverts commit 19395aac13.

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-21 16:16:12 +00:00
Logan McNaughton 53b39010a0 no vru on android (#930)
* no vru on android

* more

* more
2026-05-21 05:57:09 +00:00
Logan McNaughton 7ad8bed7df Netplay window adjustments (#929)
* adjust netplay windows

* more

* more
2026-05-20 15:01:31 +00:00
Logan McNaughton 01ea4c422a hold a ref to AndroidApp (#928)
* hold a ref to AndroidApp

* more

* more

* more

* more

* more
2026-05-20 14:16:17 +00:00
Logan McNaughton 7eb090a622 fix some popups (#927) 2026-05-20 10:43:24 +00:00
Logan McNaughton 48956dc016 move some android guards around (#926)
* move some android guards around

* more
2026-05-20 09:41:12 +00:00
Logan McNaughton 2eb8a0dc1d move save_settings into callback (#925) 2026-05-20 08:34:33 +00:00
Logan McNaughton c53ffebece only show window in safe area (#924) 2026-05-20 07:47:34 +00:00
Logan McNaughton 06d0aa53bc enable ui dragging (#923) 2026-05-20 07:14:43 +00:00
Logan McNaughton 28b1424357 don't hold android app reference (#922)
* don't hold android app reference

* more

* more
2026-05-20 06:57:56 +00:00
Logan McNaughton e852439b6a Fix open on Android (#921)
* fix open

* more

* more

* mroe
2026-05-19 19:56:36 +00:00
Logan McNaughton 5850c514f2 Populate controller list on android (#920)
* list controllers in UI for android

* more

* more
2026-05-19 17:30:09 +00:00
Logan McNaughton 11a49fd687 open crate only for gui (#919) 2026-05-19 13:36:48 +00:00
Logan McNaughton be9ba1366e move some android guards (#917)
* move some android guards

* more
2026-05-19 12:10:25 +00:00
Logan McNaughton dbe67f1034 More UI tweaks (#916)
* More UI tweaks

* more

* more
2026-05-19 09:50:01 +00:00
Logan McNaughton 7eecfa3275 use robius open (#915)
* use robius open

* more
2026-05-19 08:50:42 +00:00
Logan McNaughton e1b4039a52 dont use open on android (#914) 2026-05-19 07:43:55 +00:00
Logan McNaughton 9f2cfb5a9f read RA settings from CLI (#913)
* read RA settings from CLI

* more
2026-05-19 07:20:08 +00:00
Logan McNaughton 41e66825d0 Update UI (#911)
* rework UI

* more

* more

* more
2026-05-19 06:50:00 +00:00
Logan McNaughton a78393ca91 More basic Android stuff (#910)
* enable tokio

* data dirs

* jni pointer

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-18 15:08:01 +00:00
Logan McNaughton 8dde32cb21 fix label (#909) 2026-05-17 19:53:10 +00:00
Logan McNaughton 1538513fb7 add apk icon (#908)
* add apk icon

* more

* more

* more

* more
2026-05-17 18:07:57 +00:00
Logan McNaughton 30c8aef24e move netplay_id (#907) 2026-05-17 16:44:21 +00:00
Logan McNaughton 8daa65d93e only use --release for tags (#906)
* only use --release for tags

* more

* more

* more
2026-05-17 16:14:48 +00:00
Logan McNaughton df5fcae69b Remove Android SDK setup from build.yml (#905) 2026-05-17 15:26:13 +00:00
Logan McNaughton e3b63908de show controller during config (#904) 2026-05-17 14:43:37 +00:00
Logan McNaughton 8c53de9cc7 Don't set NDK version (#903)
* Set ANDROID_NDK_ROOT

* Update build.yml
2026-05-17 14:11:29 +00:00
Logan McNaughton 78cb758a26 Add LIBCLANG_PATH to build workflow (#902) 2026-05-17 13:40:14 +00:00
Logan McNaughton 8fea9ef212 update ndk (#901) 2026-05-17 09:51:50 +00:00
Logan McNaughton 38fdc24a49 Use cargo-ndk for env (#900)
* Use cargo-ndk for env

* Update build.yml
2026-05-17 09:00:57 +00:00
Logan McNaughton 069c64ab08 build apk (#899)
* build apk

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-16 20:09:19 +00:00
Logan McNaughton 2005fb1421 don't update window when minimized (#898) 2026-05-16 18:46:00 +00:00
Logan McNaughton 04b15fee2f fail early if cheats cannot be read (#897) 2026-05-15 18:05:20 +00:00
Logan McNaughton eda3dda613 error if cheats cannot be read (#896) 2026-05-15 17:44:04 +00:00
Logan McNaughton 03be98b571 build android lib (#895)
* build android lib

* more

* more

* more

* more
2026-05-15 14:10:23 +00:00
Logan McNaughton 93bf103f03 move around RA code (#894)
* move around RA code

* more

* more

* more
2026-05-15 12:36:11 +00:00
Logan McNaughton c8f485cd93 Build clib (#893)
* Build clib

* more

* more

* more

* linker

* more

* more

* more

* more

* more

* more
2026-05-15 11:18:48 +00:00
Logan McNaughton 3a301c517d shorten message (#891) 2026-05-15 06:58:19 +00:00
Logan McNaughton d17c577ccd use port 48646 for UNFloader (#890) 2026-05-14 19:55:42 +00:00
Logan McNaughton 8c331c25eb Sort translation files (#889)
* Sort translation files

* more
2026-05-14 15:16:53 +00:00
Logan McNaughton 8341ef5731 Add Discord Rich Presence (#888)
* add rich presence

* more

* more

* more

* more

* more

* more

* more

* more

* more

* update translations

* more
2026-05-14 14:30:36 +00:00
Logan McNaughton 40a29d927f shorten some messages (#887) 2026-05-14 08:34:42 +00:00
Logan McNaughton 9846c2243c fix typo in RAM name (#885) 2026-05-13 12:48:18 +00:00
Logan McNaughton c1dc4fe589 automatically attach assets to release (#884)
* automatically attach assets to release

* more
2026-05-13 08:03:15 +00:00
Logan McNaughton 06052b729b add missing newline (#881) 2026-05-12 11:55:42 +00:00
Logan McNaughton d7c912b53e Update russian translation (#880) 2026-05-12 11:16:28 +00:00
Logan McNaughton b792ebbd45 Change German translation for 'Transfer Pak' (#879) 2026-05-12 10:26:41 +00:00
Logan McNaughton e7071047e3 Add Russian translation (#878) 2026-05-12 10:22:44 +00:00
Logan McNaughton 6496288c4e don't reallocate rdram during load state (#877)
* don't reallocate rdram during load state

* more
2026-05-12 08:11:46 +00:00
Logan McNaughton 1ab8987f2d Update lint.yml to include fetch-depth option (#876)
Add fetch-depth option to checkout step in lint workflow.
2026-05-12 06:55:01 +00:00
Logan McNaughton 034b39bec5 Add Brazilian Portuguese translation (#875)
* Add Brazilian Portuguese translation"

* fix typo

* couple more fixes
2026-05-12 06:48:33 +00:00
Logan McNaughton 5368ae3d37 German translation updates (#874) 2026-05-12 05:46:04 +00:00
Logan McNaughton f963742009 put back checks for proper type of controller (#873) 2026-05-11 15:42:15 +00:00
Logan McNaughton a8ae9dbc6a Add more starter translations (#872)
* Add more starter translations

* more
2026-05-11 11:41:45 +00:00
Logan McNaughton b55a031971 Update translation readme (#871) 2026-05-11 10:26:40 +00:00
Logan McNaughton 27ffedbdee es changes (#870) 2026-05-11 05:35:22 +00:00
Logan McNaughton 493f08afaf remove some spaces from translation strings (#868) 2026-05-10 18:44:18 +00:00
Logan McNaughton 0659da24fa bundle AI generated spanish translation (#867)
* bundle AI generated spanish translation

* more

* more

* more

* more

* more

* more

* more
2026-05-10 14:24:34 +00:00
Logan McNaughton 5596dad111 improve some translation wrappers (#866) 2026-05-10 13:08:13 +00:00
Logan McNaughton a5b7561c51 clean up read_cart_word (#865)
* clean up read_cart_word

* more

* more
2026-05-10 09:28:48 +00:00
Logan McNaughton fe23e4bad9 put default profile first (#864) 2026-05-09 18:58:29 +00:00
Logan McNaughton 5b3b8265e5 set offset using let match (#863) 2026-05-09 18:02:18 +00:00
Logan McNaughton a7cbf866e8 pump events before flush (#862) 2026-05-09 17:05:38 +00:00
Logan McNaughton 7fae21d7ac Use SDL_GetJoystickAxisInitialState (#858)
* add initial_state to input profile

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-09 13:47:55 +00:00
Logan McNaughton 55bf63210a Don't save timestamp to GB save (#860)
* use real time for GB RTC

* more

* more

* more
2026-05-09 13:12:20 +00:00
Logan McNaughton 2b72c24dbe GB RTC saving (#857)
* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* GB RTC saving

* more
2026-05-09 07:57:25 +00:00
Logan McNaughton 455365a71d move sdl hint (#856) 2026-05-09 06:35:00 +00:00
Logan McNaughton 1f6b575440 move gb time into variable (#855)
* move gb time into variable

* more
2026-05-08 20:05:25 +00:00
Logan McNaughton 86304018dd remove un-needed window() (#854)
* remove un-needed window()

* more
2026-05-08 14:36:23 +00:00
Logan McNaughton c44ab9602c GBC RTC support (#853)
* GBC RTC support

* more

* more

* more

* more

* more

* more
2026-05-08 14:03:13 +00:00
Logan McNaughton 13be114823 look for bin in compressed files (#851)
* look for bin in compressed files

* more

* more

* more

* more

* more

* more

* more
2026-05-08 07:36:16 +00:00
Logan McNaughton f793eb06be bump to 1.1.20 (#849) 2026-05-07 06:49:49 +00:00
Logan McNaughton 61532f7b45 move sdl init (#848)
* move sdl init

* more

* more

* more

* more
2026-05-06 20:09:21 +00:00
Logan McNaughton a69a74bf37 cleanup some if statements (#846) 2026-05-06 11:57:04 +00:00
Logan McNaughton 6d4d5468f9 save rom dir (#845) 2026-05-06 11:17:28 +00:00
Logan McNaughton 7015d44da7 fix ghost image with SSAA (#844) 2026-05-06 11:05:48 +00:00
Logan McNaughton 172867527b add option for SSAA (#843) 2026-05-06 09:52:13 +00:00
Logan McNaughton 323e8cf1f0 move sdl quit calls (#842)
* move sdl quit calls

* more
2026-05-06 08:59:05 +00:00
Logan McNaughton 172d10d7cb update controllers when clicking on controller config (#841)
* update controllers when clicking on controller config

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-05-06 07:44:39 +00:00
Logan McNaughton 945d456f9c disable fps display on close (#839)
* disable fps display on close

* another check
2026-05-04 12:52:25 +00:00
Logan McNaughton a4f635265b guards around postcard (#838) 2026-05-04 11:52:46 +00:00
Logan McNaughton f45f951af8 fast forward controller hotkey (#837)
* fast forward controller hotkey

* more
2026-05-04 08:59:20 +00:00
Logan McNaughton 883852a828 more error messages (#836) 2026-05-04 08:36:50 +00:00
Logan McNaughton 90052257bb Use eprintln (#835)
* use eprintln

* more

* more
2026-05-04 07:49:38 +00:00
Logan McNaughton 1184d3d77a better error message (#834) 2026-05-03 14:22:50 +00:00
Logan McNaughton 12280bb35b more crash protection (#832)
* more crash protection

* more

* more
2026-05-03 14:02:17 +00:00
Logan McNaughton 17f3f2dc63 more crash protection (#833)
* more crash protection

* more
2026-05-03 13:59:59 +00:00
Logan McNaughton 3d6ca17a9a more crash protection (#831) 2026-05-03 13:11:08 +00:00
Logan McNaughton 01863a70a8 avoid some controller crashes (#830)
* avoid some controller crashes

* more

* more
2026-05-03 07:50:13 +00:00
Logan McNaughton 801cc1bc74 cargo update (#827) 2026-05-03 05:49:27 +00:00
Logan McNaughton 9ed8d835c0 add merge_group to workflow (#828) 2026-05-03 05:49:07 +00:00
Logan McNaughton b754891d45 ensure audio is playing (#826)
* ensure audio is playing

* more
2026-05-03 07:18:28 +02:00
Logan McNaughton 9cc6d48493 ensure file dialog order for transferpak (#824)
* ensure file dialog order for transferpak

* more
2026-05-02 22:13:59 +02:00
Logan McNaughton b865538d6e avoid panic with invalid cheat (#823)
* avoid panic with invalid cheat

* more

* more
2026-05-02 17:59:51 +02:00
Logan McNaughton abacaddf8a avoid crash on missing GB files (#822)
* avoid crash on missing GB files

* more

* more
2026-05-02 17:06:56 +02:00
Logan McNaughton b86c5daa2c add controller hotkeys (#821)
* add controller hotkeys

* more

* more

* more

* more

* more

* more
2026-05-02 09:04:42 +02:00
Logan McNaughton df8d7d9ff0 allow single controller mpk files (#820) 2026-05-01 21:32:08 +02:00
Logan McNaughton 8c7d3d4893 protect against null ptr dereference (#819)
* protect against null ptr dereference

* more
2026-05-01 20:44:14 +02:00
Logan McNaughton b88f041bd8 don't check for update inside itch app (#817) 2026-05-01 17:42:07 +02:00
Logan McNaughton e860b96bd8 Controller code cleanup (#816)
* controller hotkeys

* more

* more

* more

* more

* more
2026-04-28 08:37:59 +02:00
Logan McNaughton b2ef544513 handle controller reconnection (#815) 2026-04-27 16:38:20 +02:00
dependabot[bot] ca00ee1a42 Bump apple-actions/import-codesign-certs from 6 to 7 (#814)
Bumps [apple-actions/import-codesign-certs](https://github.com/apple-actions/import-codesign-certs) from 6 to 7.
- [Release notes](https://github.com/apple-actions/import-codesign-certs/releases)
- [Commits](https://github.com/apple-actions/import-codesign-certs/compare/v6...v7)

---
updated-dependencies:
- dependency-name: apple-actions/import-codesign-certs
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 05:59:33 +02:00
Logan McNaughton 511d8461c2 add note about modifying profile (#812) 2026-04-26 17:46:12 +02:00
Logan McNaughton fe42efadfa limit frequent audio adjustments (#810) 2026-04-25 17:05:54 +02:00
Logan McNaughton c1e73320b1 adjust audio buffer management (#809)
* adjust audio buffer management

* more

* more
2026-04-25 16:05:28 +02:00
Logan McNaughton 47dd6d61b0 update setup-butler (#808) 2026-04-25 11:20:56 +02:00
Logan McNaughton 212557610b use bash shell (#807) 2026-04-25 07:53:30 +02:00
Logan McNaughton 5e6a7731f4 update to 1.1.19 (#806)
* update to 1.1.19

* more
2026-04-25 07:22:58 +02:00
Logan McNaughton 65c63dfc5d Always set user agent (#805)
* Always set user agent

* more
2026-04-25 07:15:47 +02:00
Logan McNaughton ae4e7cb01f Update download links (#804) 2026-04-24 22:12:24 +02:00
Logan McNaughton a233c72dad add itch.io link (#803) 2026-04-24 10:54:47 +02:00
Logan McNaughton b267b83248 upload to itch.io (#802) 2026-04-24 10:23:28 +02:00
Logan McNaughton 6c2ae4dc5a update mac signing command (#801) 2026-04-23 20:01:01 +02:00
Logan McNaughton 0a884e85df Fix macos sandboxing (#800)
* Fix macos sandboxing

* more

* more

* more

* more

* more

* more

* more
2026-04-23 19:32:53 +02:00
Logan McNaughton 47098f60dd use timer for message lengths (#799)
* use timer for message lengths

* more

* more

* more

* more

* more

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

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

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

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

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

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

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

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

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

* more

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

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

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

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

* more

* more

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

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

* add note

* more

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

* more

* more
2026-04-17 09:59:32 +02:00
Logan McNaughton 28aa8ea280 use tokio::fs (#763) 2026-04-17 09:27:42 +02:00
Logan McNaughton cb87f79710 writeback GB ram files (#762) 2026-04-17 08:56:53 +02:00
Logan McNaughton be117214c3 Update README with notable features of Gopher64 (#761)
Added features list for Gopher64 emulator
2026-04-17 08:48:47 +02:00
Logan McNaughton bb056a77f4 bump version (#760) 2026-04-16 15:56:35 +02:00
Logan McNaughton d4a90155f9 Rust 1.95.0 (#759) 2026-04-16 15:21:01 +02:00
Logan McNaughton e24a9a8126 cleaner fix for MBC30 issue (#758)
* cleaner fix for MBC30 issue

* more
2026-04-16 13:31:17 +02:00
Logan McNaughton 83fab4c638 MBC30 support (#757) 2026-04-16 12:38:45 +02:00
Logan McNaughton 8196b52289 Fix MBC30 support (#756) 2026-04-16 11:56:57 +02:00
Logan McNaughton 8dadbbbf58 update slint to 1.16.0 (#755) 2026-04-16 11:09:09 +02:00
Logan McNaughton 093b39b17d Improve netplay RA guard (#752)
* remove double guard for netplay+RA

* more

* more
2026-04-15 19:00:39 +02:00
Logan McNaughton 7100750e2d show proper error on failed login (#751)
* show proper error on failed login

* more
2026-04-15 16:02:34 +02:00
Logan McNaughton 40e250386e add note about strange char* comparison (#750)
* add note about strange char* comparison

* more
2026-04-15 13:54:27 +02:00
Logan McNaughton d2e47509b2 remove unused param (#749) 2026-04-15 13:39:49 +02:00
Logan McNaughton 451d66beee video code cleanup (#748) 2026-04-15 13:25:04 +02:00
Logan McNaughton 20bad48894 store C strings in vars (#747) 2026-04-15 13:10:15 +02:00
Logan McNaughton b40ec6940f finish up RA support (#745)
* finish up RA support

* more

* capture some corner cases

* fix post data

* more

* more

* fix range

* more

* more

* more

* more

* show in-progress achievements

* more

* more

* more

* more
2026-04-15 12:57:52 +02:00
Logan McNaughton d12f16fe29 minor code cleanup (#746) 2026-04-15 12:36:21 +02:00
dependabot[bot] d061a856d2 Bump rand from 0.10.0 to 0.10.1 in the cargo group across 1 directory (#744)
Bumps the cargo group with 1 update in the / directory: [rand](https://github.com/rust-random/rand).


Updates `rand` from 0.10.0 to 0.10.1
- [Release notes](https://github.com/rust-random/rand/releases)
- [Changelog](https://github.com/rust-random/rand/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/rand/compare/0.10.0...0.10.1)

---
updated-dependencies:
- dependency-name: rand
  dependency-version: 0.10.1
  dependency-type: direct:production
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-14 07:26:44 +02:00
Logan McNaughton 7725aa2692 RA tweaks (#743)
* prevent crash

* more

* more
2026-04-13 22:38:31 +02:00
Logan McNaughton baa14de3f3 show leaderboard and challenge together (#742) 2026-04-13 21:22:36 +02:00
Logan McNaughton c14b461612 more RA (#741)
* more RA

* more

* more
2026-04-13 20:39:57 +02:00
Logan McNaughton 595089187d leaderboard tracker (#740)
* leaderboard tracker

* more

* more

* more

* more
2026-04-13 13:30:18 +02:00
Logan McNaughton c4b86eb4b1 challenge indicators (#739)
* Add Challenge Indicators

* more

* more

* use c++20

* shorten messages

* progress

* more

* more

* more

* more
2026-04-13 09:16:15 +02:00
Logan McNaughton 4240a99ad5 cache message images (#738) 2026-04-12 19:00:45 +02:00
Logan McNaughton 9d7db93c70 fix font clarity (#737) 2026-04-12 15:21:52 +02:00
Logan McNaughton 9b71d3aa0b print message for failed login (#736) 2026-04-12 12:33:11 +02:00
Logan McNaughton e8898c52fc More RA code (#733)
* implement RC_CLIENT_EVENT_SERVER_ERROR

* handle more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-04-12 12:10:15 +02:00
Logan McNaughton 4b5c8d03a9 use a const pointer (#732) 2026-04-11 16:35:04 +02:00
Logan McNaughton 2706f8f6a5 use sized savestate functions (#730)
* use sized savestate functions

* fix lint
2026-04-11 08:44:22 +02:00
Logan McNaughton e4391b3089 add some warning messages (#729) 2026-04-10 21:51:06 +02:00
Logan McNaughton 4b54ea279d Build on MacOS 15 (#728) 2026-04-10 20:21:12 +02:00
Logan McNaughton 9f6194d64f better startup message (#727)
* better startup message

* more
2026-04-10 07:56:18 +02:00
Logan McNaughton 8bb3179534 Improve startup messages (#726)
* improve startup messages

* more
2026-04-09 20:05:54 +02:00
Logan McNaughton 35dbd957cc reduce font size a bit (#725)
* reduce font size a bit

* remove duplicate messages
2026-04-09 17:22:26 +02:00
Logan McNaughton 125f79a0e6 show message for RA progress (#724) 2026-04-09 15:15:29 +02:00
Logan McNaughton f865ccfacd pull tags in github action (#723)
* pull tags in github action

* more detailed version

* more
2026-04-09 13:50:48 +02:00
Logan McNaughton 914798e717 more descriptive git naming (#722) 2026-04-09 13:13:56 +02:00
Logan McNaughton 406ebba93d run ra do_idle for VRU window (#721) 2026-04-09 11:31:00 +02:00
Logan McNaughton d8ec09d475 centralize hardcore check (#720) 2026-04-09 11:17:57 +02:00
Logan McNaughton a1da699580 add RA show profile button (#719)
* add RA show profile button

* lint
2026-04-09 09:37:57 +02:00
Logan McNaughton 1226052035 cleanup some cli options (#718) 2026-04-09 09:20:23 +02:00
Logan McNaughton 2917e662df better defaults for window size (#717) 2026-04-08 22:31:28 +02:00
Logan McNaughton 601713e102 RetroAchievements hardcore support (#716)
* enable hardcore mode

* more
2026-04-08 21:57:18 +02:00
Logan McNaughton 6ed53e4e9d Upload signed windows builds (#715)
* upload windows signed artifact

* more

* more

* more

* more
2026-04-08 13:09:32 +02:00
Logan McNaughton 650fd270e1 add messages for RA leaderboards (#714) 2026-04-08 09:54:08 +02:00
Logan McNaughton 54d82713ac dont check for update with flatpak (#713) 2026-04-07 19:42:07 +02:00
Logan McNaughton 11cb242ed2 sign mac bundle (#712)
* sign mac bundle

* xcrun

* use zip

* more
2026-04-07 19:31:04 +02:00
Logan McNaughton e932239a63 RetroAchievements (#710)
* retroachievements

* add rc_api_info

* disable hardcore

* clear password after login attempt

* proper check for hardcore

* more set_hardcore

* change checkbox

* add more stubs

* add more UI

* add button for RA website

* cleanup http callback
2026-04-07 08:59:03 +02:00
Logan McNaughton 5207bd4b27 remove governor crate (#709)
* remove governor crate

* more
2026-04-04 09:27:45 +02:00
Logan McNaughton d378ae187a write savestate in thread (#708) 2026-04-03 16:32:40 +02:00
Logan McNaughton e72ddf73f0 use 4 worker threads (#707) 2026-04-03 16:00:00 +02:00
Logan McNaughton 4bba36f0c3 add libpng to macos build (#706)
* add libpng to macos build

* more

* signing

* more

* more

* more
2026-04-02 14:52:16 +02:00
Logan McNaughton 20b5cf3e4d Improve MacOS opt flags (#705) 2026-04-02 08:19:32 +02:00
Logan McNaughton 973f4bb28d Create MacOS bundle (#701)
* Create MacOS bundle

* more

* more

* more

* Update framework link search path in build.rs

* Update framework link search path in build.rs

* Remove MoltenVK linking from build.rs

Removed MoltenVK framework linking from build script.

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-04-01 20:35:46 +02:00
Logan McNaughton de0db4b46d static link moltenvk (#700)
* static link moltenvk

* more

* more

* static freetype
2026-03-31 20:09:06 +02:00
Logan McNaughton 11d3e52779 use status vs spawn/wait (#699) 2026-03-31 19:12:06 +02:00
Logan McNaughton cef70faf1e move MACOSX_DEPLOYMENT_TARGET 2026-03-31 15:02:53 +02:00
Logan McNaughton 9b0b3e67a4 change dinput arg type 2026-03-31 13:58:56 +02:00
Logan McNaughton ebd22415cd honor setting 2026-03-31 11:40:50 +02:00
Logan McNaughton 5177aaab53 dont pass fullscreen 2026-03-31 11:38:36 +02:00
Logan McNaughton ad5dc8311a remove custom stack size (#698)
* remove custom stack size

* new command

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* config

* more

* more
2026-03-31 10:54:14 +02:00
Logan McNaughton cc538f942f use limiter clock 2026-03-30 09:20:44 +02:00
Logan McNaughton 9528473f11 use Duration for comparing time (#696) 2026-03-29 18:54:35 +02:00
Logan McNaughton 8b770cfd18 Update linux deps based on SDL wiki (#695) 2026-03-28 20:00:44 +01:00
Logan McNaughton 46cb6738a2 Use upstream sse2neon (#694)
* Use upstream sse2neon

* update commit
2026-03-28 19:24:53 +01:00
Logan McNaughton 9b8040873d Update parallel-rdp (#693)
* Update parallel-rdp

* more

* more
2026-03-26 17:02:52 +01:00
Logan McNaughton bff39ab04d Rust 1.94.1 (#692)
* Rust 1.94.1

* more

* more
2026-03-26 16:28:29 +01:00
Logan McNaughton 86ce5795fa fix netplay start if player 1 leaves (#691)
* fix netplay start if player 1 leaves

* more

* more
2026-03-25 10:56:17 +01:00
Logan McNaughton a834a4f9a4 Eliminate macOS Vulkan hint in SDL initialization (#690)
Removed macOS-specific Vulkan library hint from SDL initialization.
2026-03-24 18:06:49 +01:00
Logan McNaughton 1a4de8e911 Don't use caching for releases 2026-03-24 14:43:14 +01:00
Logan McNaughton 0f607e61aa Use MacOS 26 (#689) 2026-03-24 13:38:23 +01:00
Logan McNaughton 6220a566eb Enable Win ARM build (#686)
* Enable Win ARM build

* change ss2neon source

* update sse2neon
2026-03-24 13:29:02 +01:00
Logan McNaughton 5da6a6c3ee clippy fixes (#688)
* clippy fixes

* fix fmt
2026-03-24 11:09:16 +01:00
Logan McNaughton 567dd3a79d Reset hotkey (#682)
* Start soft reset work

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
2026-03-21 19:57:27 +01:00
dependabot[bot] 4a0a80b045 Bump rustls-webpki in the cargo group across 1 directory (#685)
Bumps the cargo group with 1 update in the / directory: [rustls-webpki](https://github.com/rustls/webpki).


Updates `rustls-webpki` from 0.103.9 to 0.103.10
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.103.9...v/0.103.10)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-version: 0.103.10
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-21 10:46:18 +01:00
Logan McNaughton 74b087e77e cargo update 2026-03-20 20:39:02 +01:00
Logan McNaughton faadefe96e format cpp code 2026-03-19 19:17:23 +01:00
Logan McNaughton 72df783d8c Add CLI option to load a save state (#681)
* Add CLI option to load a save state

* more

* more

* more
2026-03-19 14:06:53 +01:00
Logan McNaughton e7dfd02e17 cargo update 2026-03-17 19:38:00 +01:00
Logan McNaughton c6703244b3 better window cleanup 2026-03-17 19:15:19 +01:00
Logan McNaughton 8291a93337 draw every 100ms in input profile creation 2026-03-17 19:09:10 +01:00
Logan McNaughton 3e177ea4be move SDL_HINT_VULKAN_LIBRARY 2026-03-16 14:58:02 +01:00
Logan McNaughton 6de1ef3548 change message in input profile 2026-03-16 14:55:27 +01:00
Logan McNaughton 73a3e6a22a Add hint for MoltenVK (#678) 2026-03-15 10:49:24 +01:00
Logan McNaughton fcceca5976 fix mac build (#676)
* fix mac build

* add freetype

* set deployment target

* test

* test

* more

* test

* more

* more

* more

* more

* test

* put back

* link CoreFoundation

* more

* more

* more
2026-03-12 12:49:59 +01:00
Logan McNaughton acf5e08b97 cargo update 2026-03-10 09:29:49 +01:00
Logan McNaughton 768fcdaf7f bump to v1.1.16 2026-03-05 21:21:47 +01:00
Logan McNaughton 1c6e23b7f2 Update parallel-rdp 2026-03-05 20:42:24 +01:00
Logan McNaughton b4ae7db3dd Update to Rust 1.94.0 (#672) 2026-03-05 20:37:50 +01:00
Logan McNaughton e750b6eed1 upload unzipped artifact (#670) 2026-02-27 19:11:00 +01:00
Logan McNaughton f8b777248a update sdl 2026-02-22 08:12:10 +01:00
Logan McNaughton 51e9b3aa55 cargo update 2026-02-15 21:41:10 +01:00
Logan McNaughton efbeaeab88 rust 1.93.1 2026-02-12 22:49:42 +01:00
Logan McNaughton f24a5e7f05 reapply cheats when loading save state 2026-02-11 16:13:36 +01:00
Logan McNaughton 55170fcca3 use Xoshiro256PlusPlus (#668) 2026-02-09 12:16:10 +01:00
Logan McNaughton b952c45e0b cargo update 2026-02-09 10:44:19 +01:00
Logan McNaughton b13f37baf8 bump to 1.1.15 2026-02-06 09:53:09 +01:00
Logan McNaughton db40ec5034 cargo update (rand) 2026-02-06 09:51:12 +01:00
Logan McNaughton cedb2d5c22 update slint 2026-02-04 18:05:03 +01:00
Logan McNaughton 22d57677cb cargo update 2026-02-03 20:49:46 +01:00
Logan McNaughton f29f497405 bump to 1.1.14 2026-01-29 21:58:40 +01:00
Logan McNaughton 0aab9f71e1 cargo update 2026-01-29 21:58:06 +01:00
Logan McNaughton 7f84eb2aff add missing gamepad button types 2026-01-26 21:44:03 +01:00
Logan McNaughton b460d55714 use SDL_HAPTIC_INFINITY 2026-01-25 13:35:59 +01:00
Logan McNaughton ceaf56c563 bump to 1.1.13 2026-01-22 21:00:26 +01:00
Logan McNaughton 5937922558 fix issue with emux_xdetect 2026-01-22 20:44:17 +01:00
Logan McNaughton e9e71a6e02 Update to rust 1.93.0 (#659) 2026-01-22 20:24:41 +01:00
Logan McNaughton 9775f103e0 flush saves to disk (#658)
* flush saves to disk

* more

* more

* more

* more

* more

* more

* more

* more
2026-01-22 20:09:13 +01:00
Logan McNaughton bf01898c6f remove extra function 2026-01-22 09:22:47 +01:00
Logan McNaughton ce3b12bba9 print message for audio volume 2026-01-21 19:01:50 +01:00
Logan McNaughton 56ff2b260c adjust audio ratios 2026-01-21 12:13:19 +01:00
Logan McNaughton 367c22877b add system for micro adjustments to audio pitch 2026-01-21 11:47:33 +01:00
Logan McNaughton befbacc5b3 remove un-needed var 2026-01-20 15:31:03 +01:00
Logan McNaughton e1a6663bb5 remove needless copy 2026-01-20 13:21:44 +01:00
Logan McNaughton c5185e60c6 tweak ai dma calculation 2026-01-20 13:19:14 +01:00
Logan McNaughton 1aa879d759 tweak audio queue formula 2026-01-20 13:05:32 +01:00
Logan McNaughton bfad71f09f Update parallel-rdp (#656)
* Update parallel-rdp

* update cargo
2026-01-19 13:32:06 +01:00
Logan McNaughton 431b787998 update rfd 2026-01-05 19:27:16 +01:00
Logan McNaughton d48e3dc106 update rfd 2026-01-05 09:54:42 +01:00
Logan McNaughton 8be1ac7740 update SDL crate (#648)
* update SDL crate

* more
2026-01-03 10:07:27 +01:00
Logan McNaughton 56d0b1b2e2 update sse2neon to v1.9.1 2026-01-02 14:41:00 +01:00
Logan McNaughton 7f63bf1fde Update sse2neon to v1.9.0 (#642) 2025-12-22 10:08:09 +01:00
Logan McNaughton 05be960358 cargo update 2025-12-21 16:23:23 +01:00
dependabot[bot] 38a5079acf Bump actions/upload-artifact from 5 to 6 (#641)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-15 06:36:17 +01:00
Logan McNaughton 144e31c7fc bump to 1.1.12 2025-12-11 22:08:08 +01:00
Logan McNaughton e7a21171f4 fix windows build (#636) 2025-12-11 21:48:28 +01:00
Logan McNaughton 413da0178e update parallel rdp 2025-12-11 17:57:59 +01:00
Logan McNaughton d8c6669da1 Update to Rust 1.92.0 (#634) 2025-12-11 17:46:50 +01:00
Logan McNaughton c6fcfddf8c some cleanup 2025-12-10 15:12:56 +01:00
Logan McNaughton 0aa5c3e63a cleanup variable 2025-12-10 12:35:22 +01:00
Logan McNaughton 6f8433138d clippy fixes (#632)
* clippy fixes

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

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

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

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

* remove anyhow

* forgive me clippy, for I have sinned
2025-11-08 21:34:35 +01:00
Logan McNaughton c4057c2f21 bump to 1.1.11 2025-11-05 15:29:56 +01:00
136 changed files with 42612 additions and 7681 deletions
+13 -2
View File
@@ -1,16 +1,27 @@
[target.'cfg(all(target_arch = "aarch64", target_os = "linux"))']
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
[target.'cfg(target_os = "windows")']
[target.'cfg(any(target_os = "windows", target_os = "macos"))']
linker = "rust-lld"
[target.'cfg(target_os = "windows")']
rustflags = ["-C", "link-arg=/STACK:8388608"]
[target.'cfg(target_arch = "x86_64")']
rustflags = ["-C", "target-cpu=x86-64-v3"]
[target.'cfg(target_arch = "aarch64")']
[target.'cfg(all(target_arch = "aarch64", target_os = "macos"))']
rustflags = [
"-C", "target-cpu=apple-m1",
"-C", "link-arg=-headerpad_max_install_names"
]
[target.'cfg(all(target_arch = "aarch64", not(target_os = "macos")))']
rustflags = ["-C", "target-cpu=cortex-a76"]
[env]
CC = "clang"
CXX = "clang++"
AR = "llvm-ar"
SDKROOT = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk"
CMAKE_POLICY_VERSION_MINIMUM = "3.5" # for freetype
+33
View File
@@ -0,0 +1,33 @@
---
name: Bug report
about: Bugs only! No feature requests
title: ''
labels: ''
assignees: ''
type: Bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Device (please complete the following information):**
- OS: [e.g. Windows 11]
- CPU: [e.g. Intel i7-1365U]
- GPU: [e.g. Nvidia 5060 Ti]
**Additional context**
Add any other context about the problem here.
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+1
View File
@@ -8,6 +8,7 @@ updates:
directory: "/"
schedule:
interval: "weekly"
# - package-ecosystem: "cargo"
# directory: "/"
# schedule:
+180 -39
View File
@@ -9,9 +9,14 @@ on:
- "v*"
pull_request:
branches: ["main"]
merge_group:
env:
CARGO_TERM_COLOR: always
RA_HARDCORE: ${{ github.ref_type == 'tag' && 'true' || 'false' }}
NETPLAY_ID: ${{ github.ref_type == 'tag' && secrets.NETPLAY_ID || 'dev' }}
BUILD_PROFILE: ${{ github.ref_type == 'tag' && 'release' || 'dev' }}
BUILD_DIR: ${{ github.ref_type == 'tag' && 'release' || 'debug' }}
jobs:
build-linux:
@@ -29,44 +34,51 @@ jobs:
arch: linux-aarch64
llvm: ARM64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
run:
echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
packages: build-essential git make pkg-config cmake ninja-build
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev
libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev
libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev
libudev-dev libthai-dev libpipewire-0.3-dev libwayland-dev
libdecor-0-dev liburing-dev libfontconfig-dev
version: ${{ matrix.target }}-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{ matrix.llvm }}.tar.xz
force-url:
https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-${{
matrix.llvm }}.tar.xz
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
cargo build --profile ${{ env.BUILD_PROFILE }} -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
env:
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64 output/gopher64-${{ matrix.arch }}
- name: Upload file
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
archive: false
build-windows:
runs-on: ${{ matrix.os }}
@@ -78,18 +90,20 @@ jobs:
target: x86_64-pc-windows-msvc
arch: windows-x86_64
llvm: win64
#- os: windows-11-arm
# target: aarch64-pc-windows-msvc
# arch: windows-aarch64
# llvm: woa64
- os: windows-11-arm
target: aarch64-pc-windows-msvc
arch: windows-aarch64
llvm: woa64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Set LLVM_VERSION
run: |
$llvm_version = rustc --version --verbose | Select-String "LLVM version" | ForEach-Object { $_.Line.Split(":")[1].Trim() }
@@ -98,25 +112,28 @@ jobs:
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm }}.exe
force-url:
https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-${{ matrix.llvm
}}.exe
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
cargo build --profile ${{ env.BUILD_PROFILE }} -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64.exe output/gopher64-${{ matrix.arch }}.exe
env:
CC: clang-cl
CXX: clang-cl
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
- name: Upload unsigned artifact
id: upload-unsigned-artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}.exe
archive: ${{ github.ref_type == 'tag' && true || false }}
- name: SignPath
if: ${{ github.ref_type == 'tag' }}
uses: signpath/github-action-submit-signing-request@v2.0
uses: signpath/github-action-submit-signing-request@v2
with:
api-token: "${{ secrets.SIGNPATH_API_TOKEN }}"
organization-id: "${{ secrets.SIGNPATH_ORG_ID }}"
@@ -124,37 +141,161 @@ jobs:
artifact-configuration-slug: "gopher64-${{ matrix.arch }}"
signing-policy-slug: "release-signing"
github-artifact-id: "${{ steps.upload-unsigned-artifact.outputs.artifact-id }}"
wait-for-completion: false
wait-for-completion: true
wait-for-completion-timeout-in-seconds: 3600
output-artifact-directory: output-signed
- name: Upload signed artifact
if: ${{ github.ref_type == 'tag' }}
uses: actions/upload-artifact@v7
with:
name: gopher64-${{ matrix.arch }}
path: output-signed/gopher64-${{ matrix.arch }}.exe
archive: false
overwrite: true
build-macos:
if: false
runs-on: macos-15
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin
arch: mac-x86_64
- target: aarch64-apple-darwin
arch: mac-aarch64
arch: macos-aarch64
llvm: ARM64
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Set LLVM_VERSION
run:
echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url:
https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-macOS-${{
matrix.llvm }}.tar.xz
- name: Build
run: |
cargo build --release -v --target=${{ matrix.target }}
mkdir output
cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }}
cargo install cargo-bundle
brew install molten-vk
cargo build --profile ${{ env.BUILD_PROFILE }} --no-default-features -v --target=${{ matrix.target }}
mv target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64 target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64-cli
cargo bundle --profile ${{ env.BUILD_PROFILE }} --target=${{ matrix.target }} --format osx
cp target/${{ matrix.target }}/${{ env.BUILD_DIR }}/gopher64-cli target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli
env:
AR: ar
NETPLAY_ID: ${{ secrets.NETPLAY_ID }}
MACOSX_DEPLOYMENT_TARGET: "15.0"
- name: Sign Bundle Dev
if: ${{ github.ref_type != 'tag' }}
run: |
codesign -f --entitlements data/macos/entitlements_dev.plist --deep --timestamp --options runtime -s - target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
- name: Import Codesign Certs
if: ${{ github.ref_type == 'tag' }}
uses: apple-actions/import-codesign-certs@v7
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATES_FILE_BASE64 }}
p12-password: ${{ secrets.APPLE_CERTIFICATES_PASSWORD }}
- name: Sign Bundle Release
if: ${{ github.ref_type == 'tag' }}
run: |
codesign -f --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app/Contents/Frameworks/libMoltenVK.dylib
codesign -f --entitlements data/macos/entitlements_child.plist --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app/Contents/MacOS/gopher64-cli
codesign -f --entitlements data/macos/entitlements_prod.plist --timestamp --options runtime -s "${{ secrets.APPLE_DEVELOPER_ID }}" target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
zip -r gopher64-${{ matrix.arch }}.zip target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
xcrun notarytool submit --wait --apple-id "${{ secrets.APPLE_ID }}" --team-id "${{ secrets.APPLE_DEVELOPER_ID }}" --password "${{ secrets.APPLE_APP_PASSWORD }}" gopher64-${{ matrix.arch }}.zip
xcrun stapler staple target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx/Gopher64.app
- name: Upload file
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: gopher64-${{ matrix.arch }}
path: output/gopher64-${{ matrix.arch }}
path: target/${{ matrix.target }}/${{ env.BUILD_DIR }}/bundle/osx
build-android:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
platform: 33
ndk: 29.0.14206865
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: x86_64-linux-android,aarch64-linux-android
rustflags: ""
cache: ${{ github.ref_type != 'tag' }}
- name: Setup Android SDK
uses: android-actions/setup-android@v4
with:
packages: "platforms;android-${{ matrix.platform }} ndk;${{ matrix.ndk }}"
- name: Decode keystore and create jks
if: ${{ github.ref_type == 'tag' }}
working-directory: android-project
run: |
echo "${{ secrets.ANDROID_KEYSTORE }}" | base64 --decode > app/keystore.jks
cat <<EOF > keystore.properties
storePassword=${{ secrets.ANDROID_STORE_PASSWORD }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
keyAlias=gopher64
storeFile=keystore.jks
EOF
- name: Build
working-directory: android-project
run: |
cargo install cargo-ndk
./gradlew assemble${{ env.BUILD_PROFILE == 'release' && 'Release' || 'Debug' }}
mkdir output
cp app/build/outputs/apk/${{ env.BUILD_DIR }}/app-${{ env.BUILD_DIR }}.apk output/gopher64-android.apk
- name: Upload file
uses: actions/upload-artifact@v7
with:
name: gopher64-android
path: android-project/output/gopher64-android.apk
archive: false
collect-artifacts:
name: Collect release artifacts
needs: [build-linux, build-windows, build-macos, build-android]
if: github.ref_type == 'tag'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all workflow artifacts
uses: actions/download-artifact@v8
with:
skip-decompress: true
merge-multiple: true
path: release-artifacts
- name: Add files to release
uses: softprops/action-gh-release@v3
with:
draft: true
files: release-artifacts/*
- name: Setup butler
uses: remarkablegames/setup-butler@v3
- name: Upload to itch.io
run: |
butler push release-artifacts/gopher64-linux-x86_64 loganmc10/gopher64:linux-x86_64 --userversion ${GITHUB_REF_NAME#v}
butler push release-artifacts/gopher64-linux-aarch64 loganmc10/gopher64:linux-aarch64 --userversion ${GITHUB_REF_NAME#v}
butler push release-artifacts/gopher64-windows-x86_64.exe loganmc10/gopher64:windows-x86_64 --userversion ${GITHUB_REF_NAME#v}
butler push release-artifacts/gopher64-windows-aarch64.exe loganmc10/gopher64:windows-aarch64 --userversion ${GITHUB_REF_NAME#v}
butler push release-artifacts/gopher64-macos-aarch64.zip loganmc10/gopher64:macos-aarch64 --userversion ${GITHUB_REF_NAME#v}
butler push release-artifacts/gopher64-android.apk loganmc10/gopher64:android --userversion ${GITHUB_REF_NAME#v}
env:
BUTLER_API_KEY: ${{ secrets.BUTLER_API_KEY }}
+24 -10
View File
@@ -5,6 +5,7 @@ on:
branches: ["main"]
pull_request:
branches: ["main"]
merge_group:
permissions:
contents: read
@@ -16,33 +17,46 @@ jobs:
lint-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
submodules: recursive
fetch-depth: 0
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
components: clippy, rustfmt
- name: Set LLVM_VERSION
run: echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut -d':' -f2 | xargs)" >> $GITHUB_ENV
run:
echo "LLVM_VERSION=$(rustc --version --verbose | grep "LLVM version" | cut
-d':' -f2 | xargs)" >> $GITHUB_ENV
- name: Install SDL Dependencies
# https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
uses: awalsh128/cache-apt-pkgs-action@v1
with:
packages: build-essential git make \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxtst-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev \
libpipewire-0.3-dev libwayland-dev libdecor-0-dev liburing-dev libfreetype-dev libfontconfig-dev
packages: build-essential git make pkg-config cmake ninja-build
gnome-desktop-testing libasound2-dev libpulse-dev libaudio-dev
libfribidi-dev libjack-dev libsndio-dev libx11-dev libxext-dev
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev
libxtst-dev libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev
libgles2-mesa-dev libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev
libudev-dev libthai-dev libpipewire-0.3-dev libwayland-dev
libdecor-0-dev liburing-dev libfontconfig-dev gettext
version: lint-1.0
- name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2
with:
version: ${{ env.LLVM_VERSION }}
force-url: https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
force-url:
https://github.com/llvm/llvm-project/releases/download/llvmorg-${{
env.LLVM_VERSION }}/LLVM-${{ env.LLVM_VERSION }}-Linux-X64.tar.xz
- name: Rustfmt Check
uses: actions-rust-lang/rustfmt@v1
- name: Lint
run: |
cargo install slint-tr-extractor
cargo clippy -- -Dwarnings
cargo clippy --no-default-features -- -Dwarnings
find -name \*.slint | sort | xargs slint-tr-extractor -o /tmp/translations.pot
msgcmp --use-untranslated /tmp/translations.pot data/translations/gopher64.pot
msgcmp --use-untranslated data/translations/gopher64.pot /tmp/translations.pot
find data/translations -name "*.po" | xargs -I {} msgcmp {} data/translations/gopher64.pot
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
checks: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
submodules: recursive
- uses: rustsec/audit-check@v2
+6 -2
View File
@@ -1,7 +1,11 @@
[submodule "parallel-rdp/parallel-rdp-standalone"]
path = parallel-rdp/parallel-rdp-standalone
url = https://github.com/gopher64/parallel-rdp-standalone.git
branch = gopher64
url = https://github.com/Themaister/parallel-rdp-standalone.git
branch = master
[submodule "src/compat/sse2neon"]
path = src/compat/sse2neon
url = https://github.com/DLTcollab/sse2neon.git
branch = master
[submodule "retroachievements/rcheevos"]
path = retroachievements/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git
Generated
+3895 -1501
View File
File diff suppressed because it is too large Load Diff
+44 -21
View File
@@ -1,49 +1,65 @@
[package]
name = "gopher64"
version = "1.1.10"
version = "1.1.32"
edition = "2024"
rust-version = "1.91"
rust-version = "1.96.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["gui"]
gui = ["dep:rfd", "dep:slint", "dep:open", "dep:slint-build"]
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
dirs = "6.0"
zip = "6.0"
governor = "0.10"
sevenz-rust2 = { version = "0.19", default-features = false }
zip = "8.0"
sevenz-rust2 = "0.21"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-big-array = "0.5"
slint = { version = "1.12", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia-vulkan"] }
open = "5.3"
sha2 = "0.10"
sdl3-sys = { version = "0.5", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.2", features = ["build-from-source-static", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
tokio = {version = "1.46", features = ["rt-multi-thread", "macros"] }
sha2 = "0.11"
tokio = {version = "1.46", features = ["rt-multi-thread", "fs", "process"] }
spin_sleep = "1.3"
encoding_rs = "0.8"
mimalloc = "0.1"
regex = "1.11"
tokio-tungstenite = "0.28"
tokio-tungstenite = { version = "0.29", features = ["native-tls-vendored"] }
matchbox_socket = "0.14"
ggrs = { version = "0.13", features = ["sync-send"] }
rustc-hash = "2.1"
futures = "0.3"
socket2 = { version = "0.6", features = ["all"] }
rand_chacha = "0.9"
rand = { version = "0.10", features = ["serde"] }
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"] }
reqwest = { version = "0.13", default-features = false, features = ["native-tls-vendored", "charset", "http2", "system-proxy", "json", "query"] }
discord-rich-presence = "1.1"
quick-xml = "0.40"
[target.'cfg(not(target_os = "android"))'.dependencies]
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "unstable-winit-030", "backend-winit", "renderer-femtovg-wgpu", "accessibility"], optional = true }
rfd = {version = "0.17", optional = true }
dirs = "6.0"
open = { version = "5.3", optional = true }
sdl3-sys = { version = "0.6", features = ["build-from-source-static"] }
sdl3-ttf-sys = { version = "0.6", features = ["build-static-vendored", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
[target.'cfg(target_os = "android")'.dependencies]
slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-android-activity-06", "accessibility"], optional = true }
jni = "0.22"
sdl3-sys = { version = "0.6", features = ["build-from-source"] }
sdl3-ttf-sys = { version = "0.6", features = ["build-from-source", "no-sdlttf-harfbuzz", "no-sdlttf-plutosvg"] }
[build-dependencies]
winresource = "0.1"
bindgen = "0.72"
slint-build = "1.12"
sdl3-src = "3.4"
slint-build = { version = "1.12", optional = true }
cc = { version = "1.2", features = ["parallel"] }
[profile.release]
panic = "abort"
lto = "thin"
strip = true
@@ -51,4 +67,11 @@ strip = true
inherits = "release"
debug = true
strip = false
panic = "unwind"
[package.metadata.bundle]
name = "Gopher64"
identifier = "io.github.gopher64.gopher64"
category = "public.app-category.games"
icon = ["data/icon/gopher64_128x128.png","data/icon/gopher64_256x256.png","data/icon/gopher64_512x512.png"]
osx_frameworks = ["/opt/homebrew/opt/molten-vk/lib/libMoltenVK.dylib"]
osx_minimum_system_version = "15.0"
+36 -8
View File
@@ -1,9 +1,31 @@
# gopher64
Gopher64 is a cross-platform N64 emulator. Some notable features:
* Netplay
* Homebrew support
* Upscaling
* CRT shader
* Emulate CPU overclocking
* Cheats
* Savestates
* RetroAchievements
## download
Windows: https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe
<a href="https://loganmc10.itch.io/gopher64"><img src="https://static.itch.io/images/badge.svg" width="200" ></a>
Linux: https://flathub.org/apps/io.github.gopher64.gopher64
Windows:
* Standalone executable: [gopher64-windows-x86_64.exe](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-windows-x86_64.exe)
MacOS:
* App Bundle: [gopher64-macos-aarch64.zip](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-macos-aarch64.zip)
* Homebrew: `brew install --cask gopher64`
Linux:
* Standalone executable: [gopher64-linux-x86_64](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-linux-x86_64)
* Flatpak: `flatpak install flathub io.github.gopher64.gopher64`
Android:
* APK: [gopher64-android.apk](https://github.com/gopher64/gopher64/releases/latest/download/gopher64-android.apk)
## wiki
@@ -11,7 +33,7 @@ https://github.com/gopher64/gopher64/wiki
## discord
https://discord.gg/9RGXq8W8JQ
[Discord invite link](https://discord.gg/9RGXq8W8JQ)
## controls
@@ -19,7 +41,9 @@ Keys are mapped according to [these defaults](https://github.com/gopher64/gopher
## netplay
Gopher64 supports netplay (online play with others) via cloud hosted servers. You can also run the server (https://github.com/gopher64/gopher64-netplay-server) yourself on a LAN.
Gopher64 supports P2P netplay (online play with others).
Please read the [guide](https://github.com/gopher64/gopher64/wiki/Netplay-Guide) before trying out netplay.
## portable mode
@@ -35,8 +59,8 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
## building and usage
1. Linux only: install the SDL3 dependencies: https://wiki.libsdl.org/SDL3/README-linux#build-dependencies
2. Install rust: https://www.rust-lang.org/tools/install
1. Linux only: [install the SDL3 dependencies](https://wiki.libsdl.org/SDL3/README-linux#build-dependencies)
2. [Install rust](https://www.rust-lang.org/tools/install)
3. `git clone --recursive https://github.com/gopher64/gopher64.git`
4. `cd gopher64`
5. `cargo build --release`
@@ -44,14 +68,18 @@ flatpak run --filesystem=host:ro io.github.gopher64.gopher64 /path/to/rom.z64
## contributing
I am very open to contributions! Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
PRs that are vibe coded and/or co-authored by AI agents (Plan/Agent mode) are not permitted. If you are going to submit a PR, it should be scoped to a single feature/improvement.
Please contact me via a GitHub issue or Discord (loganmc10) before doing substantial work on a PR.
## license
Gopher64 is licensed under the GPLv3 license. Many portions of gopher64 have been adapted from mupen64plus and/or ares. The license for mupen64plus can be found here: https://github.com/mupen64plus/mupen64plus-core/blob/master/LICENSES. The license for ares can be found here: https://github.com/ares-emulator/ares/blob/master/LICENSE.
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).
## privacy and code signing policy
Free code signing for the Windows release is provided by [SignPath.io](https://about.signpath.io), certificate by [SignPath Foundation](https://signpath.org).
During online netplay sessions, the server logs your IP address and basic session information (game title and session name) for operational purposes. No additional personal data is collected or stored.
If you enable the RetroAchievements feature, some data is sent to their systems. Please see their terms [here](https://retroachievements.org/terms).
+39
View File
@@ -0,0 +1,39 @@
# Gradle files
.gradle/
build/
jniLibs/
# Local configuration file (sdk path, etc)
local.properties
keystore.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.aab
*.apk
output-metadata.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof
app/src/main/java/org/libsdl/app/
+4
View File
@@ -0,0 +1,4 @@
set(ANDROID_PLATFORM $ENV{ANDROID_PLATFORM})
set(ANDROID_ABI $ENV{ANDROID_ABI})
set(ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES ON)
include("$ENV{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake")
+161
View File
@@ -0,0 +1,161 @@
import groovy.json.JsonSlurper
import java.util.Properties
import java.io.FileInputStream
plugins {
alias(libs.plugins.android.application)
}
kotlin {
jvmToolchain(17)
}
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
namespace = "io.github.gopher64.gopher64"
compileSdk {
version = release(36) {
minorApiLevel = 1
}
}
ndkVersion = "29.0.14206865"
defaultConfig {
applicationId = "io.github.gopher64.gopher64"
minSdk = 33
targetSdk = 36
versionCode = semverToVersionCode(cargoPackageVersion())
versionName = cargoPackageVersion()
ndk {
abiFilters.addAll(listOf("arm64-v8a", "x86_64"))
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
if (keystorePropertiesFile.exists()) {
create("release") {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes {
release {
if (keystorePropertiesFile.exists()) {
signingConfig = signingConfigs["release"]
}
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
packaging {
jniLibs {
excludes.add("lib/**/libsevenz_rust2*.so")
}
}
}
@Suppress("UNCHECKED_CAST")
fun cargoPackageVersion(packageName: String = "gopher64"): String {
val repoRoot = rootDir.parentFile
val jsonText = providers.exec {
workingDir(repoRoot)
commandLine("cargo", "metadata", "--format-version", "1", "--no-deps")
}.standardOutput.asText.get()
val parsedJson = JsonSlurper().parseText(jsonText) as Map<String, Any>
val packages = parsedJson["packages"] as List<Map<String, Any>>
return packages.first { it["name"] == packageName }["version"] as String
}
fun semverToVersionCode(version: String): Int {
val parts = version.substringBefore('-').split(".")
val (major, minor, patch) = Triple(parts[0].toInt(), parts[1].toInt(), parts[2].toInt())
return major * 1_000_000 + minor * 1_000 + patch
}
val ndkBuild = tasks.register<Exec>("ndkBuild") {
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
workingDir = rootDir.parentFile
val toolchainPath = "$rootDir/android.toolchain.cmake"
environment("CMAKE_TOOLCHAIN_FILE", toolchainPath)
var minSdk = android.defaultConfig.minSdk
var ndkDir = androidComponents.sdkComponents.ndkDirectory.get().asFile.absolutePath
environment("ANDROID_NDK_HOME", "$ndkDir")
environment("ANDROID_NDK_ROOT", "$ndkDir")
environment("LIBCLANG_PATH", "$ndkDir/toolchains/llvm/prebuilt/linux-x86_64/musl/lib")
val jniType = if (isRelease) "release" else "debug"
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs"
commandLine(
"cargo", "ndk",
"--link-libcxx-shared",
"-P", "$minSdk",
"-t", "arm64-v8a",
"-t", "x86_64",
"-o", jniLibsFolder,
"build", "--lib",
"--profile", if (isRelease) "release" else "dev",
)
}
val sdlLibsArm64 = tasks.register<Copy>("sdlLibsArm64") {
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
val jniType = if (isRelease) "release" else "debug"
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs/arm64-v8a"
from("$rootDir/../target/aarch64-linux-android/$jniType")
into(jniLibsFolder)
include("libSDL*")
}
val sdlLibsX64 = tasks.register<Copy>("sdlLibsX64") {
val isRelease = gradle.startParameter.taskNames.any { it.endsWith("Release", ignoreCase = true) }
val jniType = if (isRelease) "release" else "debug"
val jniLibsFolder = "$rootDir/app/src/$jniType/jniLibs/x86_64"
from("$rootDir/../target/x86_64-linux-android/$jniType")
into(jniLibsFolder)
include("libSDL*")
}
tasks.named("preBuild") {
dependsOn(sdlLibsArm64)
dependsOn(sdlLibsX64)
}
tasks.named("sdlLibsArm64") {
dependsOn(ndkBuild)
}
tasks.named("sdlLibsX64") {
dependsOn(ndkBuild)
}
dependencies {
implementation(libs.androidx.appcompat)
implementation(libs.androidx.core.ktx)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
}
+82
View File
@@ -0,0 +1,82 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLActivity {
java.lang.String nativeGetHint(java.lang.String); # Java-side doesn't use this, so it gets minified, but C-side still tries to register it
java.lang.String clipboardGetText();
boolean clipboardHasText();
void clipboardSetText(java.lang.String);
int createCustomCursor(int[], int, int, int, int);
void destroyCustomCursor(int);
android.app.Activity getContext();
boolean getManifestEnvironmentVariables();
android.view.Surface getNativeSurface();
void initTouch();
boolean isAndroidTV();
boolean isChromebook();
boolean isDeXMode();
boolean isTablet();
void manualBackButton();
int messageboxShowMessageBox(int, java.lang.String, java.lang.String, int[], int[], java.lang.String[], int[]);
void minimizeWindow();
boolean openURL(java.lang.String);
void requestPermission(java.lang.String, int);
boolean showToast(java.lang.String, int, int, int, int);
boolean sendMessage(int, int);
boolean setActivityTitle(java.lang.String);
boolean setCustomCursor(int);
void setOrientation(int, int, boolean, java.lang.String);
boolean setRelativeMouseEnabled(boolean);
boolean setSystemCursor(int);
void setWindowStyle(boolean);
boolean shouldMinimizeOnFocusLoss();
boolean showTextInput(int, int, int, int, int);
boolean supportsRelativeMouse();
int openFileDescriptor(java.lang.String, java.lang.String);
boolean showFileDialog(java.lang.String[], boolean, boolean, int);
java.lang.String getPreferredLocales();
java.lang.String formatLocale(java.util.Locale);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.HIDDeviceManager {
void closeDevice(int);
boolean initialize(boolean, boolean);
boolean openDevice(int);
boolean readReport(int, byte[], boolean);
int writeReport(int, byte[], boolean);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLAudioManager {
void registerAudioDeviceCallback();
void unregisterAudioDeviceCallback();
void audioSetThreadPriority(boolean, int);
}
-keep,includedescriptorclasses,allowoptimization class org.libsdl.app.SDLControllerManager {
void joystickSetSensorsEnabled(int, boolean);
void pollInputDevices();
void joystickSetLED(int, int, int, int);
void pollHapticDevices();
void hapticRun(int, float, int);
void hapticRumble(int, float, float, int);
void hapticStop(int);
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.vulkan.version" android:required="true"
android:version="0x00401000" />
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
<uses-feature
android:name="android.hardware.gamepad"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@mipmap/ic_launcher"
android:label="Gopher64"
android:appCategory="game"
android:hardwareAccelerated="true">
<activity android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:name=".SlintActivity" android:exported="true">
<meta-data android:name="android.app.lib_name" android:value="gopher64" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".N64Activity"
android:launchMode="singleTop"
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
android:preferMinimalPostProcessing="true"
android:exported="false">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,48 @@
package io.github.gopher64.gopher64
import android.content.Intent
import android.os.PowerManager
import android.content.Context
import android.os.Bundle
import android.util.Log
import org.libsdl.app.SDLActivity
class N64Activity : SDLActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val powerManager = getContext().getSystemService(Context.POWER_SERVICE) as PowerManager
if (powerManager.isSustainedPerformanceModeSupported) {
Log.v("SDL", "Enabling sustained performance mode")
window.setSustainedPerformanceMode(true)
} else {
Log.v("SDL", "Sustained performance mode not supported")
}
}
override fun getLibraries(): Array<String> = arrayOf(
"SDL3",
"SDL3_ttf",
"gopher64"
)
override fun getMainFunction(): String = "gopher64_sdl_main"
override fun getArguments(): Array<String> {
val intent = intent ?: return super.getArguments()
val args = intent.getStringArrayExtra("args") ?: return super.getArguments()
val dataIntent = Intent()
val file_path = intent.getStringExtra("file_path")
if (file_path != null) {
dataIntent.putExtra("file_path", file_path)
}
val cheats_path = intent.getStringExtra("cheats_path")
if (cheats_path != null) {
dataIntent.putExtra("cheats_path", cheats_path)
}
setResult(RESULT_OK, dataIntent)
return args
}
}
@@ -0,0 +1,19 @@
package io.github.gopher64.gopher64
import android.app.NativeActivity
import android.content.Intent
class SlintActivity : NativeActivity() {
companion object {
init {
System.loadLibrary("gopher64")
}
}
private external fun nativeOnActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
nativeOnActivityResult(requestCode, resultCode, data)
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#419567</color>
</resources>
+4
View File
@@ -0,0 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}
+15
View File
@@ -0,0 +1,15 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/73bcfb608d1fde9fb62e462f834a3299/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/846ee0d876d26a26f37aa1ce8de73224/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/ec7520a1e057cd116f9544c42142a16b/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/4c4f879899012ff0a8b2e2117df03b0e/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9482ddec596298c84656d31d16652665/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/39701d92e1756bb2f141eb67cd4c660e/redirect
toolchainVersion=21
+20
View File
@@ -0,0 +1,20 @@
[versions]
agp = "9.2.1"
coreKtx = "1.18.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
appcompat = "1.7.1"
material = "1.14.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Binary file not shown.
@@ -0,0 +1,9 @@
#Thu May 21 08:40:22 CEST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+26
View File
@@ -0,0 +1,26 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Gopher64"
include(":app")
+151 -17
View File
@@ -1,9 +1,16 @@
fn main() {
println!("cargo::rerun-if-changed=parallel-rdp");
println!("cargo::rerun-if-changed=retroachievements");
println!("cargo::rerun-if-changed=src/compat");
println!("cargo::rerun-if-changed=data/translations");
println!("cargo::rerun-if-changed=data/ui");
let slint_config = slint_build::CompilerConfiguration::new().with_style("cosmic".into());
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
#[cfg(feature = "gui")]
{
let slint_config = slint_build::CompilerConfiguration::new()
.with_bundled_translations("data/translations");
slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap();
}
let mut simd_build = cc::Build::new();
let mut volk_build = cc::Build::new();
@@ -14,7 +21,7 @@ fn main() {
let mut rdp_build = cc::Build::new();
rdp_build
.cpp(true)
.std("c++23")
.std("c++20")
.flag("-Wno-unused-parameter")
.flag("-Wno-missing-field-initializers")
.file("parallel-rdp/parallel-rdp-standalone/parallel-rdp/command_ring.cpp")
@@ -36,7 +43,6 @@ fn main() {
.file("parallel-rdp/parallel-rdp-standalone/vulkan/image.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/indirect_layout.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/memory_allocator.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_cache.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/pipeline_event.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/query_pool.cpp")
.file("parallel-rdp/parallel-rdp-standalone/vulkan/render_pass.cpp")
@@ -54,7 +60,6 @@ fn main() {
.file("parallel-rdp/parallel-rdp-standalone/util/timeline_trace_file.cpp")
.file("parallel-rdp/parallel-rdp-standalone/util/environment.cpp")
.file("parallel-rdp/parallel-rdp-standalone/util/thread_name.cpp")
.file("parallel-rdp/parallel-rdp-standalone/util/slab_allocator.cpp")
.file("parallel-rdp/interface.cpp")
.file("parallel-rdp/wsi_platform.cpp")
.include("parallel-rdp/parallel-rdp-standalone/parallel-rdp")
@@ -63,19 +68,55 @@ fn main() {
.include("parallel-rdp/parallel-rdp-standalone/vulkan-headers/include")
.include("parallel-rdp/parallel-rdp-standalone/util")
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").to_owned().unwrap())
.join("include"),
std::path::PathBuf::from(std::env::var("DEP_SDL3_OUT_DIR").unwrap()).join("include"),
)
.include(
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").to_owned().unwrap())
std::path::PathBuf::from(std::env::var("DEP_SDL3_TTF_OUT_DIR").unwrap())
.join("include"),
);
let mut retroachievements_build = cc::Build::new();
retroachievements_build
.flag("-Wno-unused-parameter")
.include("retroachievements/rcheevos/include")
.flag("-DRC_CLIENT_SUPPORTS_HASH")
.file("retroachievements/rcheevos/src/rc_client.c")
.file("retroachievements/rcheevos/src/rc_compat.c")
.file("retroachievements/rcheevos/src/rc_util.c")
.file("retroachievements/rcheevos/src/rcheevos/alloc.c")
.file("retroachievements/rcheevos/src/rcheevos/condition.c")
.file("retroachievements/rcheevos/src/rcheevos/condset.c")
.file("retroachievements/rcheevos/src/rcheevos/consoleinfo.c")
.file("retroachievements/rcheevos/src/rcheevos/format.c")
.file("retroachievements/rcheevos/src/rcheevos/lboard.c")
.file("retroachievements/rcheevos/src/rcheevos/memref.c")
.file("retroachievements/rcheevos/src/rcheevos/operand.c")
.file("retroachievements/rcheevos/src/rcheevos/richpresence.c")
.file("retroachievements/rcheevos/src/rcheevos/runtime.c")
.file("retroachievements/rcheevos/src/rcheevos/runtime_progress.c")
.file("retroachievements/rcheevos/src/rcheevos/trigger.c")
.file("retroachievements/rcheevos/src/rcheevos/value.c")
.file("retroachievements/rcheevos/src/rapi/rc_api_common.c")
.file("retroachievements/rcheevos/src/rapi/rc_api_info.c")
.file("retroachievements/rcheevos/src/rapi/rc_api_runtime.c")
.file("retroachievements/rcheevos/src/rapi/rc_api_user.c")
.file("retroachievements/rcheevos/src/rhash/aes.c")
.file("retroachievements/rcheevos/src/rhash/cdreader.c")
.file("retroachievements/rcheevos/src/rhash/md5.c")
.file("retroachievements/rcheevos/src/rhash/hash.c")
.file("retroachievements/rcheevos/src/rhash/hash_disc.c")
.file("retroachievements/rcheevos/src/rhash/hash_encrypted.c")
.file("retroachievements/rcheevos/src/rhash/hash_rom.c")
.file("retroachievements/rcheevos/src/rhash/hash_zip.c")
.file("retroachievements/retroachievements.c");
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let opt_flag = if arch == "x86_64" {
"-march=x86-64-v3"
} else if arch == "aarch64" {
} else if arch == "aarch64" && os == "macos" {
"-march=armv8.4-a"
} else if arch == "aarch64" && os != "macos" {
"-march=armv8.2-a"
} else {
panic!("unknown arch")
@@ -84,6 +125,7 @@ fn main() {
volk_build.flag(opt_flag);
rdp_build.flag(opt_flag);
simd_build.flag(opt_flag);
retroachievements_build.flag(opt_flag);
if os == "windows" {
volk_build.flag("-DVK_USE_PLATFORM_WIN32_KHR");
@@ -93,17 +135,63 @@ fn main() {
.set_icon("data/icon/icon.ico")
.compile()
.unwrap();
} else if os == "macos" {
let output = std::process::Command::new("clang")
.args(["--print-runtime-dir"])
.output()
.unwrap();
let runtime_dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
println!("cargo:rustc-link-search=native={}", runtime_dir);
println!("cargo:rustc-link-lib=static=clang_rt.osx");
}
volk_build.flag("-flto=thin");
rdp_build.flag("-flto=thin");
simd_build.flag("-flto=thin");
retroachievements_build.flag("-flto=thin");
volk_build.compile("volk");
rdp_build.compile("parallel-rdp");
retroachievements_build.compile("retroachievements");
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
let mut retroachievements_builder = bindgen::Builder::default()
.header("retroachievements/retroachievements.h")
.allowlist_function("ra_init_client")
.allowlist_function("ra_welcome")
.allowlist_function("ra_shutdown_client")
.allowlist_function("ra_get_hardcore")
.allowlist_function("ra_load_game")
.allowlist_function("ra_unload_game")
.allowlist_function("ra_set_rdram")
.allowlist_function("ra_do_frame")
.allowlist_function("ra_do_idle")
.allowlist_function("ra_http_callback")
.allowlist_function("ra_login_user")
.allowlist_function("ra_login_token_user")
.allowlist_function("ra_state_size")
.allowlist_function("ra_save_state")
.allowlist_function("ra_load_state")
.allowlist_function("ra_get_rich_presence");
if cfg!(feature = "gui") {
retroachievements_builder = retroachievements_builder
.allowlist_function("ra_logout_user")
.allowlist_function("ra_get_username");
}
let retroachievements_bindings = retroachievements_builder
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
retroachievements_bindings
.write_to_file(out_path.join("retroachievements_bindings.rs"))
.expect("Couldn't write bindings!");
let parallel_bindings = bindgen::Builder::default()
.header("parallel-rdp/interface.hpp")
.allowlist_function("rdp_init")
@@ -114,11 +202,13 @@ fn main() {
.allowlist_function("rdp_process_commands")
.allowlist_function("rdp_onscreen_message")
.allowlist_function("rdp_check_callback")
.allowlist_function("rdp_new_processor")
.allowlist_function("rdp_check_framebuffers")
.allowlist_function("rdp_state_size")
.allowlist_function("rdp_save_state")
.allowlist_function("rdp_load_state")
.allowlist_function("rdp_idle")
.allowlist_function("rdp_set_fps")
.allowlist_function("get_joystick_event")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
@@ -185,16 +275,60 @@ fn main() {
.compile("simd");
}
let git_output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.unwrap();
let mut git_output = std::process::Command::new("git");
git_output.args(["describe", "--dirty"]);
let git_hash = String::from_utf8(git_output.stdout).unwrap();
println!("cargo:rustc-env=GIT_HASH={git_hash}");
let git_describe = if let Ok(git_output) = git_output.output()
&& git_output.status.success()
{
String::from_utf8(git_output.stdout).unwrap()
} else if let Ok(git_output) = git_output.args(["--always"]).output()
&& git_output.status.success()
{
format!(
"v{}-{}",
env!("CARGO_PKG_VERSION"),
String::from_utf8(git_output.stdout).unwrap()
)
} else {
panic!("Failed to get git describe");
};
println!("cargo:rustc-env=GIT_DESCRIBE={git_describe}");
println!("cargo:rerun-if-env-changed=NETPLAY_ID");
let netplay_id = std::env::var("NETPLAY_ID").unwrap_or("gopher64".to_string());
println!("cargo:rustc-env=NETPLAY_ID={netplay_id}");
println!("cargo:rustc-env=N64_STACK_SIZE={}", 8 * 1024 * 1024);
println!("cargo:rerun-if-env-changed=RA_HARDCORE");
println!("cargo:rustc-check-cfg=cfg(ra_hardcore_enabled)");
if let Ok(ra_hardcore) = std::env::var("RA_HARDCORE")
&& ra_hardcore.to_lowercase() == "true"
{
println!("cargo:rustc-cfg=ra_hardcore_enabled");
}
if os == "android" {
copy_dir_all(
std::path::Path::new(sdl3_src::SOURCE_DIR)
.join("android-project/app/src/main/java/org/libsdl/app"),
"android-project/app/src/main/java/org/libsdl/app",
)
.unwrap();
}
}
fn copy_dir_all(
src: impl AsRef<std::path::Path>,
dst: impl AsRef<std::path::Path>,
) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
Binary file not shown.
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.inherit</key>
<true/>
</dict>
</plist>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
+26
View File
@@ -0,0 +1,26 @@
# Adding translations to Gopher64
1. Copy and modify the `gopher64.pot` file in this directory with your translations.
2. Rename it to `gopher64.po` and put it here:
`data/translations/<lang>/LC_MESSAGES/gopher64.po`
See [here](https://docs.slint.dev/latest/docs/slint/guide/development/translations/#translating-strings) for more details on creating a .po file. I recommend using a program like [poedit](https://poedit.com) to create the .po file from the template.
---
For example, in the .pot file you will see:
```
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr ""
```
A translation will look something like this:
```
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Nova versão disponível! Clique aqui para baixar"
```
The only line you modify is the `msgstr`. The `msgctxt` and `msgid` need to remain unchanged.
@@ -0,0 +1,620 @@
# gopher64 - German Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Neue Version verfügbar! Zum Herunterladen hier klicken"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "Über"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Discord-Server"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Quellcode"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Spenden (über Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Spenden (über GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Lokales Spiel"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Controller-Konfiguration"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Einstellungen"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "Über"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "ROM auswählen"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Cheats für alle ROMs löschen"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "Keine Cheats verfügbar"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Aktiviert"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Profil"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Controller-Konfiguration"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Eingabeprofil konfigurieren"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Controller-Einstellungen"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Controller-Zuweisung"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Eingabeprofil erstellen/bearbeiten"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Profilname zum Erstellen oder Bearbeiten eingeben"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Deadzone Größe in Prozent"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "DirectInput verwenden"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Assistent zur Profilerstellung starten"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Abbrechen"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Lokales Spiel"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "ROM öffnen"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Speicherordner öffnen"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "Zuletzt verwendete ROMs"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Netplay: Sitzung erstellen"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Sitzungsname:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Sitzungsname eingeben"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Passwort (optional):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Sitzungspasswort eingeben"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Spielername:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Spielername eingeben"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "ROM auswählen"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"Für dieses Spiel sind Cheats aktiviert. Cheats werden mit anderen Spielern "
"synchronisiert."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Sitzung erstellen"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Abbrechen"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Netplay: Sitzung beitreten"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Spielername (erforderlich):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Spielername eingeben"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Passwort (falls erforderlich)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "ROM auswählen"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Sitzungsliste wird aktualisiert, bitte warten …"
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Auf eine Sitzung klicken um diese auszuwählen"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr "Keine Sitzungen verfügbar. Bitte aktualisieren, um erneut zu prüfen."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Sitzungsname"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Spielname"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Passwort"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Sitzungsliste aktualisieren"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Sitzung beitreten"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Abbrechen"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Sitzung erstellen"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Sitzung beitreten"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Netplay-Discord-Kanal"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Feedback geben"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Netplay: Warteraum"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Sitzungsname:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Spielname:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Spieler 1:"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Spieler 2:"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Spieler 3:"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Spieler 4:"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Chatnachricht eingeben"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Nachricht senden"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Eine zu niedrige Eingabeverzögerung führt zu Ruckeln im Spiel.\n"
"Sie können die Eingabeverzögerung im Spiel mit Alt+[ verringern und mit "
"Alt+] erhöhen."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Lokale Eingabeverzögerung:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Empfohlen: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Spiel starten"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "Warten auf P2P-Mesh "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Abbrechen"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "Derzeit nicht angemeldet"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Aktiviert"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Hardcore-Modus"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Challenge Tipps"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Bestenlisten-Tracker"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Rich Presence für Discord"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Profil anzeigen"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Benutzername"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Passwort"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Anmelden"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Unterstützte Spiele anzeigen"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Einstellungen"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Spiel"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Auflösung:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "SSAA-Herunterskalierung"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Pixelgenaue Skalierung"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Vollbild"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "16:9 (gestreckt)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "VSync"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "CRT-Shader anwenden"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "N64-CPU übertakten (kann Fehler verursachen)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Expansion Pak deaktivieren"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Rewind aktivieren"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "UNFLoader emulieren"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "VRU emulieren"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Benutzeroberfläche"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Thema:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "System"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Dunkel"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Hell"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "Was möchten Sie sagen?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Server:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Eigene Server-URL eingeben"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "Eigene Server-URL (optional):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Server"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Ping:"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "Server wird erstellt, bitte warten … Dies kann etwa 30 Sekunden dauern."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Port"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Controller"
@@ -0,0 +1,620 @@
# gopher64 - Spanish Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: Gemini\n"
"Language-Team: Spanish (Spain)\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "¡Nueva versión disponible! Pulsa aquí para descargar"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "Acerca de"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Servidor de Discord"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Código fuente"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Donar (a través de Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Donar (a través de GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Juego local"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Config. del mando"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Trucos"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Ajustes"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "Acerca de"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Trucos"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "Seleccionar ROM"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Borrar trucos de todas las ROMs"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "No hay trucos disponibles"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Activado"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Perfil"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Configuración del mando"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Configurar perfil de entrada"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Ajustes del mando"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Asignación del mando"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Crear/Modificar perfil de entrada"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Introduce el nombre del perfil para crear o modificar"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Porcentaje de zona muerta"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "Usar DirectInput"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Iniciar asistente de creación de perfil"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Juego local"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "Abrir ROM"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Abrir carpeta de guardados"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "ROMs recientes"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Netplay: Crear sesión"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Nombre de la sesión:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Introduce el nombre de la sesión"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Contraseña (Opcional):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Introduce la contraseña"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Nombre del jugador:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Introduce tu nombre"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "Seleccionar ROM"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"Este juego tiene trucos activados. Los trucos se sincronizarán con los otros "
"jugadores."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Crear sesión"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Netplay: Unirse a la sesión"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Nombre de jugador (requerido):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Introduce tu nombre"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Contraseña (si es necesaria)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "Seleccionar ROM"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Actualizando lista de sesiones, espera..."
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Pulsa una sesión para seleccionarla"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr "No hay sesiones disponibles. Actualiza para comprobar de nuevo."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Nombre de la sesión"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Nombre del juego"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Contraseña"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Trucos"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Actualizar lista"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Unirse a la sesión"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Crear sesión"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Unirse a la sesión"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Canal de Discord de Netplay"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Enviar comentarios"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Netplay: Sala de espera"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Nombre de la sesión:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Nombre del juego:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Jugador 1:"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Jugador 2:"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Jugador 3:"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Jugador 4:"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Escribe un mensaje en el chat"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Enviar mensaje"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Configurar un retardo de entrada demasiado bajo hará que el juego se "
"ralentice.\n"
"Puedes reducir el retardo de entrada en el juego pulsando Alt+[ y aumentarlo "
"con Alt+]."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Retardo de entrada local:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Recomendado: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Iniciar juego"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "Esperando malla P2P "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "Sesión no iniciada"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Activado"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Modo Hardcore"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Indicadores de desafío"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Indicadores de tabla de clasificación"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Rich Presence en Discord"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Ver perfil"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Usuario"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Contraseña"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Entrar"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Ver juegos compatibles"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Ajustes"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Juego"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Resolución:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "Reducción de escala SSAA"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Escalado por enteros"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Pantalla completa"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "Pantalla ancha (estirado)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "Sincronización vertical (VSync)"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "Aplicar sombreador CRT"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "Overclock CPU N64 (puede causar errores)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Desactivar Expansion Pak"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Activar rebobinado"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "Emular UNFLoader"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "Emular VRU"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Interfaz"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Tema:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "Sistema"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Oscuro"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Claro"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "¿Qué te gustaría decir?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Servidor:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Introduce la URL de servidor personalizado"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "URL de servidor personalizado (opcional):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Servidor"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Latencia (Ping):"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr "Creando servidor, espera... Puede tardar unos 30 segundos."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Puerto"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Mando"
@@ -0,0 +1,622 @@
# gopher64 - French (France) Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: \n"
"Language-Team: French (France)\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Nouvelle version disponible ! Cliquez ici pour télécharger"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "À propos"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Serveur Discord"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Code source"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Faire un don (via Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Faire un don (via GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Jeu local"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Configuration manette"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Triche"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Paramètres"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "À propos"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Triche"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "Sélectionner une ROM"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Effacer la triche pour toutes les ROMs"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "Aucune triche disponible"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Activé"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Profil"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Configuration de la manette"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Configurer le profil d'entrée"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Paramètres de la manette"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Attribution de la manette"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Créer / modifier le profil d'entrée"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Saisir le nom du profil à créer ou à modifier"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Pourcentage de zone morte"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "Utiliser DirectInput"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Lancer l'assistant de création de profil"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Annuler"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Jeu local"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "Ouvrir une ROM"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Ouvrir le dossier des sauvegardes"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "ROMs récentes"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Netplay : créer une session"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Nom de la session :"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Saisir le nom de la session"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Mot de passe (facultatif) :"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Saisir le mot de passe de la session"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Nom du joueur :"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Saisir le nom du joueur"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "Sélectionner une ROM"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"La triche est activée pour ce jeu. Elle sera synchronisée avec les autres "
"joueurs."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Créer la session"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Annuler"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Netplay : rejoindre une session"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Nom du joueur (obligatoire) :"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Saisir le nom du joueur"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Mot de passe (si requis)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "Sélectionner une ROM"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Actualisation de la liste des sessions, veuillez patienter…"
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Cliquez sur une session ci-dessous pour la sélectionner"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr ""
"Aucune session disponible. Veuillez actualiser pour vérifier à nouveau."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Nom de la session"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Nom du jeu"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Mot de passe"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Triche"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Actualiser la liste"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Rejoindre la session"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Annuler"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Créer une session"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Rejoindre une session"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Salon Discord Netplay"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Donner votre avis"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Netplay : salle d'attente"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Nom de la session :"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Nom du jeu :"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Joueur 1 :"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Joueur 2 :"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Joueur 3 :"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Joueur 4 :"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Saisir un message"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Envoyer"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Régler le délai d'entrée trop bas provoquera des ralentissements.\n"
"Vous pouvez diminuer le délai d'entrée en jeu avec Alt+[ et l'augmenter avec "
"Alt+]."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Délai d'entrée local :"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Recommandé : "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Lancer la partie"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "En attente du maillage P2P "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Annuler"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "Non connecté"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Activé"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Mode hardcore"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Indicateurs de défi"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Suivi des classements"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Rich Presence Discord"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Voir le profil"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Nom d'utilisateur"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Mot de passe"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Connexion"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Voir les jeux pris en charge"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Paramètres"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Jeu"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Résolution :"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "Réduction SSAA"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Mise à l'échelle par entiers"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Plein écran"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "Écran large (étiré)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "Synchro verticale"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "Appliquer le filtre CRT"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "Surcharger le CPU N64 (risque de bugs)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Désactiver l'Expansion Pak"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Activer le rembobinage"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "Émuler UNFLoader"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "Émuler le VRU"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Interface"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Thème :"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "Système"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Sombre"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Clair"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "Que souhaitez-vous dire ?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Serveur :"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Saisir l'URL personnalisée du serveur"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "URL personnalisée du serveur (facultatif) :"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Serveur"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Ping :"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "Création du serveur, veuillez patienter… Cela peut prendre environ 30 "
#~ "secondes."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Port"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Manette"
+581
View File
@@ -0,0 +1,581 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr ""
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr ""
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr ""
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr ""
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr ""
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr ""
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr ""
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr ""
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr ""
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr ""
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr ""
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr ""
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr ""
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr ""
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr ""
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr ""
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr ""
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr ""
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr ""
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr ""
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr ""
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr ""
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr ""
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr ""
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr ""
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr ""
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr ""
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr ""
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr ""
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr ""
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr ""
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr ""
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr ""
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr ""
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr ""
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr ""
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr ""
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr ""
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr ""
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr ""
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr ""
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr ""
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr ""
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr ""
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr ""
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr ""
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr ""
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr ""
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr ""
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr ""
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr ""
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr ""
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr ""
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr ""
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr ""
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr ""
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr ""
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr ""
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr ""
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr ""
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr ""
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr ""
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr ""
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr ""
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr ""
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr ""
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr ""
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr ""
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\nYou can lower "
"the input delay in-game by pressing Alt+[ and raise it with Alt+]."
msgstr ""
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr ""
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr ""
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr ""
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr ""
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr ""
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr ""
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr ""
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr ""
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr ""
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr ""
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr ""
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr ""
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr ""
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr ""
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr ""
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr ""
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr ""
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr ""
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr ""
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr ""
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr ""
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr ""
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr ""
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr ""
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr ""
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr ""
@@ -0,0 +1,618 @@
# gopher64 - Japanese Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "新しいバージョンがあります。クリックしてダウンロード"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "バージョン情報"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Discordサーバー"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "ソースコード"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Patreonで寄付"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "GitHubで寄付"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "ローカルゲーム"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "コントローラー設定"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "ネットプレイ"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "チート"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "設定"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "バージョン情報"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "チート"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "ROMを選択"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "すべてのROMのチートをクリア"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "利用できるチートはありません"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "有効"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "プロファイル"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "コントローラー設定"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "入力プロファイルを設定"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "コントローラー詳細設定"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "コントローラー割り当て"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "入力プロファイルの作成/変更"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "作成または変更するプロファイル名を入力"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "デッドゾーン(%)"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "DirectInputを使用"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "プロファイル作成ウィザードを開始"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "キャンセル"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "ローカルゲーム"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "ROMを開く"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "セーブフォルダーを開く"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "最近使ったROM"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "ネットプレイ:セッション作成"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "セッション名:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "セッション名を入力"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "パスワード(任意):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "セッションのパスワードを入力"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "プレイヤー名:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "プレイヤー名を入力"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "ROMを選択"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr "このゲームではチートが有効です。チートは他のプレイヤーと同期されます。"
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "セッションを作成"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "キャンセル"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "ネットプレイ:セッションに参加"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "プレイヤー名(必須):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "プレイヤー名を入力"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "パスワード(必要な場合)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "ROMを選択"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "セッション一覧を更新しています。しばらくお待ちください…"
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "下の一覧からセッションをクリックして選択"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr "利用できるセッションがありません。更新して再度確認してください。"
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "セッション名"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "ゲーム名"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "パスワード"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "チート"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "セッション一覧を更新"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "セッションに参加"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "キャンセル"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "ネットプレイ"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "セッションを作成"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "セッションに参加"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "ネットプレイ用Discordチャンネル"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "フィードバックを送る"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "ネットプレイ:待機室"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "セッション名:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "ゲーム名:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "プレイヤー1"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "プレイヤー2"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "プレイヤー3"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "プレイヤー4"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "チャットメッセージを入力"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "送信"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"入力遅延を低く設定しすぎるとゲームが遅延します。\n"
"Alt+[ でゲーム内の入力遅延を下げ、Alt+] で上げることができます。"
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "ローカル入力遅延:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "推奨: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "ゲーム開始"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "P2Pメッシュ接続待機中 "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "キャンセル"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "ログインしていません"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "有効"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "ハードコアモード"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "チャレンジ表示"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "リーダーボード追跡"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Discord のリッチプレゼンス"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "プロフィールを表示"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "ユーザー名"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "パスワード"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "ログイン"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "対応ゲームを表示"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "設定"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "ゲーム"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "解像度:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "SSAAダウンスケール"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "整数倍スケーリング"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "全画面表示"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "ワイド画面(伸ばす)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "垂直同期(VSync"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "CRTシェーダーを適用"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "N64のCPUをオーバークロック(不具合の原因になることがあります)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "拡張パックを無効にする"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "巻き戻しを有効にする"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "UNFLoaderをエミュレート"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "VRUをエミュレート"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "UI"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "テーマ:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "システム"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "ダーク"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "ライト"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "何と言いますか?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "サーバー:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "カスタムサーバーURLを入力"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "カスタムサーバーURL(任意):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "サーバー"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Ping"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "サーバーを作成しています。しばらくお待ちください… 最大約30秒かかることがあ"
#~ "ります。"
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "ポート"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "コントローラー"
@@ -0,0 +1,620 @@
# gopher64 - Dutch (Netherlands) Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: \n"
"Language-Team: Dutch (Netherlands)\n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Nieuwe versie beschikbaar! Klik hier om te downloaden"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "Over"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Discord-server"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Broncode"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Doneren (via Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Doneren (via GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Lokaal spel"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Controllerconfiguratie"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Instellingen"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "Over"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "ROM selecteren"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Cheats wissen voor alle ROM's"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "Geen cheats beschikbaar"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Ingeschakeld"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Profiel"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Controllerconfiguratie"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Invoerprofiel configureren"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Controllerinstellingen"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Controllertoewijzing"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Invoerprofiel maken/wijzigen"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Profielnaam invoeren om aan te maken of te wijzigen"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Dode-zonepercentage"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "DirectInput gebruiken"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Wizard voor profiel maken starten"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Annuleren"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Lokaal spel"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "ROM openen"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Map met opslagen openen"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "Recent gebruikte ROM's"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Netplay: sessie aanmaken"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Sessienaam:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Sessienaam invoeren"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Wachtwoord (optioneel):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Sessiewachtwoord invoeren"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Spelernaam:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Spelernaam invoeren"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "ROM selecteren"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"Voor dit spel zijn cheats ingeschakeld. Cheats worden gesynchroniseerd met "
"andere spelers."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Sessie aanmaken"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Annuleren"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Netplay: deelnemen aan sessie"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Spelernaam (verplicht):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Spelernaam invoeren"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Wachtwoord (indien vereist)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "ROM selecteren"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Sessielijst wordt vernieuwd, even geduld…"
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Klik hieronder op een sessie om die te selecteren"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr "Geen sessies beschikbaar. Vernieuw om opnieuw te controleren."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Sessienaam"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Spelnaam"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Wachtwoord"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Cheats"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Sessielijst vernieuwen"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Deelnemen aan sessie"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Annuleren"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Sessie aanmaken"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Deelnemen aan sessie"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Netplay-Discordkanaal"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Feedback geven"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Netplay: wachtruimte"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Sessienaam:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Spelnaam:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Speler 1:"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Speler 2:"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Speler 3:"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Speler 4:"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Chatbericht invoeren"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Bericht verzenden"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Een te lage invoervertraging zorgt ervoor dat het spel gaat haperen.\n"
"Je kunt de invoervertraging in het spel verlagen met Alt+[ en verhogen met "
"Alt+]."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Lokale invoervertraging:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Aanbevolen: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Spel starten"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "Wachten op P2P-mesh "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Annuleren"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "Momenteel niet ingelogd"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Ingeschakeld"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Hardcore-modus"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Challenge-indicatoren"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Leaderboard-trackers"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Discord Rich Presence"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Profiel bekijken"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Gebruikersnaam"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Wachtwoord"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Inloggen"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Ondersteunde spellen bekijken"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Instellingen"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Spel"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Resolutie:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "SSAA-downscaling"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Schalen in hele getallen"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Volledig scherm"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "Breedbeeld (uitgerekt)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "VSync"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "CRT-shader toepassen"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "N64-CPU overklokken (kan bugs veroorzaken)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Expansion Pak uitschakelen"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Terugspoelen inschakelen"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "UNFLoader emuleren"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "VRU emuleren"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Interface"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Thema:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "Systeem"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Donker"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Licht"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "Wat wil je zeggen?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Server:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Aangepaste server-URL invoeren"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "Aangepaste server-URL (optioneel):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Server"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Ping:"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "Server wordt aangemaakt, even geduld… Dit kan ongeveer 30 seconden duren."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Poort"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Controller"
@@ -0,0 +1,621 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 18:11-3000\n"
"Last-Translator: EDUARDO CARVALHO <eduardotcarvalho@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Brazilian Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Nova versão disponível! Clique aqui para baixar"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "Sobre"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Wiki"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Servidor do Discord"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Código Fonte"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Doações (via Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Doações (via GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Jogo Local"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Configurar Controle"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Trapaças"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Configurações"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "Sobre"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Trapaças"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "Selecionar ROM"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Limpar Trapaças para todas as ROMs"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "Sem trapaças disponíveis"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Habilitado"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Perfil"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Configurar Controles"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Configurar Perfil de Controle"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Configurações do controle"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Atribuição do controle"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Criar/Modificar Perfil de Comandos"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Insira o nome de perfil para criar ou modificar"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Porcentagem de Zona-morta"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "Usar DirectInput"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Iniciar Assistente de Criação de Perfil"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Jogo Local"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "Abrir ROM"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Abrir Pasta de Saves"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "ROMs Recentes"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Netplay: Criar Sessão"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Nome da Sessão:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Insira nome da sessão"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Senha: (Opcional):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Insira a senha da sessão"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Nome de Jogador:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Insira nome de jogador"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "Selecionar ROM"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"Este jogo tem trapaças habilitadas. As trapaças serão sincronizadas com "
"outros jogadores."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Criar Sessão"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Netplay: Entrar na sessão"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Nome de Jogador (obrigatório):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Insira o nome de jogador"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Senha (caso obrigatória)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "Selecionar ROM"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Recarregando lista de sessões, por favor aguarde..."
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Clique em uma sessão abaixo para selecionar"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr ""
"Sem sessões disponíveis. Por favor recarregue para verificar novamente."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Nome da Sessão"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Nome do Jogo"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Senha"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Trapaças"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Recarregar Lista de Sessões"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Entrar na Sessão"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Netplay"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Criar Sessão"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Entrar na Sessão"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Canal de Netplay do Discord"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Fornecer Feedback"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Netplay: Aguardando Sala"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Nome da Sessão:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Nome do Jogo:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Jogador 1:"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Jogador 2:"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Jogador 3:"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Jogador 4:"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Escrever mensagem no chat"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Enviar Mensagem"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Definir o atraso de entrada muito baixo causará lag no jogo.\n"
"Você pode diminuir o atraso de entrada no jogo pressionando Alt+[ e aumentá-"
"lo com Alt+]."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Atraso de entrada local:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Recomendado: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Iniciar jogo"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "Aguardando malha P2P "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Cancelar"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "Não está logado."
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Habilitado"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Modo Hardcore"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Indicadores de Desafio"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Rastreadores de Tabela"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Rich Presence no Discord"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Ver Perfil"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Usuário"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Senha"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Login"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Ver Jogos com Suporte"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Configurações"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Jogo"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Resolução:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "Redução de SSAA"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Proporção Íntegra"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Tela Cheia"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "Widescreen (esticar)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "VSync"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "Aplicar filtro de CRT"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "Overclock da CPU do N64 (pode causar bugs)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Desabilitar Expansion Pak"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Habilitar rebobinamento"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "Emular UNFLoader"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "Emular VRU"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Interface"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Tema:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "Sistema"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Escuro"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Claro"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "O que você quer falar?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Servidor:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Insira a URL personalizada do servidor"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "URL Personalizada de Servidor (opcional):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Servidor"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Ping:"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "Criando servidor, por favor aguarde...Isto poderá levar até 30 segundos."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Entrada"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Controle"
@@ -0,0 +1,621 @@
# gopher64 - Russian Translation
msgid ""
msgstr ""
"Project-Id-Version: gopher64\n"
"POT-Creation-Date: 2026-06-30 09:43+0000\n"
"PO-Revision-Date: 2026-05-11 12:00+0000\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: src/ui/gui/about_page.slint:17
msgctxt "NewVersionButton"
msgid "New version available! Click here to download"
msgstr "Доступна новая версия! Нажмите здесь для загрузки"
#: src/ui/gui/about_page.slint:33
msgctxt "About"
msgid "About"
msgstr "О программе"
#: src/ui/gui/about_page.slint:40
msgctxt "About"
msgid "Wiki"
msgstr "Вики"
#: src/ui/gui/about_page.slint:50
msgctxt "About"
msgid "Discord Server"
msgstr "Сервер Discord"
#: src/ui/gui/about_page.slint:60
msgctxt "About"
msgid "Source Code"
msgstr "Исходный код"
#: src/ui/gui/about_page.slint:70
msgctxt "About"
msgid "Donate (via Patreon)"
msgstr "Пожертвовать (через Patreon)"
#: src/ui/gui/about_page.slint:77
msgctxt "About"
msgid "Donate (via GitHub)"
msgstr "Пожертвовать (через GitHub)"
#: src/ui/gui/appwindow.slint:164
msgctxt "Menu"
msgid "Local Game"
msgstr "Локальная игра"
#: src/ui/gui/appwindow.slint:168
msgctxt "Menu"
msgid "Controller Config"
msgstr "Настройка контроллера"
#: src/ui/gui/appwindow.slint:172
msgctxt "Menu"
msgid "Netplay"
msgstr "Сетевая игра"
#: src/ui/gui/appwindow.slint:176
msgctxt "Menu"
msgid "Cheats"
msgstr "Читы"
#: src/ui/gui/appwindow.slint:180
msgctxt "Menu"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/appwindow.slint:184
msgctxt "Menu"
msgid "Settings"
msgstr "Настройки"
#: src/ui/gui/appwindow.slint:188
msgctxt "Menu"
msgid "About"
msgstr "О программе"
#: src/ui/gui/cheat_page.slint:15
msgctxt "Cheats"
msgid "Cheats"
msgstr "Читы"
#: src/ui/gui/cheat_page.slint:25
msgctxt "Cheats"
msgid "Select ROM"
msgstr "Выбрать ROM"
#: src/ui/gui/cheat_page.slint:41
msgctxt "Cheats"
msgid "Clear Cheats For All ROMs"
msgstr "Очистить читы для всех ROMов"
#: src/ui/gui/cheat_page.slint:83
msgctxt "Cheats"
msgid "No Cheats Available"
msgstr "Читы недоступны"
#: src/ui/gui/controller_page.slint:80
msgctxt "ControllerSettingsGrid"
msgid "Enabled"
msgstr "Включено"
#: src/ui/gui/controller_page.slint:93
msgctxt "ControllerSettingsGrid"
msgid "Transfer Pak"
msgstr "Transfer Pak"
#: src/ui/gui/controller_page.slint:107
msgctxt "ControllerSettingsGrid"
msgid "Profile"
msgstr "Профиль"
#: src/ui/gui/controller_page.slint:157
msgctxt "ControllerConfig"
msgid "Controller Configuration"
msgstr "Настройка контроллера"
#: src/ui/gui/controller_page.slint:171
msgctxt "ControllerConfig"
msgid "Configure Input Profile"
msgstr "Настроить профиль ввода"
#: src/ui/gui/controller_page.slint:180
msgctxt "ControllerConfig"
msgid "Controller Settings"
msgstr "Настройки контроллера"
#: src/ui/gui/controller_page.slint:190
msgctxt "ControllerConfig"
msgid "Controller Assignment"
msgstr "Назначение контроллера"
#: src/ui/gui/input_profile.slint:12
msgctxt "InputProfileDialog"
msgid "Create/Modify Input Profile"
msgstr "Создать/изменить профиль ввода"
#: src/ui/gui/input_profile.slint:25
msgctxt "InputProfileDialog"
msgid "Enter profile name to create or modify"
msgstr "Введите имя профиля для создания или изменения"
#: src/ui/gui/input_profile.slint:38
msgctxt "InputProfileDialog"
msgid "Deadzone Percentage"
msgstr "Процент мёртвой зоны"
#: src/ui/gui/input_profile.slint:61
msgctxt "InputProfileDialog"
msgid "Use DirectInput"
msgstr "Использовать DirectInput"
#: src/ui/gui/input_profile.slint:74
msgctxt "InputProfileDialog"
msgid "Start Profile Creation Wizard"
msgstr "Запустить мастер создания профиля"
#: src/ui/gui/input_profile.slint:82
msgctxt "InputProfileDialog"
msgid "Cancel"
msgstr "Отмена"
#: src/ui/gui/localgame_page.slint:19
msgctxt "LocalGame"
msgid "Local Game"
msgstr "Локальная игра"
#: src/ui/gui/localgame_page.slint:26
msgctxt "LocalGame"
msgid "Open ROM"
msgstr "Открыть ROM"
#: src/ui/gui/localgame_page.slint:37
msgctxt "LocalGame"
msgid "Open Saves Folder"
msgstr "Открыть папку сохранений"
#: src/ui/gui/localgame_page.slint:54
msgctxt "LocalGame"
msgid "Recent ROMs"
msgstr "Недавние ROMы"
#: src/ui/gui/netplay_create.slint:7
msgctxt "NetplayCreate"
msgid "Netplay: Create Session"
msgstr "Сетевая игра: Создать сессию"
#: src/ui/gui/netplay_create.slint:22
msgctxt "NetplayCreate"
msgid "Session Name:"
msgstr "Имя сессии:"
#: src/ui/gui/netplay_create.slint:26
msgctxt "NetplayCreate"
msgid "Enter session name"
msgstr "Введите имя сессии"
#: src/ui/gui/netplay_create.slint:36
msgctxt "NetplayCreate"
msgid "Password (Optional):"
msgstr "Пароль (необязательно):"
#: src/ui/gui/netplay_create.slint:40
msgctxt "NetplayCreate"
msgid "Enter session password"
msgstr "Введите пароль сессии"
#: src/ui/gui/netplay_create.slint:51
msgctxt "NetplayCreate"
msgid "Player Name:"
msgstr "Имя игрока:"
#: src/ui/gui/netplay_create.slint:55
msgctxt "NetplayCreate"
msgid "Enter player name"
msgstr "Введите имя игрока"
#: src/ui/gui/netplay_create.slint:66
msgctxt "NetplayCreate"
msgid "Select ROM"
msgstr "Выбрать ROM"
#: src/ui/gui/netplay_create.slint:82
msgctxt "NetplayCreate"
msgid ""
"This game has cheats enabled. Cheats will be synchronized with other players."
msgstr ""
"Для этой игры включены читы. Читы будут синхронизированы с другими игроками."
#: src/ui/gui/netplay_create.slint:91
msgctxt "NetplayCreate"
msgid "Create Session"
msgstr "Создать сессию"
#: src/ui/gui/netplay_create.slint:107
msgctxt "NetplayCreate"
msgid "Cancel"
msgstr "Отмена"
#: src/ui/gui/netplay_join.slint:7
msgctxt "NetplayJoin"
msgid "Netplay: Join Session"
msgstr "Сетевая игра: Присоединиться к сессии"
#: src/ui/gui/netplay_join.slint:18
msgctxt "NetplayJoin"
msgid "Player Name (required):"
msgstr "Имя игрока (обязательно):"
#: src/ui/gui/netplay_join.slint:22
msgctxt "NetplayJoin"
msgid "Enter player name"
msgstr "Введите имя игрока"
#: src/ui/gui/netplay_join.slint:31
msgctxt "NetplayJoin"
msgid "Password (if required)"
msgstr "Пароль (если требуется)"
#: src/ui/gui/netplay_join.slint:42
msgctxt "NetplayJoin"
msgid "Select ROM"
msgstr "Выбрать ROM"
#: src/ui/gui/netplay_join.slint:57
msgctxt "NetplayJoin"
msgid "Refreshing session list, please wait..."
msgstr "Обновление списка сессий, пожалуйста, подождите..."
#: src/ui/gui/netplay_join.slint:62
msgctxt "NetplayJoin"
msgid "Click on a session below to select it"
msgstr "Нажмите на сессию ниже, чтобы выбрать её"
#: src/ui/gui/netplay_join.slint:67
msgctxt "NetplayJoin"
msgid "No sessions available. Please refresh to check again."
msgstr "Нет доступных сессий. Пожалуйста, обновите, чтобы проверить снова."
#: src/ui/gui/netplay_join.slint:73
msgctxt "NetplayJoin"
msgid "Session Name"
msgstr "Имя сессии"
#: src/ui/gui/netplay_join.slint:74
msgctxt "NetplayJoin"
msgid "Game Name"
msgstr "Название игры"
#: src/ui/gui/netplay_join.slint:75
msgctxt "NetplayJoin"
msgid "Password"
msgstr "Пароль"
#: src/ui/gui/netplay_join.slint:76
msgctxt "NetplayJoin"
msgid "Cheats"
msgstr "Читы"
#: src/ui/gui/netplay_join.slint:90
msgctxt "NetplayJoin"
msgid "Refresh Session List"
msgstr "Обновить список сессий"
#: src/ui/gui/netplay_join.slint:105
msgctxt "NetplayJoin"
msgid "Join Session"
msgstr "Присоединиться к сессии"
#: src/ui/gui/netplay_join.slint:117
msgctxt "NetplayJoin"
msgid "Cancel"
msgstr "Отмена"
#: src/ui/gui/netplay_page.slint:31
msgctxt "Netplay"
msgid "Netplay"
msgstr "Сетевая игра"
#: src/ui/gui/netplay_page.slint:39
msgctxt "Netplay"
msgid "Create Session"
msgstr "Создать сессию"
#: src/ui/gui/netplay_page.slint:50
msgctxt "Netplay"
msgid "Join Session"
msgstr "Присоединиться к сессии"
#: src/ui/gui/netplay_page.slint:64
msgctxt "Netplay"
msgid "Netplay Discord Channel"
msgstr "Канал Discord для сетевой игры"
#: src/ui/gui/netplay_page.slint:71
msgctxt "Netplay"
msgid "Provide Feedback"
msgstr "Оставить отзыв"
#: src/ui/gui/netplay_wait.slint:17
msgctxt "NetplayWait"
msgid "Netplay: Waiting Room"
msgstr "Сетевая игра: Комната ожидания"
#: src/ui/gui/netplay_wait.slint:29
msgctxt "NetplayWait"
msgid "Session Name:"
msgstr "Имя сессии:"
#: src/ui/gui/netplay_wait.slint:35
msgctxt "NetplayWait"
msgid "Game Name:"
msgstr "Название игры:"
#: src/ui/gui/netplay_wait.slint:45
msgctxt "NetplayWait"
msgid "Player 1:"
msgstr "Игрок 1:"
#: src/ui/gui/netplay_wait.slint:49
msgctxt "NetplayWait"
msgid "Player 2:"
msgstr "Игрок 2:"
#: src/ui/gui/netplay_wait.slint:55
msgctxt "NetplayWait"
msgid "Player 3:"
msgstr "Игрок 3:"
#: src/ui/gui/netplay_wait.slint:59
msgctxt "NetplayWait"
msgid "Player 4:"
msgstr "Игрок 4:"
#: src/ui/gui/netplay_wait.slint:73
msgctxt "NetplayWait"
msgid "Enter chat message"
msgstr "Введите сообщение чата"
#: src/ui/gui/netplay_wait.slint:84
msgctxt "NetplayWait"
msgid "Send Message"
msgstr "Отправить сообщение"
#: src/ui/gui/netplay_wait.slint:94
msgctxt "NetplayWait"
msgid ""
"Setting the input delay too low will cause the game to lag.\n"
"You can lower the input delay in-game by pressing Alt+[ and raise it with "
"Alt+]."
msgstr ""
"Слишком низкая задержка ввода вызовет лаги в игре.\n"
"Вы можете уменьшить задержку ввода в игре, нажав Alt+[, и увеличить её с "
"помощью Alt+]."
#: src/ui/gui/netplay_wait.slint:101
msgctxt "NetplayWait"
msgid "Local Input Delay:"
msgstr "Локальная задержка ввода:"
#: src/ui/gui/netplay_wait.slint:118
msgctxt "NetplayWait"
msgid "Recommended: "
msgstr "Рекомендуется: "
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Start Game"
msgstr "Начать игру"
#: src/ui/gui/netplay_wait.slint:127
msgctxt "NetplayWait"
msgid "Waiting for P2P mesh "
msgstr "Ожидание P2P-сети "
#: src/ui/gui/netplay_wait.slint:135
msgctxt "NetplayWait"
msgid "Cancel"
msgstr "Отмена"
#: src/ui/gui/retroachievements_page.slint:19
msgctxt "RAData"
msgid "Not currently logged in"
msgstr "В данный момент не выполнен вход"
#: src/ui/gui/retroachievements_page.slint:27
msgctxt "RetroAchievements"
msgid "RetroAchievements"
msgstr "RetroAchievements"
#: src/ui/gui/retroachievements_page.slint:36
msgctxt "RetroAchievements"
msgid "Enabled"
msgstr "Включено"
#: src/ui/gui/retroachievements_page.slint:51
msgctxt "RetroAchievements"
msgid "Hardcore mode"
msgstr "Хардкорный режим"
#: src/ui/gui/retroachievements_page.slint:60
msgctxt "RetroAchievements"
msgid "Challenge Indicators"
msgstr "Индикаторы испытаний"
#: src/ui/gui/retroachievements_page.slint:69
msgctxt "RetroAchievements"
msgid "Leaderboard Trackers"
msgstr "Трекеры таблицы лидеров"
#: src/ui/gui/retroachievements_page.slint:78
msgctxt "RetroAchievements"
msgid "Discord Rich Presence"
msgstr "Расширенный статус Discord"
#: src/ui/gui/retroachievements_page.slint:96
msgctxt "RetroAchievements"
msgid "View Profile"
msgstr "Просмотреть профиль"
#: src/ui/gui/retroachievements_page.slint:106
msgctxt "RetroAchievements"
msgid "Username"
msgstr "Имя пользователя"
#: src/ui/gui/retroachievements_page.slint:116
msgctxt "RetroAchievements"
msgid "Password"
msgstr "Пароль"
#: src/ui/gui/retroachievements_page.slint:125
msgctxt "RetroAchievements"
msgid "Login"
msgstr "Войти"
#: src/ui/gui/retroachievements_page.slint:141
msgctxt "RetroAchievements"
msgid "View Supported Games"
msgstr "Просмотреть поддерживаемые игры"
#: src/ui/gui/settings_page.slint:25
msgctxt "Settings"
msgid "Settings"
msgstr "Настройки"
#: src/ui/gui/settings_page.slint:32
msgctxt "Settings"
msgid "Game"
msgstr "Игра"
#: src/ui/gui/settings_page.slint:38
msgctxt "Settings"
msgid "Resolution:"
msgstr "Разрешение:"
#: src/ui/gui/settings_page.slint:51
msgctxt "Settings"
msgid "SSAA downscaling"
msgstr "SSAA даунскейлинг"
#: src/ui/gui/settings_page.slint:59
msgctxt "Settings"
msgid "Integer Scaling"
msgstr "Целочисленное масштабирование"
#: src/ui/gui/settings_page.slint:67
msgctxt "Settings"
msgid "Fullscreen"
msgstr "Полноэкранный режим"
#: src/ui/gui/settings_page.slint:75
msgctxt "Settings"
msgid "Widescreen (stretch)"
msgstr "Широкоэкранный режим (растянуто)"
#: src/ui/gui/settings_page.slint:83
msgctxt "Settings"
msgid "VSync"
msgstr "Вертикальная синхронизация"
#: src/ui/gui/settings_page.slint:91
msgctxt "Settings"
msgid "Apply CRT shader"
msgstr "Применить шейдер ЭЛТ"
#: src/ui/gui/settings_page.slint:103
msgctxt "Settings"
msgid "Overclock N64 CPU (may cause bugs)"
msgstr "Разгон процессора N64 (может вызвать ошибки)"
#: src/ui/gui/settings_page.slint:111
msgctxt "Settings"
msgid "Disable Expansion Pak"
msgstr "Отключить Expansion Pak"
#: src/ui/gui/settings_page.slint:119
msgctxt "Settings"
msgid "Enable Rewind"
msgstr "Включить перемотку назад"
#: src/ui/gui/settings_page.slint:127
msgctxt "Settings"
msgid "Emulate UNFLoader"
msgstr "Эмулировать UNFLoader"
#: src/ui/gui/settings_page.slint:135
msgctxt "Settings"
msgid "Emulate VRU"
msgstr "Эмулировать VRU"
#: src/ui/gui/settings_page.slint:144
msgctxt "Settings"
msgid "UI"
msgstr "Интерфейс"
#: src/ui/gui/settings_page.slint:150
msgctxt "Settings"
msgid "Theme:"
msgstr "Тема:"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "System"
msgstr "Система"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Dark"
msgstr "Тёмная"
#: src/ui/gui/settings_page.slint:154
msgctxt "Settings"
msgid "Light"
msgstr "Светлая"
#: src/ui/gui/vru_dialog.slint:7
msgctxt "VruDialog"
msgid "What Would You Like To Say?"
msgstr "Что вы хотите сказать?"
#~ msgctxt "NetplayCreate"
#~ msgid "Server:"
#~ msgstr "Сервер:"
#~ msgctxt "CustomServerDialog"
#~ msgid "Enter a custom server URL"
#~ msgstr "Введите URL пользовательского сервера"
#~ msgctxt "NetplayJoin"
#~ msgid "Custom Server URL (optional):"
#~ msgstr "URL пользовательского сервера (необязательно):"
#~ msgctxt "NetplayJoin"
#~ msgid "Server"
#~ msgstr "Сервер"
#~ msgctxt "NetplayWait"
#~ msgid "Ping:"
#~ msgstr "Пинг:"
#~ msgctxt "NetplayCreate"
#~ msgid "Creating server, please wait...This may take about 30 seconds."
#~ msgstr ""
#~ "Создание сервера, пожалуйста, подождите... Это может занять около 30 "
#~ "секунд."
#~ msgctxt "ControllerConfig"
#~ msgid "Port"
#~ msgstr "Порт"
#~ msgctxt "ControllerConfig"
#~ msgid "Controller"
#~ msgstr "Контроллер"
File diff suppressed because it is too large Load Diff
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M440-280h80v-240h-80v240Zm68.5-331.5Q520-623 520-640t-11.5-28.5Q497-680 480-680t-28.5 11.5Q440-657 440-640t11.5 28.5Q463-600 480-600t28.5-11.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 553 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M280-120v-80h160v-124q-49-11-87.5-41.5T296-442q-75-9-125.5-65.5T120-640v-40q0-33 23.5-56.5T200-760h80v-80h400v80h80q33 0 56.5 23.5T840-680v40q0 76-50.5 132.5T664-442q-18 46-56.5 76.5T520-324v124h160v80H280Zm0-408v-152h-80v40q0 38 22 68.5t58 43.5Zm285 93q35-35 35-85v-240H360v240q0 50 35 85t85 35q50 0 85-35Zm115-93q36-13 58-43.5t22-68.5v-40h-80v152Zm-200-52Z"/></svg>

After

Width:  |  Height:  |  Size: 483 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-160v-80h320v80H480ZM220-320l-56-56 183-184-183-184 56-56 240 240-240 240Z"/></svg>

After

Width:  |  Height:  |  Size: 203 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M189-160q-60 0-102.5-43T42-307q0-9 1-18t3-18l84-336q14-54 57-87.5t98-33.5h390q55 0 98 33.5t57 87.5l84 336q2 9 3.5 18.5T919-306q0 61-43.5 103.5T771-160q-42 0-78-22t-54-60l-28-58q-5-10-15-15t-21-5H385q-11 0-21 5t-15 15l-28 58q-18 38-54 60t-78 22Zm3-80q19 0 34.5-10t23.5-27l28-57q15-31 44-48.5t63-17.5h190q34 0 63 18t45 48l28 57q8 17 23.5 27t34.5 10q28 0 48-18.5t21-46.5q0 1-2-19l-84-335q-7-27-28-44t-49-17H285q-28 0-49.5 17T208-659l-84 335q-2 6-2 18 0 28 20.5 47t49.5 19Zm376.5-291.5Q580-543 580-560t-11.5-28.5Q557-600 540-600t-28.5 11.5Q500-577 500-560t11.5 28.5Q523-520 540-520t28.5-11.5Zm80-80Q660-623 660-640t-11.5-28.5Q637-680 620-680t-28.5 11.5Q580-657 580-640t11.5 28.5Q603-600 620-600t28.5-11.5Zm0 160Q660-463 660-480t-11.5-28.5Q637-520 620-520t-28.5 11.5Q580-497 580-480t11.5 28.5Q603-440 620-440t28.5-11.5Zm80-80Q740-543 740-560t-11.5-28.5Q717-600 700-600t-28.5 11.5Q660-577 660-560t11.5 28.5Q683-520 700-520t28.5-11.5Zm-367 63Q370-477 370-490v-40h40q13 0 21.5-8.5T440-560q0-13-8.5-21.5T410-590h-40v-40q0-13-8.5-21.5T340-660q-13 0-21.5 8.5T310-630v40h-40q-13 0-21.5 8.5T240-560q0 13 8.5 21.5T270-530h40v40q0 13 8.5 21.5T340-460q13 0 21.5-8.5ZM480-480Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>

After

Width:  |  Height:  |  Size: 243 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm0-80h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm220-240Z"/></svg>

After

Width:  |  Height:  |  Size: 424 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>

After

Width:  |  Height:  |  Size: 771 B

+989 -711
View File
File diff suppressed because it is too large Load Diff
+70 -43
View File
@@ -4,53 +4,80 @@
#include <cstdint>
#include <stddef.h>
extern "C"
{
extern "C" {
#endif
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 {
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 vsync;
bool integer_scaling;
uint32_t upscale;
bool ssaa;
bool crt;
} GFX_INFO;
typedef struct
{
bool emu_running;
bool save_state;
bool load_state;
bool enable_speedlimiter;
bool lower_volume;
bool raise_volume;
bool paused;
bool frame_advance;
uint32_t save_state_slot;
} CALL_BACK;
typedef struct {
bool emu_running;
bool save_state;
bool load_state;
bool load_rewind;
bool reset_game;
bool enable_speedlimiter;
bool lower_volume;
bool raise_volume;
bool decrease_input_delay;
bool increase_input_delay;
bool paused;
bool frame_advance;
uint32_t save_state_slot;
} CALL_BACK;
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size);
void rdp_close();
void rdp_set_vi_register(uint32_t reg, uint32_t value);
void rdp_update_screen();
void rdp_render_frame();
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_onscreen_message(const char *_message);
void rdp_new_processor(GFX_INFO _gfx_info);
void rdp_check_framebuffers(uint32_t address, uint32_t length);
size_t rdp_state_size();
void rdp_save_state(uint8_t *state);
void rdp_load_state(const uint8_t *state);
typedef struct {
uint32_t joystick_id;
bool connected;
} JoystickEvent;
typedef enum {
MESSAGE_VERY_SHORT = 500,
MESSAGE_SHORT = 3000,
MESSAGE_LONG = 6000,
} MESSAGE_LENGTH;
void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font,
size_t font_size, uint32_t save_state_slot);
void rdp_close();
void rdp_set_vi_register(uint32_t reg, uint32_t value);
void rdp_update_screen();
void rdp_render_frame();
CALL_BACK rdp_check_callback();
uint64_t rdp_process_commands();
void rdp_idle();
void rdp_onscreen_message(const char *message, MESSAGE_LENGTH milliseconds);
void rdp_check_framebuffers(uint32_t address, uint32_t length);
size_t rdp_state_size();
void rdp_save_state(uint8_t *state);
void rdp_load_state(GFX_INFO _gfx_info, const uint8_t *state);
void rdp_set_fps(uint32_t fps, uint32_t vis);
JoystickEvent get_joystick_event();
void achievement_challenge_indicator_add(const char *achievement_title);
void achievement_challenge_indicator_remove(const char *achievement_title);
void achievement_progress_add(const char *achievement_title,
const char *progress);
void achievement_progress_remove();
void leaderboard_tracker_add(uint32_t id, const char *title,
const char *display);
void leaderboard_tracker_remove(uint32_t id);
#ifdef __cplusplus
}
+36 -54
View File
@@ -1,73 +1,55 @@
#include "wsi_platform.hpp"
#include <SDL3/SDL_vulkan.h>
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance, VkPhysicalDevice gpu)
{
VkSurfaceKHR surface = nullptr;
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
if (result != true)
{
printf("Error creating surface\n");
}
return surface;
VkSurfaceKHR SDL_WSIPlatform::create_surface(VkInstance instance,
VkPhysicalDevice gpu) {
VkSurfaceKHR surface = nullptr;
bool result = SDL_Vulkan_CreateSurface(window, instance, NULL, &surface);
if (result != true) {
printf("Error creating surface\n");
}
return surface;
}
void SDL_WSIPlatform::destroy_surface(VkInstance instance, VkSurfaceKHR surface)
{
SDL_Vulkan_DestroySurface(instance, surface, NULL);
void SDL_WSIPlatform::destroy_surface(VkInstance instance,
VkSurfaceKHR surface) {
SDL_Vulkan_DestroySurface(instance, surface, NULL);
}
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions()
{
std::vector<const char *> SDL_WSIPlatform::get_instance_extensions() {
unsigned int extensionCount = 0;
char const *const *extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (extensions == NULL)
{
printf("Error getting instance extensions\n");
}
unsigned int extensionCount = 0;
char const *const *extensions =
SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (extensions == NULL) {
printf("Error getting instance extensions\n");
}
std::vector<const char *> extensionNames;
for (unsigned int i = 0; i < extensionCount; ++i)
{
extensionNames.push_back(extensions[i]);
}
return extensionNames;
std::vector<const char *> extensionNames;
for (unsigned int i = 0; i < extensionCount; ++i) {
extensionNames.push_back(extensions[i]);
}
return extensionNames;
}
uint32_t SDL_WSIPlatform::get_surface_width()
{
int w, h;
SDL_GetWindowSize(window, &w, &h);
return w;
uint32_t SDL_WSIPlatform::get_surface_width() {
int w, h;
SDL_GetWindowSize(window, &w, &h);
return w;
}
uint32_t SDL_WSIPlatform::get_surface_height()
{
int w, h;
SDL_GetWindowSize(window, &w, &h);
return h;
uint32_t SDL_WSIPlatform::get_surface_height() {
int w, h;
SDL_GetWindowSize(window, &w, &h);
return h;
}
bool SDL_WSIPlatform::alive(Vulkan::WSI &wsi)
{
return true;
}
bool SDL_WSIPlatform::alive(Vulkan::WSI &wsi) { return true; }
void SDL_WSIPlatform::poll_input()
{
}
void SDL_WSIPlatform::poll_input() { SDL_PumpEvents(); }
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler)
{
}
void SDL_WSIPlatform::poll_input_async(Granite::InputTrackerHandler *handler) {}
void SDL_WSIPlatform::set_window(SDL_Window *_window)
{
window = _window;
}
void SDL_WSIPlatform::set_window(SDL_Window *_window) { window = _window; }
void SDL_WSIPlatform::do_resize()
{
resize = true;
}
void SDL_WSIPlatform::do_resize() { resize = true; }
+13 -13
View File
@@ -3,20 +3,20 @@
#include "wsi.hpp"
#include <SDL3/SDL.h>
class SDL_WSIPlatform : public Vulkan::WSIPlatform
{
class SDL_WSIPlatform : public Vulkan::WSIPlatform {
public:
VkSurfaceKHR create_surface(VkInstance instance, VkPhysicalDevice gpu) override;
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override;
std::vector<const char *> get_instance_extensions() override;
uint32_t get_surface_width() override;
uint32_t get_surface_height() override;
bool alive(Vulkan::WSI &wsi) override;
void poll_input() override;
void poll_input_async(Granite::InputTrackerHandler *handler) override;
void set_window(SDL_Window *_window);
void do_resize();
VkSurfaceKHR create_surface(VkInstance instance,
VkPhysicalDevice gpu) override;
void destroy_surface(VkInstance instance, VkSurfaceKHR surface) override;
std::vector<const char *> get_instance_extensions() override;
uint32_t get_surface_width() override;
uint32_t get_surface_height() override;
bool alive(Vulkan::WSI &wsi) override;
void poll_input() override;
void poll_input_async(Granite::InputTrackerHandler *handler) override;
void set_window(SDL_Window *_window);
void do_resize();
private:
SDL_Window *window;
SDL_Window *window;
};
+421
View File
@@ -0,0 +1,421 @@
#include "retroachievements.h"
#include "../parallel-rdp/interface.hpp"
#include <rc_client.h>
#include <rc_consoles.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void rust_server_call(const char *url, const char *post_data,
const char *content_type,
rc_client_server_callback_t callback,
void *callback_data);
void store_retroachievements_credentials(const char *username,
const char *token, void *userdata);
void notify_load_game(void *userdata);
static rc_client_t *g_client = NULL;
static const uint8_t *g_rdram = NULL;
static size_t g_rdram_size = 0;
static bool g_challenge = false;
static bool g_leaderboard = false;
static rc_client_leaderboard_list_t *g_leaderboard_list = NULL;
static char g_rich_presence_buffer[512];
static uint32_t read_memory(uint32_t address, uint8_t *buffer,
uint32_t num_bytes, rc_client_t *client) {
if (address + num_bytes > g_rdram_size)
memset(buffer, 0, num_bytes);
else
memcpy(buffer, &g_rdram[address], num_bytes);
return num_bytes;
}
static void server_call(const rc_api_request_t *request,
rc_client_server_callback_t callback,
void *callback_data, rc_client_t *client) {
rust_server_call(request->url, request->post_data, request->content_type,
callback, callback_data);
}
void ra_http_callback(const char *content, size_t content_size, int status_code,
void *callback, void *callback_data) {
// Prepare a data object to pass the HTTP response to the callback
rc_api_server_response_t server_response;
memset(&server_response, 0, sizeof(server_response));
server_response.body = content;
server_response.body_length = content_size;
server_response.http_status_code = status_code;
// handle non-http errors (socket timeout, no internet available, etc)
if (status_code == 0) {
// Let rc_client know the error was not catastrophic and could be retried.
// It may decide to retry or just immediately pass the error to the
// callback. To prevent possible retries, use
// RC_API_SERVER_RESPONSE_CLIENT_ERROR.
server_response.http_status_code =
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR;
}
((rc_client_server_callback_t)callback)(&server_response, callback_data);
}
static void log_message(const char *message, const rc_client_t *client) {
printf("RetroAchievements: %s\n", message);
}
static void login_callback(int result, const char *error_message,
rc_client_t *client, void *userdata) {
// If not successful, just report the error and bail.
if (result != RC_OK) {
if (client && !rc_client_get_userdata(client))
rc_client_set_userdata(client, strdup(error_message));
store_retroachievements_credentials(NULL, NULL, userdata);
return;
}
// Login was successful. Capture the token for future logins so we don't have
// to store the password anywhere.
const rc_client_user_t *user = rc_client_get_user_info(client);
store_retroachievements_credentials(user->username, user->token, userdata);
}
const char *ra_get_username() {
const rc_client_user_t *user = rc_client_get_user_info(g_client);
return user ? user->username : NULL;
}
void ra_logout_user() { rc_client_logout(g_client); }
void ra_login_user(const char *username, const char *password, void *userdata) {
// This will generate an HTTP payload and call the server_call chain above.
// Eventually, login_callback will be called to let us know if the login was
// successful.
rc_client_begin_login_with_password(g_client, username, password,
login_callback, userdata);
}
void ra_login_token_user(const char *username, const char *token,
void *userdata) {
// This is exactly the same functionality as
// rc_client_begin_login_with_password, but uses the token captured from the
// first login instead of a password. Note that it uses the same callback.
rc_client_begin_login_with_token(g_client, username, token, login_callback,
userdata);
}
static void load_game_callback(int result, const char *error_message,
rc_client_t *client, void *userdata) {
if (result != RC_OK) {
if (client && !rc_client_get_userdata(client))
rc_client_set_userdata(client, strdup(error_message));
rc_client_set_hardcore_enabled(client, false);
notify_load_game(userdata);
return;
}
if (!rc_client_is_processing_required(client)) {
rc_client_set_hardcore_enabled(client, false);
}
g_leaderboard_list = rc_client_create_leaderboard_list(
client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
notify_load_game(userdata);
}
void ra_welcome(const char **game_title, const char **game_image_url) {
if (!g_client) {
*game_title = NULL;
*game_image_url = NULL;
return;
}
char buffer[512];
char *error_message = rc_client_get_userdata(g_client);
if (error_message) {
snprintf(buffer, sizeof(buffer), "RA Error: %s", error_message);
rdp_onscreen_message(buffer, MESSAGE_LONG);
free(error_message);
rc_client_set_userdata(g_client, NULL);
*game_title = NULL;
*game_image_url = NULL;
return;
}
const rc_client_game_t *game = rc_client_get_game_info(g_client);
if (!game) {
*game_title = NULL;
*game_image_url = NULL;
return;
}
rc_client_user_game_summary_t summary;
rc_client_get_user_game_summary(g_client, &summary);
int message_length = snprintf(
buffer, sizeof(buffer), "%s\nMode: %s\n", game->title,
rc_client_get_hardcore_enabled(g_client) ? "Hardcore" : "Casual");
if (summary.num_core_achievements != 0) {
snprintf(buffer + message_length, sizeof(buffer) - message_length,
"%u/%u achievements unlocked", summary.num_unlocked_achievements,
summary.num_core_achievements);
} else {
snprintf(buffer + message_length, sizeof(buffer) - message_length,
"Game has no achievements");
}
rdp_onscreen_message(buffer, MESSAGE_LONG);
*game_title = game->title;
*game_image_url = game->badge_url;
}
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata) {
rc_client_begin_identify_and_load_game(g_client, RC_CONSOLE_NINTENDO_64, NULL,
rom, rom_size, load_game_callback,
userdata);
}
void ra_unload_game() {
if (g_leaderboard_list) {
rc_client_destroy_leaderboard_list(g_leaderboard_list);
g_leaderboard_list = NULL;
}
rc_client_unload_game(g_client);
}
void ra_set_rdram(const uint8_t *rdram, size_t rdram_size) {
g_rdram = rdram;
g_rdram_size = rdram_size;
}
static void leaderboard_submitted(const rc_client_leaderboard_t *leaderboard) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Leaderboard submitted: %s - %s",
leaderboard->title, leaderboard->tracker_value);
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
static void achievement_triggered(const rc_client_achievement_t *achievement) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Unlocked: %s", achievement->title);
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
static void game_completed(rc_client_t *client) {
char buffer[512];
const rc_client_game_t *game = rc_client_get_game_info(client);
snprintf(buffer, sizeof(buffer), "%s: %s",
rc_client_get_hardcore_enabled(client) ? "Mastered" : "Completed",
game->title);
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
static void subset_completed(const rc_client_subset_t *subset,
rc_client_t *client) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "Subset %s: %s",
rc_client_get_hardcore_enabled(client) ? "mastered" : "completed",
subset->title);
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
static void server_error(const rc_client_server_error_t *server_error) {
char buffer[512];
snprintf(buffer, sizeof(buffer), "RA server error: %s",
server_error->error_message);
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
static const char *get_leaderboard_title(const char *display) {
if (g_leaderboard_list == NULL)
return NULL;
for (uint32_t i = 0; i < g_leaderboard_list->num_buckets; i++) {
for (uint32_t j = 0; j < g_leaderboard_list->buckets[i].num_leaderboards;
j++) {
// this looks like a mistake, but it is intentional.
// Leaderboard trackers don't contain the title,
// but we take advantage of the fact that tracker_value and display point
// to the same char* to find a matching leaderboard.
if (g_leaderboard_list->buckets[i].leaderboards[j]->tracker_value ==
display) {
return g_leaderboard_list->buckets[i].leaderboards[j]->title;
}
}
}
return NULL;
}
static void event_handler(const rc_client_event_t *event, rc_client_t *client) {
switch (event->type) {
case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED:
achievement_triggered(event->achievement);
break;
case RC_CLIENT_EVENT_LEADERBOARD_STARTED:
break;
case RC_CLIENT_EVENT_LEADERBOARD_FAILED:
break;
case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED:
if (g_leaderboard)
leaderboard_submitted(event->leaderboard);
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW:
if (g_challenge)
achievement_challenge_indicator_add(event->achievement->title);
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE:
if (g_challenge)
achievement_challenge_indicator_remove(event->achievement->title);
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW:
achievement_progress_add(event->achievement->title,
event->achievement->measured_progress);
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE:
achievement_progress_remove();
break;
case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE:
achievement_progress_add(event->achievement->title,
event->achievement->measured_progress);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW:
if (g_leaderboard)
leaderboard_tracker_add(
event->leaderboard_tracker->id,
get_leaderboard_title(event->leaderboard_tracker->display),
event->leaderboard_tracker->display);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE:
if (g_leaderboard)
leaderboard_tracker_remove(event->leaderboard_tracker->id);
break;
case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE:
if (g_leaderboard)
leaderboard_tracker_add(
event->leaderboard_tracker->id,
get_leaderboard_title(event->leaderboard_tracker->display),
event->leaderboard_tracker->display);
break;
case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD:
break;
case RC_CLIENT_EVENT_GAME_COMPLETED:
game_completed(client);
break;
case RC_CLIENT_EVENT_SUBSET_COMPLETED:
subset_completed(event->subset, client);
break;
case RC_CLIENT_EVENT_SERVER_ERROR:
server_error(event->server_error);
break;
default:
printf("RetroAchievements: Unhandled event %d\n", event->type);
break;
}
}
void ra_init_client(bool hardcore, bool challenge, bool leaderboard) {
// Create the client instance (using a global variable simplifies this
// example)
if (!g_client)
g_client = rc_client_create(read_memory, server_call);
g_rdram = NULL;
g_rdram_size = 0;
// Provide a logging function to simplify debugging
rc_client_enable_logging(g_client, RC_CLIENT_LOG_LEVEL_WARN, log_message);
rc_client_set_event_handler(g_client, event_handler);
rc_client_set_hardcore_enabled(g_client, hardcore);
g_challenge = challenge;
g_leaderboard = leaderboard;
}
bool ra_get_hardcore() { return rc_client_get_hardcore_enabled(g_client); }
void ra_shutdown_client() {
if (g_client) {
// Release resources associated to the client instance
rc_client_destroy(g_client);
g_client = NULL;
}
}
void ra_do_frame() { rc_client_do_frame(g_client); }
void ra_do_idle() { rc_client_idle(g_client); }
const char *ra_get_rich_presence() {
size_t size = rc_client_get_rich_presence_message(
g_client, g_rich_presence_buffer, sizeof(g_rich_presence_buffer));
if (size == 0) {
return NULL;
}
return g_rich_presence_buffer;
}
size_t ra_state_size() { return rc_client_progress_size(g_client); }
void ra_save_state(uint8_t *state, size_t state_size) {
if (!rc_client_is_game_loaded(g_client))
return;
if (rc_client_serialize_progress_sized(g_client, state, state_size) !=
RC_OK) {
printf("RetroAchievements: Failed to serialize progress\n");
}
}
void ra_load_state(const uint8_t *state, size_t state_size) {
if (!rc_client_is_game_loaded(g_client))
return;
if (rc_client_deserialize_progress_sized(g_client, state, state_size) !=
RC_OK) {
printf("RetroAchievements: Failed to deserialize progress\n");
}
}
void ra_display_inprogress_achievements(void *userdata) {
if (!rc_client_is_game_loaded(g_client))
return;
rc_client_achievement_list_t *list = rc_client_create_achievement_list(
g_client, RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE);
char buffer[1024] = {0};
size_t buffer_length = 0;
for (uint32_t i = 0; i < list->num_buckets; i++) {
if (list->buckets[i].bucket_type == RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED) {
for (uint32_t j = 0; j < list->buckets[i].num_achievements; j++) {
const rc_client_achievement_t *achievement =
list->buckets[i].achievements[j];
if (achievement->measured_percent > 0.0f) {
if (buffer_length < sizeof(buffer) - 1) {
buffer_length += snprintf(
buffer + buffer_length, sizeof(buffer) - buffer_length,
"%s: %s\n", achievement->title, achievement->measured_progress);
}
}
}
}
}
if (buffer_length > 0) {
rdp_onscreen_message(buffer, MESSAGE_SHORT);
}
rc_client_destroy_achievement_list(list);
}
+33
View File
@@ -0,0 +1,33 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
void ra_init_client(bool hardcore, bool challenge, bool leaderboard);
void ra_shutdown_client();
void ra_welcome(const char **game_title, const char **game_image_url);
bool ra_get_hardcore();
void ra_load_game(const uint8_t *rom, size_t rom_size, void *userdata);
void ra_unload_game();
void ra_set_rdram(const uint8_t *rdram, size_t rdram_size);
void ra_do_frame();
void ra_do_idle();
void ra_http_callback(const char *content, size_t content_size, int status_code,
void *callback, void *callback_data);
void ra_logout_user();
void ra_login_user(const char *username, const char *password, void *userdata);
void ra_login_token_user(const char *username, const char *token,
void *userdata);
const char *ra_get_username();
size_t ra_state_size();
void ra_save_state(uint8_t *state, size_t state_size);
void ra_load_state(const uint8_t *state, size_t state_size);
const char *ra_get_rich_presence();
#ifdef __cplusplus
extern "C" {
#endif
void ra_display_inprogress_achievements(void *userdata);
#ifdef __cplusplus
}
#endif
+1 -1
View File
@@ -1,2 +1,2 @@
[toolchain]
channel = "1.91.0"
channel = "1.96.1"
+32 -17
View File
@@ -8,23 +8,31 @@ pub struct DecodedCheat {
data: u16,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cheats {
pub cheats: Vec<Vec<DecodedCheat>>,
pub boot: bool,
pub enabled: bool,
}
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct CheatData {
pub note: String,
pub data: Vec<String>,
pub options: Option<std::collections::BTreeMap<String, String>>,
}
pub type Cheat = std::collections::BTreeMap<String, std::collections::BTreeMap<String, CheatData>>;
pub fn init(
device: &mut device::Device,
cheat_settings: std::collections::HashMap<String, Option<String>>,
cheat_settings: rustc_hash::FxHashMap<String, Option<String>>,
) {
let cheats =
serde_json::from_slice::<ui::cheats::Cheats>(include_bytes!("../data/cheats.json"))
.unwrap()
.get(&ui::storage::get_game_crc(&device.cart.rom))
.cloned()
.unwrap_or_default();
let cheats = serde_json::from_slice::<Cheat>(include_bytes!("../data/cheats.json"))
.unwrap()
.get(&ui::storage::get_game_crc(&device.cart.rom))
.cloned()
.unwrap_or_default();
let re = regex::Regex::new(r"\?+").unwrap();
for cheat_setting in cheat_settings.iter() {
@@ -40,7 +48,8 @@ pub fn init(
{
option_value = Some(found_option_value.to_string());
} else {
panic!("Cheat option: {option} not found");
eprintln!("Cheat option: {option} not found");
continue;
}
}
@@ -57,21 +66,27 @@ pub fn init(
}
let mut split = result.split_whitespace();
let first_part = u32::from_str_radix(split.next().unwrap(), 16).unwrap();
decoded_cheat.push(DecodedCheat {
code_type: (first_part >> 24) as u8,
address: first_part & 0x00FFFFFF,
data: u16::from_str_radix(split.next().unwrap(), 16).unwrap(),
});
if let Ok(data) = u16::from_str_radix(split.next().unwrap(), 16) {
decoded_cheat.push(DecodedCheat {
code_type: (first_part >> 24) as u8,
address: first_part & 0x00FFFFFF,
data,
})
} else {
eprintln!("Could not parse data for: {}", cheat_setting.0);
};
}
if !decoded_cheat.is_empty() {
device.cheats.cheats.push(decoded_cheat);
}
device.cheats.cheats.push(decoded_cheat);
} else {
println!("Could not find cheat: {}", cheat_setting.0);
eprintln!("Could not find cheat: {}", cheat_setting.0);
}
}
if !device.cheats.cheats.is_empty() {
device.cheats.enabled = true;
ui::video::onscreen_message(&device.ui, "Cheats enabled");
ui::video::onscreen_message("Cheats enabled", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT);
}
}
+183 -88
View File
@@ -10,10 +10,13 @@ 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};
const N64_EXTENSIONS_UNCOMPRESSED: [&str; 8] =
["n64", "v64", "z64", "bin", "N64", "V64", "Z64", "BIN"];
use crate::{cheats, netplay, ui};
use std::{collections::HashMap, fs, io::Read};
use rand::{Rng, SeedableRng};
use crate::{cheats, netplay, retroachievements, savestates, ui};
use std::io::Read;
pub mod ai;
pub mod cache;
@@ -44,24 +47,55 @@ pub mod tlb;
pub mod unmapped;
pub mod vi;
pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, game_settings: ui::gui::GameSettings) {
device.ui.video.fullscreen = game_settings.fullscreen;
pub fn run_game(
device: &mut Device,
rom_contents: &[u8],
game_settings: ui::GameSettings,
ra_config: retroachievements::RAConfig,
netplay_config: Option<netplay::NetplayConfig>,
) {
device.cpu.overclock = game_settings.overclock;
if game_settings.disable_expansion_pak {
device.rdram.size = 0x400000;
}
init_rng(device);
if let Some(slot) = game_settings.load_savestate_slot {
device.ui.storage.save_state_slot = slot;
device.savestate.load_state = true;
}
cart::rom::init(device, rom_contents); // cart needs to come before rdram
// rdram pointer is shared with parallel-rdp
// rdram pointer is shared with parallel-rdp and retroachievements
rdram::init(device);
ui::video::init(device);
ui::audio::init(&mut device.ui, device.ai.freq);
ui::video::init(device, netplay_config.is_some());
ui::audio::init(device);
ui::input::init(&mut device.ui);
// must be after video init
if let Some(netplay_config) = &netplay_config {
device.netplay = netplay::init(
device,
netplay_config,
cart::rom::is_system_pal(rom_contents),
);
if device.netplay.is_none() {
ui::input::close(&mut device.ui);
ui::audio::close(&mut device.ui);
ui::video::close(&device.ui);
return;
}
}
init_rng_rtc(device);
// must be after video init
let (discord_watch_tx, discord_handle) = if ra_config.enabled && device.netplay.is_none() {
retroachievements::load_game(rom_contents, rom_contents.len(), ra_config.rich_presence)
} else {
(None, None)
};
mi::init(device);
pif::init(device);
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
@@ -76,32 +110,53 @@ pub fn run_game(device: &mut Device, rom_contents: Vec<u8>, game_settings: ui::g
ui::storage::init(&mut device.ui, &device.cart.rom);
ui::storage::load_saves(&mut device.ui, &mut device.netplay);
ui::storage::format_saves(device);
cheats::init(device, game_settings.cheats);
if !retroachievements::get_hardcore() {
cheats::init(device, game_settings.cheats);
}
cpu::run(device);
retroachievements::unload_game(discord_watch_tx, discord_handle);
ui::storage::write_saves(device);
if let Some(netplay) = &mut device.netplay {
netplay::close(netplay);
}
ui::input::close(&mut device.ui);
ui::audio::close(&mut device.ui);
ui::video::close(&device.ui);
}
fn set_rng() -> rand_chacha::ChaCha8Rng {
rand_chacha::ChaCha8Rng::from_os_rng()
fn set_rng() -> rand::rngs::Xoshiro256PlusPlus {
rand::rngs::Xoshiro256PlusPlus::try_from_rng(&mut rand::rngs::SysRng).unwrap()
}
fn init_rng(device: &mut Device) {
fn init_rng_rtc(device: &mut Device) {
let mut rng_seed = set_rng().next_u64();
if device.netplay.is_some() {
let netplay = device.netplay.as_mut().unwrap();
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rng(netplay, rng_seed);
} else {
rng_seed = netplay::receive_rng(netplay);
}
}
device.rng = rand_chacha::ChaCha8Rng::seed_from_u64(rng_seed);
device.rng = rand::rngs::Xoshiro256PlusPlus::seed_from_u64(rng_seed);
device.cart.rtc_timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
if let Some(netplay) = &mut device.netplay {
if netplay.player_number == 0 {
netplay::send_rtc(netplay, device.cart.rtc_timestamp);
} else {
device.cart.rtc_timestamp = netplay::receive_rtc(netplay);
}
}
}
fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
@@ -130,36 +185,47 @@ fn swap_rom(contents: Vec<u8>) -> Option<Vec<u8>> {
}
}
pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
pub fn get_rom_contents(file_path: &std::path::PathBuf) -> Option<Vec<u8>> {
let mut contents = vec![];
if file_path.extension().unwrap().eq_ignore_ascii_case("zip") {
let zip_file = fs::File::open(file_path).unwrap();
if file_path
.extension()
.unwrap_or_default()
.eq_ignore_ascii_case("zip")
{
#[cfg(target_os = "android")]
let zip_file = ui::android::get_file_from_uri(file_path).unwrap();
#[cfg(not(target_os = "android"))]
let zip_file = std::fs::File::open(file_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let extension = file
.enclosed_name()
.unwrap()
.extension()
.unwrap()
.to_ascii_lowercase();
if extension == "z64" || extension == "n64" || extension == "v64" {
if let Some(extension) = file.enclosed_name().unwrap().extension()
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
{
file.read_to_end(&mut contents)
.expect("could not read zip file");
break;
}
}
} else if file_path.extension().unwrap().eq_ignore_ascii_case("7z") {
} else if file_path
.extension()
.unwrap_or_default()
.eq_ignore_ascii_case("7z")
{
#[cfg(target_os = "android")]
let sevenz_file = ui::android::get_file_from_uri(file_path).unwrap();
#[cfg(not(target_os = "android"))]
let sevenz_file = std::fs::File::open(file_path).unwrap();
let mut archive =
sevenz_rust2::ArchiveReader::open(file_path, sevenz_rust2::Password::empty()).unwrap();
sevenz_rust2::ArchiveReader::new(sevenz_file, sevenz_rust2::Password::empty()).unwrap();
let mut found = false;
archive
.for_each_entries(
&mut |entry: &sevenz_rust2::ArchiveEntry, reader: &mut dyn std::io::Read| {
let name = entry.name().to_ascii_lowercase();
if !found
&& (name.ends_with("z64") || name.ends_with("n64") || name.ends_with("v64"))
&& let Some(extension) = std::path::PathBuf::from(entry.name()).extension()
&& N64_EXTENSIONS_UNCOMPRESSED.contains(&extension.to_str().unwrap())
{
reader
.read_to_end(&mut contents)
@@ -174,7 +240,11 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
)
.expect("ok");
} else {
contents = fs::read(file_path).expect("Should have been able to read the file");
#[cfg(target_os = "android")]
let mut file = ui::android::get_file_from_uri(file_path).unwrap();
#[cfg(not(target_os = "android"))]
let mut file = std::fs::File::open(file_path).unwrap();
file.read_to_end(&mut contents).unwrap();
}
if contents.is_empty() {
@@ -184,15 +254,28 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SpeedLimiter {
pub limit_freq: u64,
#[serde(skip)]
#[serde(default = "std::time::Instant::now")]
pub limit_freq_check: std::time::Instant,
pub frame_counter: u64,
pub enabled: bool,
pub min_wait_time: std::time::Duration,
#[serde(skip)]
pub next_pace_deadline: Option<std::time::Instant>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Device {
#[serde(skip)]
pub netplay: Option<netplay::Netplay>,
#[serde(skip, default = "ui::Ui::default")]
#[serde(skip)]
pub ui: ui::Ui,
pub speed_limiter: SpeedLimiter,
pub byte_swap: usize,
pub save_state: bool,
pub load_state: bool,
pub savestate: savestates::Savestate,
pub cpu: cpu::Cpu,
pub pif: pif::Pif,
pub cart: cart::Cart,
@@ -206,10 +289,8 @@ pub struct Device {
pub ai: ai::Ai,
pub si: si::Si,
pub ri: ri::Ri,
#[serde(skip, default = "set_rng")]
pub rng: rand_chacha::ChaCha8Rng,
pub rng: rand::rngs::Xoshiro256PlusPlus,
pub vru: controller::vru::Vru,
pub vru_window: controller::vru::VruWindow,
pub transferpaks: [controller::transferpak::TransferPak; 4],
pub cheats: cheats::Cheats,
}
@@ -219,7 +300,27 @@ pub fn zero_m128i() -> __m128i {
}
impl Device {
pub fn new() -> Device {
pub fn clone_state(&mut self, device: &Device) {
self.rng.clone_from(&device.rng);
self.cpu.clone_from(&device.cpu);
self.pif.clone_from(&device.pif);
self.cart.clone_from(&device.cart);
self.memory.clone_from(&device.memory);
self.rsp.clone_from(&device.rsp);
self.rdp.clone_from(&device.rdp);
self.rdram.clone_from(&device.rdram);
self.mi.clone_from(&device.mi);
self.pi.clone_from(&device.pi);
self.vi.clone_from(&device.vi);
self.ai.clone_from(&device.ai);
self.si.clone_from(&device.si);
self.ri.clone_from(&device.ri);
self.vru.clone_from(&device.vru);
self.transferpaks.clone_from(&device.transferpaks);
self.cheats.clone_from(&device.cheats);
}
pub fn new(with_ui: bool) -> Box<Device> {
let mut byte_swap: usize = 0;
let test: [u8; 4] = [1, 2, 3, 4];
// if the host computer is little endian, that means the RDRAM will be stored as little endian
@@ -227,35 +328,41 @@ impl Device {
if u32::from_le_bytes(test) == u32::from_ne_bytes(test) {
byte_swap = 3;
}
Device {
Box::new(Device {
netplay: None,
ui: ui::Ui::new(),
ui: if with_ui {
ui::Ui::new()
} else {
ui::Ui::default()
},
speed_limiter: SpeedLimiter {
limit_freq: 2,
limit_freq_check: std::time::Instant::now(),
frame_counter: 0,
enabled: true,
min_wait_time: std::time::Duration::from_secs(1),
next_pace_deadline: None,
},
byte_swap,
save_state: false,
load_state: false,
savestate: savestates::Savestate {
save_state: false,
load_state: false,
load_rewind: false,
last_rewind_saved: 0.0,
rewind_pool: std::sync::Arc::new(tokio::sync::Mutex::new(
std::collections::BTreeMap::new(),
)),
},
cpu: cpu::Cpu {
cop0: cop0::Cop0 {
regs: [0; cop0::COP0_REGS_COUNT as usize],
reg_write_masks: [0; cop0::COP0_REGS_COUNT as usize],
regs: [0; cop0::COP0_REGS_COUNT],
reg_write_masks: [0; cop0::COP0_REGS_COUNT],
reg_latch: 0,
is_event: false,
pending_compare_interrupt: false,
instrs: [cop0::reserved; 32],
instrs2: [cop0::reserved; 32],
tlb_lut_w: vec![
tlb::TlbLut {
address: 0,
cached: false,
};
0x100000
],
tlb_lut_r: vec![
tlb::TlbLut {
address: 0,
cached: false,
};
0x100000
],
tlb_lut_w: rustc_hash::FxHashMap::default(),
tlb_lut_r: rustc_hash::FxHashMap::default(),
tlb_entries: [tlb::TlbEntry {
mask: 0,
vpn2: 0,
@@ -322,7 +429,6 @@ impl Device {
pif: pif::Pif {
rom: [0; 1984],
ram: [0; 64],
input_gathered: false,
channels: [pif::PifChannel {
tx: None,
tx_buf: None,
@@ -342,9 +448,9 @@ impl Device {
rtc_timestamp: 0,
rtc: cart::AfRtc { control: 0x0200 },
sc64: cart::sc64::Sc64 {
regs: [0; cart::sc64::SC64_REGS_COUNT as usize],
regs: [0; cart::sc64::SC64_REGS_COUNT],
regs_locked: true,
cfg: [0; cart::sc64::SC64_CFG_COUNT as usize],
cfg: [0; cart::sc64::SC64_CFG_COUNT],
sector: 0,
buffer: vec![0; 8192],
writeback_sector: vec![0; 256],
@@ -380,7 +486,7 @@ impl Device {
rdram: rdram::Rdram {
mem: vec![],
size: 0x800000,
regs: [[0; rdram::RDRAM_REGS_COUNT as usize]; 4],
regs: [[0; rdram::RDRAM_REGS_COUNT]; 4],
},
rsp: rsp_interface::Rsp {
cpu: rsp_cpu::Cpu {
@@ -425,8 +531,8 @@ impl Device {
instrs: [rsp_su_instructions::reserved; 64],
vec_instrs: [rsp_vu_instructions::reserved; 64],
},
regs: [0; rsp_interface::SP_REGS_COUNT as usize],
regs2: [0; rsp_interface::SP_REGS2_COUNT as usize],
regs: [0; rsp_interface::SP_REGS_COUNT],
regs2: [0; rsp_interface::SP_REGS2_COUNT],
mem: [0; 0x2000],
last_status_value: 0,
run_after_dma: false,
@@ -438,19 +544,19 @@ impl Device {
}; 2],
},
rdp: rdp::Rdp {
regs_dpc: [0; rdp::DPC_REGS_COUNT as usize],
regs_dps: [0; rdp::DPS_REGS_COUNT as usize],
regs_dpc: [0; rdp::DPC_REGS_COUNT],
regs_dps: [0; rdp::DPS_REGS_COUNT],
wait_frozen: false,
last_status_value: 0,
},
mi: mi::Mi {
regs: [0; mi::MI_REGS_COUNT as usize],
regs: [0; mi::MI_REGS_COUNT],
},
pi: pi::Pi {
regs: [0; pi::PI_REGS_COUNT as usize],
regs: [0; pi::PI_REGS_COUNT],
},
ai: ai::Ai {
regs: [0; ai::AI_REGS_COUNT as usize],
regs: [0; ai::AI_REGS_COUNT],
last_read: 0,
freq: 33600,
delayed_carry: false,
@@ -461,32 +567,21 @@ impl Device {
}; 2],
},
si: si::Si {
regs: [0; si::SI_REGS_COUNT as usize],
regs: [0; si::SI_REGS_COUNT],
dma_dir: si::DmaDir::None,
},
ri: ri::Ri {
regs: [0; ri::RI_REGS_COUNT as usize],
regs: [0; ri::RI_REGS_COUNT],
ram_init: false,
},
vi: vi::Vi {
regs: [0; vi::VI_REGS_COUNT as usize],
regs: [0; vi::VI_REGS_COUNT],
clock: 0,
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(),
},
vru_window: controller::vru::VruWindow {
window_notifier: None,
word_receiver: None,
elapsed_time: 0.0,
},
vru: controller::vru::Vru {
status: 0,
@@ -496,7 +591,7 @@ impl Device {
word_buffer: [0; 40],
words: Vec::new(),
talking: false,
word_mappings: HashMap::new(),
word_mappings: rustc_hash::FxHashMap::default(),
},
rng: set_rng(),
transferpaks: [
@@ -510,6 +605,6 @@ impl Device {
boot: true,
enabled: false,
},
}
})
}
}
+28 -28
View File
@@ -1,19 +1,19 @@
use crate::{device, ui};
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;
const AI_DRAM_ADDR_REG: usize = 0;
const AI_LEN_REG: usize = 1;
// const AI_CONTROL_REG: usize = 2;
pub const AI_STATUS_REG: usize = 3;
const AI_DACRATE_REG: usize = 4;
// const AI_BITRATE_REG: usize = 5;
pub const AI_REGS_COUNT: usize = 6;
const AI_STATUS_BUSY: u32 = 0x40000000;
pub const AI_STATUS_BUSY: u32 = 0x40000000;
const AI_STATUS_FULL: u32 = 0x80000000;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Ai {
pub regs: [u32; AI_REGS_COUNT as usize],
pub regs: [u32; AI_REGS_COUNT],
pub fifo: [AiDma; 2],
pub last_read: u64,
pub delayed_carry: bool,
@@ -38,18 +38,17 @@ fn get_remaining_dma_length(device: &device::Device) -> u64 {
}
let remaining_dma_duration =
next_ai_event.unwrap().count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize];
next_ai_event.unwrap().count - device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG];
let dma_length = remaining_dma_duration * device.ai.fifo[0].length / device.ai.fifo[0].duration;
dma_length & !7
}
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;
let length = (device.ai.regs[AI_LEN_REG] & !7) as u64;
length * (device.cpu.clock_rate / (bytes_per_sample * samples_per_sec))
(length * device.cpu.clock_rate) / (bytes_per_sample * device.ai.freq)
}
fn do_dma(device: &mut device::Device) {
@@ -74,34 +73,36 @@ fn do_dma(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 {
if (device.ai.regs[AI_STATUS_REG] & AI_STATUS_BUSY) != 0 {
device.ai.fifo[1].address =
device.ai.regs[AI_DRAM_ADDR_REG as usize] as u64 & device::rdram::RDRAM_MASK as u64;
device.ai.fifo[1].length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
device.ai.regs[AI_DRAM_ADDR_REG] as u64 & device::rdram::RDRAM_MASK as u64;
device.ai.fifo[1].length = (device.ai.regs[AI_LEN_REG] & !7) as u64;
device.ai.fifo[1].duration = duration;
device.ai.regs[AI_STATUS_REG as usize] |= AI_STATUS_FULL;
device.ai.regs[AI_STATUS_REG] |= AI_STATUS_FULL;
} else {
device.ai.fifo[0].address =
device.ai.regs[AI_DRAM_ADDR_REG as usize] as u64 & device::rdram::RDRAM_MASK as u64;
device.ai.fifo[0].length = (device.ai.regs[AI_LEN_REG as usize] & !7) as u64;
device.ai.regs[AI_DRAM_ADDR_REG] as u64 & device::rdram::RDRAM_MASK as u64;
device.ai.fifo[0].length = (device.ai.regs[AI_LEN_REG] & !7) as u64;
device.ai.fifo[0].duration = duration;
device.ai.regs[AI_STATUS_REG as usize] |= AI_STATUS_BUSY;
device.ai.regs[AI_STATUS_REG] |= AI_STATUS_BUSY;
do_dma(device);
ui::audio::resume_game_audio(&mut device.ui);
}
}
fn fifo_pop(device: &mut device::Device) {
if device.ai.regs[AI_STATUS_REG as usize] & AI_STATUS_FULL != 0 {
if device.ai.regs[AI_STATUS_REG] & AI_STATUS_FULL != 0 {
device.ai.fifo[0].address = device.ai.fifo[1].address;
device.ai.fifo[0].length = device.ai.fifo[1].length;
device.ai.fifo[0].duration = device.ai.fifo[1].duration;
device.ai.regs[AI_STATUS_REG as usize] &= !AI_STATUS_FULL;
device.ai.regs[AI_STATUS_REG] &= !AI_STATUS_FULL;
do_dma(device);
} else {
device.ai.regs[AI_STATUS_REG as usize] &= !AI_STATUS_BUSY;
device.ai.regs[AI_STATUS_REG] &= !AI_STATUS_BUSY;
device.ai.delayed_carry = false;
ui::audio::pause_game_audio(&mut device.ui);
}
}
@@ -111,7 +112,7 @@ pub fn read_regs(
_access_size: device::memory::AccessSize,
) -> u32 {
let reg = (address & 0xFFFF) >> 2;
let return_value = match reg as u32 {
let return_value = match reg as usize {
AI_LEN_REG => {
let value = get_remaining_dma_length(device);
if value < device.ai.last_read {
@@ -135,7 +136,7 @@ pub fn read_regs(
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
AI_LEN_REG => {
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask);
if device.ai.regs[reg as usize] != 0 {
@@ -146,8 +147,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
AI_DACRATE_REG => {
if device.ai.regs[reg as usize] != value & mask {
device.ai.freq = device.vi.clock / (1 + (value & mask)) as u64;
ui::audio::close_game_audio(&mut device.ui);
ui::audio::init_game_audio(&mut device.ui, device.ai.freq)
ui::audio::update_freq(device);
}
device::memory::masked_write_32(&mut device.ai.regs[reg as usize], value, mask)
}
+7 -10
View File
@@ -21,7 +21,7 @@ const JDT_EEPROM_16K: u16 = 0xc000; /* 16k EEPROM */
const EEPROM_BLOCK_SIZE: usize = 8;
pub const EEPROM_MAX_SIZE: usize = 0x800;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct AfRtc {
pub control: u16,
}
@@ -31,7 +31,7 @@ fn byte2bcd(mut n: u32) -> u8 {
(((n / 10) << 4) | (n % 10)) as u8
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cart {
#[serde(skip)]
pub rom: Vec<u8>,
@@ -130,8 +130,6 @@ pub fn format_eeprom(device: &mut device::Device) {
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.storage.saves.eeprom.data[address..address + EEPROM_BLOCK_SIZE],
);
@@ -140,20 +138,19 @@ fn eeprom_read_block(device: &mut device::Device, block: usize, offset: 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.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.storage.saves.eeprom.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
}
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();
let timestamp = device.cart.rtc_timestamp + device.vi.elapsed_time as i64;
let now = chrono::DateTime::from_timestamp(timestamp, 0)
.unwrap()
.with_timezone(&chrono::Local);
device.pif.ram[offset] = byte2bcd(now.second());
device.pif.ram[offset + 1] = byte2bcd(now.minute());
+33 -52
View File
@@ -1,26 +1,18 @@
use crate::device;
use crate::netplay;
use crate::ui;
use sha2::{Digest, Sha256};
pub const CART_MASK: usize = 0xFFFFFFF;
fn read_cart_word(device: &device::Device, address: usize) -> u32 {
let mut data: [u8; 4] = [0; 4];
for i in 0..4 {
if let Some(value) = device
.ui
.storage
.saves
.romsave
.data
.get(&(address as u32 + i))
{
data[i as usize] = *value;
} else {
data[i as usize] = *device.cart.rom.get(address + i as usize).unwrap_or(&0);
}
}
u32::from_be_bytes(data)
let romsave = &device.ui.storage.saves.romsave.data;
let rom = &device.cart.rom;
u32::from_be_bytes(std::array::from_fn(|i| {
romsave
.get(&((address + i) as u32))
.copied()
.unwrap_or_else(|| *rom.get(address + i).unwrap_or(&0))
}))
}
pub fn read_mem_fast(
@@ -40,8 +32,8 @@ pub fn read_mem(
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 {
// well known cart ROM oddity, if a read is performed 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] & device::pi::PI_STATUS_IO_BUSY != 0 {
device.cart.latch
} else {
let masked_address = address as usize & CART_MASK;
@@ -63,12 +55,12 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
.data
.insert((masked_address + i) as u32, *item);
}
device.ui.storage.saves.romsave.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
}
device.cart.latch = value & mask;
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
device.pi.regs[device::pi::PI_STATUS_REG] |= 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);
@@ -94,7 +86,7 @@ pub fn dma_read(
.unwrap_or(&0),
);
}
device.ui.storage.saves.romsave.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Romsave);
}
device::pi::calculate_cycles(device, 1, length)
@@ -112,19 +104,15 @@ pub fn dma_write(
let mut i = dram_addr;
let mut j = cart_addr;
while i < dram_addr + length {
if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *value;
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = if let Some(value) = device.ui.storage.saves.romsave.data.get(&j) {
*value
} else {
*device
.rdram
.mem
.get_mut(i as usize ^ device.byte_swap)
.unwrap_or(&mut 0) = *device.cart.rom.get(j as usize).unwrap_or(&0);
}
*device.cart.rom.get(j as usize).unwrap_or(&0)
};
i += 1;
j += 1;
}
@@ -132,22 +120,11 @@ pub fn dma_write(
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);
}
}
pub fn init(device: &mut device::Device, rom_file: &[u8]) {
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]);
device.cart.rom = rom_file.to_vec();
device.cart.pal = is_system_pal(&device.cart.rom);
set_cic(device);
device.ui.game_hash = calculate_hash(&device.cart.rom);
@@ -158,10 +135,10 @@ pub fn init(device: &mut device::Device, rom_file: Vec<u8>) {
}
}
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'];
pub fn is_system_pal(rom_contents: &[u8]) -> bool {
let pal_codes: [u8; 8] = *b"DFIPSUXY";
for i in pal_codes {
if country == i {
if rom_contents[0x3E] == i {
return true;
}
}
@@ -200,5 +177,9 @@ fn set_cic(device: &mut device::Device) {
pub fn calculate_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:X}", hasher.finalize())
hasher
.finalize()
.iter()
.map(|b| format!("{:02X}", b))
.collect()
}
+51 -71
View File
@@ -3,29 +3,29 @@ 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;
const SC64_SCR_REG: usize = 0;
const SC64_DATA0_REG: usize = 1;
const SC64_DATA1_REG: usize = 2;
const SC64_IDENTIFIER_REG: usize = 3;
const SC64_KEY_REG: usize = 4;
//const SC64_IRQ_REG: usize = 5;
//const SC64_AUX_REG: usize = 6;
pub const SC64_REGS_COUNT: usize = 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;
pub const SC64_CFG_COUNT: usize = 15;
const SC64_BUFFER_MASK: usize = 0x1FFF;
const SC64_EEPROM_MASK: usize = 0xFFF;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Sc64 {
pub buffer: Vec<u8>,
pub regs: [u32; SC64_REGS_COUNT as usize],
pub regs: [u32; SC64_REGS_COUNT],
pub regs_locked: bool,
pub cfg: [u32; SC64_CFG_COUNT as usize],
pub cfg: [u32; SC64_CFG_COUNT],
pub sector: u32,
pub writeback_sector: Vec<u32>,
pub usb_buffer: Vec<u8>,
@@ -49,7 +49,7 @@ pub fn read_regs(
return 0;
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
SC64_SCR_REG | SC64_DATA0_REG | SC64_DATA1_REG => device.cart.sc64.regs[reg as usize],
SC64_IDENTIFIER_REG => 0x53437632,
_ => panic!("unknown read reg {reg} address {address:#x}"),
@@ -58,12 +58,12 @@ pub fn read_regs(
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
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 {
if device.cart.sc64.regs[SC64_KEY_REG] == 0x4F434B5F {
device.cart.sc64.regs_locked = false;
} else if device.cart.sc64.regs[SC64_KEY_REG as usize] == 0xFFFFFFFF {
} else if device.cart.sc64.regs[SC64_KEY_REG] == 0xFFFFFFFF {
device.cart.sc64.regs_locked = true;
}
}
@@ -81,22 +81,22 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
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;
device.cart.sc64.regs[SC64_DATA0_REG] = (2 << 16) | 20;
device.cart.sc64.regs[SC64_DATA1_REG] = 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]
device.cart.sc64.regs[SC64_DATA1_REG] =
device.cart.sc64.cfg[device.cart.sc64.regs[SC64_DATA0_REG] as usize]
}
'C' => {
// set config
if device.cart.sc64.regs[SC64_DATA0_REG as usize] == SC64_SAVE_TYPE {
if device.cart.sc64.regs[SC64_DATA0_REG] == 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] {
match device.cart.sc64.regs[SC64_DATA1_REG] {
0 => {
vec![]
}
@@ -115,20 +115,20 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => {
panic!(
"unknown sc64 save type: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
device.cart.sc64.regs[SC64_DATA1_REG]
)
}
}
}
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],
[device.cart.sc64.regs[SC64_DATA0_REG] as usize],
&mut device.cart.sc64.regs[SC64_DATA1_REG],
);
}
'i' => {
// sd card operation
match device.cart.sc64.regs[SC64_DATA1_REG as usize] {
match device.cart.sc64.regs[SC64_DATA1_REG] {
0 => { //Init SD card
}
1 => { //Deinit SD card
@@ -136,23 +136,21 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
_ => {
panic!(
"unknown sc64 sd card operation: {}",
device.cart.sc64.regs[SC64_DATA1_REG as usize]
device.cart.sc64.regs[SC64_DATA1_REG]
)
}
}
}
'I' => {
// set sd sector
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG as usize];
device.cart.sc64.sector = device.cart.sc64.regs[SC64_DATA0_REG];
}
's' => {
format_sdcard(device);
// read sd card
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let address = device.cart.sc64.regs[SC64_DATA0_REG] 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 length = (device.cart.sc64.regs[SC64_DATA1_REG] * 512) as usize;
let mut i = 0;
while i < length {
@@ -182,11 +180,9 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
'S' => {
format_sdcard(device);
// write sd card
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let address = device.cart.sc64.regs[SC64_DATA0_REG] 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 length = (device.cart.sc64.regs[SC64_DATA1_REG] * 512) as usize;
let mut i = 0;
while i < length {
@@ -208,45 +204,35 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
.copy_from_slice(&data);
i += 4;
}
device.ui.storage.saves.sdcard.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sdcard);
}
'U' => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
device.cart.sc64.regs[SC64_DATA0_REG] = 0;
}
'u' => {
// used to notify the game that there is data to read
if let Some(cart_rx) = device.ui.usb.cart_rx.as_mut() {
match cart_rx.try_recv() {
Ok(data) => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] =
data.data_type & 0xFF; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] =
device.cart.sc64.regs[SC64_DATA0_REG] = data.data_type & 0xFF; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG] =
data.data_size & 0xFFFFFF; // length
device.cart.sc64.usb_buffer = data.data; // store the data to be read
}
Err(err) => {
match err {
tokio::sync::broadcast::error::TryRecvError::Lagged(_) => {
panic!("cart_rx lagged: {err}");
}
_ => {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
}
}
Err(_err) => {
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG] = 0; // length
}
}
} else {
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG as usize] = 0; // length
device.cart.sc64.regs[SC64_DATA0_REG] = 0; // read_status/type
device.cart.sc64.regs[SC64_DATA1_REG] = 0; // length
}
}
'M' => {
// Send data from from flashcart to USB
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length =
device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize & 0xFFFFFF;
let address = device.cart.sc64.regs[SC64_DATA0_REG] as u64 & 0x1FFFFFFF;
let length = device.cart.sc64.regs[SC64_DATA1_REG] as usize & 0xFFFFFF;
if let Some(usb_tx) = device.ui.usb.usb_tx.as_mut() {
let mut usb_buffer = vec![0; length];
@@ -297,18 +283,16 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
usb_tx,
ui::usb::UsbData {
data: usb_buffer,
data_type: device.cart.sc64.regs[SC64_DATA1_REG as usize] >> 24,
data_size: device.cart.sc64.regs[SC64_DATA1_REG as usize]
& 0xFFFFFF,
data_type: device.cart.sc64.regs[SC64_DATA1_REG] >> 24,
data_size: device.cart.sc64.regs[SC64_DATA1_REG] & 0xFFFFFF,
},
);
}
}
'm' => {
// Receive data from USB to flashcart
let address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64 & 0x1FFFFFFF;
let length = device.cart.sc64.regs[SC64_DATA1_REG as usize] as usize;
let address = device.cart.sc64.regs[SC64_DATA0_REG] as u64 & 0x1FFFFFFF;
let length = device.cart.sc64.regs[SC64_DATA1_REG] as usize;
let mut i = 0;
@@ -338,11 +322,11 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
'w' => {
// SD card writeback pending
device.cart.sc64.regs[SC64_DATA0_REG as usize] = 0;
device.cart.sc64.regs[SC64_DATA0_REG] = 0;
}
'W' => {
let writeback_sectors_address =
device.cart.sc64.regs[SC64_DATA0_REG as usize] as u64;
device.cart.sc64.regs[SC64_DATA0_REG] as u64;
for i in 0..256 {
device.cart.sc64.writeback_sector[i] = device::memory::data_read(
device,
@@ -376,7 +360,6 @@ pub fn read_mem(
_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]
@@ -395,7 +378,6 @@ pub fn read_mem(
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]
@@ -405,7 +387,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
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
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
} else {
let masked_address = address as usize & SC64_BUFFER_MASK;
let mut data = u32::from_be_bytes(
@@ -427,9 +409,8 @@ pub fn dma_read(
) -> 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;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Eeprom4k);
&mut device.ui.storage.saves.eeprom.data
} else {
cart_addr &= SC64_BUFFER_MASK as u32;
@@ -459,7 +440,6 @@ pub fn dma_write(
) -> 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 {
+10 -22
View File
@@ -8,7 +8,7 @@ pub const MX29L1100_ID: u32 = 0x00c2001e;
const MX29L0000_ID: u32 = 0x00c20000;
const MX29L0001_ID: u32 = 0x00c20001;
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum FlashramMode {
ReadArray,
ReadSiliconId,
@@ -18,7 +18,7 @@ pub enum FlashramMode {
PageProgram,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Flashram {
pub status: u32,
pub mode: FlashramMode,
@@ -28,13 +28,13 @@ pub struct Flashram {
pub silicon_id: [u32; 2],
}
fn format_sram(device: &mut device::Device) {
pub 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)
}
}
fn format_flash(device: &mut device::Device) {
pub fn format_flash(device: &mut device::Device) {
if device.ui.storage.saves.flash.data.len() < FLASHRAM_SIZE {
device
.ui
@@ -49,8 +49,6 @@ fn format_flash(device: &mut device::Device) {
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.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
@@ -96,8 +94,6 @@ pub fn read_mem(
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.storage.saves.sram.data[masked_address..masked_address + 4]
.try_into()
@@ -107,7 +103,7 @@ fn write_mem_sram(device: &mut device::Device, address: u64, value: u32, mask: u
device.ui.storage.saves.sram.data[masked_address..masked_address + 4]
.copy_from_slice(&data.to_be_bytes());
device.ui.storage.saves.sram.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
}
fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask: u32) {
@@ -116,7 +112,6 @@ fn write_mem_flash(device: &mut device::Device, address: u64, value: u32, mask:
device.cart.flashram.status = (value & mask) & 0xff;
} else if (address & 0x1ffff) == 0x10000 {
/* set command */
format_flash(device);
flashram_command(device, value & mask);
} else {
/* other accesses are not implemented */
@@ -136,7 +131,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
write_mem_flash(device, address, value, mask)
}
device.pi.regs[device::pi::PI_STATUS_REG as usize] |= device::pi::PI_STATUS_IO_BUSY;
device.pi.regs[device::pi::PI_STATUS_REG] |= device::pi::PI_STATUS_IO_BUSY;
let cycles = device::pi::calculate_cycles(device, 2, 4);
device::events::create_event(device, device::events::EVENT_TYPE_PI, cycles);
@@ -148,8 +143,6 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
let mut i = dram_addr;
let mut j = cart_addr;
format_sram(device);
while i < dram_addr + length {
*device
.ui
@@ -167,12 +160,10 @@ fn dma_read_sram(device: &mut device::Device, mut cart_addr: u32, mut dram_addr:
j += 1;
}
device.ui.storage.saves.sram.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Sram);
}
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.cart.flashram.mode == FlashramMode::PageProgram
@@ -217,8 +208,6 @@ fn dma_write_sram(
let mut i = dram_addr;
let mut j = cart_addr;
format_sram(device);
while i < dram_addr + length {
*device
.rdram
@@ -266,7 +255,6 @@ fn dma_write_flash(
} else if (cart_addr & 0x1ffff) < 0x10000
&& device.cart.flashram.mode == FlashramMode::ReadArray
{
format_flash(device);
/* adjust flashram address before starting DMA. */
if device.cart.flashram.silicon_id[1] == MX29L1100_ID
|| device.cart.flashram.silicon_id[1] == MX29L0000_ID
@@ -331,12 +319,12 @@ fn flashram_command(device: &mut device::Device, command: u32) {
for i in 0..128 * 128 {
device.ui.storage.saves.flash.data[offset + i] = 0xFF;
}
device.ui.storage.saves.flash.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
} else if device.cart.flashram.mode == FlashramMode::ChipErase {
for i in 0..FLASHRAM_SIZE {
device.ui.storage.saves.flash.data[i] = 0xFF;
}
device.ui.storage.saves.flash.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
} else {
panic!("Unexpected flash erase command")
}
@@ -356,7 +344,7 @@ fn flashram_command(device: &mut device::Device, command: u32) {
for i in 0..128 {
device.ui.storage.saves.flash.data[offset + i] = device.cart.flashram.page_buf[i];
}
device.ui.storage.saves.flash.written = true;
ui::storage::schedule_save(device, ui::storage::SaveTypes::Flash);
/* clear program busy flag, set program success flag, transition to status mode */
device.cart.flashram.status &= !0x01;
+23 -17
View File
@@ -1,4 +1,4 @@
use crate::{device, netplay, savestates, ui};
use crate::{device, savestates, ui};
pub mod gbcart;
pub mod mempak;
@@ -56,15 +56,10 @@ pub fn process(device: &mut device::Device, channel: usize) {
}
JCMD_CONTROLLER_READ => {
let offset = device.pif.channels[channel].rx_buf.unwrap();
let input = if device.netplay.is_none() {
ui::input::get(&device.ui, channel)
let input = if let Some(netplay) = &mut device.netplay {
netplay.inputs[channel].0
} else {
if device.netplay.as_ref().unwrap().player_number as usize == channel {
let local_input = ui::input::get(&device.ui, 0);
netplay::send_input(device.netplay.as_ref().unwrap(), local_input);
}
netplay::get_input(device, channel)
ui::input::get(&mut device.ui, channel)
};
device.pif.ram[offset..offset + 4].copy_from_slice(&input.data.to_ne_bytes());
@@ -77,7 +72,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
device::events::create_event(
device,
device::events::EVENT_TYPE_PAK,
device.cpu.clock_rate, // 1 second
device.cpu.clock_rate / 2, // 500ms
)
}
}
@@ -96,7 +91,7 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.channels[channel].rx_buf.unwrap(),
channel,
),
_ => println!("unknown controller command {cmd}"),
_ => eprintln!("unknown controller command {cmd}"),
}
}
@@ -162,10 +157,12 @@ 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() {
if let Some(netplay) = &device.netplay {
if netplay.player_number == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
}
} else {
device::ui::input::set_rumble(&device.ui, i, 0);
} else if device.netplay.as_ref().unwrap().player_number as usize == i {
device::ui::input::set_rumble(&device.ui, 0, 0);
}
let new_pak_type = match channel.change_pak {
@@ -189,21 +186,30 @@ pub fn pak_switch_event(device: &mut device::Device) {
write: device::controller::mempak::write,
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "MemPak selected");
ui::video::onscreen_message(
&format!("P{} MemPak selected", i + 1),
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
);
} else if new_pak_type == PakType::RumblePak {
channel.pak_handler = Some(device::controller::PakHandler {
read: device::controller::rumble::read,
write: device::controller::rumble::write,
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "RumblePak selected");
ui::video::onscreen_message(
&format!("P{} RumblePak selected", i + 1),
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
);
} else if new_pak_type == PakType::TransferPak {
channel.pak_handler = Some(device::controller::PakHandler {
read: device::controller::transferpak::read,
write: device::controller::transferpak::write,
pak_type: new_pak_type,
});
ui::video::onscreen_message(&device.ui, "TransferPak selected");
ui::video::onscreen_message(
&format!("P{} TransferPak selected", i + 1),
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
);
}
channel.change_pak = PakType::None;
}
+163 -61
View File
@@ -1,6 +1,14 @@
use crate::device;
#[derive(Default, serde::Serialize, serde::Deserialize)]
use std::io::Write;
const MBC3_RTC_SECONDS: usize = 0;
const MBC3_RTC_MINUTES: usize = 1;
const MBC3_RTC_HOURS: usize = 2;
const MBC3_RTC_DAYS_L: usize = 3;
const MBC3_RTC_DAYS_H: usize = 4;
const MBC3_RTC_REGS_COUNT: usize = 5;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct GbCart {
pub enabled: bool,
#[serde(skip)]
@@ -11,14 +19,12 @@ pub struct GbCart {
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,
pub rtc_regs: [u8; MBC3_RTC_REGS_COUNT],
pub rtc_regs_latch: [u8; MBC3_RTC_REGS_COUNT],
pub last_elapsed_time: i64,
}
#[derive(Default, Copy, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Copy, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum CartType {
#[default]
None,
@@ -28,6 +34,135 @@ pub enum CartType {
MBC5RamBatt,
}
pub fn init(gb_cart: &mut device::controller::gbcart::GbCart, rom: &[u8], ram: &[u8]) {
gb_cart.rom = rom.to_vec();
let offset = match ram.len() % 8192 {
44 => {
gb_cart.ram = ram[..ram.len() - 44].to_vec();
ram.len() - 44
}
48 => {
gb_cart.ram = ram[..ram.len() - 48].to_vec();
ram.len() - 48
}
_ => {
gb_cart.ram = ram.to_vec();
return;
}
};
gb_cart.rtc_regs[MBC3_RTC_SECONDS] =
u32::from_le_bytes(ram[offset..offset + 4].try_into().unwrap()) as u8;
gb_cart.rtc_regs[MBC3_RTC_MINUTES] =
u32::from_le_bytes(ram[offset + 4..offset + 8].try_into().unwrap()) as u8;
gb_cart.rtc_regs[MBC3_RTC_HOURS] =
u32::from_le_bytes(ram[offset + 8..offset + 12].try_into().unwrap()) as u8;
gb_cart.rtc_regs[MBC3_RTC_DAYS_L] =
u32::from_le_bytes(ram[offset + 12..offset + 16].try_into().unwrap()) as u8;
gb_cart.rtc_regs[MBC3_RTC_DAYS_H] =
u32::from_le_bytes(ram[offset + 16..offset + 20].try_into().unwrap()) as u8;
gb_cart.rtc_regs_latch[MBC3_RTC_SECONDS] =
u32::from_le_bytes(ram[offset + 20..offset + 24].try_into().unwrap()) as u8;
gb_cart.rtc_regs_latch[MBC3_RTC_MINUTES] =
u32::from_le_bytes(ram[offset + 24..offset + 28].try_into().unwrap()) as u8;
gb_cart.rtc_regs_latch[MBC3_RTC_HOURS] =
u32::from_le_bytes(ram[offset + 28..offset + 32].try_into().unwrap()) as u8;
gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_L] =
u32::from_le_bytes(ram[offset + 32..offset + 36].try_into().unwrap()) as u8;
gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_H] =
u32::from_le_bytes(ram[offset + 36..offset + 40].try_into().unwrap()) as u8;
}
pub fn save(
gb_cart: &mut device::controller::gbcart::GbCart,
elapsed_time: i64,
ram_path: &std::string::String,
) {
if cfg!(target_os = "android") {
return;
}
update_rtc_regs(gb_cart, elapsed_time);
if let Ok(file) = std::fs::File::create(ram_path) {
let mut f = std::io::BufWriter::new(file);
f.write_all(&gb_cart.ram).unwrap();
if gb_cart.cart_type == CartType::MBC3RamBattRtc {
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_SECONDS] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_MINUTES] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_HOURS] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_DAYS_L] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs[MBC3_RTC_DAYS_H] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_SECONDS] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_MINUTES] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_HOURS] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_L] as u32).to_le_bytes())
.unwrap();
f.write_all(&(gb_cart.rtc_regs_latch[MBC3_RTC_DAYS_H] as u32).to_le_bytes())
.unwrap();
let max = ((i32::MAX as i64) << 32) | (i32::MAX as i64);
f.write_all(&max.to_le_bytes()).unwrap();
f.flush().unwrap();
}
} else {
eprintln!("Error saving TransferPak RAM to {ram_path}");
}
}
fn update_rtc_regs(cart: &mut device::controller::gbcart::GbCart, elapsed_time: i64) {
let mut diff = elapsed_time - cart.last_elapsed_time;
cart.last_elapsed_time = elapsed_time;
if diff > 0 {
cart.rtc_regs[MBC3_RTC_SECONDS] += (diff % 60) as u8;
if cart.rtc_regs[MBC3_RTC_SECONDS] >= 60 {
cart.rtc_regs[MBC3_RTC_SECONDS] -= 60;
cart.rtc_regs[MBC3_RTC_MINUTES] += 1;
}
diff /= 60;
cart.rtc_regs[MBC3_RTC_MINUTES] += (diff % 60) as u8;
if cart.rtc_regs[MBC3_RTC_MINUTES] >= 60 {
cart.rtc_regs[MBC3_RTC_MINUTES] -= 60;
cart.rtc_regs[MBC3_RTC_HOURS] += 1;
}
diff /= 60;
cart.rtc_regs[MBC3_RTC_HOURS] += (diff % 24) as u8;
if cart.rtc_regs[MBC3_RTC_HOURS] >= 24 {
cart.rtc_regs[MBC3_RTC_HOURS] -= 24;
cart.rtc_regs[MBC3_RTC_DAYS_L] += 1;
}
diff /= 24;
/* update days counter */
let days = ((((cart.rtc_regs[MBC3_RTC_DAYS_H] & 0x01) as i64) << 8)
| cart.rtc_regs[MBC3_RTC_DAYS_L] as i64)
+ diff;
cart.rtc_regs[MBC3_RTC_DAYS_L] = (days & 0xff) as u8;
cart.rtc_regs[MBC3_RTC_DAYS_H] &= !0x01;
cart.rtc_regs[MBC3_RTC_DAYS_H] |= ((days >> 8) & 1) as u8;
/* set carry bit if days overflow */
if days > 511 {
cart.rtc_regs[MBC3_RTC_DAYS_H] |= 0x80;
}
}
}
fn write_mbc1(
pif_ram: &[u8],
cart: &mut device::controller::gbcart::GbCart,
@@ -105,12 +240,13 @@ fn write_mbc3(
address: u16,
data: usize,
size: usize,
elapsed_time: i64,
) {
let value = pif_ram[data + size - 1];
if address < 0x2000 {
cart.ram_enabled = value & 0xf == 0xa;
} else if address < 0x4000 {
let bank = value & 0x7f;
let bank = value & if cart.rom.len() > 2097152 { 0xff } else { 0x7f };
cart.rom_bank = bank as u32;
if cart.rom_bank == 0 {
cart.rom_bank = 1;
@@ -118,10 +254,10 @@ fn write_mbc3(
} else if address < 0x6000 {
cart.ram_bank = (value & 0xf) as u16;
} else if address < 0x8000 {
if !cart.set_latch && value != 0 {
// not implemented
if cart.cart_type == CartType::MBC3RamBattRtc && (value & 0x1) != 0 {
update_rtc_regs(cart, elapsed_time);
cart.rtc_regs_latch = cart.rtc_regs;
}
cart.set_latch = value != 0;
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
@@ -130,29 +266,8 @@ fn write_mbc3(
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 if cart.cart_type == CartType::MBC3RamBattRtc {
cart.rtc_regs[cart.ram_bank as usize - 0x8] = value;
}
} else {
panic!("Unsupported write address {address:x}");
@@ -161,7 +276,7 @@ fn write_mbc3(
fn read_mbc3(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -182,29 +297,15 @@ fn read_mbc3(
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);
}
};
let banked_address = address - 0xA000 + (cart.ram_bank * 0x2000);
pif_ram[data..data + size].copy_from_slice(
cart.ram
.get(banked_address as usize..banked_address as usize + size)
.unwrap_or(&vec![0; size]),
);
} else if cart.cart_type == CartType::MBC3RamBattRtc {
for i in 0..size {
pif_ram[data + i] = latch;
pif_ram[data + i] = cart.rtc_regs_latch[cart.ram_bank as usize - 0x8];
}
}
} else {
@@ -231,7 +332,7 @@ fn write_mbc5(
} else if address < 0x6000 {
cart.ram_bank = (value & 0xf) as u16;
} else if address < 0xa000 {
println!("Unknown MBC5 write address {address:x}");
eprintln!("Unknown MBC5 write address {address:x}");
} else if (0xa000..0xc000).contains(&address) {
if !cart.ram_enabled {
return;
@@ -278,7 +379,7 @@ fn read_mbc5(
pub fn read(
pif_ram: &mut [u8],
cart: &device::controller::gbcart::GbCart,
cart: &mut device::controller::gbcart::GbCart,
address: u16,
data: usize,
size: usize,
@@ -304,14 +405,15 @@ pub fn write(
address: u16,
data: usize,
size: usize,
elapsed_time: i64,
) {
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::MBC3RamBatt => write_mbc3(pif_ram, cart, address, data, size, elapsed_time),
CartType::MBC3RamBattRtc => write_mbc3(pif_ram, cart, address, data, size, elapsed_time),
CartType::MBC5RamBatt => write_mbc5(pif_ram, cart, address, data, size),
_ => panic!("Unsupported cart type"),
}
+8 -6
View File
@@ -1,9 +1,10 @@
use crate::device;
use crate::ui;
pub const MEMPAK_SIZE: usize = 0x8000;
const MPK_PAGE_SIZE: usize = 256;
fn format_mempak(device: &mut device::Device) {
pub fn format_mempak(device: &mut device::Device) {
if device.ui.storage.saves.mempak.data.len() < MEMPAK_SIZE * 4 {
device
.ui
@@ -42,6 +43,11 @@ fn format_mempak(device: &mut device::Device) {
];
for i in 0..4 {
let offset = i * MEMPAK_SIZE;
if device.ui.storage.saves.mempak.data[offset..offset + MEMPAK_SIZE] != [0; MEMPAK_SIZE]
{
// don't overwrite existing data
continue;
}
/* Fill Page 0 with pre-initialized content */
device.ui.storage.saves.mempak.data[offset..offset + MPK_PAGE_SIZE]
@@ -76,8 +82,6 @@ fn format_mempak(device: &mut device::Device) {
pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if (address as usize) < MEMPAK_SIZE {
format_mempak(device);
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.pif.ram[data..data + size]
.copy_from_slice(&device.ui.storage.saves.mempak.data[offset..offset + size])
@@ -90,12 +94,10 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if (address as usize) < MEMPAK_SIZE {
format_mempak(device);
let offset = (channel * MEMPAK_SIZE) + address as usize;
device.ui.storage.saves.mempak.data[offset..offset + size]
.copy_from_slice(&device.pif.ram[data..data + size]);
device.ui.storage.saves.mempak.written = true
ui::storage::schedule_save(device, ui::storage::SaveTypes::Mempak);
}
}
+6 -3
View File
@@ -15,10 +15,13 @@ pub fn read(device: &mut device::Device, _channel: usize, address: u16, data: us
pub fn write(device: &mut device::Device, channel: usize, address: u16, data: usize, size: usize) {
if address == 0xc000 {
let rumble = device.pif.ram[data + size - 1];
if device.netplay.is_none() {
if let Some(netplay) = &device.netplay {
if netplay.player_number == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
} else {
device::ui::input::set_rumble(&device.ui, channel, rumble);
} else if device.netplay.as_ref().unwrap().player_number as usize == channel {
device::ui::input::set_rumble(&device.ui, 0, rumble);
}
}
}
+6 -5
View File
@@ -1,6 +1,6 @@
use crate::device;
#[derive(Default, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct TransferPak {
pub enabled: bool,
pub reset_state: u8,
@@ -54,14 +54,14 @@ pub fn read(device: &mut device::Device, channel: usize, address: u16, data: usi
0xC..=0xF => {
device::controller::gbcart::read(
&mut device.pif.ram,
&pak.cart,
&mut pak.cart,
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
);
}
_ => {
println!("unknown transfer pak read {:x}", address >> 12);
eprintln!("unknown transfer pak read {:x}", address >> 12);
}
}
}
@@ -84,7 +84,7 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
}
pak.enabled = true;
}
_ => println!("unknown transfer pak value"),
_ => eprintln!("unknown transfer pak value"),
}
return;
}
@@ -124,10 +124,11 @@ pub fn write(device: &mut device::Device, channel: usize, address: u16, data: us
0x4000 * pak.bank + (address & 0x7fff) - 0x4000,
data,
size,
device.vi.elapsed_time as i64,
);
}
_ => {
println!("unknown transfer pak write {:x}", address >> 12);
eprintln!("unknown transfer pak write {:x}", address >> 12);
}
}
}
+11 -23
View File
@@ -1,5 +1,6 @@
use crate::{device, ui};
use std::collections::HashMap;
use crate::device;
#[cfg(all(feature = "gui", not(target_os = "android")))]
use crate::ui;
const JCMD_VRU_READ: u8 = 0x09;
const JCMD_VRU_WRITE: u8 = 0x0A;
@@ -16,7 +17,7 @@ const VOICE_STATUS_BUSY: u8 = 0x05;
const JDT_VRU: u16 = 0x0100; /* VRU */
const CONT_FLAVOR: u16 = JDT_VRU;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Vru {
pub status: u8,
pub voice_state: u8,
@@ -26,15 +27,7 @@ pub struct Vru {
pub word_buffer: [u16; 40],
pub words: Vec<String>,
pub talking: bool,
pub word_mappings: HashMap<String, String>,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct VruWindow {
#[serde(skip)]
pub window_notifier: Option<tokio::sync::mpsc::Sender<Option<Vec<String>>>>,
#[serde(skip)]
pub word_receiver: Option<tokio::sync::mpsc::Receiver<String>>,
pub word_mappings: rustc_hash::FxHashMap<String, String>,
}
pub fn init(device: &mut device::Device) {
@@ -187,17 +180,12 @@ pub fn process(device: &mut device::Device, channel: usize) {
device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0;
}
JCMD_VRU_READ => {
let index = if device.vru_window.window_notifier.is_some() {
ui::vru::prompt_for_match(
&device.vru.words,
device.vru_window.window_notifier.as_ref().unwrap(),
device.vru_window.word_receiver.as_mut().unwrap(),
)
} else {
0x7FFF
};
#[cfg(all(feature = "gui", not(target_os = "android")))]
let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time);
#[cfg(any(not(feature = "gui"), target_os = "android"))]
let index = 0x7FFF;
let num_results = if index == 0x7FFF { 0 } else { 1 };
let data: HashMap<usize, u16> = HashMap::from([
let data: rustc_hash::FxHashMap<usize, u16> = rustc_hash::FxHashMap::from_iter([
(0, 0x8000),
(2, 0x0F00),
(4, 0), // error flags
@@ -258,7 +246,7 @@ pub fn vru_talking_event(device: &mut device::Device) {
}
fn create_word_mappings(device: &mut device::Device) {
device.vru.word_mappings = HashMap::from([
device.vru.word_mappings = rustc_hash::FxHashMap::from_iter([
(
String::from("03A50024000303CF00A80003036000EA"),
String::from("pikachu"),
+111 -110
View File
@@ -1,42 +1,44 @@
use crate::{device, savestates};
pub const COP0_INDEX_REG: u32 = 0;
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;
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;
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;
const COP0_PREVID_REG: u32 = 15;
const COP0_CONFIG_REG: u32 = 16;
pub const COP0_LLADDR_REG: u32 = 17;
//const COP0_WATCHLO_REG: u32 = 18;
//const COP0_WATCHHI_REG: u32 = 19;
pub const COP0_XCONTEXT_REG: u32 = 20;
//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;
//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_INDEX_REG: usize = 0;
const COP0_RANDOM_REG: usize = 1;
pub const COP0_ENTRYLO0_REG: usize = 2;
pub const COP0_ENTRYLO1_REG: usize = 3;
pub const COP0_CONTEXT_REG: usize = 4;
pub const COP0_PAGEMASK_REG: usize = 5;
const COP0_WIRED_REG: usize = 6;
//const COP0_UNUSED_7: usize = 7;
pub const COP0_BADVADDR_REG: usize = 8;
pub const COP0_COUNT_REG: usize = 9;
pub const COP0_ENTRYHI_REG: usize = 10;
const COP0_COMPARE_REG: usize = 11;
pub const COP0_STATUS_REG: usize = 12;
pub const COP0_CAUSE_REG: usize = 13;
pub const COP0_EPC_REG: usize = 14;
const COP0_PREVID_REG: usize = 15;
const COP0_CONFIG_REG: usize = 16;
pub const COP0_LLADDR_REG: usize = 17;
//const COP0_WATCHLO_REG: usize = 18;
//const COP0_WATCHHI_REG: usize = 19;
pub const COP0_XCONTEXT_REG: usize = 20;
//const COP0_UNUSED_21: usize = 21;
//const COP0_UNUSED_22: usize = 22;
//const COP0_UNUSED_23: usize = 23;
//const COP0_UNUSED_24: usize = 24;
//const COP0_UNUSED_25: usize = 25;
//const COP0_PARITYERR_REG: usize = 26;
//const COP0_CACHEERR_REG: usize = 27;
pub const COP0_TAGLO_REG: usize = 28;
//const COP0_TAGHI_REG: usize = 29;
pub const COP0_ERROREPC_REG: usize = 30;
//const COP0_UNUSED_31: usize = 31;
pub const COP0_REGS_COUNT: usize = 32;
pub const COP0_STATUS_IE: u64 = 1 << 0;
pub const COP0_STATUS_EXL: u64 = 1 << 1;
pub const COP0_STATUS_ERL: u64 = 1 << 2;
pub const COP0_STATUS_SR: u64 = 1 << 20;
pub const COP0_STATUS_TS: u64 = 1 << 21;
pub const COP0_STATUS_BEV: u64 = 1 << 22;
pub const COP0_STATUS_FR: u64 = 1 << 26;
pub const COP0_STATUS_CU1: u64 = 1 << 29;
@@ -55,6 +57,7 @@ pub const COP0_CAUSE_EXCCODE_CPU: u64 = 11 << 2;
pub const COP0_CAUSE_EXCCODE_TR: u64 = 13 << 2;
pub const COP0_CAUSE_EXCCODE_FPE: u64 = 15 << 2;
pub const COP0_CAUSE_IP2: u64 = 1 << 10;
pub const COP0_CAUSE_IP4: u64 = 1 << 12;
pub const COP0_CAUSE_IP7: u64 = 1 << 15;
pub const COP0_CAUSE_BD: u64 = 1 << 31;
@@ -62,7 +65,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,20 +91,19 @@ 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)]
#[derive(Clone, 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],
pub regs: [u64; COP0_REGS_COUNT],
pub reg_write_masks: [u64; COP0_REGS_COUNT],
#[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_lut_r: rustc_hash::FxHashMap<u64, device::tlb::TlbLut>,
pub tlb_lut_w: rustc_hash::FxHashMap<u64, device::tlb::TlbLut>,
pub tlb_entries: [device::tlb::TlbEntry; 32],
pub is_event: bool,
pub pending_compare_interrupt: bool,
}
fn mfc0(device: &mut device::Device, opcode: u32) {
@@ -134,11 +136,11 @@ fn dmtc0(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])
device::tlb::read(device, device.cpu.cop0.regs[COP0_INDEX_REG])
}
fn tlbwi(device: &mut device::Device, _opcode: u32) {
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG as usize])
device::tlb::write(device, device.cpu.cop0.regs[COP0_INDEX_REG])
}
fn tlbwr(device: &mut device::Device, _opcode: u32) {
@@ -151,12 +153,12 @@ fn tlbp(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
if device.cpu.cop0.regs[COP0_STATUS_REG] & COP0_STATUS_ERL != 0 {
device.cpu.pc = device.cpu.cop0.regs[COP0_ERROREPC_REG];
device.cpu.cop0.regs[COP0_STATUS_REG] &= !COP0_STATUS_ERL
} else {
device.cpu.pc = device.cpu.cop0.regs[COP0_EPC_REG as usize];
device.cpu.cop0.regs[COP0_STATUS_REG as usize] &= !COP0_STATUS_EXL
device.cpu.pc = device.cpu.cop0.regs[COP0_EPC_REG];
device.cpu.cop0.regs[COP0_STATUS_REG] &= !COP0_STATUS_EXL
}
device.cpu.branch_state.state = device::cpu::State::Exception;
device.cpu.llbit = false;
@@ -179,8 +181,10 @@ pub fn reserved(device: &mut device::Device, _opcode: u32) {
device::exceptions::reserved_exception(device, 0);
}
pub fn emux_xdetect(_device: &mut device::Device, _opcode: u32) {}
fn get_control_registers(device: &device::Device, index: u32) -> u64 {
match index {
match index as usize {
COP0_COUNT_REG => device.cpu.cop0.regs[index as usize] >> 1,
COP0_RANDOM_REG => set_random_register(device),
7 | 21 | 22 | 23 | 24 | 25 | 31 => device.cpu.cop0.reg_latch,
@@ -190,22 +194,18 @@ fn get_control_registers(device: &device::Device, index: u32) -> u64 {
fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64) {
device.cpu.cop0.reg_latch = data;
match index {
match index as usize {
COP0_COUNT_REG => {
data &= 0xFFFFFFFF;
data <<= 1;
device::events::translate_events(
device,
device.cpu.cop0.regs[COP0_COUNT_REG as usize],
data,
);
device.cpu.cop0.regs[COP0_COUNT_REG as usize] = data;
device::events::translate_events(device, device.cpu.cop0.regs[COP0_COUNT_REG], data);
device.cpu.cop0.regs[COP0_COUNT_REG] = data;
return;
}
COP0_WIRED_REG => device.cpu.cop0.regs[COP0_RANDOM_REG as usize] = 31,
COP0_WIRED_REG => device.cpu.cop0.regs[COP0_RANDOM_REG] = 31,
COP0_COMPARE_REG => {
data &= 0xFFFFFFFF;
let current_count = (device.cpu.cop0.regs[COP0_COUNT_REG as usize] >> 1) & 0xFFFFFFFF;
let current_count = (device.cpu.cop0.regs[COP0_COUNT_REG] >> 1) & 0xFFFFFFFF;
let mut compare_event_diff = (data as u32).wrapping_sub(current_count as u32);
if compare_event_diff == 0 {
@@ -217,13 +217,12 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
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;
device.cpu.cop0.regs[COP0_CAUSE_REG] &= !COP0_CAUSE_IP7;
}
COP0_STATUS_REG => {
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR {
device::cop1::set_fgr_registers(device, data)
}
COP0_STATUS_REG
if data & COP0_STATUS_FR != device.cpu.cop0.regs[index as usize] & COP0_STATUS_FR =>
{
device::cop1::set_fgr_registers(device, data)
}
_ => {}
}
@@ -236,7 +235,9 @@ fn set_control_registers(device: &mut device::Device, index: u32, mut data: u64)
}
pub fn compare_event(device: &mut device::Device) {
device.cpu.cop0.pending_compare_interrupt = true;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_IP7;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_EXCCODE_MASK;
device::events::create_event_at(
device,
device::events::EVENT_TYPE_COMPARE,
@@ -246,53 +247,53 @@ pub fn compare_event(device: &mut device::Device) {
}
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
if device.cpu.cop0.regs[COP0_WIRED_REG] > 31 {
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG]) & 0x3F
} else {
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG as usize])
% (32 - device.cpu.cop0.regs[COP0_WIRED_REG as usize])
+ device.cpu.cop0.regs[COP0_WIRED_REG as usize]
(u64::MAX - device.cpu.cop0.regs[COP0_COUNT_REG])
% (32 - device.cpu.cop0.regs[COP0_WIRED_REG])
+ device.cpu.cop0.regs[COP0_WIRED_REG]
}
}
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
device.cpu.cop0.regs[COP0_COUNT_REG] += cycles // COUNT_REG is shifted right 1 bit when read by MFC0
}
pub fn map_instructions(device: &mut device::Device) {
device.cpu.cop0.instrs = [
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
device::cop0::reserved, // 2
device::cop0::reserved, // 3
device::cop0::mtc0, // 4
device::cop0::dmtc0, // 5
device::cop0::reserved, // 6
device::cop0::reserved, // 7
device::cop0::reserved, // 8
device::cop0::reserved, // 9
device::cop0::reserved, // 10
device::cop0::reserved, // 11
device::cop0::reserved, // 12
device::cop0::reserved, // 13
device::cop0::reserved, // 14
device::cop0::reserved, // 15
device::cop0::execute_cp0, // 16
device::cop0::reserved, // 17
device::cop0::reserved, // 18
device::cop0::reserved, // 19
device::cop0::reserved, // 20
device::cop0::reserved, // 21
device::cop0::reserved, // 22
device::cop0::reserved, // 23
device::cop0::reserved, // 24
device::cop0::reserved, // 25
device::cop0::reserved, // 26
device::cop0::reserved, // 27
device::cop0::reserved, // 28
device::cop0::reserved, // 29
device::cop0::reserved, // 30
device::cop0::reserved, // 31
device::cop0::mfc0, // 0
device::cop0::dmfc0, // 1
device::cop0::reserved, // 2
device::cop0::reserved, // 3
device::cop0::mtc0, // 4
device::cop0::dmtc0, // 5
device::cop0::reserved, // 6
device::cop0::reserved, // 7
device::cop0::reserved, // 8
device::cop0::reserved, // 9
device::cop0::reserved, // 10
device::cop0::reserved, // 11
device::cop0::reserved, // 12
device::cop0::reserved, // 13
device::cop0::reserved, // 14
device::cop0::reserved, // 15
device::cop0::execute_cp0, // 16
device::cop0::reserved, // 17
device::cop0::reserved, // 18
device::cop0::reserved, // 19
device::cop0::emux_xdetect, // 20
device::cop0::reserved, // 21
device::cop0::reserved, // 22
device::cop0::reserved, // 23
device::cop0::reserved, // 24
device::cop0::reserved, // 25
device::cop0::reserved, // 26
device::cop0::reserved, // 27
device::cop0::reserved, // 28
device::cop0::reserved, // 29
device::cop0::reserved, // 30
device::cop0::reserved, // 31
];
device.cpu.cop0.instrs2 = [
@@ -370,14 +371,14 @@ pub fn init(device: &mut device::Device) {
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;
device.cpu.cop0.regs[COP0_STATUS_REG as usize] = 0b00000000010000000000000000000100;
device.cpu.cop0.regs[COP0_PREVID_REG as usize] = 0b00000000000000000000101100100010;
device.cpu.cop0.regs[COP0_EPC_REG as usize] = 0b11111111111111111111111111111111;
device.cpu.cop0.regs[COP0_ERROREPC_REG as usize] = 0b11111111111111111111111111111111;
device.cpu.cop0.regs[COP0_BADVADDR_REG as usize] = 0xFFFFFFFF;
device.cpu.cop0.regs[COP0_CONTEXT_REG as usize] = 0x7FFFF0;
device.cpu.cop0.regs[COP0_RANDOM_REG] = 0b00000000000000000000000000011111;
device.cpu.cop0.regs[COP0_CONFIG_REG] = 0b01110000000001101110010001100000;
device.cpu.cop0.regs[COP0_STATUS_REG] = 0b00000000010000000000000000000100;
device.cpu.cop0.regs[COP0_PREVID_REG] = 0b00000000000000000000101100100010;
device.cpu.cop0.regs[COP0_EPC_REG] = 0b11111111111111111111111111111111;
device.cpu.cop0.regs[COP0_ERROREPC_REG] = 0b11111111111111111111111111111111;
device.cpu.cop0.regs[COP0_BADVADDR_REG] = 0xFFFFFFFF;
device.cpu.cop0.regs[COP0_CONTEXT_REG] = 0x7FFFF0;
device::events::create_event(device, device::events::EVENT_TYPE_COMPARE, u32::MAX as u64)
}
+23 -67
View File
@@ -24,7 +24,7 @@ const FCR31_CAUSE_MASK: u32 = 0b00000000000000111111000000000000;
const FCR31_ENABLE_MASK: u32 = 0b00000000000000000000111110000000;
const FCR31_WRITE_MASK: u32 = 0b00000001100000111111111111111111;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cop1 {
pub fcr0: u32,
pub fcr31: u32,
@@ -47,9 +47,7 @@ pub struct Cop1 {
}
pub fn lwc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -79,9 +77,7 @@ pub fn lwc1(device: &mut device::Device, opcode: u32) {
}
pub fn ldc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -118,9 +114,7 @@ pub fn ldc1(device: &mut device::Device, opcode: u32) {
}
pub fn swc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -145,9 +139,7 @@ pub fn swc1(device: &mut device::Device, opcode: u32) {
}
pub fn sdc1(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -181,9 +173,7 @@ pub fn sdc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -193,9 +183,7 @@ fn mfc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -205,9 +193,7 @@ fn dmfc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -217,9 +203,7 @@ fn cfc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -229,9 +213,7 @@ fn dcfc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -247,9 +229,7 @@ fn mtc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -260,9 +240,7 @@ fn dmtc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -274,9 +252,7 @@ fn ctc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -286,9 +262,7 @@ fn dctc1(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -296,9 +270,7 @@ fn execute_cop1_b(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -306,9 +278,7 @@ fn execute_cop1_s(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -316,9 +286,7 @@ fn execute_cop1_d(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -326,9 +294,7 @@ fn execute_cop1_l(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -340,9 +306,7 @@ fn unusable(device: &mut device::Device, _opcode: u32) {
}
pub fn reserved(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU1
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU1 == 0 {
return unusable(device, opcode);
}
@@ -406,9 +370,7 @@ fn set_control_registers_fpu(device: &mut device::Device, index: u32, data: u32)
pub fn set_fpr_single(device: &mut device::Device, index: usize, value: f32, clear_high: bool) {
let bytes = value.to_ne_bytes();
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
device.cpu.cop1.fgr32[index] = bytes;
} else {
let bytes_lo = bytes;
@@ -422,9 +384,7 @@ pub fn set_fpr_single(device: &mut device::Device, index: usize, value: f32, cle
}
pub fn get_fpr_single(device: &device::Device, index: usize) -> f32 {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
f32::from_ne_bytes(device.cpu.cop1.fgr32[index])
} else {
f32::from_ne_bytes(device.cpu.cop1.fgr64[index][0..4].try_into().unwrap())
@@ -433,9 +393,7 @@ pub fn get_fpr_single(device: &device::Device, index: usize) -> f32 {
pub fn set_fpr_double(device: &mut device::Device, index: usize, value: f64) {
let bytes = value.to_ne_bytes();
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
device.cpu.cop1.fgr32[index & !1] = bytes[0..4].try_into().unwrap();
device.cpu.cop1.fgr32[(index & !1) + 1] = bytes[4..8].try_into().unwrap();
} else {
@@ -444,9 +402,7 @@ pub fn set_fpr_double(device: &mut device::Device, index: usize, value: f64) {
}
pub fn get_fpr_double(device: &device::Device, index: usize) -> f64 {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_FR
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_FR == 0 {
let bytes_lo = device.cpu.cop1.fgr32[index & !1];
let bytes_hi = device.cpu.cop1.fgr32[(index & !1) + 1];
f64::from_ne_bytes([bytes_lo, bytes_hi].concat().try_into().unwrap())
+8 -22
View File
@@ -1,6 +1,6 @@
use crate::{device, savestates};
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cop2 {
#[serde(skip, default = "savestates::default_instructions")]
pub instrs: [fn(&mut device::Device, u32); 32],
@@ -8,9 +8,7 @@ pub struct Cop2 {
}
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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
@@ -18,18 +16,14 @@ fn mfc2(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] = device.cpu.cop2.reg_latch
}
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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize] =
@@ -37,27 +31,21 @@ fn cfc2(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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
device.cpu.cop2.reg_latch = device.cpu.gpr[device::cpu_instructions::rt(opcode) as usize]
}
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
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
@@ -65,9 +53,7 @@ fn ctc2(device: &mut device::Device, opcode: u32) {
}
pub fn reserved(device: &mut device::Device, opcode: u32) {
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_CU2
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_CU2 == 0 {
return unusable(device, opcode);
}
+5 -6
View File
@@ -1,6 +1,6 @@
use crate::{device, savestates, ui};
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum State {
Step,
Take,
@@ -11,13 +11,13 @@ pub enum State {
Exception,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: State,
pub pc: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cpu {
pub cop0: device::cop0::Cop0,
pub cop1: device::cop1::Cop1,
@@ -228,8 +228,8 @@ pub fn init(device: &mut device::Device) {
93750000
} else {
ui::video::onscreen_message(
&device.ui,
"Overclocking enabled, setting clock rate to 125 MHz",
ui::video::MESSAGE_LENGTH_MESSAGE_SHORT,
);
125000000
};
@@ -300,8 +300,7 @@ pub fn run(device: &mut device::Device) {
State::Exception => device.cpu.branch_state.state = State::Step,
}
device::cop0::add_cycles(device, 1);
if device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] > device.cpu.next_event_count
{
if device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] > device.cpu.next_event_count {
device::events::trigger_event(device)
}
}
+16 -18
View File
@@ -56,7 +56,7 @@ pub fn check_relative_idle_loop(device: &mut device::Device, opcode: u32) {
device::memory::AccessSize::Word,
) == 0
{
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] = device.cpu.next_event_count
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
}
}
@@ -68,7 +68,7 @@ fn check_absolute_idle_loop(device: &mut device::Device, target: u64) {
device::memory::AccessSize::Word,
) == 0
{
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] = device.cpu.next_event_count
device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] = device.cpu.next_event_count
}
}
@@ -647,17 +647,17 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
valid = 1
}
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
valid << 7,
0x80,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
0,
0x40,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
(device.memory.icache[icache_line].tag >> 4) as u64,
0xFFFFF00,
)
@@ -665,10 +665,9 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
0x08 => {
//icache store tag
device.memory.icache[icache_line].valid =
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x80) >> 7 != 0;
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
device.memory.icache[icache_line].tag =
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0xFFFFF00) << 4)
as u32
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
}
0x10 => {
//icache hit invalidate
@@ -704,17 +703,17 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
dirty = 1
}
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
valid << 7,
0x80,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
dirty << 6,
0x40,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG],
(device.memory.dcache[dcache_line].tag >> 4) as u64,
0xFFFFF00,
)
@@ -722,12 +721,11 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
0x09 => {
//dcache index store tag
device.memory.dcache[dcache_line].valid =
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x80) >> 7 != 0;
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x80) >> 7 != 0;
device.memory.dcache[dcache_line].dirty =
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0x40) >> 6 != 0;
(device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0x40) >> 6 != 0;
device.memory.dcache[dcache_line].tag =
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG as usize] & 0xFFFFF00) << 4)
as u32
((device.cpu.cop0.regs[device::cop0::COP0_TAGLO_REG] & 0xFFFFF00) << 4) as u32
}
0x0D => {
//dcache create dirty exclusive
@@ -765,7 +763,7 @@ pub fn cache(device: &mut device::Device, opcode: u32) {
}
}
_ => {
panic!("unknown cache code {:#01x}", rt(opcode))
panic!("unknown cache code {:x}", rt(opcode))
}
}
}
@@ -787,7 +785,7 @@ pub fn ll(device: &mut device::Device, opcode: u32) {
cached,
) as i32);
device.cpu.llbit = true;
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG as usize] = phys_address >> 4
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
}
pub fn lld(device: &mut device::Device, opcode: u32) {
@@ -816,7 +814,7 @@ pub fn lld(device: &mut device::Device, opcode: u32) {
device.cpu.gpr[rt(opcode) as usize] = ((w[0] as u64) << 32) | (w[1]) as u64;
device.cpu.llbit = true;
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG as usize] = phys_address >> 4
device.cpu.cop0.regs[device::cop0::COP0_LLADDR_REG] = phys_address >> 4
}
pub fn ld(device: &mut device::Device, opcode: u32) {
+9 -2
View File
@@ -1,4 +1,5 @@
use crate::device;
use crate::ui;
pub const EVENT_TYPE_NONE: usize = 0;
pub const EVENT_TYPE_AI: usize = 1;
@@ -12,7 +13,10 @@ 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;
pub const EVENT_TYPE_SAVE: usize = 12;
pub const EVENT_TYPE_NMI: usize = 13;
pub const EVENT_TYPE_UPDATE_SCREEN: usize = 14;
pub const EVENT_TYPE_COUNT: usize = 15;
#[derive(PartialEq, Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Event {
@@ -23,7 +27,7 @@ pub struct Event {
pub fn create_event(device: &mut device::Device, name: usize, when: u64) {
device.cpu.events[name] = Event {
enabled: true,
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG as usize] + when,
count: device.cpu.cop0.regs[device::cop0::COP0_COUNT_REG] + when,
};
set_next_event(device);
}
@@ -60,6 +64,9 @@ fn get_event_handler(name: usize) -> fn(&mut device::Device) {
EVENT_TYPE_COMPARE => device::cop0::compare_event,
EVENT_TYPE_VRU => device::controller::vru::vru_talking_event,
EVENT_TYPE_PAK => device::controller::pak_switch_event,
EVENT_TYPE_SAVE => ui::storage::save_event,
EVENT_TYPE_NMI => device::exceptions::reset_event,
EVENT_TYPE_UPDATE_SCREEN => device::vi::update_screen,
_ => dummy_event,
}
}
+49 -48
View File
@@ -1,7 +1,8 @@
use crate::device;
use crate::ui;
pub fn check_pending_interrupts(device: &mut device::Device) {
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG]
& (device::cop0::COP0_STATUS_IE
| device::cop0::COP0_STATUS_EXL
| device::cop0::COP0_STATUS_ERL))
@@ -11,17 +12,14 @@ pub fn check_pending_interrupts(device: &mut device::Device) {
return;
}
if device.mi.regs[device::mi::MI_INTR_REG as usize]
& device.mi.regs[device::mi::MI_INTR_MASK_REG as usize]
!= 0
{
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP2;
} else if device.cpu.cop0.pending_compare_interrupt {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] = device::cop0::COP0_CAUSE_IP7;
if device.mi.regs[device::mi::MI_INTR_REG] & device.mi.regs[device::mi::MI_INTR_MASK_REG] != 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_IP2;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &=
!device::cop0::COP0_CAUSE_EXCCODE_MASK;
}
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize]
if (device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG]
& device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG]
& device::cop0::COP0_CAUSE_IP_MASK)
== 0
{
@@ -41,43 +39,37 @@ pub fn interrupt_exception(device: &mut device::Device) {
}
pub fn floating_point_exception(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_FPE;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_FPE;
exception_general(device, 0x180)
}
pub fn trap_exception(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_TR;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TR;
exception_general(device, 0x180)
}
pub fn syscall_exception(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_SYS;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_SYS;
exception_general(device, 0x180)
}
pub fn break_exception(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_BP;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_BP;
exception_general(device, 0x180)
}
pub fn reserved_exception(device: &mut device::Device, cop: u64) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_RI | cop;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_RI | cop;
exception_general(device, 0x180)
}
pub fn cop_unusable_exception(device: &mut device::Device, cop: u64) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_CPU | cop;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_CPU | cop;
exception_general(device, 0x180)
}
@@ -88,31 +80,29 @@ pub fn tlb_miss_exception(
access_type: device::memory::AccessType,
) {
if access_type == device::memory::AccessType::Read {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_TLBL
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TLBL
} else {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device::cop0::COP0_CAUSE_EXCCODE_TLBS
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] = device::cop0::COP0_CAUSE_EXCCODE_TLBS
}
device.cpu.cop0.regs[device::cop0::COP0_BADVADDR_REG as usize] = address;
device.cpu.cop0.regs[device::cop0::COP0_BADVADDR_REG] = address;
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_CONTEXT_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_CONTEXT_REG],
address >> 9,
device::cop0::COP0_CONTEXT_BADVPN2_MASK,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG],
address >> 9,
device::cop0::COP0_XCONTEXT_BADVPN2_MASK,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_XCONTEXT_REG],
address >> 31,
device::cop0::COP0_XCONTEXT_REGION_MASK,
);
device::memory::masked_write_64(
&mut device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG as usize],
&mut device.cpu.cop0.regs[device::cop0::COP0_ENTRYHI_REG],
address,
0xFFFFE000,
);
@@ -123,7 +113,7 @@ pub fn tlb_miss_exception(
if address & !3 >= i.start_even && address & !3 <= i.end_even {
valid = i.v_even != 0;
if valid && access_type == device::memory::AccessType::Write && i.d_even == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] =
device::cop0::COP0_CAUSE_EXCCODE_MOD;
valid = false;
}
@@ -132,7 +122,7 @@ pub fn tlb_miss_exception(
if address & !3 >= i.start_odd && address & !3 <= i.end_odd {
valid = i.v_odd != 0;
if valid && access_type == device::memory::AccessType::Write && i.d_odd == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] =
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] =
device::cop0::COP0_CAUSE_EXCCODE_MOD;
valid = false;
}
@@ -140,8 +130,7 @@ pub fn tlb_miss_exception(
}
}
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_EXL
== 0
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_EXL == 0
&& valid
{
vector_offset = 0;
@@ -150,26 +139,38 @@ pub fn tlb_miss_exception(
exception_general(device, vector_offset)
}
pub fn reset_event(device: &mut device::Device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_IP4;
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] |= device::cop0::COP0_STATUS_ERL
| device::cop0::COP0_STATUS_SR
| device::cop0::COP0_STATUS_BEV;
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] &= !device::cop0::COP0_STATUS_TS;
device.cpu.cop0.regs[device::cop0::COP0_ERROREPC_REG] = device.cpu.pc;
device.cpu.pc = 0xBFC00000;
device.cpu.branch_state.state = device::cpu::State::Step;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] = 0;
device::pif::reset_pif(device, true);
ui::video::onscreen_message("Game reset", ui::video::MESSAGE_LENGTH_MESSAGE_SHORT);
}
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
{
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG as usize] = device.cpu.pc;
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_EXL == 0 {
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG] = device.cpu.pc;
if device::cpu::in_delay_slot(device) {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] |=
device::cop0::COP0_CAUSE_BD;
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG as usize] -= 4;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_BD;
device.cpu.cop0.regs[device::cop0::COP0_EPC_REG] -= 4;
} else {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG as usize] &=
!device::cop0::COP0_CAUSE_BD;
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_BD;
}
}
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] |= device::cop0::COP0_STATUS_EXL;
device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] |= device::cop0::COP0_STATUS_EXL;
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG as usize] & device::cop0::COP0_STATUS_BEV
== 0
{
if device.cpu.cop0.regs[device::cop0::COP0_STATUS_REG] & device::cop0::COP0_STATUS_BEV == 0 {
device.cpu.pc = device::cpu_instructions::se32((0x80000000 + vector_offset) as i32);
} else {
device.cpu.pc = device::cpu_instructions::se32((0xBFC00200 + vector_offset) as i32);
+8 -3
View File
@@ -38,7 +38,7 @@ pub enum AccessSize {
Icache = 32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Memory {
#[serde(skip, default = "savestates::default_memory_read_fast")]
pub fast_read: [fn(&device::Device, u64, AccessSize) -> u32; 0x2000], // fast_read is used for lookups that try to detect idle loops
@@ -62,11 +62,16 @@ pub fn masked_write_64(dst: &mut u64, value: u64, mask: u64) {
pub fn translate_address(
device: &mut device::Device,
address: u64,
mut address: u64,
access_type: AccessType,
) -> (u64, bool, bool) {
if (address & 0xc0000000) != 0x80000000 {
return device::tlb::get_physical_address(device, address, access_type);
if (address & 0xFFFFFFFF00000000) == 0x9000000000000000 {
// this is a hack to support 64-bit addresses used by some homebrew
address |= 0xA0000000;
} else {
return device::tlb::get_physical_address(device, address, access_type);
}
}
(address & 0x1FFFFFFF, address & 0x20000000 == 0, false)
}
+35 -37
View File
@@ -1,10 +1,10 @@
use crate::device;
pub const MI_INIT_MODE_REG: u32 = 0;
const MI_VERSION_REG: u32 = 1;
pub const MI_INTR_REG: u32 = 2;
pub const MI_INTR_MASK_REG: u32 = 3;
pub const MI_REGS_COUNT: u32 = 4;
pub const MI_INIT_MODE_REG: usize = 0;
const MI_VERSION_REG: usize = 1;
pub const MI_INTR_REG: usize = 2;
pub const MI_INTR_MASK_REG: usize = 3;
pub const MI_REGS_COUNT: usize = 4;
/* read */
pub const MI_INTR_SP: u32 = 1 << 0;
@@ -44,9 +44,9 @@ const MI_SET_RDRAM: u32 = 1 << 13;
pub const MI_INIT_LENGTH_MASK: u32 = 0b1111111;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Mi {
pub regs: [u32; MI_REGS_COUNT as usize],
pub regs: [u32; MI_REGS_COUNT],
}
pub fn read_regs(
@@ -60,46 +60,45 @@ pub fn read_regs(
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
MI_INIT_MODE_REG => update_init_mode(device, value),
MI_INTR_MASK_REG => update_intr_mask(device, value),
_ => 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_IP2;
if device.mi.regs[MI_INTR_REG] & device.mi.regs[MI_INTR_MASK_REG] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_IP2;
}
device::exceptions::check_pending_interrupts(device)
}
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;
device.mi.regs[MI_INIT_MODE_REG] &= !MI_INIT_LENGTH_MASK;
device.mi.regs[MI_INIT_MODE_REG] |= w & MI_INIT_LENGTH_MASK;
if w & MI_CLR_INIT != 0 {
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem;
}
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_INIT_MODE
device.mi.regs[MI_INIT_MODE_REG] &= !MI_INIT_MODE
}
if w & MI_SET_INIT != 0 {
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem_repeat;
}
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_INIT_MODE
device.mi.regs[MI_INIT_MODE_REG] |= MI_INIT_MODE
}
if w & MI_CLR_EBUS != 0 {
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_EBUS_MODE
device.mi.regs[MI_INIT_MODE_REG] &= !MI_EBUS_MODE
}
if w & MI_SET_EBUS != 0 {
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_EBUS_MODE
device.mi.regs[MI_INIT_MODE_REG] |= MI_EBUS_MODE
}
if w & MI_CLR_RDRAM != 0 {
device.mi.regs[MI_INIT_MODE_REG as usize] &= !MI_RDRAM_MODE
device.mi.regs[MI_INIT_MODE_REG] &= !MI_RDRAM_MODE
}
if w & MI_SET_RDRAM != 0 {
device.mi.regs[MI_INIT_MODE_REG as usize] |= MI_RDRAM_MODE
device.mi.regs[MI_INIT_MODE_REG] |= MI_RDRAM_MODE
}
if w & MI_CLR_DP_INTR != 0 {
@@ -109,57 +108,56 @@ fn update_init_mode(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
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_SP
}
if w & MI_SET_SP != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_SP
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_SP
}
if w & MI_CLR_SI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_SI
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_SI
}
if w & MI_SET_SI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_SI
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_SI
}
if w & MI_CLR_AI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_AI
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_AI
}
if w & MI_SET_AI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_AI
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_AI
}
if w & MI_CLR_VI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_VI
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_VI
}
if w & MI_SET_VI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_VI
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_VI
}
if w & MI_CLR_PI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_PI
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_PI
}
if w & MI_SET_PI != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_PI
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_PI
}
if w & MI_CLR_DP != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] &= !MI_INTR_DP
device.mi.regs[MI_INTR_MASK_REG] &= !MI_INTR_DP
}
if w & MI_SET_DP != 0 {
device.mi.regs[MI_INTR_MASK_REG as usize] |= MI_INTR_DP
device.mi.regs[MI_INTR_MASK_REG] |= MI_INTR_DP
}
}
pub fn clear_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] &= !interrupt;
device.mi.regs[MI_INTR_REG] &= !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_IP2;
if device.mi.regs[MI_INTR_REG] & device.mi.regs[MI_INTR_MASK_REG] == 0 {
device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] &= !device::cop0::COP0_CAUSE_IP2;
}
}
pub fn set_rcp_interrupt(device: &mut device::Device, interrupt: u32) {
device.mi.regs[MI_INTR_REG as usize] |= interrupt;
device.mi.regs[MI_INTR_REG] |= interrupt;
device::exceptions::check_pending_interrupts(device)
}
pub fn init(device: &mut device::Device) {
device.mi.regs[MI_VERSION_REG as usize] = 0x02020102
device.mi.regs[MI_VERSION_REG] = 0x02020102
}
+46 -50
View File
@@ -1,22 +1,22 @@
use crate::device;
use crate::ui;
use rand_chacha::rand_core::RngCore;
use rand::Rng;
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;
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;
const PI_DRAM_ADDR_REG: usize = 0;
const PI_CART_ADDR_REG: usize = 1;
const PI_RD_LEN_REG: usize = 2;
const PI_WR_LEN_REG: usize = 3;
pub const PI_STATUS_REG: usize = 4;
const PI_BSD_DOM1_LAT_REG: usize = 5;
const PI_BSD_DOM1_PWD_REG: usize = 6;
const PI_BSD_DOM1_PGS_REG: usize = 7;
const PI_BSD_DOM1_RLS_REG: usize = 8;
const PI_BSD_DOM2_LAT_REG: usize = 9;
const PI_BSD_DOM2_PWD_REG: usize = 10;
const PI_BSD_DOM2_PGS_REG: usize = 11;
const PI_BSD_DOM2_RLS_REG: usize = 12;
//const UNKNOWN_REG: usize = 13; //LibDragon
pub const PI_REGS_COUNT: usize = 14;
/* PI_STATUS - read */
const PI_STATUS_DMA_BUSY: u32 = 1 << 0;
@@ -28,9 +28,9 @@ const PI_STATUS_INTERRUPT: u32 = 1 << 3;
const PI_STATUS_RESET: u32 = 1 << 0;
const PI_STATUS_CLR_INTR: u32 = 1 << 1;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Pi {
pub regs: [u32; PI_REGS_COUNT as usize],
pub regs: [u32; PI_REGS_COUNT],
}
struct PiHandler {
@@ -45,7 +45,7 @@ pub fn read_regs(
) -> u32 {
device::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
PI_WR_LEN_REG | PI_RD_LEN_REG => 0x7F,
PI_CART_ADDR_REG => device.pi.regs[reg as usize] & 0xFFFFFFFE,
PI_DRAM_ADDR_REG => device.pi.regs[reg as usize] & 0xFFFFFE,
@@ -54,11 +54,11 @@ pub fn read_regs(
}
fn dma_read(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG]);
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_RD_LEN_REG as usize] & 0xFFFFFF) + 1;
let cart_addr = device.pi.regs[PI_CART_ADDR_REG] & !1;
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG] & 0xFFFFFE;
let mut length = (device.pi.regs[PI_RD_LEN_REG] & 0xFFFFFF) + 1;
/* 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 {
@@ -70,20 +70,18 @@ fn dma_read(device: &mut device::Device) {
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] =
(device.pi.regs[PI_DRAM_ADDR_REG as usize] + length + 7) & !7;
device.pi.regs[PI_CART_ADDR_REG as usize] =
(device.pi.regs[PI_CART_ADDR_REG as usize] + length + 1) & !1;
device.pi.regs[PI_DRAM_ADDR_REG] = (device.pi.regs[PI_DRAM_ADDR_REG] + length + 7) & !7;
device.pi.regs[PI_CART_ADDR_REG] = (device.pi.regs[PI_CART_ADDR_REG] + length + 1) & !1;
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_DMA_BUSY
}
fn dma_write(device: &mut device::Device) {
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG as usize]);
let handler = get_handler(device.pi.regs[PI_CART_ADDR_REG]);
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;
let cart_addr = device.pi.regs[PI_CART_ADDR_REG] & !1;
let dram_addr = device.pi.regs[PI_DRAM_ADDR_REG] & 0xFFFFFE;
let mut length = (device.pi.regs[PI_WR_LEN_REG] & 0xFFFFFF) + 1;
/* 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 {
@@ -101,12 +99,10 @@ fn dma_write(device: &mut device::Device) {
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] =
(device.pi.regs[PI_DRAM_ADDR_REG as usize] + length + 7) & !7;
device.pi.regs[PI_CART_ADDR_REG as usize] =
(device.pi.regs[PI_CART_ADDR_REG as usize] + length + 1) & !1;
device.pi.regs[PI_DRAM_ADDR_REG] = (device.pi.regs[PI_DRAM_ADDR_REG] + length + 7) & !7;
device.pi.regs[PI_CART_ADDR_REG] = (device.pi.regs[PI_CART_ADDR_REG] + length + 1) & !1;
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_DMA_BUSY
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_DMA_BUSY
}
fn get_handler(address: u32) -> PiHandler {
@@ -138,7 +134,7 @@ fn get_handler(address: u32) -> PiHandler {
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
PI_RD_LEN_REG => {
device::memory::masked_write_32(&mut device.pi.regs[reg as usize], value, mask);
device::pi::dma_read(device)
@@ -153,7 +149,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_PI);
}
if value & mask & PI_STATUS_RESET != 0 {
device.pi.regs[PI_STATUS_REG as usize] = 0
device.pi.regs[PI_STATUS_REG] = 0
}
}
PI_BSD_DOM1_LAT_REG | PI_BSD_DOM1_PWD_REG | PI_BSD_DOM2_LAT_REG | PI_BSD_DOM2_PWD_REG => {
@@ -169,7 +165,7 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u
}
}
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
rng.next_u64() % 0x10
}
@@ -179,15 +175,15 @@ pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -
let page_size_base: f64 = 2.0;
if domain == 1 {
latency = (device.pi.regs[PI_BSD_DOM1_LAT_REG as usize] + 1) as f64;
pulse_width = (device.pi.regs[PI_BSD_DOM1_PWD_REG as usize] + 1) as f64;
release = (device.pi.regs[PI_BSD_DOM1_RLS_REG as usize] + 1) as f64;
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM1_PGS_REG as usize] + 2) as f64);
latency = (device.pi.regs[PI_BSD_DOM1_LAT_REG] + 1) as f64;
pulse_width = (device.pi.regs[PI_BSD_DOM1_PWD_REG] + 1) as f64;
release = (device.pi.regs[PI_BSD_DOM1_RLS_REG] + 1) as f64;
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM1_PGS_REG] + 2) as f64);
} else if domain == 2 {
latency = (device.pi.regs[PI_BSD_DOM2_LAT_REG as usize] + 1) as f64;
pulse_width = (device.pi.regs[PI_BSD_DOM2_PWD_REG as usize] + 1) as f64;
release = (device.pi.regs[PI_BSD_DOM2_RLS_REG as usize] + 1) as f64;
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM2_PGS_REG as usize] + 2) as f64);
latency = (device.pi.regs[PI_BSD_DOM2_LAT_REG] + 1) as f64;
pulse_width = (device.pi.regs[PI_BSD_DOM2_PWD_REG] + 1) as f64;
release = (device.pi.regs[PI_BSD_DOM2_RLS_REG] + 1) as f64;
page_size = page_size_base.powf((device.pi.regs[PI_BSD_DOM2_PGS_REG] + 2) as f64);
} else {
panic!("unknown pi dma")
}
@@ -201,8 +197,8 @@ pub fn calculate_cycles(device: &mut device::Device, domain: i32, length: u32) -
}
pub fn dma_event(device: &mut device::Device) {
device.pi.regs[PI_STATUS_REG as usize] &= !(PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY);
device.pi.regs[PI_STATUS_REG as usize] |= PI_STATUS_INTERRUPT;
device.pi.regs[PI_STATUS_REG] &= !(PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY);
device.pi.regs[PI_STATUS_REG] |= PI_STATUS_INTERRUPT;
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_PI)
}
+26 -21
View File
@@ -1,14 +1,13 @@
mod rom;
use crate::device;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, 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],
pub input_gathered: bool,
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
@@ -69,7 +68,7 @@ pub fn write_mem(device: &mut device::Device, address: u64, value: u32, mask: u3
device.si.dma_dir = device::si::DmaDir::Write;
device::events::create_event(device, device::events::EVENT_TYPE_SI, 3200); //based on https://github.com/rasky/n64-systembench
device.si.regs[device::si::SI_STATUS_REG as usize] |=
device.si.regs[device::si::SI_STATUS_REG] |=
device::si::SI_STATUS_DMA_BUSY | device::si::SI_STATUS_IO_BUSY
}
@@ -96,9 +95,6 @@ fn process_channel(device: &mut device::Device, channel: usize) -> usize {
}
pub fn update_pif_ram(device: &mut device::Device) -> u64 {
unsafe { sdl3_sys::events::SDL_PumpEvents() };
device.pif.input_gathered = true;
let mut active_channels = 0;
for k in 0..PIF_CHANNELS_COUNT {
active_channels += process_channel(device, k)
@@ -222,11 +218,11 @@ pub fn process_ram(device: &mut device::Device) {
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] {
if let Some(netplay) = &device.netplay {
if netplay.connected[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
} else if device.ui.config.input.controller_enabled[i] {
device.pif.channels[i].process = Some(device::controller::process);
}
}
@@ -253,14 +249,23 @@ fn get_default_handler(device: &device::Device) -> device::controller::PakHandle
}
}
pub fn init(device: &mut device::Device) {
pub fn reset_pif(device: &mut device::Device, is_nmi_reset: bool) {
if device.cart.pal {
device.pif.rom = rom::PAL_PIF_ROM;
} else {
device.pif.rom = rom::NTSC_PIF_ROM;
}
device.pif.ram[0x26] = device.cart.cic_seed;
device.pif.ram[0x27] = device.cart.cic_seed;
let reset_type = u32::from(is_nmi_reset);
let rom_type = 0u32;
let s7 = 0u32;
let cic = u32::from(device.cart.cic_seed);
let word = (rom_type << 19) | (s7 << 18) | (reset_type << 17) | (cic << 8) | 0x3f;
device.pif.ram[0x24..0x28].copy_from_slice(&word.to_be_bytes());
}
pub fn init(device: &mut device::Device) {
reset_pif(device, false);
let default_handler = get_default_handler(device);
let tpak_handler = device::controller::PakHandler {
@@ -272,16 +277,16 @@ pub fn init(device: &mut device::Device) {
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(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
if let Some(netplay) = &device.netplay {
if netplay.connected[i] {
device.pif.channels[i].pak_handler = Some(default_handler);
}
} else if device.ui.config.input.controller_enabled[i] {
if device.transferpaks[i].cart.rom.is_empty() {
device.pif.channels[i].pak_handler = Some(default_handler);
} else {
device.pif.channels[i].pak_handler = Some(tpak_handler);
}
} else if device.netplay.as_ref().unwrap().player_data[i].reg_id != 0 {
device.pif.channels[i].pak_handler = Some(default_handler);
}
}
if device.ui.config.input.emulate_vru && device.netplay.is_none() {
+44 -54
View File
@@ -1,20 +1,20 @@
use crate::{device, savestates, ui};
use crate::{device, 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;
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 DPC_START_REG: usize = 0;
pub const DPC_END_REG: usize = 1;
pub const DPC_CURRENT_REG: usize = 2;
pub const DPC_STATUS_REG: usize = 3;
const DPC_CLOCK_REG: usize = 4;
const DPC_BUFBUSY_REG: usize = 5;
const DPC_PIPEBUSY_REG: usize = 6;
const DPC_TMEM_REG: usize = 7;
pub const DPC_REGS_COUNT: usize = 8;
//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;
//const DPS_TBIST_REG: usize = 0;
//const DPS_TEST_MODE_REG: usize = 1;
//const DPS_BUFTEST_ADDR_REG: usize = 2;
//const DPS_BUFTEST_DATA_REG: usize = 3;
pub const DPS_REGS_COUNT: usize = 4;
/* DPC status - read */
const DPC_STATUS_XBUS_DMEM_DMA: u32 = 1 << 0;
@@ -40,10 +40,10 @@ 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)]
#[derive(Clone, 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 regs_dpc: [u32; DPC_REGS_COUNT],
pub regs_dps: [u32; DPS_REGS_COUNT],
pub wait_frozen: bool,
pub last_status_value: u32,
}
@@ -57,7 +57,7 @@ pub fn read_regs_dpc(
device::cop0::add_cycles(device, 20);
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
DPC_STATUS_REG => {
let value =
device.rdp.regs_dpc[reg as usize] & (DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
@@ -80,18 +80,18 @@ pub fn read_regs_dpc(
pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
DPC_CURRENT_REG | DPC_CLOCK_REG | DPC_BUFBUSY_REG | DPC_PIPEBUSY_REG | DPC_TMEM_REG => {}
DPC_STATUS_REG => update_dpc_status(device, value & mask),
DPC_START_REG => {
if (device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_START_VALID) == 0 {
if (device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_START_VALID) == 0 {
device::memory::masked_write_32(
&mut device.rdp.regs_dpc[reg as usize],
value & 0xFFFFF8,
mask,
)
}
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_START_VALID
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_START_VALID
}
DPC_END_REG => {
device::memory::masked_write_32(
@@ -99,12 +99,11 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
value & 0xFFFFF8,
mask,
);
if (device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_START_VALID) != 0 {
device.rdp.regs_dpc[DPC_CURRENT_REG as usize] =
device.rdp.regs_dpc[DPC_START_REG as usize];
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_START_VALID
if (device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_START_VALID) != 0 {
device.rdp.regs_dpc[DPC_CURRENT_REG] = device.rdp.regs_dpc[DPC_START_REG];
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_START_VALID
}
if device.rdp.regs_dpc[DPC_STATUS_REG as usize] & DPC_STATUS_FREEZE == 0 {
if device.rdp.regs_dpc[DPC_STATUS_REG] & DPC_STATUS_FREEZE == 0 {
run_rdp(device)
} else {
device.rdp.wait_frozen = true;
@@ -116,20 +115,11 @@ pub fn write_regs_dpc(device: &mut device::Device, address: u64, value: u32, mas
fn run_rdp(device: &mut device::Device) {
let timer = ui::video::process_rdp_list();
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
device.rdp.regs_dpc[DPC_STATUS_REG] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0xFFFFFF;
if timer != 0 {
if device.save_state {
// Right after full sync, good time for save state
device.save_state = false;
savestates::create_savestate(device);
} else if device.load_state {
// Right after full sync, good time for save state
device.load_state = false;
savestates::load_savestate(device);
}
device::events::create_event(device, device::events::EVENT_TYPE_DP, timer)
}
}
@@ -154,59 +144,59 @@ pub fn write_regs_dps(device: &mut device::Device, address: u64, value: u32, mas
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
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_XBUS_DMEM_DMA
}
if w & DPC_SET_XBUS_DMEM_DMA != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_XBUS_DMEM_DMA
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_XBUS_DMEM_DMA
}
/* clear / set freeze */
if w & DPC_CLR_FREEZE != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_FREEZE;
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_FREEZE;
if device.rdp.wait_frozen {
run_rdp(device);
device.rdp.wait_frozen = false;
}
}
if w & DPC_SET_FREEZE != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_FREEZE;
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_FREEZE;
}
/* clear / set flush */
if w & DPC_CLR_FLUSH != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_FLUSH
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_FLUSH
}
if w & DPC_SET_FLUSH != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |= DPC_STATUS_FLUSH
device.rdp.regs_dpc[DPC_STATUS_REG] |= DPC_STATUS_FLUSH
}
if w & DPC_CLR_TMEM_CTR != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_TMEM_BUSY;
device.rdp.regs_dpc[DPC_TMEM_REG as usize] = 0
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_TMEM_BUSY;
device.rdp.regs_dpc[DPC_TMEM_REG] = 0
}
if w & DPC_CLR_PIPE_CTR != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_PIPE_BUSY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_PIPE_BUSY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0
}
if w & DPC_CLR_CMD_CTR != 0 {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &= !DPC_STATUS_CMD_BUSY;
device.rdp.regs_dpc[DPC_BUFBUSY_REG as usize] = 0
device.rdp.regs_dpc[DPC_STATUS_REG] &= !DPC_STATUS_CMD_BUSY;
device.rdp.regs_dpc[DPC_BUFBUSY_REG] = 0
}
/* clear clock counter */
if w & DPC_CLR_CLOCK_CTR != 0 {
device.rdp.regs_dpc[DPC_CLOCK_REG as usize] = 0
device.rdp.regs_dpc[DPC_CLOCK_REG] = 0
}
}
pub fn init(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] |=
device.rdp.regs_dpc[DPC_STATUS_REG] |=
DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CBUF_READY;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG as usize] = 0xFFFFFF;
device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0xFFFFFF;
}
pub fn rdp_interrupt_event(device: &mut device::Device) {
device.rdp.regs_dpc[DPC_STATUS_REG as usize] &=
device.rdp.regs_dpc[DPC_STATUS_REG] &=
!(DPC_STATUS_START_GCLK | DPC_STATUS_PIPE_BUSY | DPC_STATUS_CMD_BUSY);
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_DP)
+22 -20
View File
@@ -1,26 +1,27 @@
use crate::device;
use crate::retroachievements;
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;
//const RDRAM_CONFIG_REG: usize = 0;
//const RDRAM_DEVICE_ID_REG: usize = 1;
//const RDRAM_DELAY_REG: usize = 2;
const RDRAM_MODE_REG: usize = 3;
//const RDRAM_REF_INTERVAL_REG: usize = 4;
//const RDRAM_REF_ROW_REG: usize = 5;
//const RDRAM_RAS_INTERVAL_REG: usize = 6;
//const RDRAM_MIN_INTERVAL_REG: usize = 7;
//const RDRAM_ADDR_SELECT_REG: usize = 8;
//const RDRAM_DEVICE_MANUF_REG: usize = 9;
pub const RDRAM_REGS_COUNT: usize = 10;
pub const RDRAM_MASK: usize = 0xFFFFFF;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Rdram {
pub mem: Vec<u8>,
pub size: u32,
pub regs: [[u32; RDRAM_REGS_COUNT as usize]; 4],
pub regs: [[u32; RDRAM_REGS_COUNT]; 4],
}
pub fn read_mem_fast(
@@ -90,9 +91,8 @@ pub fn write_mem_repeat(device: &mut device::Device, address: u64, value: u32, m
panic!("RDRAM write_mem_repeat called with mask {:#x}", mask);
}
let repeat_length = (device.mi.regs[device::mi::MI_INIT_MODE_REG as usize]
& device::mi::MI_INIT_LENGTH_MASK)
+ 1;
let repeat_length =
(device.mi.regs[device::mi::MI_INIT_MODE_REG] & device::mi::MI_INIT_LENGTH_MASK) + 1;
if !repeat_length.is_multiple_of(4) {
panic!(
@@ -112,7 +112,7 @@ pub fn write_mem_repeat(device: &mut device::Device, address: u64, value: u32, m
.copy_from_slice(&value.to_ne_bytes());
}
device.mi.regs[device::mi::MI_INIT_MODE_REG as usize] &= !device::mi::MI_INIT_MODE;
device.mi.regs[device::mi::MI_INIT_MODE_REG] &= !device::mi::MI_INIT_MODE;
for i in 0..(0x3F00000 >> 16) {
device.memory.memory_map_write[i] = device::rdram::write_mem;
}
@@ -126,7 +126,7 @@ pub fn read_regs(
device::cop0::add_cycles(device, 20);
let chip_id = (address >> 13) & 3;
let reg = (address & 0x3FF) >> 2;
match reg as u32 {
match reg as usize {
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],
@@ -151,6 +151,8 @@ pub fn init(device: &mut device::Device) {
device.rdram.mem =
unsafe { Vec::from_raw_parts(ptr, device.rdram.size as usize, device.rdram.size as usize) };
retroachievements::set_rdram(device.rdram.mem.as_ptr(), device.rdram.size as usize);
// hack, skip RDRAM initialization
device
.rdram
@@ -166,8 +168,8 @@ pub fn init(device: &mut device::Device) {
.unwrap_or(&mut [0; 4])
.copy_from_slice(&device.rdram.size.to_ne_bytes());
device.ri.regs[device::ri::RI_MODE_REG as usize] = 0x0e;
device.ri.regs[device::ri::RI_CONFIG_REG as usize] = 0x40;
device.ri.regs[device::ri::RI_MODE_REG] = 0x0e;
device.ri.regs[device::ri::RI_CONFIG_REG] = 0x40;
}
pub fn rdram_calculate_cycles(length: u64) -> u64 {
+13 -13
View File
@@ -1,18 +1,18 @@
use crate::device;
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;
//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;
pub const RI_MODE_REG: usize = 0;
pub const RI_CONFIG_REG: usize = 1;
//const RI_CURRENT_LOAD_REG: usize = 2;
pub const RI_SELECT_REG: usize = 3;
pub const RI_REFRESH_REG: usize = 4;
//const RI_LATENCY_REG: usize = 5;
//const RI_ERROR_REG: usize = 6;
//const RI_WERROR_REG: usize = 7;
pub const RI_REGS_COUNT: usize = 8;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Ri {
pub regs: [u32; RI_REGS_COUNT as usize],
pub regs: [u32; RI_REGS_COUNT],
pub ram_init: bool,
}
@@ -23,7 +23,7 @@ pub fn read_regs(
) -> u32 {
device::cop0::add_cycles(device, 20);
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
RI_SELECT_REG => {
if !device.ri.ram_init {
device::cop0::add_cycles(device, device.cpu.clock_rate / 2); // hack, simulate RDRAM initialization
@@ -38,7 +38,7 @@ pub fn read_regs(
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
if reg as u32 == RI_SELECT_REG {
if reg as usize == RI_SELECT_REG {
device.ri.ram_init = false;
}
device::memory::masked_write_32(&mut device.ri.regs[reg as usize], value, mask);
+10 -11
View File
@@ -6,7 +6,7 @@ use crate::{device, savestates};
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct BranchState {
pub state: device::cpu::State,
pub pc: u32,
@@ -25,7 +25,7 @@ pub enum InstructionType {
Vu,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Cpu {
#[serde(with = "serde_big_array::BigArray")]
pub instructions: [Instructions; 0x1000 / 4],
@@ -132,48 +132,47 @@ pub fn run(device: &mut device::Device) -> u64 {
device.rsp.cpu.gpr[0] = 0; // gpr 0 is read only
let instruction = device.rsp.cpu.instructions
[(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] / 4) as usize];
[(device.rsp.regs2[device::rsp_interface::SP_PC_REG] / 4) as usize];
(instruction.func)(device, instruction.opcode);
match device.rsp.cpu.branch_state.state {
device::cpu::State::Step => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
if device.rsp.cpu.broken {
break;
}
}
device::cpu::State::Take => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
device.rsp.cpu.branch_state.state = device::cpu::State::DelaySlotTaken
}
device::cpu::State::NotTaken => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
device.rsp.cpu.branch_state.state = device::cpu::State::DelaySlotNotTaken
}
device::cpu::State::DelaySlotTaken => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] =
device.rsp.cpu.branch_state.pc;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] = device.rsp.cpu.branch_state.pc;
device.rsp.cpu.branch_state.state = device::cpu::State::Step;
if device.rsp.cpu.broken {
break;
}
}
device::cpu::State::DelaySlotNotTaken => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 4;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 4;
device.rsp.cpu.branch_state.state = device::cpu::State::Step;
if device.rsp.cpu.broken {
break;
}
}
device::cpu::State::Discard => {
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] += 8;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] += 8;
device.rsp.cpu.branch_state.state = device::cpu::State::Step
}
device::cpu::State::Exception => {
device.rsp.cpu.branch_state.state = device::cpu::State::Step
}
}
device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] &= 0xFFC;
device.rsp.regs2[device::rsp_interface::SP_PC_REG] &= 0xFFC;
if device.rsp.cpu.instruction_type == device.rsp.cpu.last_instruction_type {
device.rsp.cpu.cycle_counter += 1;
+71 -71
View File
@@ -1,19 +1,19 @@
use crate::device;
use crate::ui;
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;
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;
const SP_MEM_ADDR_REG: usize = 0;
const SP_DRAM_ADDR_REG: usize = 1;
const SP_RD_LEN_REG: usize = 2;
const SP_WR_LEN_REG: usize = 3;
pub const SP_STATUS_REG: usize = 4;
const SP_DMA_FULL_REG: usize = 5;
const SP_DMA_BUSY_REG: usize = 6;
const SP_SEMAPHORE_REG: usize = 7;
pub const SP_REGS_COUNT: usize = 8;
pub const SP_PC_REG: u32 = 0;
//const SP_IBIST_REG: u32 = 1;
pub const SP_REGS2_COUNT: u32 = 2;
pub const SP_PC_REG: usize = 0;
//const SP_IBIST_REG: usize = 1;
pub const SP_REGS2_COUNT: usize = 2;
/* SP_STATUS - read */
pub const SP_STATUS_HALT: u32 = 1 << 0;
@@ -76,11 +76,11 @@ pub struct RspDma {
pub dramaddr: u32,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, 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],
pub regs: [u32; SP_REGS_COUNT],
pub regs2: [u32; SP_REGS2_COUNT],
#[serde(with = "serde_big_array::BigArray")]
pub mem: [u8; 0x2000],
pub fifo: [RspDma; 2],
@@ -204,10 +204,10 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
j += 1;
}
}
device.rsp.regs[SP_MEM_ADDR_REG as usize] = (mem_addr & 0xfff) + (dma.memaddr & 0x1000);
device.rsp.regs[SP_DRAM_ADDR_REG as usize] = dram_addr;
device.rsp.regs[SP_RD_LEN_REG as usize] = 0xff8;
device.rsp.regs[SP_WR_LEN_REG as usize] = 0xff8;
device.rsp.regs[SP_MEM_ADDR_REG] = (mem_addr & 0xfff) + (dma.memaddr & 0x1000);
device.rsp.regs[SP_DRAM_ADDR_REG] = dram_addr;
device.rsp.regs[SP_RD_LEN_REG] = 0xff8;
device.rsp.regs[SP_WR_LEN_REG] = 0xff8;
device::events::create_event(
device,
@@ -217,52 +217,52 @@ fn do_dma(device: &mut device::Device, dma: RspDma) {
}
fn fifo_push(device: &mut device::Device, dir: DmaDir) {
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
panic!("RSP DMA already full")
}
device.rsp.cpu.sync_point = true;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] != 0 {
if device.rsp.regs[SP_DMA_BUSY_REG] != 0 {
device.rsp.fifo[1].dir = dir;
if dir == DmaDir::Read {
device.rsp.fifo[1].length = device.rsp.regs[SP_WR_LEN_REG as usize]
device.rsp.fifo[1].length = device.rsp.regs[SP_WR_LEN_REG]
} else {
device.rsp.fifo[1].length = device.rsp.regs[SP_RD_LEN_REG as usize]
device.rsp.fifo[1].length = device.rsp.regs[SP_RD_LEN_REG]
}
device.rsp.fifo[1].memaddr = device.rsp.regs[SP_MEM_ADDR_REG as usize];
device.rsp.fifo[1].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG as usize];
device.rsp.regs[SP_DMA_FULL_REG as usize] = 1;
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_DMA_FULL
device.rsp.fifo[1].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
device.rsp.fifo[1].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
device.rsp.regs[SP_DMA_FULL_REG] = 1;
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_FULL
} else {
device.rsp.fifo[0].dir = dir;
if dir == DmaDir::Read {
device.rsp.fifo[0].length = device.rsp.regs[SP_WR_LEN_REG as usize]
device.rsp.fifo[0].length = device.rsp.regs[SP_WR_LEN_REG]
} else {
device.rsp.fifo[0].length = device.rsp.regs[SP_RD_LEN_REG as usize]
device.rsp.fifo[0].length = device.rsp.regs[SP_RD_LEN_REG]
}
device.rsp.fifo[0].memaddr = device.rsp.regs[SP_MEM_ADDR_REG as usize];
device.rsp.fifo[0].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG as usize];
device.rsp.regs[SP_DMA_BUSY_REG as usize] = 1;
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_DMA_BUSY;
device.rsp.fifo[0].memaddr = device.rsp.regs[SP_MEM_ADDR_REG];
device.rsp.fifo[0].dramaddr = device.rsp.regs[SP_DRAM_ADDR_REG];
device.rsp.regs[SP_DMA_BUSY_REG] = 1;
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_DMA_BUSY;
do_dma(device, device.rsp.fifo[0])
}
}
pub fn fifo_pop(device: &mut device::Device) {
if device.rsp.regs[SP_DMA_FULL_REG as usize] != 0 {
if device.rsp.regs[SP_DMA_FULL_REG] != 0 {
device.rsp.fifo[0].dir = device.rsp.fifo[1].dir;
device.rsp.fifo[0].length = device.rsp.fifo[1].length;
device.rsp.fifo[0].memaddr = device.rsp.fifo[1].memaddr;
device.rsp.fifo[0].dramaddr = device.rsp.fifo[1].dramaddr;
device.rsp.regs[SP_DMA_FULL_REG as usize] = 0;
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_DMA_FULL;
device.rsp.regs[SP_DMA_FULL_REG] = 0;
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_FULL;
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_DMA_BUSY_REG] = 0;
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_DMA_BUSY;
if device.rsp.run_after_dma {
device.rsp.run_after_dma = false;
do_task(device);
@@ -279,7 +279,7 @@ pub fn read_regs(
device::cop0::add_cycles(device, 20);
}
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
SP_STATUS_REG => {
let value = device.rsp.regs[reg as usize]
& (SP_STATUS_SIG0
@@ -311,7 +311,7 @@ pub fn read_regs(
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
SP_STATUS_REG => update_sp_status(device, value),
SP_RD_LEN_REG => {
device::memory::masked_write_32(&mut device.rsp.regs[reg as usize], value, mask);
@@ -339,7 +339,7 @@ pub fn read_regs2(
pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
SP_PC_REG => {
device::memory::masked_write_32(
&mut device.rsp.regs2[reg as usize],
@@ -352,20 +352,20 @@ pub fn write_regs2(device: &mut device::Device, address: u64, value: u32, mask:
}
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;
let was_halted = device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT != 0;
/* clear / set halt */
if w & SP_CLR_HALT != 0 && w & SP_SET_HALT == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_HALT
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_HALT
}
if w & SP_SET_HALT != 0 && w & SP_CLR_HALT == 0 {
device::events::remove_event(device, device::events::EVENT_TYPE_SP);
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT
}
/* clear broke */
if w & SP_CLR_BROKE != 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_BROKE
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_BROKE
}
/* clear SP interrupt */
@@ -379,85 +379,85 @@ fn update_sp_status(device: &mut device::Device, w: u32) {
/* clear / set single step */
if w & SP_CLR_SSTEP != 0 && w & SP_SET_SSTEP == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SSTEP
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SSTEP
}
if w & SP_SET_SSTEP != 0 && w & SP_CLR_SSTEP == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SSTEP
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SSTEP
}
/* clear / set interrupt on break */
if w & SP_CLR_INTR_BREAK != 0 && w & SP_SET_INTR_BREAK == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_INTR_BREAK
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_INTR_BREAK
}
if w & SP_SET_INTR_BREAK != 0 && w & SP_CLR_INTR_BREAK == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_INTR_BREAK
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_INTR_BREAK
}
/* clear / set signal 0 */
if w & SP_CLR_SIG0 != 0 && w & SP_SET_SIG0 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG0
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG0
}
if w & SP_SET_SIG0 != 0 && w & SP_CLR_SIG0 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG0
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG0
}
/* clear / set signal 1 */
if w & SP_CLR_SIG1 != 0 && w & SP_SET_SIG1 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG1
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG1
}
if w & SP_SET_SIG1 != 0 && w & SP_CLR_SIG1 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG1
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG1
}
/* clear / set signal 2 */
if w & SP_CLR_SIG2 != 0 && w & SP_SET_SIG2 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG2
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG2
}
if w & SP_SET_SIG2 != 0 && w & SP_CLR_SIG2 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG2
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG2
}
/* clear / set signal 3 */
if w & SP_CLR_SIG3 != 0 && w & SP_SET_SIG3 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG3
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG3
}
if w & SP_SET_SIG3 != 0 && w & SP_CLR_SIG3 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG3
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG3
}
/* clear / set signal 4 */
if w & SP_CLR_SIG4 != 0 && w & SP_SET_SIG4 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG4
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG4
}
if w & SP_SET_SIG4 != 0 && w & SP_CLR_SIG4 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG4
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG4
}
/* clear / set signal 5 */
if w & SP_CLR_SIG5 != 0 && w & SP_SET_SIG5 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG5
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG5
}
if w & SP_SET_SIG5 != 0 && w & SP_CLR_SIG5 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG5
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG5
}
/* clear / set signal 6 */
if w & SP_CLR_SIG6 != 0 && w & SP_SET_SIG6 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG6
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG6
}
if w & SP_SET_SIG6 != 0 && w & SP_CLR_SIG6 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG6
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG6
}
/* clear / set signal 7 */
if w & SP_CLR_SIG7 != 0 && w & SP_SET_SIG7 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] &= !SP_STATUS_SIG7
device.rsp.regs[SP_STATUS_REG] &= !SP_STATUS_SIG7
}
if w & SP_SET_SIG7 != 0 && w & SP_CLR_SIG7 == 0 {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_SIG7
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_SIG7
}
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_HALT == 0 && was_halted {
if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_HALT == 0 && was_halted {
device.rsp.cpu.broken = false;
device.rsp.cpu.halted = false;
do_task(device);
@@ -468,7 +468,7 @@ fn do_task(device: &mut device::Device) {
device.rsp.cpu.sync_point = false;
device.rsp.last_status_value = 0;
device.rdp.last_status_value = 0;
if device.rsp.regs[SP_DMA_BUSY_REG as usize] == 1 {
if device.rsp.regs[SP_DMA_BUSY_REG] == 1 {
device.rsp.run_after_dma = true
} else {
let timer = device::rsp_cpu::run(device);
@@ -479,21 +479,21 @@ fn do_task(device: &mut device::Device) {
pub fn rsp_event(device: &mut device::Device) {
if device.rsp.cpu.broken {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT | SP_STATUS_BROKE;
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT | SP_STATUS_BROKE;
if device.rsp.regs[SP_STATUS_REG as usize] & SP_STATUS_INTR_BREAK != 0 {
if device.rsp.regs[SP_STATUS_REG] & SP_STATUS_INTR_BREAK != 0 {
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SP)
}
return;
}
if device.rsp.cpu.halted {
device.rsp.regs[SP_STATUS_REG as usize] |= SP_STATUS_HALT;
device.rsp.regs[SP_STATUS_REG] |= SP_STATUS_HALT;
return;
}
do_task(device)
}
pub fn init(device: &mut device::Device) {
device.rsp.regs[SP_STATUS_REG as usize] = 1;
device.rsp.regs[SP_STATUS_REG] = 1;
device::rsp_cpu::init(device);
}
+24 -35
View File
@@ -183,23 +183,22 @@ pub fn j(device: &mut device::Device, opcode: u32) {
return;
}
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 4) & 0xF0000000
| ((opcode & 0x3FFFFFF) << 2)
device.rsp.cpu.branch_state.pc = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 4)
& 0xF0000000
| ((opcode & 0x3FFFFFF) << 2)
}
pub fn jal(device: &mut device::Device, opcode: u32) {
if device::rsp_cpu::in_delay_slot_taken(device) {
device.rsp.cpu.gpr[31] = (device.rsp.cpu.branch_state.pc + 4) & 0xFFF
} else {
device.rsp.cpu.gpr[31] =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
}
if !device::rsp_cpu::in_delay_slot_taken(device) {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 4) & 0xF0000000
| ((opcode & 0x3FFFFFF) << 2)
device.rsp.cpu.branch_state.pc = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 4)
& 0xF0000000
| ((opcode & 0x3FFFFFF) << 2)
} else if !device::rsp_cpu::in_delay_slot(device) {
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
}
@@ -208,8 +207,7 @@ pub fn jal(device: &mut device::Device, opcode: u32) {
pub fn beq(device: &mut device::Device, opcode: u32) {
if device.rsp.cpu.gpr[rs(opcode) as usize] == device.rsp.cpu.gpr[rt(opcode) as usize] {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -220,8 +218,7 @@ pub fn beq(device: &mut device::Device, opcode: u32) {
pub fn bne(device: &mut device::Device, opcode: u32) {
if device.rsp.cpu.gpr[rs(opcode) as usize] != device.rsp.cpu.gpr[rt(opcode) as usize] {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -232,8 +229,7 @@ pub fn bne(device: &mut device::Device, opcode: u32) {
pub fn blez(device: &mut device::Device, opcode: u32) {
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 <= 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -244,8 +240,7 @@ pub fn blez(device: &mut device::Device, opcode: u32) {
pub fn bgtz(device: &mut device::Device, opcode: u32) {
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 > 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -485,7 +480,7 @@ pub fn jalr(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.gpr[rd(opcode) as usize] = (device.rsp.cpu.branch_state.pc + 4) & 0xFFF
} else {
device.rsp.cpu.gpr[rd(opcode) as usize] =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
(device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
}
}
@@ -547,8 +542,7 @@ pub fn sltu(device: &mut device::Device, opcode: u32) {
pub fn bltz(device: &mut device::Device, opcode: u32) {
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) < 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -559,8 +553,7 @@ pub fn bltz(device: &mut device::Device, opcode: u32) {
pub fn bgez(device: &mut device::Device, opcode: u32) {
if device.rsp.cpu.gpr[rs(opcode) as usize] as i32 >= 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
@@ -571,34 +564,30 @@ pub fn bgez(device: &mut device::Device, opcode: u32) {
pub fn bltzal(device: &mut device::Device, opcode: u32) {
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) < 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
}
device.rsp.cpu.gpr[31] =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
}
pub fn bgezal(device: &mut device::Device, opcode: u32) {
if (device.rsp.cpu.gpr[rs(opcode) as usize] as i32) >= 0 {
device.rsp.cpu.branch_state.state = device::cpu::State::Take;
device.rsp.cpu.branch_state.pc = device.rsp.regs2
[device::rsp_interface::SP_PC_REG as usize]
device.rsp.cpu.branch_state.pc = device.rsp.regs2[device::rsp_interface::SP_PC_REG]
.wrapping_add(se16(imm(opcode) as i16) << 2)
+ 4;
} else {
device.rsp.cpu.branch_state.state = device::cpu::State::NotTaken;
}
device.rsp.cpu.gpr[31] =
(device.rsp.regs2[device::rsp_interface::SP_PC_REG as usize] + 8) & 0xFFF
device.rsp.cpu.gpr[31] = (device.rsp.regs2[device::rsp_interface::SP_PC_REG] + 8) & 0xFFF
}
pub fn mfc0(device: &mut device::Device, opcode: u32) {
device.rsp.cpu.cycle_counter += 2;
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT as u32 {
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rsp_interface::read_regs(
device,
(rd(opcode) << 2) as u64,
@@ -607,14 +596,14 @@ pub fn mfc0(device: &mut device::Device, opcode: u32) {
} else {
device.rsp.cpu.gpr[rt(opcode) as usize] = device::rdp::read_regs_dpc(
device,
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT) << 2) as u64,
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT as u32) << 2) as u64,
device::memory::AccessSize::Word,
)
}
}
pub fn mtc0(device: &mut device::Device, opcode: u32) {
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT {
if rd(opcode) < device::rsp_interface::SP_REGS_COUNT as u32 {
device::rsp_interface::write_regs(
device,
(rd(opcode) << 2) as u64,
@@ -624,15 +613,15 @@ pub fn mtc0(device: &mut device::Device, opcode: u32) {
} else {
device::rdp::write_regs_dpc(
device,
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT) << 2) as u64,
((rd(opcode) - device::rsp_interface::SP_REGS_COUNT as u32) << 2) as u64,
device.rsp.cpu.gpr[rt(opcode) as usize],
0xFFFFFFFF,
)
}
if rd(opcode) == device::rsp_interface::SP_STATUS_REG
if rd(opcode) as usize == device::rsp_interface::SP_STATUS_REG
&& device.rsp.cpu.gpr[rt(opcode) as usize] & device::rsp_interface::SP_SET_HALT != 0
{
device.rsp.regs[device::rsp_interface::SP_STATUS_REG as usize] &=
device.rsp.regs[device::rsp_interface::SP_STATUS_REG] &=
!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
}
+2 -2
View File
@@ -844,7 +844,7 @@ pub fn vrcpl(device: &mut device::Device, opcode: u32) {
let vte = vte(device, vt(opcode), ve(opcode) as usize);
let input = if device.rsp.cpu.divdp {
((device.rsp.cpu.divin as i32) << 16)
| get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as u16 as i32
| get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as i32
} else {
get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as i16 as i32
};
@@ -901,7 +901,7 @@ pub fn vrsql(device: &mut device::Device, opcode: u32) {
let vte = vte(device, vt(opcode), ve(opcode) as usize);
let input = if device.rsp.cpu.divdp {
((device.rsp.cpu.divin as i32) << 16)
| get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as u16 as i32
| get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as i32
} else {
get_vpr16(device.rsp.cpu.vpr[vt(opcode) as usize], ve(opcode) as u8) as i16 as i32
};
+19 -20
View File
@@ -1,32 +1,31 @@
use rand_chacha::rand_core::RngCore;
use crate::device;
use crate::ui;
use rand::Rng;
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;
const SI_DRAM_ADDR_REG: usize = 0;
const SI_PIF_ADDR_RD64B_REG: usize = 1;
//const SI_R2_REG: usize = 2;
//const SI_R3_REG: usize = 3;
const SI_PIF_ADDR_WR64B_REG: usize = 4;
//const SI_R5_REG: usize = 5;
pub const SI_STATUS_REG: usize = 6;
pub const SI_REGS_COUNT: usize = 7;
pub const SI_STATUS_DMA_BUSY: u32 = 1 << 0;
pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
#[derive(PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum DmaDir {
None,
Write,
Read,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct Si {
pub regs: [u32; SI_REGS_COUNT as usize],
pub regs: [u32; SI_REGS_COUNT],
pub dma_dir: DmaDir,
}
@@ -39,7 +38,7 @@ pub fn read_regs(
device.si.regs[((address & 0xFFFF) >> 2) as usize]
}
fn randomize_interrupt_time(rng: &mut rand_chacha::ChaCha8Rng) -> u64 {
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
rng.next_u64() % 0x100
}
@@ -48,7 +47,7 @@ fn dma_read(device: &mut device::Device) {
let duration = device::pif::update_pif_ram(device);
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
device.si.regs[SI_STATUS_REG] |= SI_STATUS_DMA_BUSY;
let length = duration + randomize_interrupt_time(&mut device.rng);
@@ -60,7 +59,7 @@ fn dma_write(device: &mut device::Device) {
copy_pif_rdram(device);
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_DMA_BUSY;
device.si.regs[SI_STATUS_REG] |= SI_STATUS_DMA_BUSY;
let length = 6000 + randomize_interrupt_time(&mut device.rng); //based on https://github.com/rasky/n64-systembench
@@ -69,7 +68,7 @@ fn dma_write(device: &mut device::Device) {
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
let reg = (address & 0xFFFF) >> 2;
match reg as u32 {
match reg as usize {
SI_STATUS_REG => {
device.si.regs[reg as usize] &= !SI_STATUS_INTERRUPT;
device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_SI)
@@ -82,7 +81,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
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;
let dram_addr = device.si.regs[SI_DRAM_ADDR_REG] as usize & device::rdram::RDRAM_MASK;
if device.si.dma_dir == DmaDir::Write {
let mut i = 0;
while i < device::pif::PIF_RAM_SIZE {
@@ -126,8 +125,8 @@ pub fn dma_event(device: &mut device::Device) {
panic!("si dma unknown")
}
device.si.dma_dir = DmaDir::None;
device.si.regs[SI_STATUS_REG as usize] &= !(SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY);
device.si.regs[SI_STATUS_REG as usize] |= SI_STATUS_INTERRUPT;
device.si.regs[SI_STATUS_REG] &= !(SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY);
device.si.regs[SI_STATUS_REG] |= SI_STATUS_INTERRUPT;
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SI)
}

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