Compare commits

..

24 Commits

Author SHA1 Message Date
Astell
4444ecae41 fix super mario galaxy 0x25E00040 error (ryubing/ryujinx!155)
See merge request ryubing/ryujinx!155
2025-10-10 17:37:42 -05:00
Neo
4f5a236c21 UI: Settings → Interface Tab + General Settings (ryubing/ryujinx!154)
See merge request ryubing/ryujinx!154
2025-10-09 16:32:33 -05:00
Lorenzo
db31ff15c7 it_IT translation update (ryubing/ryujinx!148)
See merge request ryubing/ryujinx!148
2025-10-06 20:22:15 -05:00
Bluey Enjoyer
e70cd08c9a cleanup work for my last MR/PR (ryubing/ryujinx!153)
See merge request ryubing/ryujinx!153
2025-10-04 13:07:31 -05:00
Neo
60b9723df4 UI: Main Window + General (ryubing/ryujinx!150)
See merge request ryubing/ryujinx!150
2025-10-03 16:02:37 -05:00
KeatonTheBot
1900924a78 Fix push descriptors bugfix logic for Intel Arc on Linux 2025-10-01 21:58:22 -05:00
GreemDev
5fb0b5e7ec vulkan: Intel Arc on Linux also has the push descriptors bug. 2025-10-01 13:03:22 -05:00
Bluey Enjoyer
8d0e28ed9d New RPC images and compat updates (ryubing/ryujinx!151)
See merge request ryubing/ryujinx!151
2025-10-01 12:29:27 -05:00
Babib3l
a4eafd01f5 es_ES and fr_FR translation updates (ryubing/ryujinx!129)
See merge request ryubing/ryujinx!129
2025-09-28 14:58:32 -05:00
在中国的泰国青年_
f55aa87ab9 update thai language additional (ryubing/ryujinx!108)
See merge request ryubing/ryujinx!108
2025-09-27 23:27:56 -05:00
Mcost45
bb4f8d8749 Include SL/SR default bindings for single joycons (ryubing/ryujinx!149)
See merge request ryubing/ryujinx!149
2025-09-22 14:23:40 -05:00
Alula
a6cb681f10 feat: resolve real module names in HLE debugger (ryubing/ryujinx!147)
See merge request ryubing/ryujinx!147
2025-09-20 07:05:44 -05:00
GreemDev
00ff3e6b1b chore: CI: oops missed another zsync reference 2025-09-14 01:35:05 -05:00
GreemDev
12510a5396 chore: CI: actually remove zsync files this time
I'm blind, the previous commit failed CI since it still thought they were being generated
2025-09-14 00:37:30 -05:00
GreemDev
ea30a0ed24 chore: ci: Remove .zsync 2025-09-14 00:26:09 -05:00
Coxxs
df40a69872 Fix headless mode (ryubing/ryujinx!146)
See merge request ryubing/ryujinx!146
2025-09-10 11:43:50 -05:00
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
54 changed files with 1997 additions and 2372 deletions

View File

@@ -134,16 +134,13 @@ jobs:
exit 1
fi
export UFLAG="gh-releases-zsync|${{ secrets.RC_OWNER }}${{ secrets.RC_CANARY_NAME }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage"
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
shell: bash
macos_release:

View File

@@ -125,16 +125,13 @@ jobs:
exit 1
fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
popd
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage"
gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync"
shell: bash
macos_release:

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

@@ -6,7 +6,6 @@ cd "$ROOTDIR"
BUILDDIR=${BUILDDIR:-publish}
OUTDIR=${OUTDIR:-publish_appimage}
UFLAG=${UFLAG:-"gh-releases-zsync|Ryubing|ryujinx|latest|*-x64.AppImage.zsync"}
rm -rf AppDir
mkdir -p AppDir/usr/bin
@@ -24,10 +23,4 @@ chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR"
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
-u "$UFLAG" \
AppDir "$OUTDIR"/Ryujinx.AppImage
# Move zsync file needed for delta updates
if [ "$RELEASE" = "1" ]; then
mv ./*.AppImage.zsync "$OUTDIR"
fi

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
@@ -1175,6 +1175,7 @@
0100EB100AB42000,"FINAL FANTASY XII THE ZODIAC AGE",opengl;vulkan-backend-bug,playable,2024-08-11 07:01:54
010068F00AA78000,"FINAL FANTASY XV POCKET EDITION HD",,playable,2021-01-05 17:52:08
0100CE4010AAC000,"FINAL FANTASY® CRYSTAL CHRONICLES™ Remastered Edition",,playable,2023-04-02 23:39:12
010038B015560000,FINAL FANTASY TACTICS - The Ivalice Chronicles,gpu,boots,2024-09-30 02:59:00
01001BA00AE4E000,"Final Light, The Prison",,playable,2020-07-31 21:48:44
0100FF100FB68000,"Finding Teddy 2 : Definitive Edition",gpu,ingame,2024-04-19 16:51:33
0100F4E013AAE000,"Fire & Water",,playable,2020-12-15 15:43:20
@@ -1243,7 +1244,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
@@ -1889,7 +1890,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
@@ -2271,6 +2272,7 @@
0100ABF008968000,"Pokémon Sword + Pokémon Sword Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-26 15:40:37
01009AD008C4C000,"Pokémon: Let's Go, Pikachu! demo",slow;demo,playable,2023-11-26 11:23:20
0100000011D90000,"Pokémon™ Brilliant Diamond",gpu;ldn-works,ingame,2024-08-28 13:26:35
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
01005D100807A000,"Pokémon™ Quest",,playable,2022-02-22 16:12:32
@@ -2314,7 +2316,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
@@ -2580,6 +2582,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
@@ -2706,6 +2709,8 @@
01008F701C074000,"SONIC SUPERSTARS",gpu;nvdec,ingame,2023-10-28 17:48:07
010088801C150000,"Sonic Superstars Digital Art Book with Mini Digital Soundtrack",,playable,2024-08-20 13:26:56
01005EA01C0FC000,"SONIC X SHADOW GENERATIONS",crash,ingame,2025-01-07 04:20:45
010064B0242BE000,"Sonic Racing: CrossWorlds - Demo",gpu;vulkan-backend-bug;demo,ingame,2024-09-25 11:27:53
01006E001823C000,"Sonic Racing: CrossWorlds",gpu;vulkan-backend-bug;ldn-broken,ingame,2024-09-30 17:23:00
010064F00C212000,"Soul Axiom Rebooted",nvdec;slow,ingame,2020-09-04 12:41:01
0100F2100F0B2000,"Soul Searching",,playable,2020-07-09 18:39:07
01008F2005154000,"South Park™: The Fractured but Whole™ - Standard Edition",slow;online-broken;vulkan-backend-bug;gpu,ingame,2025-01-21 17:35:10
@@ -2863,11 +2868,13 @@
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
010099C022B96000,"Super Mario Galaxy",slow;vulkan;amd-vendor-bug;vulkan-vendor-bug,ingame,2025-10-01 15:30:00
0100FD8022DAA000,"Super Mario Galaxy 2",slow;vulkan;amd-vendor-bug;vulkan-vendor-bug;deadlock,ingame,2025-10-04 18:50:00
010028600EBDA000,"Super Mario™ 3D World + Bowsers Fury",ldn-works,playable,2024-07-31 10:45:37
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
01009C200D60E000,"Super Meat Boy Forever",gpu,boots,2021-04-26 14:25:39
0100BDD00EC5C000,"Super Mega Space Blaster Special Turbo",online,playable,2020-08-06 12:13:25
010031F019294000,"Super Monkey Ball Banana Rumble",,playable,2024-06-28 10:39:18
010031F019294000,"Super Monkey Ball Banana Rumble",ldn-broken,playable,2025-10-01 18:03:00
0100B2A00E1E0000,"Super Monkey Ball: Banana Blitz HD",online-broken,playable,2022-09-16 13:16:25
01006D000D2A0000,"Super Mutant Alien Assault",,playable,2020-06-07 23:32:45
01004D600AC14000,"Super Neptunia RPG",nvdec,playable,2022-08-17 16:38:52
@@ -2933,6 +2940,7 @@
0100B76011DAA000,"Taxi Chaos",slow;online-broken;UE4,playable,2022-10-25 19:13:00
0100F43011E5A000,"Tcheco in the Castle of Lucio",,playable,2020-06-27 13:35:43
010092B0091D0000,"Team Sonic Racing",online-broken;ldn-works,playable,2024-02-05 15:05:27
010084B00B36E000,"Team Sonic Racing",online-broken;ldn-works,playable,2024-02-05 15:05:27
0100FE701475A000,"Teenage Mutant Ninja Turtles: Shredder's Revenge",deadlock;crash,boots,2024-09-28 09:31:39
01005CF01E784000,"Teenage Mutant Ninja Turtles: Splintered Fate",,playable,2024-08-03 13:50:42
0100FDB0154E4000,"Teenage Mutant Ninja Turtles: The Cowabunga Collection",,playable,2024-01-22 19:39:04
@@ -2978,6 +2986,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
@@ -3218,6 +3227,7 @@
010038400C2FE000,"TY the Tasmanian Tiger™ HD",32-bit;crash;nvdec,menus,2020-12-17 21:15:00
010073A00C4B2000,"Tyd wag vir Niemand",,playable,2021-03-02 13:39:53
0100D5B00D6DA000,"Type:Rider",,playable,2021-01-06 13:12:55
01008AF01AD22000,"The Patrick Star Game",,playable,2025-10-01 17:55:30
010040D01222C000,"UBERMOSH: SANTICIDE",,playable,2020-11-27 15:05:01
0100992010BF8000,"Ubongo",,playable,2021-02-04 21:15:01
010079000B56C000,"UglyDolls: An Imperfect Adventure",nvdec;UE4,playable,2022-08-25 14:42:16
@@ -3464,3 +3474,6 @@
0100F4401940A000,"超探偵事件簿 レインコード (Master Detective Archives: Rain Code)",crash,ingame,2024-02-12 20:58:31
010064801A01C000,"超次元ゲイム ネプテューヌ GameMaker R:Evolution",crash,nothing,2023-10-30 22:37:40
0100F3400332C000,"ブレイド2",deadlock;amd-vendor-bug,ingame,2024-03-28 14:31:41
010075000ECBE000,"超级马力欧 奥德赛",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010075100E8EC000,"马力欧卡丁车8 豪华版",32-bit;ldn-works;LAN;amd-vendor-bug,playable,2024-09-19 11:55:17
0100E8C00F506000,"新 超级马力欧兄弟U 豪华版",32-bit,playable,2023-10-08 02:06:37
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
1175 0100EB100AB42000 FINAL FANTASY XII THE ZODIAC AGE opengl;vulkan-backend-bug playable 2024-08-11 07:01:54
1176 010068F00AA78000 FINAL FANTASY XV POCKET EDITION HD playable 2021-01-05 17:52:08
1177 0100CE4010AAC000 FINAL FANTASY® CRYSTAL CHRONICLES™ Remastered Edition playable 2023-04-02 23:39:12
1178 010038B015560000 FINAL FANTASY TACTICS - The Ivalice Chronicles gpu boots 2024-09-30 02:59:00
1179 01001BA00AE4E000 Final Light, The Prison playable 2020-07-31 21:48:44
1180 0100FF100FB68000 Finding Teddy 2 : Definitive Edition gpu ingame 2024-04-19 16:51:33
1181 0100F4E013AAE000 Fire & Water playable 2020-12-15 15:43:20
1244 010003F00BD48000 Friday the 13th: Killer Puzzle playable 2021-01-28 01:33:38
1245 010092A00C4B6000 Friday the 13th: The Game Ultimate Slasher Edition nvdec;online-broken;UE4 playable 2022-09-06 17:33:27
1246 0100F200178F4000 FRONT MISSION 1st: Remake playable 2023-06-09 07:44:24
1247 0100c4e018a24000 0100C4E018A24000 FRONT MISSION 2: Remake playable 2025-07-30 12:11:23
1248 01007E6019872000 FRONT MISSION 3: Remake playable 2025-07-30 12:12:02
1249 0100861012474000 Frontline Zed playable 2020-10-03 12:55:59
1250 0100B5300B49A000 Frost playable 2022-07-27 12:00:36
1890 010097800EA20000 Monster Energy Supercross - The Official Videogame 3 UE4;audout;nvdec;online playable 2021-06-14 12:37:54
1891 0100E9900ED74000 Monster Farm 32-bit;nvdec playable 2021-05-05 19:29:13
1892 0100770008DD8000 Monster Hunter Generations Ultimate™ 32-bit;online-broken;ldn-works playable 2024-03-18 14:35:36
1893 0100B04011742000 Monster Hunter Rise MONSTER HUNTER RISE gpu;slow;crash;nvdec;online-broken;Needs Update;ldn-works ingame 2024-08-24 11:04:59
1894 010093A01305C000 Monster Hunter Rise Demo online-broken;ldn-works;demo playable 2022-10-18 23:04:17
1895 0100E21011446000 Monster Hunter Stories 2: Wings of Ruin services ingame 2022-07-10 19:27:30
1896 010042501329E000 MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version demo playable 2022-11-13 22:20:26
2272 0100ABF008968000 Pokémon Sword + Pokémon Sword Expansion Pass deadlock;crash;online-broken;ldn-works;LAN ingame 2024-08-26 15:40:37
2273 01009AD008C4C000 Pokémon: Let's Go, Pikachu! demo slow;demo playable 2023-11-26 11:23:20
2274 0100000011D90000 Pokémon™ Brilliant Diamond gpu;ldn-works ingame 2024-08-28 13:26:35
2275 010018E011D92000 Pokémon™ Shining Pearl gpu;ldn-works ingame 2024-08-28 13:26:35
2276 010015F008C54000 Pokémon™ HOME Needs Update;crash;services menus 2020-12-06 06:01:51
2277 01001F5010DFA000 Pokémon™ Legends: Arceus gpu;Needs Update;ldn-works ingame 2024-09-19 10:02:02
2278 01005D100807A000 Pokémon™ Quest playable 2022-02-22 16:12:32
2316 010077B00BDD8000 Professional Farmer: Nintendo Switch™ Edition slow playable 2020-12-16 13:38:19
2317 010018300C83A000 Professor Lupo and his Horrible Pets playable 2020-06-12 00:08:45
2318 0100D1F0132F6000 Professor Lupo: Ocean playable 2021-04-14 16:33:33
2319 0100c3a017834000 0100C3A017834000 Prodeus playable 2025-07-30 12:07:52
2320 0100BBD00976C000 Project Highrise: Architect's Edition playable 2022-08-10 17:19:12
2321 0100ACE00DAB6000 Project Nimbus: Complete Edition nvdec;UE4;vulkan-backend-bug playable 2022-08-10 17:35:43
2322 01002980140F6000 Project TRIANGLE STRATEGY™ Debut Demo UE4;demo playable 2022-10-24 21:40:27
2582 0100C610154CA000 Shadowrun: Hong Kong - Extended Edition gpu;Needs Update ingame 2022-10-04 20:53:09
2583 010000000EEF0000 Shadows 2: Perfidia playable 2020-08-07 12:43:46
2584 0100AD700CBBE000 Shadows of Adam playable 2021-01-11 13:35:58
2585 010037A01F96C000 Shadows of the Damned: Hella Remastered playable 2025-09-05 11:34:32
2586 01002A800C064000 Shadowverse Champions Battle playable 2022-10-02 22:59:29
2587 01003B90136DA000 Shadowverse: Champion's Battle crash nothing 2023-03-06 00:31:50
2588 0100820013612000 Shady Part of Me playable 2022-10-20 11:31:55
2709 01008F701C074000 SONIC SUPERSTARS gpu;nvdec ingame 2023-10-28 17:48:07
2710 010088801C150000 Sonic Superstars Digital Art Book with Mini Digital Soundtrack playable 2024-08-20 13:26:56
2711 01005EA01C0FC000 SONIC X SHADOW GENERATIONS crash ingame 2025-01-07 04:20:45
2712 010064B0242BE000 Sonic Racing: CrossWorlds - Demo gpu;vulkan-backend-bug;demo ingame 2024-09-25 11:27:53
2713 01006E001823C000 Sonic Racing: CrossWorlds gpu;vulkan-backend-bug;ldn-broken ingame 2024-09-30 17:23:00
2714 010064F00C212000 Soul Axiom Rebooted nvdec;slow ingame 2020-09-04 12:41:01
2715 0100F2100F0B2000 Soul Searching playable 2020-07-09 18:39:07
2716 01008F2005154000 South Park™: The Fractured but Whole™ - Standard Edition slow;online-broken;vulkan-backend-bug;gpu ingame 2025-01-21 17:35:10
2868 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
2869 Super Mario World homebrew boots 2024-06-13 01:40:31
2870 010049900F546000 Super Mario™ 3D All-Stars services-horizon;slow;vulkan;amd-vendor-bug ingame 2024-05-07 02:38:16
2871 010099C022B96000 Super Mario Galaxy slow;vulkan;amd-vendor-bug;vulkan-vendor-bug ingame 2025-10-01 15:30:00
2872 0100FD8022DAA000 Super Mario Galaxy 2 slow;vulkan;amd-vendor-bug;vulkan-vendor-bug;deadlock ingame 2025-10-04 18:50:00
2873 010028600EBDA000 Super Mario™ 3D World + Bowser’s Fury ldn-works playable 2024-07-31 10:45:37
2874 01004F8006A78000 Super Meat Boy services playable 2020-04-02 23:10:07
2875 01009C200D60E000 Super Meat Boy Forever gpu boots 2021-04-26 14:25:39
2876 0100BDD00EC5C000 Super Mega Space Blaster Special Turbo online playable 2020-08-06 12:13:25
2877 010031F019294000 Super Monkey Ball Banana Rumble ldn-broken playable 2024-06-28 10:39:18 2025-10-01 18:03:00
2878 0100B2A00E1E0000 Super Monkey Ball: Banana Blitz HD online-broken playable 2022-09-16 13:16:25
2879 01006D000D2A0000 Super Mutant Alien Assault playable 2020-06-07 23:32:45
2880 01004D600AC14000 Super Neptunia RPG nvdec playable 2022-08-17 16:38:52
2940 0100B76011DAA000 Taxi Chaos slow;online-broken;UE4 playable 2022-10-25 19:13:00
2941 0100F43011E5A000 Tcheco in the Castle of Lucio playable 2020-06-27 13:35:43
2942 010092B0091D0000 Team Sonic Racing online-broken;ldn-works playable 2024-02-05 15:05:27
2943 010084B00B36E000 Team Sonic Racing online-broken;ldn-works playable 2024-02-05 15:05:27
2944 0100FE701475A000 Teenage Mutant Ninja Turtles: Shredder's Revenge deadlock;crash boots 2024-09-28 09:31:39
2945 01005CF01E784000 Teenage Mutant Ninja Turtles: Splintered Fate playable 2024-08-03 13:50:42
2946 0100FDB0154E4000 Teenage Mutant Ninja Turtles: The Cowabunga Collection playable 2024-01-22 19:39:04
2986 0100EBA01548E000 The Cruel King and the Great Hero gpu;services ingame 2022-12-02 07:02:08
2987 010051800E922000 The Dark Crystal: Age of Resistance Tactics playable 2020-08-11 13:43:41
2988 01003DE00918E000 The Darkside Detective playable 2020-06-03 22:16:18
2989 010032B015D66000 The DioField Chronicle playable 2025-09-05 11:35:50
2990 01000A10041EA000 The Elder Scrolls V: Skyrim gpu;crash ingame 2024-07-14 03:21:31
2991 01004A9006B84000 The End Is Nigh playable 2020-06-01 11:26:45
2992 0100CA100489C000 The Escapists 2 nvdec playable 2020-09-24 12:31:31
3227 010038400C2FE000 TY the Tasmanian Tiger™ HD 32-bit;crash;nvdec menus 2020-12-17 21:15:00
3228 010073A00C4B2000 Tyd wag vir Niemand playable 2021-03-02 13:39:53
3229 0100D5B00D6DA000 Type:Rider playable 2021-01-06 13:12:55
3230 01008AF01AD22000 The Patrick Star Game playable 2025-10-01 17:55:30
3231 010040D01222C000 UBERMOSH: SANTICIDE playable 2020-11-27 15:05:01
3232 0100992010BF8000 Ubongo playable 2021-02-04 21:15:01
3233 010079000B56C000 UglyDolls: An Imperfect Adventure nvdec;UE4 playable 2022-08-25 14:42:16
3474 0100F4401940A000 超探偵事件簿 レインコード (Master Detective Archives: Rain Code) crash ingame 2024-02-12 20:58:31
3475 010064801A01C000 超次元ゲイム ネプテューヌ GameMaker R:Evolution crash nothing 2023-10-30 22:37:40
3476 0100F3400332C000 ゼノブレイド2 deadlock;amd-vendor-bug ingame 2024-03-28 14:31:41
3477 010075000ECBE000 超级马力欧 奥德赛 nvdec;intel-vendor-bug;mac-bug playable 2024-08-25 01:32:34
3478 010075100E8EC000 马力欧卡丁车8 豪华版 32-bit;ldn-works;LAN;amd-vendor-bug playable 2024-09-19 11:55:17
3479 0100E8C00F506000 新 超级马力欧兄弟U 豪华版 32-bit playable 2023-10-08 02:06:37

View File

@@ -46,10 +46,11 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
// PS5 touchpad button
Touchpad,
// Virtual buttons for single joycon
// Virtual buttons for single joycon (left)
SingleLeftTrigger0,
SingleRightTrigger0,
// Virtual buttons for single joycon (right)
SingleLeftTrigger1,
SingleRightTrigger1,

View File

@@ -83,6 +83,8 @@ namespace Ryujinx.Common
"010049900F546002", // Super Mario Sunshine
"0100a3900c3e2000", // Paper Mario: The Origami King
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
"01009cC022b96000", // Super Mario Galaxy
"0100fd8022daa000", // Super Mario Galaxy 2
//Pikmin Franchise
"0100aa80194b0000", // Pikmin 1
@@ -158,6 +160,8 @@ namespace Ryujinx.Common
"01009aa000faa000", // Sonic Mania
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
"01005ea01c0fc001", // ^
"01006e001823c000", // Sonic Racing: CrossWorlds
"010064b0242be000", // Sonic Racing: CrossWorlds - Demo
//Xenoblade Franchise
"0100ff500e34a000", // Xenoblade Chronicles - Definitive Edition

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

@@ -149,6 +149,7 @@ namespace Ryujinx.Graphics.GAL
B8G8R8A8Srgb,
B10G10R10A2Unorm,
X8UintD24Unorm,
A8B8G8R8Uint,
}
public static class FormatExtensions

View File

@@ -359,6 +359,7 @@ namespace Ryujinx.Graphics.Gpu.Image
A2B10G10R10Sint = (A2B10G10R10 << 21) | (Sint << 27), // 0x1e000000
A2B10G10R10Uscaled = (A2B10G10R10 << 21) | (Uscaled << 27), // 0x2e000000
A2B10G10R10Sscaled = (A2B10G10R10 << 21) | (Sscaled << 27), // 0x36000000
A8B8G8R8Uint = (A8B8G8R8 << 21) | (Uint << 27), // 0x25E00040
}
private static readonly Dictionary<TextureFormat, FormatInfo> _textureFormats = new()
@@ -554,6 +555,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ VertexAttributeFormat.A2B10G10R10Sint, Format.R10G10B10A2Sint },
{ VertexAttributeFormat.A2B10G10R10Uscaled, Format.R10G10B10A2Uscaled },
{ VertexAttributeFormat.A2B10G10R10Sscaled, Format.R10G10B10A2Sscaled },
{ VertexAttributeFormat.A8B8G8R8Uint, Format.A8B8G8R8Uint },
};
#pragma warning restore IDE0055

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)
{
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)
{
// 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,52 +515,60 @@ 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);
List<Buffer> overlaps = [];
address = Math.Min(address, overlaps[0].Address);
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
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
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
_buffers.Add(buffer);
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
_buffers.Lock.ExitWriteLock();
}
/// <summary>
@@ -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);
current = current.Next;
}
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps);
_buffers.Lock.ExitWriteLock();
_buffers.Add(newBuffer);
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
}
else
{
_buffers.Lock.ExitWriteLock();
}
}
else
{
_buffers.Lock.ExitWriteLock();
// No overlap, just create a new buffer.
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
_buffers.Add(buffer);
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
}
if (newBuffer is not null)
{
_buffers.Lock.EnterWriteLock();
_buffers.Add(newBuffer);
_buffers.Lock.ExitWriteLock();
}
_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

@@ -80,8 +80,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.
@@ -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> current = first;
while (last != null && current != last.Next)
{
overlaps.Add(current);
current = current.Next;
}
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
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

@@ -211,6 +211,9 @@ namespace Ryujinx.Graphics.Vulkan
case Format.R16G16B16Uint:
format = VkFormat.R16G16B16A16Uint;
break;
case Format.A8B8G8R8Uint:
format = VkFormat.A8B8G8R8UintPack32;
break;
default:
Logger.Error?.Print(LogClass.Gpu, $"Format {srcFormat} is not supported by the host.");
break;

View File

@@ -160,7 +160,7 @@ namespace Ryujinx.Graphics.Vulkan
private static bool HasPushDescriptorsBug(VulkanRenderer gd)
{
// Those GPUs/drivers do not work properly with push descriptors, so we must force disable them.
return gd.IsNvidiaPreTuring || (gd.IsIntelArc && gd.IsIntelWindows);
return gd.IsNvidiaPreTuring || (gd.IsIntelArc && (gd.IsIntelWindows || gd.IsIntelLinux));
}
private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)

View File

@@ -91,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
internal Vendor Vendor { get; private set; }
internal bool IsAmdWindows { get; private set; }
internal bool IsIntelWindows { get; private set; }
internal bool IsIntelLinux { get; private set; }
internal bool IsAmdGcn { get; private set; }
internal bool IsAmdRdna3 { get; private set; }
internal bool IsNvidiaPreTuring { get; private set; }
@@ -359,6 +360,7 @@ namespace Ryujinx.Graphics.Vulkan
IsAmdWindows = Vendor == Vendor.Amd && OperatingSystem.IsWindows();
IsIntelWindows = Vendor == Vendor.Intel && OperatingSystem.IsWindows();
IsIntelLinux = Vendor == Vendor.Intel && OperatingSystem.IsLinux();
IsTBDR =
Vendor is Vendor.Apple or
Vendor.Qualcomm or
@@ -398,7 +400,7 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (Vendor == Vendor.Intel)
{
IsIntelArc = GpuRenderer.StartsWith("Intel(R) Arc(TM)");
IsIntelArc = GpuRenderer.StartsWithIgnoreCase("Intel(R) Arc(TM)");
}
IsQualcommProprietary = hasDriverProperties && driverProperties.DriverID == DriverId.QualcommProprietary;

View File

@@ -1105,7 +1105,7 @@ namespace Ryujinx.HLE.Debugger
{
var image = images[i];
ulong endAddress = image.BaseAddress + image.Size - 1;
string name = debugger.GetGuessedNsoNameFromIndex(i);
string name = image.Name;
sb.AppendLine($" 0x{image.BaseAddress:x10} - 0x{endAddress:x10} {name}");
}
}

View File

@@ -501,53 +501,13 @@ 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:
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);
VerifyKeysFile(keysSource);
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
}
else
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
}
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)

View File

@@ -20,6 +20,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public class Image
{
public string Name { get; internal set; }
public ulong BaseAddress { get; }
public ulong Size { get; }
public ulong EndAddress => BaseAddress + Size;
@@ -31,6 +32,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
BaseAddress = baseAddress;
Size = size;
Symbols = symbols;
Name = "(unknown)";
}
}
@@ -60,7 +62,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
if (!String.IsNullOrEmpty(ThreadName))
{
trace.AppendLine($"Thread ID: {thread.ThreadUid} ({ThreadName})");
} else {
}
else
{
trace.AppendLine($"Thread ID: {thread.ThreadUid}");
}
@@ -254,11 +258,66 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
info.SubName = string.Empty;
}
info.ImageName = GetGuessedNsoNameFromIndex(imageIndex);
info.ImageName = image.Name;
return true;
}
private bool GetModuleName(out string moduleName, Image image)
{
moduleName = string.Empty;
var rodataStart = image.BaseAddress + image.Size;
KMemoryInfo roInfo = _owner.MemoryManager.QueryMemory(rodataStart);
if (roInfo.Permission != KMemoryPermission.Read)
{
return false;
}
var rwdataStart = roInfo.Address + roInfo.Size;
try
{
Span<byte> rodataBuf = stackalloc byte[0x208];
_owner.CpuMemory.Read(rodataStart, rodataBuf);
ulong deprecatedRwDataOffset = BitConverter.ToUInt64(rodataBuf);
// no name if using old format
if (image.BaseAddress + deprecatedRwDataOffset == rwdataStart)
{
return false;
}
uint zero = BitConverter.ToUInt32(rodataBuf);
int pathLength = BitConverter.ToInt32(rodataBuf.Slice(4));
if (zero != 0 || pathLength <= 0)
{
// try again with 12 byte offset, 20.0.0+
_owner.CpuMemory.Read(rodataStart + 12, rodataBuf);
zero = BitConverter.ToUInt32(rodataBuf);
pathLength = BitConverter.ToInt32(rodataBuf.Slice(4));
}
if (zero != 0 || pathLength <= 0)
{
return false;
}
pathLength = Math.Min(pathLength, rodataBuf.Length - 8);
var pathBuf = rodataBuf.Slice(8, pathLength);
int lastSlash = pathBuf.LastIndexOfAny(new byte[] { (byte)'\\', (byte)'/' });
moduleName = Encoding.ASCII.GetString(pathBuf.Slice(lastSlash + 1).TrimEnd((byte)0));
return true;
}
catch (InvalidMemoryRegionException)
{
return false;
}
}
private bool AnalyzePointerFromStack(out PointerInfo info, ulong address, KThread thread)
{
info = default;
@@ -293,31 +352,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
return null;
}
public string GetGuessedNsoNameFromIndex(int index)
{
if ((uint)index > 11)
{
return "???";
}
if (index == 0)
{
return "rtld";
}
else if (index == 1)
{
return "main";
}
else if (index == GetImagesCount() - 1)
{
return "sdk";
}
else
{
return "subsdk" + (index - 2);
}
}
private int GetImagesCount()
{
lock (_images)
@@ -329,7 +363,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public List<Image> GetLoadedImages()
{
EnsureLoaded();
lock (_images)
{
return [.. _images];
@@ -449,7 +483,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
lock (_images)
{
_images.Add(new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray()));
var image = new Image(textOffset, textSize, symbols.OrderBy(x => x.Value).ToArray());
string moduleName;
if (!GetModuleName(out moduleName, image))
{
var newIndex = _images.Count;
moduleName = $"(unknown{newIndex})";
}
image.Name = moduleName;
_images.Add(image);
}
}

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

@@ -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,15 +66,7 @@ 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,28 +404,12 @@ 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)
{
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)
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)
{
_quickAccess.TryAdd(address, Items[index]);
Items[index].QuickAccessAddresses.Add(address);
result = [];
}
else
{
result = new RangeItem<T>[endIndex - index];
Array.Copy(Items, index, result, 0, endIndex - index);
}
return (Items[index], Items[endIndex - 1]);
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

@@ -19,6 +19,7 @@
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
<Color x:Key="CustomConfig">#00B5B8</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -38,6 +39,7 @@
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
<Color x:Key="CustomConfig">#00B5B8</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -57,6 +59,7 @@
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
<Color x:Key="CustomConfig">#00B5B8</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

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

@@ -174,8 +174,8 @@ namespace Ryujinx.Headless
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger0,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger0,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
@@ -196,8 +196,8 @@ namespace Ryujinx.Headless
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger1,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger1,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>

View File

@@ -428,7 +428,7 @@ namespace Ryujinx.Headless
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
public bool EnableGdbStub { get; set; }
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
[Option("gdb-stub-port", Required = false, Default = (ushort)55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
public ushort GdbStubPort { get; set; }
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]

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

@@ -97,8 +97,10 @@ namespace Ryujinx.Ava.Systems.PlayReport
//TODO DLC Locations
_ => 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,16 +15,38 @@ 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(
LogClass.Application,
args.Length is 0 ? format : format.Format(args),
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)
{
@@ -41,22 +64,18 @@ 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;
}
if (_versionResponse == null)
{
// logging is done via the UpdateClient library

View File

@@ -101,8 +101,8 @@ namespace Ryujinx.Ava.Systems
await Dispatcher.UIThread.InvokeAsync(async () =>
{
string newVersionString = ReleaseInformation.IsCanaryBuild
? $"Canary {currentVersion} -> Canary {newVersion}"
: $"{currentVersion} -> {newVersion}";
? $"Canary {currentVersion} Canary {newVersion}"
: $"{currentVersion} {newVersion}";
Logger.Info?.Print(LogClass.Application, $"Version found: {newVersionString}");

View File

@@ -114,16 +114,20 @@
Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{ext:Icon fa-solid fa-arrow-rotate-right}"
ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<Separator/>
<MenuItem
Command="{Binding NukePtcCache}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuCacheManagementNukePptc}"
Icon="{ext:Icon fa-solid fa-trash-can}" />
Icon="{ext:Icon fa-solid fa-trash-can}"
IsEnabled="{Binding HasPtcCacheFiles}" />
<MenuItem
Command="{Binding PurgeShaderCache}"
CommandParameter="{Binding}"
Header="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
Icon="{ext:Icon fa-solid fa-trash-can}" />
Icon="{ext:Icon fa-solid fa-trash-can}"
IsEnabled="{Binding HasShaderCacheFiles}" />
<Separator/>
<MenuItem
Command="{Binding OpenPtcDirectory}"
CommandParameter="{Binding}"

View File

@@ -354,8 +354,8 @@ namespace Ryujinx.Ava.UI.Helpers
primary,
secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.DialogUpdaterShowChangelogMessage],
(int)Symbol.Help,
UserResult.Yes);

View File

@@ -14,6 +14,8 @@ namespace Ryujinx.Ava.UI.Helpers
{ Glyph.List, char.ConvertFromUtf32((int)Symbol.List) },
{ Glyph.Grid, char.ConvertFromUtf32((int)Symbol.ViewAll) },
{ Glyph.Chip, char.ConvertFromUtf32(59748) },
{ Glyph.Device, char.ConvertFromUtf32(0xE7F7) },
{ Glyph.Bug, char.ConvertFromUtf32(0xEBE8) },
{ Glyph.Important, char.ConvertFromUtf32((int)Symbol.Important) },
};

View File

@@ -5,6 +5,8 @@ namespace Ryujinx.Ava.UI.Helpers
List,
Grid,
Chip,
Device,
Bug,
Important,
}
}

View File

@@ -732,8 +732,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ButtonMinus = ConfigGamepadInputId.Minus,
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger0,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger0,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
@@ -751,8 +751,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
ButtonPlus = ConfigGamepadInputId.Plus,
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound,
ButtonSl = ConfigGamepadInputId.SingleLeftTrigger1,
ButtonSr = ConfigGamepadInputId.SingleRightTrigger1,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{

View File

@@ -562,7 +562,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool StartGamesWithoutUI
public bool StartGamesWithoutUi
{
get => ConfigurationState.Instance.UI.StartNoUI;
set
@@ -974,9 +974,8 @@ 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(() =>
{
@@ -1206,15 +1205,14 @@ namespace Ryujinx.Ava.UI.ViewModels
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[dirSelectDialogTitle],
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.LoadApplicationFromFileDialogTitle],
AllowMultiple = false,
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]);
@@ -1596,18 +1549,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task OpenFolder()
{
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
{
Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle],
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);
@@ -1812,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])
@@ -1824,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);
}
}
}
@@ -1945,7 +1897,7 @@ namespace Ryujinx.Ava.UI.ViewModels
secondaryText,
LocaleManager.Instance[LocaleKeys.Continue],
LocaleManager.Instance[LocaleKeys.Cancel],
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogTitle]
LocaleManager.Instance[LocaleKeys.GameListContextMenuTrimXCI]
);
if (result == UserResult.Yes)
@@ -2169,7 +2121,8 @@ namespace Ryujinx.Ava.UI.ViewModels
});
public static AsyncRelayCommand<MainWindowViewModel> NukePtcCache { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null &&
vm.HasPtcCacheFiles(),
async viewModel =>
{
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
@@ -2218,8 +2171,22 @@ namespace Ryujinx.Ava.UI.ViewModels
}
});
private bool HasPtcCacheFiles()
{
if (this.SelectedApplication == null) return false;
DirectoryInfo mainDir = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath,
this.SelectedApplication.IdString, "cache", "cpu", "0"));
DirectoryInfo backupDir = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath,
this.SelectedApplication.IdString, "cache", "cpu", "1"));
return (mainDir.Exists && (mainDir.EnumerateFiles("*.cache").Any() || mainDir.EnumerateFiles("*.info").Any())) ||
(backupDir.Exists && (backupDir.EnumerateFiles("*.cache").Any() || backupDir.EnumerateFiles("*.info").Any()));
}
public static AsyncRelayCommand<MainWindowViewModel> PurgeShaderCache { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
Commands.CreateConditional<MainWindowViewModel>(
vm => vm?.SelectedApplication != null && vm.HasShaderCacheFiles(),
async viewModel =>
{
UserResult result = await ContentDialogHelper.CreateLocalizedConfirmationDialog(
@@ -2276,6 +2243,20 @@ namespace Ryujinx.Ava.UI.ViewModels
}
});
private bool HasShaderCacheFiles()
{
if (this.SelectedApplication == null) return false;
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath,
this.SelectedApplication.IdString, "cache", "shader"));
if (!shaderCacheDir.Exists) return false;
return shaderCacheDir.EnumerateDirectories("*").Any() ||
shaderCacheDir.GetFiles("*.toc").Any() ||
shaderCacheDir.GetFiles("*.data").Any();
}
public static RelayCommand<MainWindowViewModel> OpenPtcDirectory { get; } =
Commands.CreateConditional<MainWindowViewModel>(vm => vm?.SelectedApplication != null,
viewModel =>

View File

@@ -27,11 +27,6 @@
<StackPanel
Grid.Column="0"
Orientation="Horizontal">
<TextBlock
Margin="10,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Text="{ext:Locale CommonSort}" />
<DropDownButton
Width="150"
HorizontalAlignment="Left"

View File

@@ -92,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>
@@ -235,11 +235,6 @@
Name="AboutWindowMenuItem"
Header="{ext:Locale MenuBarHelpAbout}"
Icon="{ext:Icon fa-solid fa-circle-info}" />
<MenuItem
Name="UpdateMenuItem"
IsEnabled="{Binding CanUpdate}"
Header="{ext:Locale MenuBarHelpCheckForUpdates}"
Icon="{ext:Icon fa-solid fa-rotate}" />
<MenuItem
Name="CompatibilityListMenuItem"
Header="{ext:Locale CompatibilityListOpen}"
@@ -255,21 +250,24 @@
Name="SetupGuideMenuItem"
Header="{ext:Locale MenuBarHelpSetup}"
Icon="{ext:Icon fa-brands fa-gitlab}"
CommandParameter="{x:Static common:SharedConstants.SetupGuideWikiUrl}"
ToolTip.Tip="{ext:Locale MenuBarHelpSetupTooltip}" />
CommandParameter="{x:Static common:SharedConstants.SetupGuideWikiUrl}" />
<MenuItem
Name="LdnGuideMenuItem"
Header="{ext:Locale MenuBarHelpMultiplayer}"
Icon="{ext:Icon fa-brands fa-gitlab}"
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}"
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}" />
<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}" />
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}" />
</MenuItem>
<Separator />
<MenuItem
Name="UpdateMenuItem"
IsEnabled="{Binding CanUpdate}"
Header="{ext:Locale MenuBarHelpCheckForUpdates}"
Icon="{ext:Icon fa-solid fa-rotate}" />
</MenuItem>
</Menu>
</DockPanel>

View File

@@ -46,10 +46,6 @@
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
Glyph="{helpers:GlyphValueConverter Grid}" />
</Button>
<TextBlock
Margin="10,0,5,0"
VerticalAlignment="Center"
Text="{ext:Locale IconSize}" />
<controls:SliderScroll
Width="150"
Height="35"
@@ -171,11 +167,5 @@
</Flyout>
</DropDownButton.Flyout>
</DropDownButton>
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{ext:Locale CommonSort}" />
</DockPanel>
</UserControl>

View File

@@ -35,7 +35,7 @@
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="5" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="CornerRadius" Value="15" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).GridItemSelectorSize}" />
@@ -53,7 +53,7 @@
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
ClipToBounds="True"
CornerRadius="4">
CornerRadius="12.5">
<Grid RowDefinitions="Auto,Auto">
<Image
Grid.Row="0"
@@ -72,42 +72,26 @@
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
</Panel>
</Grid>
</Border>
<ui:SymbolIcon
Margin="5,5,0,0"
Margin="2.5,2.5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="18"
FontSize="23"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<Border
Margin="15,35,5,15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="90"
Height="20"
CornerRadius="4"
IsVisible="{Binding HasIndependentConfiguration}"
Background="{DynamicResource ThemeContentBackgroundColor}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
<ui:SymbolIcon
Margin="0,2.5,2.5,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
FontSize="23"
Foreground="{DynamicResource CustomConfig}"
IsVisible="{Binding HasIndependentConfiguration}"
Symbol="SettingsFilled" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@@ -34,6 +34,9 @@
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="CornerRadius" Value="15" />
</Style>
<Style Selector="ListBoxItem:selected /template/ Rectangle#SelectionIndicator">
<Setter Property="MinHeight" Value="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ListItemSelectorSize}" />
</Style>
@@ -46,9 +49,11 @@
Padding="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ClipToBounds="True"
CornerRadius="5">
ClipToBounds="True">
<Grid ColumnDefinitions="Auto,10,*,150,100">
<Border
ClipToBounds="True"
CornerRadius="7">
<Image
Grid.RowSpan="3"
Grid.Column="0"
@@ -58,6 +63,8 @@
Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}"
Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}"
Source="{Binding Icon, Converter={x:Static helpers:BitmapArrayValueConverter.Instance}}" />
</Border>
<Border
Grid.Column="2"
Margin="0,0,5,0"
@@ -91,7 +98,7 @@
IsVisible="{Binding HasPlayabilityInfo}"
Background="{DynamicResource AppListBackgroundColor}"
Margin="-1, 0, 0, 0"
Padding="0">
Padding="1.5">
<ToolTip.Tip>
<StackPanel Orientation="Vertical">
<TextBlock
@@ -175,7 +182,7 @@
Text="{ext:Locale GameSpecificConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
Foreground="{DynamicResource CustomConfig}" />
</StackPanel>
<StackPanel
Grid.Column="4"
@@ -203,13 +210,23 @@
<ui:SymbolIcon
Grid.Row="0"
Grid.Column="0"
Margin="-5,-5,0,0"
Margin="-7.5,-7.5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
FontSize="18"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<ui:SymbolIcon
Grid.Row="0"
Grid.Column="0"
Margin="0,-7.5,-7.5,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
FontSize="18"
Foreground="{DynamicResource CustomConfig}"
IsVisible="{Binding HasIndependentConfiguration}"
Symbol="SettingsFilled" />
</Grid>
</Border>
</Grid>

View File

@@ -27,13 +27,6 @@
Spacing="10">
<TextBlock Classes="h1" Text="{ext:Locale SettingsTabGeneralGeneral}" />
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</StackPanel>
</CheckBox>
<CheckBox
IsEnabled="{Binding !IsGameTitleNotNull}"
Opacity="{Binding PanelOpacity}"
@@ -62,6 +55,13 @@
<TextBlock Classes="globalConfigMarker" IsVisible="{Binding IsGameTitleNotNull}" />
</StackPanel>
</CheckBox>
<CheckBox IsChecked="{Binding EnableDiscordIntegration}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}"
Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" />
</StackPanel>
</CheckBox>
<StackPanel
Margin="0, 15, 0, 0"
Orientation="Horizontal">
@@ -101,7 +101,7 @@
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunchOff}" />
<TextBlock Text="{ext:Locale CommonOff}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock
@@ -123,7 +123,7 @@
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralHideCursorNever}" />
<TextBlock Text="{ext:Locale Never}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralHideCursorOnIdle}" />
@@ -146,7 +146,7 @@
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeAuto}" />
<TextBlock Text="{ext:Locale CommonAuto}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralThemeLight}" />
@@ -198,14 +198,13 @@
<TextBox
Name="GameDirPathBox"
Margin="0"
ToolTip.Tip="{ext:Locale AddGameDirBoxTooltip}"
Watermark="{ext:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddGameDirButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddGameDirTooltip}">
Margin="10,0,0,0">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
@@ -213,8 +212,7 @@
Name="RemoveGameDirButton"
Grid.Column="2"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale RemoveGameDirTooltip}"
Margin="5,0,0,0"
Click="RemoveGameDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralRemove}" />
@@ -252,14 +250,13 @@
<TextBox
Name="AutoloadDirPathBox"
Margin="0"
ToolTip.Tip="{ext:Locale AddAutoloadDirBoxTooltip}"
Watermark="{ext:Locale AddGameDirBoxTooltip}"
VerticalAlignment="Stretch" />
<Button
Name="AddAutoloadDirButton"
Grid.Column="1"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale AddAutoloadDirTooltip}">
Margin="10,0,0,0">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralAdd}" />
</Button>
@@ -267,8 +264,7 @@
Name="RemoveAutoloadDirButton"
Grid.Column="2"
MinWidth="90"
Margin="10,0,0,0"
ToolTip.Tip="{ext:Locale RemoveAutoloadDirTooltip}"
Margin="5,0,0,0"
Click="RemoveAutoloadDirButton_OnClick">
<TextBlock HorizontalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralRemove}" />

View File

@@ -28,7 +28,6 @@
Orientation="Horizontal"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<Label Content="{ext:Locale CommonSort}" VerticalAlignment="Center" />
<ComboBox SelectedIndex="{Binding SortIndex}" Width="100">
<ComboBoxItem>
<Label

View File

@@ -32,12 +32,6 @@
Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}"
TextChanged="TextBox_OnTextChanged"/>
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="10, 5, 0, 5">
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{ext:Locale CommonSort}" />
<DropDownButton
Width="150"
HorizontalAlignment="Right"
@@ -102,12 +96,6 @@
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto" Name="NormalControls">
<TextBox Name="SearchBoxNormal" Grid.Column="0" Margin="15, 5, 0, 5" HorizontalAlignment="Stretch" Watermark="{ext:Locale CompatibilityListSearchBoxWatermarkWithCount}" TextChanged="TextBox_OnTextChanged" />
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="10, 5, 5, 5">
<TextBlock
Margin="10,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
DockPanel.Dock="Right"
Text="{ext:Locale CommonSort}" />
<DropDownButton
Width="150"
HorizontalAlignment="Right"

View File

@@ -39,11 +39,11 @@
<Grid Name="Pages" IsVisible="False" Grid.Row="3">
<settings:SettingsUiView Name="UiPage" />
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsHotkeysView Name="HotkeysPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsHotkeysView Name="HotkeysPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsDebugView Name="DebugPage" />
@@ -54,7 +54,7 @@
IsSettingsVisible="False"
Name="NavPanel"
IsBackEnabled="False"
Margin="10,10,10,0"
Margin="10,0,10,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
OpenPaneLength="200">
@@ -62,16 +62,17 @@
<ui:NavigationViewItem
IsSelected="True"
Content="{ext:Locale SettingsTabGeneral}"
Tag="UiPage"
IconSource="New" />
Tag="UiPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Device}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabInput}"
Tag="InputPage"
IconSource="Games" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabHotkeys}"
Tag="HotkeysPage"
IconSource="Keyboard" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabSystem}"
Tag="SystemPage"
@@ -93,6 +94,10 @@
Content="{ext:Locale SettingsTabAudio}"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabHotkeys}"
Tag="HotkeysPage"
IconSource="Keyboard" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabNetwork}"
Tag="NetworkPage"
@@ -103,8 +108,13 @@
IconSource="Document" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabDebug}"
Tag="DebugPage"
IconSource="Star" />
Tag="DebugPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Bug}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"