Compare commits

...

24 Commits

Author SHA1 Message Date
LotP
b000f91dad Memory changes 2.2.1 (ryubing/ryujinx!144)
See merge request ryubing/ryujinx!144
2025-09-06 13:51:08 -05:00
LotP
a60b2a0ba3 Memory changes 2.2 (ryubing/ryujinx!143)
See merge request ryubing/ryujinx!143
2025-09-06 11:10:55 -05:00
Hack茶ん
4c9b48b754 Update Korean translation (ryubing/ryujinx!141)
See merge request ryubing/ryujinx!141
2025-09-05 13:06:59 -05:00
Neo
3bd7d5904e Compat: New Entries + Minor Adjustments (ryubing/ryujinx!140)
See merge request ryubing/ryujinx!140
2025-09-05 03:50:49 -05:00
GreemDev
23eb9a3043 improvement: Make the updater log a special error message in some cases
specifically about potentially not being connected to the internet on a connection error or name resolution error
2025-09-05 03:12:15 -05:00
GreemDev
931ec44406 [ci skip] fix: <Reset> text in Pokemon Scarlet/Violet play report rich presence 2025-09-05 03:02:16 -05:00
GreemDev
d68efa98ba [ci skip] Update NuGet packages 2025-09-05 02:25:05 -05:00
GreemDev
ded76801d1 removal: Installing keys from a zip
Also cleaned up a bit
2025-09-04 19:04:50 -05:00
GreemDev
6084df7473 Silksong compatibility entry and RPC image 2025-09-04 16:23:38 -05:00
LotP1
f3953c6039 Fix a crash when no controller is connected 2025-09-04 16:18:07 -05:00
LotP1
91f5247e7f add missing field 2025-09-04 16:17:59 -05:00
LotP1
5658402c6b fix spelling 2025-09-04 16:17:53 -05:00
LotP1
1b2c93e188 Add version comment 2025-09-04 16:14:38 -05:00
LotP1
9e599ff325 remove stub 2025-09-04 16:14:18 -05:00
LotP1
7a5f430b59 hle: Basic event handle implementation for IApplicationFunctions 210
Lets Hollow Knight: Silksong boot.
2025-09-04 16:14:03 -05:00
shinyoyo
1e340ce2f3 Update Simplified Chinese translation. (ryubing/ryujinx!139)
See merge request ryubing/ryujinx!139
2025-09-03 15:49:14 -05:00
WilliamWsyHK
dbb4e63e1e Update zh-TW translation (ryubing/ryujinx!130)
See merge request ryubing/ryujinx!130
2025-09-03 03:28:37 -05:00
Hack茶ん
d00ab52fa2 Update Korean translation (ryubing/ryujinx!138)
See merge request ryubing/ryujinx!138
2025-09-03 03:26:33 -05:00
VewDev
959af3613d doc: update documentation for Shared property in Switch class (ryubing/ryujinx!137)
See merge request ryubing/ryujinx!137
2025-09-03 03:25:43 -05:00
GreemDev
3309fb2351 chore: Replace dead URLs in the changelog with archive.org links (that I could find) 2025-09-03 00:49:30 -05:00
GreemDev
e1e8628a6f chore: remove BuildAndPushLibraries.sh 2025-09-03 00:25:08 -05:00
Neo
3969191605 UI: Menubar & Game Context Menu Updates (ryubing/ryujinx!134)
See merge request ryubing/ryujinx!134
2025-09-02 03:32:06 -05:00
Hack茶ん
07c7b39053 Update Korean translation (ryubing/ryujinx!136)
See merge request ryubing/ryujinx!136
2025-09-01 17:57:44 -05:00
Hack茶ん
053efaa414 Update Korean translation (ryubing/ryujinx!135)
See merge request ryubing/ryujinx!135
2025-09-01 01:36:27 -05:00
38 changed files with 1011 additions and 1006 deletions

View File

@@ -1,18 +0,0 @@
function pub {
dotnet publish -c release
}
function package {
cd src/$1
pub
mv bin/Release/$1.1.0.0.nupkg ../../pkgs/$1.1.0.0.nupkg
cd ../../
}
rm -rf pkgs
mkdir pkgs
package ARMeilleure
package Ryujinx.Memory
dotnet nuget push pkgs/*.nupkg --source RyubingPkgs

View File

@@ -21,8 +21,8 @@ Additionally, 1.2.74 & 75 were fixes for uploading Windows build artifacts.
1.2.76 fixes a rare crash on startup.
## [1.2.72](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.72>) - 2024-11-03
PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://github.com/GreemDev/Ryujinx/pull/164>), [#139](<https://github.com/GreemDev/Ryujinx/pull/139>)
## [1.2.72](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.72>) - 2024-11-03
PRs [#163](<https://web.archive.org/web/20241123015123/https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://web.archive.org/web/20250307192526/https://github.com/Ryubing/Ryujinx/pull/164>), [#139](<https://web.archive.org/web/20250306123457/https://github.com/Ryubing/Ryujinx/pull/139>)
### HLE:
- Add DebugMouse HID device.
- Fixes "Clock Tower Rewind" crashing while loading.
@@ -32,7 +32,7 @@ PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://gith
### misc:
- Update macOS distribution .icns.
## [1.2.69](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.69>) - 2024-11-01
## [1.2.69](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.69>) - 2024-11-01
### Infra:
- Compile the native libraries into the Ryujinx executable.
- Remove `libarmeilleure-jitsupport.dylib` from Windows & Linux releases (dylibs are macOS-only)
@@ -42,8 +42,8 @@ PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://gith
- Replace "" with `string.Empty`.
- Code cleanups & simplifications.
## [1.2.67](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.67>) - 2024-11-01
PRs [#36](<https://github.com/GreemDev/Ryujinx/pull/36>), [#135](<https://github.com/GreemDev/Ryujinx/pull/135>)
## [1.2.67](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.67>) - 2024-11-01
PRs [#36](<https://web.archive.org/web/20250306215917/https://github.com/Ryubing/Ryujinx/pull/36>), [#135](<https://web.archive.org/web/20241122135125/https://github.com/GreemDev/Ryujinx/pull/135>)
### GUI:
- Set UseFloatingWatermark to false when watermark is empty
@@ -54,8 +54,8 @@ PRs [#36](<https://github.com/GreemDev/Ryujinx/pull/36>), [#135](<https://github
- Fix homebrew loading.
## [1.2.64](https://github.com/GreemDev/Ryujinx/releases/tag/1.2.64) - 2024-10-30
PRs [#92](https://github.com/GreemDev/Ryujinx/pull/92), [#96](https://github.com/GreemDev/Ryujinx/pull/96), [#97](https://github.com/GreemDev/Ryujinx/pull/97), [#101](https://github.com/GreemDev/Ryujinx/pull/101), [#103](https://github.com/GreemDev/Ryujinx/pull/103)
## [1.2.64](https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.64) - 2024-10-30
PRs [#92](https://web.archive.org/web/20241118052724/https://github.com/GreemDev/Ryujinx/pull/92), ~~[#96](https://github.com/GreemDev/Ryujinx/pull/96)~~, ~~[#97](https://github.com/GreemDev/Ryujinx/pull/97)~~, [#101](https://web.archive.org/web/20250306223605/https://github.com/Ryubing/Ryujinx/pull/101), ~~[#103](https://github.com/GreemDev/Ryujinx/pull/103)~~
### GUI:
- Option to show classic-style title bar. Requires restart of emulator to take effect.
- This is only relevant on Windows. Other Operating Systems default to this being on and not being changeable, because the custom (current) title bar only works on Windows in the first place.
@@ -71,14 +71,14 @@ PRs [#92](https://github.com/GreemDev/Ryujinx/pull/92), [#96](https://github.com
## 1.2.59 - 2024-10-27
PRs [#88](https://github.com/GreemDev/Ryujinx/pull/88), [#87](https://github.com/GreemDev/Ryujinx/pull/87)
PRs ~~[#88](https://github.com/GreemDev/Ryujinx/pull/88), [#87](https://github.com/GreemDev/Ryujinx/pull/87)~~
### i18n:
- fr_FR:
- Add missing translations for new features & fix a couple wrong ones.
- Fix Ignore Missing Services / Ignore Applet tooltip.
## 1.2.57 - 2024-10-27
PRs [#60](https://github.com/GreemDev/Ryujinx/pull/60), [#42](https://github.com/GreemDev/Ryujinx/pull/42)
PRs ~~[#60](https://github.com/GreemDev/Ryujinx/pull/60)~~, [#42](https://web.archive.org/web/20241126203614/https://github.com/GreemDev/Ryujinx/pull/42)
### GUI:
- Automatically remove invalid DLC & updates as part of autoload.
- Added Thai translation for Ignore Applet hover tooltip.
@@ -104,7 +104,7 @@ PRs [#60](https://github.com/GreemDev/Ryujinx/pull/60), [#42](https://github.com
- Code cleanup.
## 1.2.44 - 2024-10-25
PR [#59](https://github.com/GreemDev/Ryujinx/pull/59)
PR [#59](https://web.archive.org/web/20241125060420/https://github.com/GreemDev/Ryujinx/pull/59)
### GUI:
- Add descriptions for "ignoring applet" translated into other languages.
@@ -117,9 +117,9 @@ NOTE: The translation isn't referenced in the code yet, it will be in the next u
## 1.2.42 - 2024-10-24
Sources:
Init function: https://github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9
Init function: [archive of github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9](https://web.archive.org/web/20241122193401/https://github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9)
Shader counter: https://github.com/MutantAura/Ryujinx/commit/67b873645fd593e83d042a77bf7ab12e5ec97357
Shader counter: ~~https://github.com/MutantAura/Ryujinx/commit/67b873645fd593e83d042a77bf7ab12e5ec97357~~ Original commit has been lost
Thanks MutantAura :D
### GUI:
@@ -127,14 +127,14 @@ Thanks MutantAura :D
- Remove graphics backend / GPU name event logic in favor of a single init function.
## 1.2.41 - 2024-10-24
PR [#54](https://github.com/GreemDev/Ryujinx/pull/54)
PR ~~[#54](https://github.com/GreemDev/Ryujinx/pull/54)~~
Thanks Whitescatz!
### i18n:
- th_TH (Thai): Added missing translations, reduce transliterated words, fix grammar.
## 1.2.40 - 2024-10-23
PR [#40](https://github.com/GreemDev/Ryujinx/pull/40)
PR ~~[#40](https://github.com/GreemDev/Ryujinx/pull/40)~~
Thanks Вова С!
### GUI:
@@ -148,30 +148,30 @@ Thanks Вова С!
- Should prevent crashing on config loads in some circumstances.
## 1.2.38 - 2024-10-23
PR [#51](https://github.com/GreemDev/Ryujinx/pull/51)
PR [#51](https://web.archive.org/web/20241127022413/https://github.com/GreemDev/Ryujinx/pull/51)
### i18n:
- zh_CH (Simplified Chinese): Add some missing translations.
## 1.2.37 - 2024-10-23
PR [#37](https://github.com/GreemDev/Ryujinx/pull/37)
PR [#37](https://web.archive.org/web/20241123010103/https://github.com/GreemDev/Ryujinx/pull/37)
Thanks Last Breath!
### GUI:
- Set the default controller to the Pro Controller.
## 1.2.36 - 2024-10-21
PR [#30](https://github.com/GreemDev/Ryujinx/pull/30)
PR ~~[#30](https://github.com/GreemDev/Ryujinx/pull/30)~~
### GUI:
- Fix repeated dialog popup notifying you of new updates when there aren't any, while having a bundled update inside an XCI and an external update file.
## 1.2.35 - 2024-10-21
PR [#32](https://github.com/GreemDev/Ryujinx/pull/32)
PR [#32](https://web.archive.org/web/20241127010942/https://github.com/GreemDev/Ryujinx/pull/32)
### GUI:
- Replace "expand DRAM" option with a DRAM size dropdown.
- Allows for using mods which require a ridiculous amount of memory to allocate from.
## 1.2.34 - 2024-10-21
PR [#29](https://github.com/GreemDev/Ryujinx/pull/29)
PR [#29](https://web.archive.org/web/20241125093029/https://github.com/GreemDev/Ryujinx/pull/29)
### GUI:
- Fix duplicate controller names when 2 controllers of the same type are connected.
### INPUT:
@@ -248,7 +248,7 @@ Added Low-power PPTC mode strings to the translation files.
## 1.2.1-1.2.19 - 2024-10-08 - 2024-10-11
### GUI/INFRA/MISC:
- Remove GTK UI.
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
- Autoload DLC/Updates from dir ([#12](https://web.archive.org/web/20241127004005/https://github.com/GreemDev/Ryujinx/pull/12)).
- Changed executable icon to rainbow logo.
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.

View File

@@ -19,8 +19,8 @@
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
<PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="Humanizer" Version="2.14.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
@@ -42,11 +42,11 @@
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.116" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.29" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.29" />
<PackageVersion Include="Gommon" Version="2.7.1.1" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.7.2.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.6.0" />
<PackageVersion Include="Sep" Version="0.11.1" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />

File diff suppressed because it is too large Load Diff

View File

@@ -978,7 +978,7 @@
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
010018900DD00000,"DOOM (1993)",nvdec;online-broken,menus,2022-09-06 13:32:19
01008CB01E52E000,"DOOM + DOOM II",opengl;ldn-untested;LAN,playable,2024-09-12 07:06:01
010029D00E740000,"DOOM 3",crash,menus,2024-08-03 05:25:47
010029D00E740000,"DOOM 3",crash;slow,menus,2024-08-03 05:25:47
01005D700E742000,"DOOM 64",nvdec;vulkan,playable,2020-10-13 23:47:28
0100D4F00DD02000,"DOOM II (Classic)",nvdec;online,playable,2021-06-03 20:10:01
0100B1A00D8CE000,"DOOM® Eternal",gpu;slow;nvdec;online-broken,ingame,2024-08-28 15:57:17
@@ -1097,7 +1097,7 @@
0100F9600E746000,"ESP Ra.De. Psi",audio;slow,ingame,2024-03-07 15:05:08
010073000FE18000,"Esports powerful pro yakyuu 2020",gpu;crash;Needs More Attention,ingame,2024-04-29 05:34:14
01004F9012FD8000,"Estranged: The Departure",nvdec;UE4,playable,2022-10-24 10:37:58
010018f01e0a0000,"Eternights",,playable,2025-07-30 12:10:24
010018F01E0A0000,"Eternights",,playable,2025-07-30 12:10:24
0100CB900B498000,"Eternum Ex",,playable,2021-01-13 20:28:32
010092501EB2C000,"Europa (Demo)",gpu;crash;UE4,ingame,2024-04-23 10:47:12
01007BE0160D6000,"EVE ghost enemies",gpu,ingame,2023-01-14 03:13:30
@@ -1243,7 +1243,7 @@
010003F00BD48000,"Friday the 13th: Killer Puzzle",,playable,2021-01-28 01:33:38
010092A00C4B6000,"Friday the 13th: The Game Ultimate Slasher Edition",nvdec;online-broken;UE4,playable,2022-09-06 17:33:27
0100F200178F4000,"FRONT MISSION 1st: Remake",,playable,2023-06-09 07:44:24
0100c4e018a24000,"FRONT MISSION 2: Remake",,playable,2025-07-30 12:11:23
0100C4E018A24000,"FRONT MISSION 2: Remake",,playable,2025-07-30 12:11:23
01007E6019872000,"FRONT MISSION 3: Remake",,playable,2025-07-30 12:12:02
0100861012474000,"Frontline Zed",,playable,2020-10-03 12:55:59
0100B5300B49A000,"Frost",,playable,2022-07-27 12:00:36
@@ -1450,6 +1450,7 @@
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
010013C00E930000,"Hollow Knight: Silksong",,playable,2025-09-04 17:23:22
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
010071B00C904000,"HoPiKo",,playable,2021-01-13 20:12:38
@@ -1888,7 +1889,7 @@
010097800EA20000,"Monster Energy Supercross - The Official Videogame 3",UE4;audout;nvdec;online,playable,2021-06-14 12:37:54
0100E9900ED74000,"Monster Farm",32-bit;nvdec,playable,2021-05-05 19:29:13
0100770008DD8000,"Monster Hunter Generations Ultimate™",32-bit;online-broken;ldn-works,playable,2024-03-18 14:35:36
0100B04011742000,"Monster Hunter Rise",gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works,ingame,2024-08-24 11:04:59
0100B04011742000,"MONSTER HUNTER RISE",gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works,ingame,2024-08-24 11:04:59
010093A01305C000,"Monster Hunter Rise Demo",online-broken;ldn-works;demo,playable,2022-10-18 23:04:17
0100E21011446000,"Monster Hunter Stories 2: Wings of Ruin",services,ingame,2022-07-10 19:27:30
010042501329E000,"MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version",demo,playable,2022-11-13 22:20:26
@@ -2313,7 +2314,7 @@
010077B00BDD8000,"Professional Farmer: Nintendo Switch™ Edition",slow,playable,2020-12-16 13:38:19
010018300C83A000,"Professor Lupo and his Horrible Pets",,playable,2020-06-12 00:08:45
0100D1F0132F6000,"Professor Lupo: Ocean",,playable,2021-04-14 16:33:33
0100c3a017834000,"Prodeus",,playable,2025-07-30 12:07:52
0100C3A017834000,"Prodeus",,playable,2025-07-30 12:07:52
0100BBD00976C000,"Project Highrise: Architect's Edition",,playable,2022-08-10 17:19:12
0100ACE00DAB6000,"Project Nimbus: Complete Edition",nvdec;UE4;vulkan-backend-bug,playable,2022-08-10 17:35:43
01002980140F6000,"Project TRIANGLE STRATEGY™ Debut Demo",UE4;demo,playable,2022-10-24 21:40:27
@@ -2579,6 +2580,7 @@
0100C610154CA000,"Shadowrun: Hong Kong - Extended Edition",gpu;Needs Update,ingame,2022-10-04 20:53:09
010000000EEF0000,"Shadows 2: Perfidia",,playable,2020-08-07 12:43:46
0100AD700CBBE000,"Shadows of Adam",,playable,2021-01-11 13:35:58
010037A01F96C000,"Shadows of the Damned: Hella Remastered",,playable,2025-09-05 11:34:32
01002A800C064000,"Shadowverse Champions Battle",,playable,2022-10-02 22:59:29
01003B90136DA000,"Shadowverse: Champion's Battle",crash,nothing,2023-03-06 00:31:50
0100820013612000,"Shady Part of Me",,playable,2022-10-20 11:31:55
@@ -2977,6 +2979,7 @@
0100EBA01548E000,"The Cruel King and the Great Hero",gpu;services,ingame,2022-12-02 07:02:08
010051800E922000,"The Dark Crystal: Age of Resistance Tactics",,playable,2020-08-11 13:43:41
01003DE00918E000,"The Darkside Detective",,playable,2020-06-03 22:16:18
010032B015D66000,"The DioField Chronicle",,playable,2025-09-05 11:35:50
01000A10041EA000,"The Elder Scrolls V: Skyrim",gpu;crash,ingame,2024-07-14 03:21:31
01004A9006B84000,"The End Is Nigh",,playable,2020-06-01 11:26:45
0100CA100489C000,"The Escapists 2",nvdec,playable,2020-09-24 12:31:31
1 title_id game_name labels status last_updated
978 0100416004C00000 DOOM gpu;slow;nvdec;online-broken ingame 2024-09-23 15:40:07
979 010018900DD00000 DOOM (1993) nvdec;online-broken menus 2022-09-06 13:32:19
980 01008CB01E52E000 DOOM + DOOM II opengl;ldn-untested;LAN playable 2024-09-12 07:06:01
981 010029D00E740000 DOOM 3 crash crash;slow menus 2024-08-03 05:25:47
982 01005D700E742000 DOOM 64 nvdec;vulkan playable 2020-10-13 23:47:28
983 0100D4F00DD02000 DOOM II (Classic) nvdec;online playable 2021-06-03 20:10:01
984 0100B1A00D8CE000 DOOM® Eternal gpu;slow;nvdec;online-broken ingame 2024-08-28 15:57:17
1097 0100F9600E746000 ESP Ra.De. Psi audio;slow ingame 2024-03-07 15:05:08
1098 010073000FE18000 Esports powerful pro yakyuu 2020 gpu;crash;Needs More Attention ingame 2024-04-29 05:34:14
1099 01004F9012FD8000 Estranged: The Departure nvdec;UE4 playable 2022-10-24 10:37:58
1100 010018f01e0a0000 010018F01E0A0000 Eternights playable 2025-07-30 12:10:24
1101 0100CB900B498000 Eternum Ex playable 2021-01-13 20:28:32
1102 010092501EB2C000 Europa (Demo) gpu;crash;UE4 ingame 2024-04-23 10:47:12
1103 01007BE0160D6000 EVE ghost enemies gpu ingame 2023-01-14 03:13:30
1243 010003F00BD48000 Friday the 13th: Killer Puzzle playable 2021-01-28 01:33:38
1244 010092A00C4B6000 Friday the 13th: The Game Ultimate Slasher Edition nvdec;online-broken;UE4 playable 2022-09-06 17:33:27
1245 0100F200178F4000 FRONT MISSION 1st: Remake playable 2023-06-09 07:44:24
1246 0100c4e018a24000 0100C4E018A24000 FRONT MISSION 2: Remake playable 2025-07-30 12:11:23
1247 01007E6019872000 FRONT MISSION 3: Remake playable 2025-07-30 12:12:02
1248 0100861012474000 Frontline Zed playable 2020-10-03 12:55:59
1249 0100B5300B49A000 Frost playable 2022-07-27 12:00:36
1450 0100F7300ED2C000 Hoggy2 playable 2022-10-10 13:53:35
1451 0100F7E00C70E000 Hogwarts Legacy UE4;slow ingame 2024-09-03 19:53:58
1452 0100633007D48000 Hollow Knight nvdec playable 2023-01-16 15:44:56
1453 010013C00E930000 Hollow Knight: Silksong playable 2025-09-04 17:23:22
1454 0100F2100061E800 Hollow0 UE4;gpu ingame 2021-03-03 23:42:56
1455 0100342009E16000 Holy Potatoes! What The Hell?! playable 2020-07-03 10:48:56
1456 010071B00C904000 HoPiKo playable 2021-01-13 20:12:38
1889 010097800EA20000 Monster Energy Supercross - The Official Videogame 3 UE4;audout;nvdec;online playable 2021-06-14 12:37:54
1890 0100E9900ED74000 Monster Farm 32-bit;nvdec playable 2021-05-05 19:29:13
1891 0100770008DD8000 Monster Hunter Generations Ultimate™ 32-bit;online-broken;ldn-works playable 2024-03-18 14:35:36
1892 0100B04011742000 Monster Hunter Rise MONSTER HUNTER RISE gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works ingame 2024-08-24 11:04:59
1893 010093A01305C000 Monster Hunter Rise Demo online-broken;ldn-works;demo playable 2022-10-18 23:04:17
1894 0100E21011446000 Monster Hunter Stories 2: Wings of Ruin services ingame 2022-07-10 19:27:30
1895 010042501329E000 MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version demo playable 2022-11-13 22:20:26
2314 010077B00BDD8000 Professional Farmer: Nintendo Switch™ Edition slow playable 2020-12-16 13:38:19
2315 010018300C83A000 Professor Lupo and his Horrible Pets playable 2020-06-12 00:08:45
2316 0100D1F0132F6000 Professor Lupo: Ocean playable 2021-04-14 16:33:33
2317 0100c3a017834000 0100C3A017834000 Prodeus playable 2025-07-30 12:07:52
2318 0100BBD00976C000 Project Highrise: Architect's Edition playable 2022-08-10 17:19:12
2319 0100ACE00DAB6000 Project Nimbus: Complete Edition nvdec;UE4;vulkan-backend-bug playable 2022-08-10 17:35:43
2320 01002980140F6000 Project TRIANGLE STRATEGY™ Debut Demo UE4;demo playable 2022-10-24 21:40:27
2580 0100C610154CA000 Shadowrun: Hong Kong - Extended Edition gpu;Needs Update ingame 2022-10-04 20:53:09
2581 010000000EEF0000 Shadows 2: Perfidia playable 2020-08-07 12:43:46
2582 0100AD700CBBE000 Shadows of Adam playable 2021-01-11 13:35:58
2583 010037A01F96C000 Shadows of the Damned: Hella Remastered playable 2025-09-05 11:34:32
2584 01002A800C064000 Shadowverse Champions Battle playable 2022-10-02 22:59:29
2585 01003B90136DA000 Shadowverse: Champion's Battle crash nothing 2023-03-06 00:31:50
2586 0100820013612000 Shady Part of Me playable 2022-10-20 11:31:55
2979 0100EBA01548E000 The Cruel King and the Great Hero gpu;services ingame 2022-12-02 07:02:08
2980 010051800E922000 The Dark Crystal: Age of Resistance Tactics playable 2020-08-11 13:43:41
2981 01003DE00918E000 The Darkside Detective playable 2020-06-03 22:16:18
2982 010032B015D66000 The DioField Chronicle playable 2025-09-05 11:35:50
2983 01000A10041EA000 The Elder Scrolls V: Skyrim gpu;crash ingame 2024-07-14 03:21:31
2984 01004A9006B84000 The End Is Nigh playable 2020-06-01 11:26:45
2985 0100CA100489C000 The Escapists 2 nvdec playable 2020-09-24 12:31:31

View File

@@ -182,6 +182,7 @@ namespace Ryujinx.Common
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"0100f7e00c70e000", // Hogwarts Legacy
"010013c00e930000", // Hollow Knight: Silksong
"010085500130a000", // Lego City: Undercover
"010073c01af34000", // LEGO Horizon Adventures
"0100d71004694000", // Minecraft

View File

@@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.Device
uint alignedOffset = index * RegisterSize;
DebugWrite(alignedOffset, data);
GetRefIntAlignedUncheck(index) = data;
SetIntAlignedUncheck(index, data);
_writeCallbacks[index]?.Invoke(data);
}
@@ -112,9 +112,7 @@ namespace Ryujinx.Graphics.Device
uint alignedOffset = index * RegisterSize;
DebugWrite(alignedOffset, data);
ref int storage = ref GetRefIntAlignedUncheck(index);
changed = storage != data;
storage = data;
changed = SetIntAlignedUncheckChanged(index, data);
_writeCallbacks[index]?.Invoke(data);
}
@@ -154,5 +152,24 @@ namespace Ryujinx.Graphics.Device
{
return ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetIntAlignedUncheck(ulong index, int data)
{
Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index) = data;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool SetIntAlignedUncheckChanged(ulong index, int data)
{
ref int val = ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index);
if (val == data)
{
return false;
}
val = data;
return true;
}
}
}

View File

@@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong size,
BufferStage stage,
bool sparseCompatible,
List<Buffer> baseBuffers)
RangeItem<Buffer>[] baseBuffers)
{
_context = context;
_physicalMemory = physicalMemory;
@@ -128,18 +128,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
List<IRegionHandle> baseHandles = null;
if (baseBuffers.Count != 0)
if (baseBuffers.Length != 0)
{
baseHandles = new List<IRegionHandle>();
foreach (Buffer buffer in baseBuffers)
foreach (RangeItem<Buffer> item in baseBuffers)
{
if (buffer._useGranular)
if (item.Value._useGranular)
{
baseHandles.AddRange((buffer._memoryTrackingGranular.GetHandles()));
baseHandles.AddRange((item.Value._memoryTrackingGranular.GetHandles()));
}
else
{
baseHandles.Add(buffer._memoryTracking);
baseHandles.Add(item.Value._memoryTracking);
}
}
}

View File

@@ -1,4 +1,5 @@
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
@@ -56,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="parent">Parent buffer</param>
/// <param name="stage">Initial buffer stage</param>
/// <param name="baseBuffers">Buffers to inherit state from</param>
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, List<Buffer> baseBuffers)
public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem<Buffer>[] baseBuffers)
{
_size = (int)parent.Size;
_systemMemoryType = context.Capabilities.MemoryType;
@@ -72,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
BufferStage storageFlags = stage & BufferStage.StorageMask;
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Count == 0)
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Length == 0)
{
_desiredType = BufferBackingType.DeviceMemory;
}
@@ -100,11 +101,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// TODO: Might be nice to force atomic access to be device local for any stage.
}
if (baseBuffers.Count != 0)
if (baseBuffers.Length != 0)
{
foreach (Buffer buffer in baseBuffers)
foreach (RangeItem<Buffer> item in baseBuffers)
{
CombineState(buffer.BackingState);
CombineState(item.Value.BackingState);
}
}
}

View File

@@ -81,13 +81,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
MemoryRange subRange = range.GetSubRange(index);
_buffers.Lock.EnterReadLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(subRange.Address, subRange.Size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size);
RangeItem<Buffer> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
current.Value.Unmapped(subRange.Address, subRange.Size);
current = current.Next;
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
}
_buffers.Lock.ExitReadLock();
@@ -489,10 +487,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
{
_buffers.Lock.EnterWriteLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
Buffer newBuffer = null;
if (first is not null)
_buffers.Lock.EnterWriteLock();
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (overlaps.Length != 0)
{
// The buffer already exists. We can just return the existing buffer
// if the buffer we need is fully contained inside the overlapping buffer.
@@ -502,7 +502,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong endAddress = address + size;
if (first.Address > address || first.EndAddress < endAddress)
if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
{
bool anySparseCompatible = false;
@@ -515,53 +515,61 @@ namespace Ryujinx.Graphics.Gpu.Memory
// sequential memory.
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
// range crosses a page, and after alignment, ends having a size of 2 pages.
if (first == last &&
address >= first.Address &&
endAddress - first.EndAddress <= BufferAlignmentSize * 2)
if (overlaps.Length == 1 &&
address >= overlaps[0].Address &&
endAddress - overlaps[0].EndAddress <= BufferAlignmentSize * 2)
{
// Try to grow the buffer by 1.5x of its current size.
// This improves performance in the cases where the buffer is resized often by small amounts.
ulong existingSize = first.Value.Size;
ulong existingSize = overlaps[0].Value.Size;
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
size = Math.Max(size, growthSize);
endAddress = address + size;
(first, last) = _buffers.FindOverlaps(address, size);
overlaps = _buffers.FindOverlapsAsSpan(address, size);
}
address = Math.Min(address, first.Address);
endAddress = Math.Max(endAddress, last.EndAddress);
address = Math.Min(address, overlaps[0].Address);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
List<Buffer> overlaps = [];
RangeItem<Buffer> current = first;
while (current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
anySparseCompatible |= current.Value.SparseCompatible;
overlaps.Add(current.Value);
_buffers.Remove(current.Value);
current = current.Next;
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
}
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
_buffers.Lock.ExitWriteLock();
ulong newSize = endAddress - address;
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps);
_buffers.Add(newBuffer);
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
}
}
else
{
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
_buffers.Lock.ExitWriteLock();
_buffers.Add(buffer);
// No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
}
/// <summary>
/// Creates a new buffer for the specified range, if needed.
@@ -575,67 +583,74 @@ namespace Ryujinx.Graphics.Gpu.Memory
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
{
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
Buffer newBuffer = null;
_buffers.Lock.EnterWriteLock();
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
if (first is not null)
if (overlaps.Length != 0)
{
// If the buffer already exists, make sure if covers the entire range,
// and make sure it is properly aligned, otherwise sparse mapping may fail.
ulong endAddress = address + size;
if (first.Address > address ||
first.EndAddress < endAddress ||
(first.Address & (alignment - 1)) != 0 ||
(!first.Value.SparseCompatible && sparseAligned))
if (overlaps[0].Address > address ||
overlaps[0].EndAddress < endAddress ||
(overlaps[0].Address & (alignment - 1)) != 0 ||
(!overlaps[0].Value.SparseCompatible && sparseAligned))
{
// We need to make sure the new buffer is properly aligned.
// However, after the range is aligned, it is possible that it
// overlaps more buffers, so try again after each extension
// and ensure we cover all overlaps.
RangeItem<Buffer> oldFirst;
endAddress = Math.Max(endAddress, last.EndAddress);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
int oldOverlapCount;
do
{
address = Math.Min(address, first.Address);
address = Math.Min(address, overlaps[0].Address);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
address &= ~(alignment - 1);
oldFirst = first;
(first, last) = _buffers.FindOverlaps(address, endAddress - address);
oldOverlapCount = overlaps.Length;
overlaps = _buffers.FindOverlapsAsSpan(address, endAddress - address);
}
while (oldFirst != first);
while (oldOverlapCount != overlaps.Length);
ulong newSize = endAddress - address;
List<Buffer> overlaps = [];
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
RangeItem<Buffer> current = first;
while (current != last.Next)
{
overlaps.Add(current.Value);
_buffers.Remove(current.Value);
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
current = current.Next;
_buffers.Lock.ExitWriteLock();
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps);
_buffers.Add(newBuffer);
else
{
_buffers.Lock.ExitWriteLock();
}
}
else
{
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
_buffers.Add(buffer);
}
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
}
/// <summary>
@@ -648,13 +663,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="stage">The type of usage that created the buffer</param>
/// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
/// <param name="overlaps">Buffers overlapping the range</param>
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, List<Buffer> overlaps)
private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem<Buffer>[] overlaps)
{
Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps);
for (int index = 0; index < overlaps.Count; index++)
for (int index = 0; index < overlaps.Length; index++)
{
Buffer buffer = overlaps[index];
Buffer buffer = overlaps[index].Value;
int dstOffset = (int)(buffer.Address - newBuffer.Address);
@@ -882,7 +897,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
MemoryRange subRange = range.GetSubRange(i);
Buffer subBuffer = _buffers.FindOverlapFast(subRange.Address, subRange.Size).Value;
Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value;
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
@@ -930,7 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (size != 0)
{
buffer = _buffers.FindOverlapFast(address, size).Value;
buffer = _buffers.FindOverlap(address, size).Value;
buffer.CopyFromDependantVirtualBuffers();
buffer.SynchronizeMemory(address, size);
@@ -980,7 +995,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
if (size != 0)
{
Buffer buffer = _buffers.FindOverlapFast(address, size).Value;
Buffer buffer = _buffers.FindOverlap(address, size).Value;
if (copyBackVirtual)
{

View File

@@ -81,8 +81,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
private BufferMigration _source;
private BufferModifiedRangeList _migrationTarget;
private List<RangeItem<BufferModifiedRange>> _overlaps;
/// <summary>
/// Whether the modified range list has any entries or not.
/// </summary>
@@ -108,7 +106,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
_context = context;
_parent = parent;
_flushAction = flushAction;
_overlaps = [];
}
/// <summary>
@@ -122,12 +119,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = current.Value;
BufferModifiedRange overlap = overlaps[i].Value;
if (overlap.Address > address)
{
@@ -138,7 +134,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
// Remaining region is after this overlap.
size -= overlap.EndAddress - address;
address = overlap.EndAddress;
current = current.Next;
}
Lock.ExitReadLock();
@@ -158,12 +153,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="size">Size of the modified region in bytes</param>
public void SignalModified(ulong address, ulong size)
{
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
ulong endAddress = address + size;
ulong syncNumber = _context.SyncNumber;
// We may overlap with some existing modified regions. They must be cut into by the new entry.
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
if (first is null)
{
@@ -172,8 +166,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
return;
}
if (first == last)
{
if (first.Address == address && first.EndAddress == endAddress)
@@ -260,19 +252,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
{
Lock.EnterReadLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = current.Value;
BufferModifiedRange overlap = overlaps[i].Value;
if (overlap.SyncNumber == syncNumber)
{
rangeAction(overlap.Address, overlap.Size);
}
current = current.Next;
}
Lock.ExitReadLock();
@@ -288,22 +277,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
// We use the non-span method here because keeping the lock will cause a deadlock.
Lock.EnterReadLock();
_overlaps.Clear();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
_overlaps.Add(current);
current = current.Next;
}
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
Lock.ExitReadLock();
for (int i = 0; i < _overlaps.Count; i++)
for (int i = 0; i < overlaps.Length; i++)
{
BufferModifiedRange overlap = _overlaps[i].Value;
BufferModifiedRange overlap = overlaps[i].Value;
rangeAction(overlap.Address, overlap.Size);
}
}
@@ -404,8 +383,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong endAddress = address + size;
ulong currentSync = _context.SyncNumber;
List<RangeItem<BufferModifiedRange>> overlaps = [];
// Range list must be consistent for this operation
if (_migrationTarget != null)
{
@@ -416,16 +393,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
Lock.EnterWriteLock();
// We use the non-span method here because the array is partially modified by the code, which would invalidate a span.
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
RangeItem<BufferModifiedRange> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
int rangeCount = overlaps.Count;
int rangeCount = overlaps.Length;
if (rangeCount == 0)
{
@@ -503,6 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ranges._migrationTarget = this;
Lock.EnterWriteLock();
foreach (BufferModifiedRange range in inheritRanges)
{
Add(range);
@@ -582,7 +553,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
{
ulong endAddress = address + size;
Lock.EnterWriteLock();
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
if (first is null)
{

View File

@@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
ulong originalVa = gpuVa;
_virtualRanges.Lock.EnterWriteLock();
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlaps(gpuVa, size);
(RangeItem<VirtualRange> first, RangeItem<VirtualRange> last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size);
if (first is not null)
{

View File

@@ -501,54 +501,14 @@ namespace Ryujinx.HLE.FileSystem
using FileStream file = File.OpenRead(keysSource);
switch (info.Extension)
if (info.Extension is ".keys")
{
case ".zip":
using (ZipArchive archive = ZipFile.OpenRead(keysSource))
{
InstallKeysFromZip(archive, installDirectory);
}
break;
case ".keys":
VerifyKeysFile(keysSource);
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
break;
default:
}
else
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
}
}
private static void InstallKeysFromZip(ZipArchive archive, string installDirectory)
{
string temporaryDirectory = Path.Combine(installDirectory, "temp");
if (Directory.Exists(temporaryDirectory))
{
Directory.Delete(temporaryDirectory, true);
}
Directory.CreateDirectory(temporaryDirectory);
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase))
{
string extractDestination = Path.Combine(temporaryDirectory, entry.Name);
entry.ExtractToFile(extractDestination, overwrite: true);
try
{
VerifyKeysFile(extractDestination);
File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true);
}
catch (Exception)
{
Directory.Delete(temporaryDirectory, true);
throw;
}
}
}
Directory.Delete(temporaryDirectory, true);
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
{

View File

@@ -865,7 +865,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
lock (_threadingLock)
{
thread.ProcessListNode = _threads.AddLast(thread);
_threads.AddLast(thread.ProcessListNode);
}
}
@@ -1227,7 +1227,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
if (!thread.DebugHalt.WaitOne(TimeSpan.FromMilliseconds(50)))
if (!thread.DebugHalt.Wait(TimeSpan.FromMilliseconds(50)))
{
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
}

View File

@@ -13,16 +13,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
Monitor.Exit(mutex);
currentThread.Withholder = threadList;
currentThread.Reschedule(ThreadSchedState.Paused);
currentThread.WithholderNode = threadList.AddLast(currentThread);
if (currentThread.TerminationRequested)
{
threadList.Remove(currentThread.WithholderNode);
currentThread.Reschedule(ThreadSchedState.Running);
currentThread.Withholder = null;
@@ -31,6 +23,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
else
{
currentThread.Withholder = threadList;
currentThread.Reschedule(ThreadSchedState.Paused);
threadList.AddLast(currentThread.WithholderNode);
if (timeout > 0)
{
context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// even if they are not scheduled on guest cores.
if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
{
currentThread.SchedulerWaitEvent.WaitOne();
currentThread.SchedulerWaitEvent.Wait();
}
}
}

View File

@@ -194,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
SuggestedQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
_suggestedPrioritiesPerCore[core] |= 1L << prio;
}
@@ -223,7 +223,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
ScheduledQueue(prio, core).AddLast(thread.SiblingsPerCore[core]);
_scheduledPrioritiesPerCore[core] |= 1L << prio;
}
@@ -235,7 +235,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return;
}
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
ScheduledQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
_scheduledPrioritiesPerCore[core] |= 1L << prio;
}
@@ -251,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
queue.Remove(thread.SiblingsPerCore[core]);
thread.SiblingsPerCore[core] = queue.AddLast(thread);
queue.AddLast(thread.SiblingsPerCore[core]);
return queue.First.Value;
}

View File

@@ -318,11 +318,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
if (nextThread == null)
{
ActivateIdleThread();
currentThread.SchedulerWaitEvent.WaitOne();
currentThread.SchedulerWaitEvent.Wait();
}
else
{
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
nextThread.SchedulerWaitEvent.Set();
currentThread.SchedulerWaitEvent.Wait();
}
}
else

View File

@@ -39,9 +39,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public const int MaxWaitSyncObjects = 64;
private ManualResetEvent _schedulerWaitEvent;
private ManualResetEventSlim _schedulerWaitEvent;
public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent;
public ManualResetEventSlim SchedulerWaitEvent => _schedulerWaitEvent;
public Thread HostThread { get; private set; }
@@ -93,6 +93,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private LinkedListNode<KThread> _mutexWaiterNode;
private readonly LinkedList<KThread> _pinnedWaiters;
private LinkedListNode<KThread> _pinnedWaiterNode;
public KThread MutexOwner { get; private set; }
@@ -135,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly Lock _activityOperationLock = new();
internal readonly ManualResetEvent DebugHalt = new(false);
internal readonly ManualResetEventSlim DebugHalt = new(false);
public KThread(KernelContext context) : base(context)
{
@@ -144,8 +145,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
for (int i = 0; i < SiblingsPerCore.Length; i++)
{
SiblingsPerCore[i] = new LinkedListNode<KThread>(this);
}
_mutexWaiters = [];
_pinnedWaiters = [];
WithholderNode = new LinkedListNode<KThread>(this);
ProcessListNode = new LinkedListNode<KThread>(this);
_mutexWaiterNode = new LinkedListNode<KThread>(this);
_pinnedWaiterNode = new LinkedListNode<KThread>(this);
}
public Result Initialize(
@@ -631,7 +642,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
break;
}
_pinnedWaiters.AddLast(currentThread);
_pinnedWaiters.AddLast(_pinnedWaiterNode);
currentThread.Reschedule(ThreadSchedState.Paused);
}
@@ -848,7 +859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
return KernelResult.ThreadTerminating;
}
_pinnedWaiters.AddLast(currentThread);
_pinnedWaiters.AddLast(_pinnedWaiterNode);
currentThread.Reschedule(ThreadSchedState.Paused);
}
@@ -1262,7 +1273,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
if (_schedulerWaitEvent == null)
{
ManualResetEvent schedulerWaitEvent = new(false);
ManualResetEventSlim schedulerWaitEvent = new(false);
if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
{
@@ -1277,7 +1288,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private void ThreadStart()
{
_schedulerWaitEvent.WaitOne();
_schedulerWaitEvent.Wait();
DebugHalt.Reset();
KernelStatic.SetKernelContext(KernelContext, this);

View File

@@ -31,11 +31,13 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
private readonly KEvent _friendInvitationStorageChannelEvent;
private readonly KEvent _notificationStorageChannelEvent;
private readonly KEvent _healthWarningDisappearedSystemEvent;
private readonly KEvent _unknownEvent;
private int _gpuErrorDetectedSystemEventHandle;
private int _friendInvitationStorageChannelEventHandle;
private int _notificationStorageChannelEventHandle;
private int _healthWarningDisappearedSystemEventHandle;
private int _unknownEventHandle;
private bool _gamePlayRecordingState;
@@ -50,6 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
_friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
_notificationStorageChannelEvent = new KEvent(system.KernelContext);
_healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
_unknownEvent = new KEvent(system.KernelContext);
_horizon = system.LibHacHorizonManager.AmClient;
}
@@ -648,6 +651,23 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
return ResultCode.Success;
}
[CommandCmif(210)] // 20.0.0+
// GetUnknownEvent() -> handle<copy>
public ResultCode GetUnknownEvent(ServiceCtx context)
{
if (_unknownEventHandle == 0)
{
if (context.Process.HandleTable.GenerateHandle(_unknownEvent.ReadableEvent, out _unknownEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_unknownEventHandle);
return ResultCode.Success;
}
[CommandCmif(1001)] // 10.0.0+
// PrepareForJit()
public ResultCode PrepareForJit(ServiceCtx context)

View File

@@ -39,9 +39,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
public ref AtomicStorage<T> GetCurrentAtomicEntryRef()
{
ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), 1);
ulong countAvailable = Math.Min(Math.Max(0, ReadCurrentCount()), 1);
if (countAvailaible == 0)
if (countAvailable == 0)
{
_storage[0] = default;
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
while (true)
{
int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible) % MaxEntries);
int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailable) % MaxEntries);
ref AtomicStorage<T> result = ref storageSpan[inputEntryIndex];
@@ -63,9 +63,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1)
{
ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible);
ulong tempCount = Math.Min(ReadCurrentCount(), countAvailable);
countAvailaible = Math.Min(tempCount, 1);
countAvailable = Math.Min(tempCount, 1);
index = ReadCurrentIndex();
continue;

View File

@@ -0,0 +1,23 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
struct NpadCondition
{
#pragma warning disable CS0414 // Field is assigned but its value is never used
private uint _00;
private uint _04;
private NpadJoyHoldType _holdType;
private uint _0C;
#pragma warning restore CS0414 // Field is assigned but its value is never used
public static NpadCondition Create()
{
return new NpadCondition()
{
_00 = 0,
_04 = 1,
_holdType = NpadJoyHoldType.Horizontal,
_0C = 1,
};
}
}
}

View File

@@ -41,6 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
public NpadLarkType LarkTypeRight;
public NpadLuciaType LuciaType;
public uint Unknown43EC;
public ulong SixAxisSensorPropertiesArray;
[StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)]
private struct Reserved2Struct { }

View File

@@ -53,6 +53,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
[FieldOffset(0x3DC00)]
public RingLifo<DebugMouseState> DebugMouse;
/// <summary>
/// Pad Condition.
/// </summary>
[FieldOffset(0x3e200)]
public NpadCondition Condition;
public static SharedMemory Create()
{
SharedMemory result = new()
@@ -61,6 +67,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
TouchScreen = RingLifo<TouchScreenState>.Create(),
Mouse = RingLifo<MouseState>.Create(),
Keyboard = RingLifo<KeyboardState>.Create(),
Condition = NpadCondition.Create(),
};
Span<NpadState> npadsSpan = result.Npads.AsSpan();

View File

@@ -20,6 +20,18 @@ namespace Ryujinx.HLE
{
public class Switch : IDisposable
{
/// <summary>
/// Currently running emulated Switch, if there is one.
/// <para>
/// Proper usage of this property null checks it before use, unless the caller is certain that the emulation is running.
/// </para>
/// <para>
/// In case the emulation is running, there might be a way to directly pass the <see cref="Switch" /> instance, which is preferred.
/// </para>
/// <para>
/// The instance is set to <c>this</c> on any <see cref="Switch" /> instantiation, and set to <c>null</c> on any <see cref="Switch" /> disposal.
/// </para>
/// </summary>
public static Switch Shared { get; private set; }
public HleConfiguration Configuration { get; }

View File

@@ -12,9 +12,6 @@ namespace Ryujinx.Memory.Range
/// <typeparam name="T">Type of the range.</typeparam>
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange
{
private readonly Dictionary<ulong, RangeItem<T>> _quickAccess = new(AddressEqualityComparer.Comparer);
private readonly Dictionary<ulong, RangeItem<T>> _fastQuickAccess = new(AddressEqualityComparer.Comparer);
public readonly ReaderWriterLockSlim Lock = new();
/// <summary>
@@ -44,8 +41,6 @@ namespace Ryujinx.Memory.Range
RangeItem<T> rangeItem = new(item);
Insert(index, rangeItem);
_quickAccess.Add(item.Address, rangeItem);
}
/// <summary>
@@ -71,16 +66,8 @@ namespace Ryujinx.Memory.Range
Items[index + 1].Previous = rangeItem;
}
foreach (ulong addr in Items[index].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
Items[index] = rangeItem;
_quickAccess[item.Address] = rangeItem;
return true;
}
@@ -108,19 +95,8 @@ namespace Ryujinx.Memory.Range
Items[index + 1].Previous = rangeItem;
}
foreach (ulong addr in item.QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
Items[index] = rangeItem;
if (item.Address != rangeItem.Address)
_quickAccess.Remove(item.Address);
_quickAccess[rangeItem.Address] = rangeItem;
return true;
}
@@ -196,14 +172,6 @@ namespace Ryujinx.Memory.Range
if (index >= 0 && Items[index].Value.Equals(item))
{
_quickAccess.Remove(item.Address);
foreach (ulong addr in Items[index].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
RemoveAt(index);
return true;
@@ -232,16 +200,6 @@ namespace Ryujinx.Memory.Range
(int startIndex, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
for (int i = startIndex; i < endIndex; i++)
{
_quickAccess.Remove(Items[i].Address);
foreach (ulong addr in Items[i].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
}
if (endIndex < Count)
{
Items[endIndex].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
@@ -279,13 +237,6 @@ namespace Ryujinx.Memory.Range
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
{
_quickAccess.Remove(Items[endIndex].Address);
foreach (ulong addr in Items[endIndex].QuickAccessAddresses)
{
_quickAccess.Remove(addr);
_fastQuickAccess.Remove(addr);
}
if (endIndex == Count - 1)
{
break;
@@ -321,8 +272,6 @@ namespace Ryujinx.Memory.Range
{
Lock.EnterWriteLock();
Count = 0;
_quickAccess.Clear();
_fastQuickAccess.Clear();
Lock.ExitWriteLock();
}
@@ -344,7 +293,7 @@ namespace Ryujinx.Memory.Range
// So we need to return both the split 0-1 and 1-2 ranges.
Lock.EnterWriteLock();
(RangeItem<T> first, RangeItem<T> last) = FindOverlaps(address, size);
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size);
list = new List<T>();
if (first is null)
@@ -436,11 +385,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlap(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
{
return overlap;
}
int index = BinarySearchLeftEdge(address, address + size);
if (index < 0)
@@ -448,12 +392,6 @@ namespace Ryujinx.Memory.Range
return null;
}
if (Items[index].Address < address)
{
_quickAccess.TryAdd(address, Items[index]);
Items[index].QuickAccessAddresses.Add(address);
}
return Items[index];
}
@@ -466,11 +404,6 @@ namespace Ryujinx.Memory.Range
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap) || _fastQuickAccess.TryGetValue(address, out overlap))
{
return overlap;
}
int index = BinarySearch(address, address + size);
if (index < 0)
@@ -478,17 +411,6 @@ namespace Ryujinx.Memory.Range
return null;
}
if (Items[index].Address < address)
{
_quickAccess.TryAdd(address, Items[index]);
}
else
{
_fastQuickAccess.TryAdd(address, Items[index]);
}
Items[index].QuickAccessAddresses.Add(address);
return Items[index];
}
@@ -499,18 +421,8 @@ namespace Ryujinx.Memory.Range
/// <param name="size">Size in bytes of the range</param>
/// <returns>The first and last overlapping items, or null if none are found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public (RangeItem<T>, RangeItem<T>) FindOverlaps(ulong address, ulong size)
public (RangeItem<T>, RangeItem<T>) FindOverlapsAsNodes(ulong address, ulong size)
{
if (_quickAccess.TryGetValue(address, out RangeItem<T> overlap))
{
if (overlap.Next is null || overlap.Next.Address >= address + size)
{
return (overlap, overlap);
}
return (overlap, Items[BinarySearchRightEdge(address, address + size)]);
}
(int index, int endIndex) = BinarySearchEdges(address, address + size);
if (index < 0)
@@ -518,13 +430,45 @@ namespace Ryujinx.Memory.Range
return (null, null);
}
if (Items[index].Address < address)
{
_quickAccess.TryAdd(address, Items[index]);
Items[index].QuickAccessAddresses.Add(address);
return (Items[index], Items[endIndex - 1]);
}
return (Items[index], Items[endIndex - 1]);
public RangeItem<T>[] FindOverlapsAsArray(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
RangeItem<T>[] result;
if (index < 0)
{
result = [];
}
else
{
result = new RangeItem<T>[endIndex - index];
Array.Copy(Items, index, result, 0, endIndex - index);
}
return result;
}
public Span<RangeItem<T>> FindOverlapsAsSpan(ulong address, ulong size)
{
(int index, int endIndex) = BinarySearchEdges(address, address + size);
Span<RangeItem<T>> result;
if (index < 0)
{
result = [];
}
else
{
result = Items.AsSpan().Slice(index, endIndex - index);
}
return result;
}
public override IEnumerator<T> GetEnumerator()

View File

@@ -1,4 +1,5 @@
using Ryujinx.Memory.Range;
using System;
using System.Collections.Generic;
namespace Ryujinx.Memory.Tracking
@@ -79,12 +80,10 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = current.Value;
VirtualRegion region = overlaps[i].Value;
// If the region has been fully remapped, signal that it has been mapped again.
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
@@ -94,7 +93,6 @@ namespace Ryujinx.Memory.Tracking
}
region.UpdateProtection();
current = current.Next;
}
regions.Lock.ExitReadLock();
}
@@ -118,15 +116,11 @@ namespace Ryujinx.Memory.Tracking
{
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = current.Value;
region.SignalMappingChanged(false);
current = current.Next;
overlaps[i].Value.SignalMappingChanged(false);
}
regions.Lock.ExitReadLock();
}
@@ -303,21 +297,13 @@ namespace Ryujinx.Memory.Tracking
lock (TrackingLock)
{
NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
List<RangeItem<VirtualRegion>> overlaps = [];
// We use the non-span method here because keeping the lock will cause a deadlock.
regions.Lock.EnterReadLock();
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(address, size);
RangeItem<VirtualRegion> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size);
regions.Lock.ExitReadLock();
if (first is null && !precise)
if (overlaps.Length == 0 && !precise)
{
if (_memoryManager.IsRangeMapped(address, size))
{
@@ -338,7 +324,7 @@ namespace Ryujinx.Memory.Tracking
size += (ulong)_pageSize;
}
for (int i = 0; i < overlaps.Count; i++)
for (int i = 0; i < overlaps.Length; i++)
{
VirtualRegion region = overlaps[i].Value;

View File

@@ -1,6 +1,5 @@
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Gommon;
using LibHac;
using LibHac.Account;
using LibHac.Common;
@@ -411,7 +410,7 @@ namespace Ryujinx.Ava.Common
public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName)
{
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
Gommon.Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
});
@@ -424,7 +423,7 @@ namespace Ryujinx.Ava.Common
public static async Task ExtractSection(IStorageProvider storageProvider, NcaSectionType ncaSectionType, string titleFilePath, string titleName, int programIndex = 0)
{
Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
Gommon.Optional<IStorageFolder> result = await storageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
});

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.Common.Locale
private readonly Dictionary<LocaleKeys, string> _localeStrings;
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
private string _localeLanguageCode;
public string CurrentLanguageCode => _localeLanguageCode;
public static LocaleManager Instance { get; } = new();
public event Action LocaleChanged;

View File

@@ -1045,7 +1045,7 @@ namespace Ryujinx.Ava.Systems
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
}
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUi)
{
_viewModel.ShowMenuAndStatusBar = false;
}

View File

@@ -117,7 +117,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
using UniqueRef<IFile> npdmFile = new();
Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
LibHac.Result result = pfs.OpenFile(ref npdmFile.Ref, "/main.npdm".ToU8Span(), OpenMode.Read);
if (ResultFs.PathNotFound.Includes(result))
{

View File

@@ -98,7 +98,9 @@ namespace Ryujinx.Ava.Systems.PlayReport
_ => FormattedValue.ForceReset
};
return $"{playStatus} in {locations}";
return locations.Reset
? FormattedValue.ForceReset
: $"{playStatus} in {locations}";
}
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)

View File

@@ -7,6 +7,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Systems.Update.Client;
using Ryujinx.Systems.Update.Common;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Ryujinx.Ava.Systems
@@ -14,9 +15,11 @@ namespace Ryujinx.Ava.Systems
internal static partial class Updater
{
private static VersionResponse _versionResponse;
private static UpdateClient _updateClient;
private static UpdateClient CreateUpdateClient()
=> UpdateClient.Builder()
private static async Task<Return<VersionResponse>> QueryLatestVersionAsync()
{
_updateClient ??= UpdateClient.Builder()
.WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency.
.WithLogger((format, args, caller) =>
Logger.Info?.Print(
@@ -25,6 +28,26 @@ namespace Ryujinx.Ava.Systems
caller: caller)
);
try
{
return await _updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
? ReleaseChannel.Canary
: ReleaseChannel.Stable);
}
catch (HttpRequestException hre)
when (hre.HttpRequestError is HttpRequestError.ConnectionError)
{
return Return<VersionResponse>.Failure(
new MessageError("Connection error occurred. Is your internet down?"));
}
catch (HttpRequestException hre)
when (hre.HttpRequestError is HttpRequestError.NameResolutionError)
{
return Return<VersionResponse>.Failure(
new MessageError("DNS resolution error occurred. Is your internet down?"));
}
}
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
{
if (!Version.TryParse(Program.Version, out Version currentVersion))
@@ -41,17 +64,13 @@ namespace Ryujinx.Ava.Systems
return default;
}
using UpdateClient updateClient = CreateUpdateClient();
try
{
_versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
? ReleaseChannel.Canary
: ReleaseChannel.Stable);
_versionResponse = await QueryLatestVersionAsync().Then(x => x.Unwrap());
}
catch (Exception e)
{
Logger.Error?.Print(LogClass.Application, $"An error occurred when requesting for updates ({e.GetType().AsFullNamePrettyString()}): {e.Message}");
Logger.Error?.Print(LogClass.Application, $"{e.GetType().AsPrettyString()} thrown when requesting updates: {e.Message}");
_running = false;
return default;

View File

@@ -71,12 +71,12 @@
Command="{Binding OpenTitleUpdateManager}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuManageTitleUpdates}"
Icon="{ext:Icon fa-solid fa-code-compare}" />
Icon="{ext:Icon fa-solid fa-diagram-predecessor}" />
<MenuItem
Command="{Binding OpenDownloadableContentManager}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuManageDlc}"
Icon="{ext:Icon fa-solid fa-download}" />
Icon="{ext:Icon fa-solid fa-puzzle-piece}" />
<MenuItem
Command="{Binding OpenCheatManager}"
CommandParameter="{Binding}"
@@ -92,12 +92,12 @@
Command="{Binding OpenModsDirectory}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuOpenModsDirectory}"
Icon="{ext:Icon fa-solid fa-folder}" />
Icon="{ext:Icon fa-solid fa-folder-closed}" />
<MenuItem
Command="{Binding OpenSdModsDirectory}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuOpenSdModsDirectory}"
Icon="{ext:Icon fa-solid fa-folder}"
Icon="{ext:Icon fa-solid fa-folder-closed}"
ToolTip.Tip="{ext:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem
@@ -128,12 +128,12 @@
Command="{Binding OpenPtcDirectory}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
Icon="{ext:Icon fa-solid fa-folder}" />
Icon="{ext:Icon fa-solid fa-folder-closed}" />
<MenuItem
Command="{Binding OpenShaderCacheDirectory}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
Icon="{ext:Icon fa-solid fa-folder}" />
Icon="{ext:Icon fa-solid fa-folder-closed}" />
</MenuItem>
<MenuItem Header="{ext:Locale GameListContextMenuExtractData}" Icon="{ext:Icon fa-solid fa-file-export}">
<MenuItem

View File

@@ -2,7 +2,8 @@
x:Class="Ryujinx.Ava.RyujinxApp"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sty="using:FluentAvalonia.Styling">
xmlns:sty="using:FluentAvalonia.Styling"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -19,7 +20,7 @@
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="About Ryujinx" Click="AboutRyujinx_OnClick" />
<NativeMenuItem Header="{ext:Locale MenuBarHelpAbout}" Click="AboutRyujinx_OnClick" />
</NativeMenu>
</NativeMenu.Menu>
</Application>

View File

@@ -562,7 +562,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool StartGamesWithoutUI
public bool StartGamesWithoutUi
{
get => ConfigurationState.Instance.UI.StartNoUI;
set
@@ -975,8 +975,7 @@ namespace Ryujinx.Ava.UI.ViewModels
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle);
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
bool alreadyKesyInstalled = ContentManager.AreKeysAlredyPresent(systemDirectory);
if (alreadyKesyInstalled)
if (ContentManager.AreKeysAlredyPresent(systemDirectory))
{
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage);
}
@@ -994,7 +993,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result == UserResult.Yes)
{
Logger.Info?.Print(LogClass.Application, $"Installing Keys");
Logger.Info?.Print(LogClass.Application, $"Installing keys from {filename}");
Thread thread = new(() =>
{
@@ -1204,17 +1203,16 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.Set();
}
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected)
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle)
{
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Optional<IReadOnlyList<IStorageFolder>> result = await StorageProvider.OpenMultiFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = true,
Title = LocaleManager.Instance[dirSelectDialogTitle]
});
if (result.Count > 0)
if (result.TryGet(out IReadOnlyList<IStorageFolder> foldersToLoad))
{
List<string> dirs = result.Select(it => it.Path.LocalPath).ToList();
List<string> dirs = foldersToLoad.Select(it => it.Path.LocalPath).ToList();
int numAdded = onDirsSelected(dirs, out int numRemoved);
string msg = string.Join("\n",
@@ -1270,51 +1268,26 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void TakeScreenshot()
{
AppHost.ScreenshotRequested = true;
}
public void TakeScreenshot() => AppHost.ScreenshotRequested = true;
public void HideUi()
{
ShowMenuAndStatusBar = false;
}
public void HideUi() => ShowMenuAndStatusBar = false;
public void ToggleStartGamesInFullscreen()
{
StartGamesInFullscreen = !StartGamesInFullscreen;
}
public void ToggleStartGamesInFullscreen() => StartGamesInFullscreen = !StartGamesInFullscreen;
public void ToggleStartGamesWithoutUI()
{
StartGamesWithoutUI = !StartGamesWithoutUI;
}
public void ToggleStartGamesWithoutUi() => StartGamesWithoutUi = !StartGamesWithoutUi;
public void ToggleShowConsole()
{
ShowConsole = !ShowConsole;
}
public void ToggleShowConsole() => ShowConsole = !ShowConsole;
public void SetListMode()
{
Glyph = Glyph.List;
}
public void SetListMode() => Glyph = Glyph.List;
public void SetGridMode()
{
Glyph = Glyph.Grid;
}
public void SetGridMode() => Glyph = Glyph.Grid;
public void SetAspectRatio(AspectRatio aspectRatio)
{
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
}
public void SetAspectRatio(AspectRatio aspectRatio) => ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
public async Task InstallFirmwareFromFile()
{
IReadOnlyList<IStorageFile> result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
@@ -1338,69 +1311,50 @@ namespace Ryujinx.Ava.UI.ViewModels
},
});
if (result.Count > 0)
if (result.HasValue)
{
await HandleFirmwareInstallation(result[0].Path.LocalPath);
await HandleFirmwareInstallation(result.Value.Path.LocalPath);
}
}
public async Task InstallFirmwareFromFolder()
{
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
if (result.Count > 0)
if (result.HasValue)
{
await HandleFirmwareInstallation(result[0].Path.LocalPath);
await HandleFirmwareInstallation(result.Value.Path.LocalPath);
}
}
public async Task InstallKeysFromFile()
{
IReadOnlyList<IStorageFile> result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
{
Patterns = ["*.keys", "*.zip"],
AppleUniformTypeIdentifiers = ["com.ryujinx.xci", "public.zip-archive"],
MimeTypes = ["application/keys", "application/zip"],
},
new("KEYS")
{
Patterns = ["*.keys"],
AppleUniformTypeIdentifiers = ["com.ryujinx.xci"],
MimeTypes = ["application/keys"],
},
new("ZIP")
{
Patterns = ["*.zip"],
AppleUniformTypeIdentifiers = ["public.zip-archive"],
MimeTypes = ["application/zip"],
},
},
});
if (result.Count > 0)
if (result.HasValue)
{
await HandleKeysInstallation(result[0].Path.LocalPath);
await HandleKeysInstallation(result.Value.Path.LocalPath);
}
}
public async Task InstallKeysFromFolder()
{
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
AllowMultiple = false,
});
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
if (result.Count > 0)
if (result.HasValue)
{
await HandleKeysInstallation(result[0].Path.LocalPath);
await HandleKeysInstallation(result.Value.Path.LocalPath);
}
}
@@ -1503,10 +1457,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task OpenFile()
{
IReadOnlyList<IStorageFile> result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
AllowMultiple = false,
Title = LocaleManager.Instance[LocaleKeys.LoadApplicationFromFileDialogTitle],
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
@@ -1562,9 +1515,9 @@ namespace Ryujinx.Ava.UI.ViewModels
},
});
if (result.Count > 0)
if (result.HasValue)
{
if (ApplicationLibrary.TryGetApplicationsFromFile(result[0].Path.LocalPath,
if (ApplicationLibrary.TryGetApplicationsFromFile(result.Value.Path.LocalPath,
out List<ApplicationData> applications))
{
await LoadApplication(applications[0]);
@@ -1581,7 +1534,8 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadContentFromFolder(
LocaleKeys.AutoloadDlcAddedMessage,
LocaleKeys.AutoloadDlcRemovedMessage,
ApplicationLibrary.AutoLoadDownloadableContents);
ApplicationLibrary.AutoLoadDownloadableContents,
LocaleKeys.LoadDLCFromFolderDialogTitle);
}
public async Task LoadTitleUpdatesFromFolder()
@@ -1589,23 +1543,23 @@ namespace Ryujinx.Ava.UI.ViewModels
await LoadContentFromFolder(
LocaleKeys.AutoloadUpdateAddedMessage,
LocaleKeys.AutoloadUpdateRemovedMessage,
ApplicationLibrary.AutoLoadTitleUpdates);
ApplicationLibrary.AutoLoadTitleUpdates,
LocaleKeys.LoadTitleUpdatesFromFolderDialogTitle);
}
public async Task OpenFolder()
{
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
AllowMultiple = false,
Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle]
});
if (result.Count > 0)
if (result.TryGet(out IStorageFolder value))
{
ApplicationData applicationData = new()
{
Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
Path = result[0].Path.LocalPath,
Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath),
Path = value.Path.LocalPath,
};
await LoadApplication(applicationData);
@@ -1810,10 +1764,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
{
IReadOnlyList<IStorageFile> result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
@@ -1822,9 +1775,10 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
});
if (result.Count > 0)
if (result.HasValue)
{
AppHost.Device.System.ScanAmiiboFromBin(result[0].Path.LocalPath);
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
}
}
}

View File

@@ -39,36 +39,29 @@
Header="{ext:Locale MenuBarFileOpenUnpacked}"
Icon="{ext:Icon fa-solid fa-folder-open}"
IsEnabled="{Binding EnableNonGameRunningControls}" />
<MenuItem
Command="{Binding LoadDlcFromFolder}"
Header="{ext:Locale MenuBarFileLoadDlcFromFolder}"
Icon="{ext:Icon fa-solid fa-download}"
IsEnabled="{Binding EnableNonGameRunningControls}" />
<MenuItem
Command="{Binding LoadTitleUpdatesFromFolder}"
Header="{ext:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
Icon="{ext:Icon fa-solid fa-code-compare}"
Icon="{ext:Icon fa-solid fa-diagram-predecessor}"
IsEnabled="{Binding EnableNonGameRunningControls}" />
<MenuItem Header="{ext:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{ext:Icon fa-solid fa-microchip}">
<MenuItem
Name="MiiAppletMenuItem"
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
Icon="{ext:Icon fa-solid fa-person}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
</MenuItem>
Command="{Binding LoadDlcFromFolder}"
Header="{ext:Locale MenuBarFileLoadDlcFromFolder}"
Icon="{ext:Icon fa-solid fa-puzzle-piece}"
IsEnabled="{Binding EnableNonGameRunningControls}" />
<Separator />
<MenuItem
Command="{Binding OpenRyujinxFolder}"
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
Icon="{ext:Icon fa-solid fa-folder-closed}" />
<MenuItem
Command="{Binding OpenScreenshotsFolder}"
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
Icon="{ext:Icon fa-solid fa-desktop}" />
<MenuItem
Command="{Binding OpenLogsFolder}"
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
Icon="{ext:Icon fa-solid fa-file-lines}" />
Icon="{ext:Icon fa-solid fa-terminal}" />
<MenuItem
Command="{Binding OpenScreenshotsFolder}"
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
Icon="{ext:Icon fa-solid fa-image}" />
<Separator />
<MenuItem
Name="CloseRyujinxMenuItem"
@@ -99,14 +92,14 @@
</MenuItem>
<MenuItem
Padding="0"
Command="{Binding ToggleStartGamesWithoutUI}"
Command="{Binding ToggleStartGamesWithoutUi}"
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}"
Classes="withCheckbox">
<MenuItem.Icon>
<CheckBox
MinWidth="{DynamicResource CheckBoxSize}"
MinHeight="{DynamicResource CheckBoxSize}"
IsChecked="{Binding StartGamesWithoutUI, Mode=TwoWay}"
IsChecked="{Binding StartGamesWithoutUi, Mode=TwoWay}"
Padding="0" />
</MenuItem.Icon>
</MenuItem>
@@ -132,11 +125,6 @@
Icon="{ext:Icon fa-solid fa-globe}"
Classes="withCheckbox">
</MenuItem>
<MenuItem
Name="ToggleFileTypesMenuItem"
Padding="-10,0,0,0"
Header="{ext:Locale MenuBarShowFileTypes}" />
<Separator />
<MenuItem
Name="OpenSettingsMenuItem"
Padding="0"
@@ -222,20 +210,25 @@
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
</MenuItem>
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" />
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsUninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" />
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}" Icon="{ext:Icon fa-solid fa-clipboard}">
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" Icon="{ext:Icon fa-solid fa-square-plus}" />
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsUninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" Icon="{ext:Icon fa-solid fa-square-minus}" />
</MenuItem>
<Separator />
<MenuItem Header="{ext:Locale MenuBarActionsTools}" Icon="{ext:Icon fa-solid fa-toolbox}">
<MenuItem
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBarActionsOpenMiiEditor}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" ToolTip.Tip="{ext:Locale MenuBarActionsOpenMiiEditorToolTip}" />
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBarActionsXCITrimmer}" Icon="{ext:Icon fa-solid fa-scissors}" />
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarViewWindow}">
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarViewWindow}" Icon="{ext:Icon fa-solid fa-window-restore}">
<MenuItem Name="WindowSize720PMenuItem" Header="{ext:Locale MenuBarViewWindow720}" CommandParameter="1280 720" />
<MenuItem Name="WindowSize1080PMenuItem" Header="{ext:Locale MenuBarViewWindow1080}" CommandParameter="1920 1080" />
<MenuItem Name="WindowSize1440PMenuItem" Header="{ext:Locale MenuBarViewWindow1440}" CommandParameter="2560 1440" />
<MenuItem Name="WindowSize2160PMenuItem" Header="{ext:Locale MenuBarViewWindow2160}" CommandParameter="3840 2160" />
</MenuItem>
<MenuItem Name="ToggleFileTypesMenuItem" Header="{ext:Locale MenuBarShowFileTypes}" Icon="{ext:Icon fa-solid fa-tags}" />
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelp}">
<MenuItem
@@ -258,12 +251,6 @@
IsEnabled="{Binding IsRyuLdnEnabled}"/>
<Separator />
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelpFaqAndGuides}" Icon="{ext:Icon fa-solid fa-question}" >
<MenuItem
Name="FaqMenuItem"
Header="{ext:Locale MenuBarHelpFaq}"
Icon="{ext:Icon fa-brands fa-gitlab}"
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}"
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
<MenuItem
Name="SetupGuideMenuItem"
Header="{ext:Locale MenuBarHelpSetup}"
@@ -276,6 +263,12 @@
Icon="{ext:Icon fa-brands fa-gitlab}"
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}"
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
<MenuItem
Name="FaqMenuItem"
Header="{ext:Locale MenuBarHelpFaq}"
Icon="{ext:Icon fa-brands fa-gitlab}"
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}"
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
</MenuItem>
</MenuItem>
</Menu>

View File

@@ -61,6 +61,14 @@ namespace Ryujinx.Ava.UI.Views.Main
WindowSize1080PMenuItem.Command =
WindowSize1440PMenuItem.Command =
WindowSize2160PMenuItem.Command = Commands.Create<string>(ChangeWindowSize);
LocaleManager.Instance.LocaleChanged += OnLocaleChanged;
}
private void OnLocaleChanged()
{
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
Menu.Close();
}
private IEnumerable<CheckBox> GenerateToggleFileTypeItems() =>
@@ -80,6 +88,7 @@ namespace Ryujinx.Ava.UI.Views.Main
const string LocalePath = "Ryujinx/Assets/Locale.json";
string languageJson = EmbeddedResources.ReadAllText(LocalePath);
string currentLanguageCode = LocaleManager.Instance.CurrentLanguageCode;
LocalesJson locales = JsonHelper.Deserialize(languageJson, LocalesJsonContext.Default.LocalesJson);
@@ -105,7 +114,7 @@ namespace Ryujinx.Ava.UI.Views.Main
Padding = new Thickness(15, 0, 0, 0),
Margin = new Thickness(3, 0, 3, 0),
HorizontalAlignment = HorizontalAlignment.Stretch,
Header = languageName,
Header = language == currentLanguageCode ? $"{languageName} ✔" : languageName,
Command = Commands.Create(() => MainWindowViewModel.ChangeLanguage(language))
};