mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-09-12 13:05:26 +00:00
Compare commits
29 Commits
Canary-1.3
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
df40a69872 | ||
|
b000f91dad | ||
|
a60b2a0ba3 | ||
|
4c9b48b754 | ||
|
3bd7d5904e | ||
|
23eb9a3043 | ||
|
931ec44406 | ||
|
d68efa98ba | ||
|
ded76801d1 | ||
|
6084df7473 | ||
|
f3953c6039 | ||
|
91f5247e7f | ||
|
5658402c6b | ||
|
1b2c93e188 | ||
|
9e599ff325 | ||
|
7a5f430b59 | ||
|
1e340ce2f3 | ||
|
dbb4e63e1e | ||
|
d00ab52fa2 | ||
|
959af3613d | ||
|
3309fb2351 | ||
|
e1e8628a6f | ||
|
3969191605 | ||
|
07c7b39053 | ||
|
053efaa414 | ||
|
56e6339553 | ||
|
042362ee2b | ||
|
7347ee2212 | ||
|
01a9b636af |
@@ -1,18 +0,0 @@
|
|||||||
function pub {
|
|
||||||
dotnet publish -c release
|
|
||||||
}
|
|
||||||
|
|
||||||
function package {
|
|
||||||
cd src/$1
|
|
||||||
pub
|
|
||||||
mv bin/Release/$1.1.0.0.nupkg ../../pkgs/$1.1.0.0.nupkg
|
|
||||||
cd ../../
|
|
||||||
}
|
|
||||||
|
|
||||||
rm -rf pkgs
|
|
||||||
mkdir pkgs
|
|
||||||
|
|
||||||
package ARMeilleure
|
|
||||||
package Ryujinx.Memory
|
|
||||||
|
|
||||||
dotnet nuget push pkgs/*.nupkg --source RyubingPkgs
|
|
40
CHANGELOG.md
40
CHANGELOG.md
@@ -21,8 +21,8 @@ Additionally, 1.2.74 & 75 were fixes for uploading Windows build artifacts.
|
|||||||
|
|
||||||
1.2.76 fixes a rare crash on startup.
|
1.2.76 fixes a rare crash on startup.
|
||||||
|
|
||||||
## [1.2.72](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.72>) - 2024-11-03
|
## [1.2.72](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.72>) - 2024-11-03
|
||||||
PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://github.com/GreemDev/Ryujinx/pull/164>), [#139](<https://github.com/GreemDev/Ryujinx/pull/139>)
|
PRs [#163](<https://web.archive.org/web/20241123015123/https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://web.archive.org/web/20250307192526/https://github.com/Ryubing/Ryujinx/pull/164>), [#139](<https://web.archive.org/web/20250306123457/https://github.com/Ryubing/Ryujinx/pull/139>)
|
||||||
### HLE:
|
### HLE:
|
||||||
- Add DebugMouse HID device.
|
- Add DebugMouse HID device.
|
||||||
- Fixes "Clock Tower Rewind" crashing while loading.
|
- Fixes "Clock Tower Rewind" crashing while loading.
|
||||||
@@ -32,7 +32,7 @@ PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://gith
|
|||||||
### misc:
|
### misc:
|
||||||
- Update macOS distribution .icns.
|
- Update macOS distribution .icns.
|
||||||
|
|
||||||
## [1.2.69](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.69>) - 2024-11-01
|
## [1.2.69](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.69>) - 2024-11-01
|
||||||
### Infra:
|
### Infra:
|
||||||
- Compile the native libraries into the Ryujinx executable.
|
- Compile the native libraries into the Ryujinx executable.
|
||||||
- Remove `libarmeilleure-jitsupport.dylib` from Windows & Linux releases (dylibs are macOS-only)
|
- Remove `libarmeilleure-jitsupport.dylib` from Windows & Linux releases (dylibs are macOS-only)
|
||||||
@@ -42,8 +42,8 @@ PRs [#163](<https://github.com/GreemDev/Ryujinx/pull/163>), [#164](<https://gith
|
|||||||
- Replace "" with `string.Empty`.
|
- Replace "" with `string.Empty`.
|
||||||
- Code cleanups & simplifications.
|
- Code cleanups & simplifications.
|
||||||
|
|
||||||
## [1.2.67](<https://github.com/GreemDev/Ryujinx/releases/tag/1.2.67>) - 2024-11-01
|
## [1.2.67](<https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.67>) - 2024-11-01
|
||||||
PRs [#36](<https://github.com/GreemDev/Ryujinx/pull/36>), [#135](<https://github.com/GreemDev/Ryujinx/pull/135>)
|
PRs [#36](<https://web.archive.org/web/20250306215917/https://github.com/Ryubing/Ryujinx/pull/36>), [#135](<https://web.archive.org/web/20241122135125/https://github.com/GreemDev/Ryujinx/pull/135>)
|
||||||
|
|
||||||
### GUI:
|
### GUI:
|
||||||
- Set UseFloatingWatermark to false when watermark is empty
|
- Set UseFloatingWatermark to false when watermark is empty
|
||||||
@@ -54,8 +54,8 @@ PRs [#36](<https://github.com/GreemDev/Ryujinx/pull/36>), [#135](<https://github
|
|||||||
- Fix homebrew loading.
|
- Fix homebrew loading.
|
||||||
|
|
||||||
|
|
||||||
## [1.2.64](https://github.com/GreemDev/Ryujinx/releases/tag/1.2.64) - 2024-10-30
|
## [1.2.64](https://git.ryujinx.app/ryubing/ryujinx/-/tags/1.2.64) - 2024-10-30
|
||||||
PRs [#92](https://github.com/GreemDev/Ryujinx/pull/92), [#96](https://github.com/GreemDev/Ryujinx/pull/96), [#97](https://github.com/GreemDev/Ryujinx/pull/97), [#101](https://github.com/GreemDev/Ryujinx/pull/101), [#103](https://github.com/GreemDev/Ryujinx/pull/103)
|
PRs [#92](https://web.archive.org/web/20241118052724/https://github.com/GreemDev/Ryujinx/pull/92), ~~[#96](https://github.com/GreemDev/Ryujinx/pull/96)~~, ~~[#97](https://github.com/GreemDev/Ryujinx/pull/97)~~, [#101](https://web.archive.org/web/20250306223605/https://github.com/Ryubing/Ryujinx/pull/101), ~~[#103](https://github.com/GreemDev/Ryujinx/pull/103)~~
|
||||||
### GUI:
|
### GUI:
|
||||||
- Option to show classic-style title bar. Requires restart of emulator to take effect.
|
- Option to show classic-style title bar. Requires restart of emulator to take effect.
|
||||||
- This is only relevant on Windows. Other Operating Systems default to this being on and not being changeable, because the custom (current) title bar only works on Windows in the first place.
|
- This is only relevant on Windows. Other Operating Systems default to this being on and not being changeable, because the custom (current) title bar only works on Windows in the first place.
|
||||||
@@ -71,14 +71,14 @@ PRs [#92](https://github.com/GreemDev/Ryujinx/pull/92), [#96](https://github.com
|
|||||||
|
|
||||||
## 1.2.59 - 2024-10-27
|
## 1.2.59 - 2024-10-27
|
||||||
|
|
||||||
PRs [#88](https://github.com/GreemDev/Ryujinx/pull/88), [#87](https://github.com/GreemDev/Ryujinx/pull/87)
|
PRs ~~[#88](https://github.com/GreemDev/Ryujinx/pull/88), [#87](https://github.com/GreemDev/Ryujinx/pull/87)~~
|
||||||
### i18n:
|
### i18n:
|
||||||
- fr_FR:
|
- fr_FR:
|
||||||
- Add missing translations for new features & fix a couple wrong ones.
|
- Add missing translations for new features & fix a couple wrong ones.
|
||||||
- Fix Ignore Missing Services / Ignore Applet tooltip.
|
- Fix Ignore Missing Services / Ignore Applet tooltip.
|
||||||
|
|
||||||
## 1.2.57 - 2024-10-27
|
## 1.2.57 - 2024-10-27
|
||||||
PRs [#60](https://github.com/GreemDev/Ryujinx/pull/60), [#42](https://github.com/GreemDev/Ryujinx/pull/42)
|
PRs ~~[#60](https://github.com/GreemDev/Ryujinx/pull/60)~~, [#42](https://web.archive.org/web/20241126203614/https://github.com/GreemDev/Ryujinx/pull/42)
|
||||||
### GUI:
|
### GUI:
|
||||||
- Automatically remove invalid DLC & updates as part of autoload.
|
- Automatically remove invalid DLC & updates as part of autoload.
|
||||||
- Added Thai translation for Ignore Applet hover tooltip.
|
- Added Thai translation for Ignore Applet hover tooltip.
|
||||||
@@ -104,7 +104,7 @@ PRs [#60](https://github.com/GreemDev/Ryujinx/pull/60), [#42](https://github.com
|
|||||||
- Code cleanup.
|
- Code cleanup.
|
||||||
|
|
||||||
## 1.2.44 - 2024-10-25
|
## 1.2.44 - 2024-10-25
|
||||||
PR [#59](https://github.com/GreemDev/Ryujinx/pull/59)
|
PR [#59](https://web.archive.org/web/20241125060420/https://github.com/GreemDev/Ryujinx/pull/59)
|
||||||
### GUI:
|
### GUI:
|
||||||
- Add descriptions for "ignoring applet" translated into other languages.
|
- Add descriptions for "ignoring applet" translated into other languages.
|
||||||
|
|
||||||
@@ -117,9 +117,9 @@ NOTE: The translation isn't referenced in the code yet, it will be in the next u
|
|||||||
## 1.2.42 - 2024-10-24
|
## 1.2.42 - 2024-10-24
|
||||||
Sources:
|
Sources:
|
||||||
|
|
||||||
Init function: https://github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9
|
Init function: [archive of github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9](https://web.archive.org/web/20241122193401/https://github.com/MutantAura/Ryujinx/commit/9cef4ceba40d66492ff775af793ff70e6e7551a9)
|
||||||
|
|
||||||
Shader counter: https://github.com/MutantAura/Ryujinx/commit/67b873645fd593e83d042a77bf7ab12e5ec97357
|
Shader counter: ~~https://github.com/MutantAura/Ryujinx/commit/67b873645fd593e83d042a77bf7ab12e5ec97357~~ Original commit has been lost
|
||||||
|
|
||||||
Thanks MutantAura :D
|
Thanks MutantAura :D
|
||||||
### GUI:
|
### GUI:
|
||||||
@@ -127,14 +127,14 @@ Thanks MutantAura :D
|
|||||||
- Remove graphics backend / GPU name event logic in favor of a single init function.
|
- Remove graphics backend / GPU name event logic in favor of a single init function.
|
||||||
|
|
||||||
## 1.2.41 - 2024-10-24
|
## 1.2.41 - 2024-10-24
|
||||||
PR [#54](https://github.com/GreemDev/Ryujinx/pull/54)
|
PR ~~[#54](https://github.com/GreemDev/Ryujinx/pull/54)~~
|
||||||
|
|
||||||
Thanks Whitescatz!
|
Thanks Whitescatz!
|
||||||
### i18n:
|
### i18n:
|
||||||
- th_TH (Thai): Added missing translations, reduce transliterated words, fix grammar.
|
- th_TH (Thai): Added missing translations, reduce transliterated words, fix grammar.
|
||||||
|
|
||||||
## 1.2.40 - 2024-10-23
|
## 1.2.40 - 2024-10-23
|
||||||
PR [#40](https://github.com/GreemDev/Ryujinx/pull/40)
|
PR ~~[#40](https://github.com/GreemDev/Ryujinx/pull/40)~~
|
||||||
|
|
||||||
Thanks Вова С!
|
Thanks Вова С!
|
||||||
### GUI:
|
### GUI:
|
||||||
@@ -148,30 +148,30 @@ Thanks Вова С!
|
|||||||
- Should prevent crashing on config loads in some circumstances.
|
- Should prevent crashing on config loads in some circumstances.
|
||||||
|
|
||||||
## 1.2.38 - 2024-10-23
|
## 1.2.38 - 2024-10-23
|
||||||
PR [#51](https://github.com/GreemDev/Ryujinx/pull/51)
|
PR [#51](https://web.archive.org/web/20241127022413/https://github.com/GreemDev/Ryujinx/pull/51)
|
||||||
### i18n:
|
### i18n:
|
||||||
- zh_CH (Simplified Chinese): Add some missing translations.
|
- zh_CH (Simplified Chinese): Add some missing translations.
|
||||||
|
|
||||||
## 1.2.37 - 2024-10-23
|
## 1.2.37 - 2024-10-23
|
||||||
PR [#37](https://github.com/GreemDev/Ryujinx/pull/37)
|
PR [#37](https://web.archive.org/web/20241123010103/https://github.com/GreemDev/Ryujinx/pull/37)
|
||||||
|
|
||||||
Thanks Last Breath!
|
Thanks Last Breath!
|
||||||
### GUI:
|
### GUI:
|
||||||
- Set the default controller to the Pro Controller.
|
- Set the default controller to the Pro Controller.
|
||||||
|
|
||||||
## 1.2.36 - 2024-10-21
|
## 1.2.36 - 2024-10-21
|
||||||
PR [#30](https://github.com/GreemDev/Ryujinx/pull/30)
|
PR ~~[#30](https://github.com/GreemDev/Ryujinx/pull/30)~~
|
||||||
### GUI:
|
### GUI:
|
||||||
- Fix repeated dialog popup notifying you of new updates when there aren't any, while having a bundled update inside an XCI and an external update file.
|
- Fix repeated dialog popup notifying you of new updates when there aren't any, while having a bundled update inside an XCI and an external update file.
|
||||||
|
|
||||||
## 1.2.35 - 2024-10-21
|
## 1.2.35 - 2024-10-21
|
||||||
PR [#32](https://github.com/GreemDev/Ryujinx/pull/32)
|
PR [#32](https://web.archive.org/web/20241127010942/https://github.com/GreemDev/Ryujinx/pull/32)
|
||||||
### GUI:
|
### GUI:
|
||||||
- Replace "expand DRAM" option with a DRAM size dropdown.
|
- Replace "expand DRAM" option with a DRAM size dropdown.
|
||||||
- Allows for using mods which require a ridiculous amount of memory to allocate from.
|
- Allows for using mods which require a ridiculous amount of memory to allocate from.
|
||||||
|
|
||||||
## 1.2.34 - 2024-10-21
|
## 1.2.34 - 2024-10-21
|
||||||
PR [#29](https://github.com/GreemDev/Ryujinx/pull/29)
|
PR [#29](https://web.archive.org/web/20241125093029/https://github.com/GreemDev/Ryujinx/pull/29)
|
||||||
### GUI:
|
### GUI:
|
||||||
- Fix duplicate controller names when 2 controllers of the same type are connected.
|
- Fix duplicate controller names when 2 controllers of the same type are connected.
|
||||||
### INPUT:
|
### INPUT:
|
||||||
@@ -248,7 +248,7 @@ Added Low-power PPTC mode strings to the translation files.
|
|||||||
## 1.2.1-1.2.19 - 2024-10-08 - 2024-10-11
|
## 1.2.1-1.2.19 - 2024-10-08 - 2024-10-11
|
||||||
### GUI/INFRA/MISC:
|
### GUI/INFRA/MISC:
|
||||||
- Remove GTK UI.
|
- Remove GTK UI.
|
||||||
- Autoload DLC/Updates from dir ([#12](https://github.com/GreemDev/Ryujinx/pull/12)).
|
- Autoload DLC/Updates from dir ([#12](https://web.archive.org/web/20241127004005/https://github.com/GreemDev/Ryujinx/pull/12)).
|
||||||
- Changed executable icon to rainbow logo.
|
- Changed executable icon to rainbow logo.
|
||||||
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
- Extract Data > Logo now also extracts the square thumbnail you see for the game in the UI.
|
||||||
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
- The "use random UUID hack" checkbox in the Amiibo screen now remembers its last state when you reopen the window in a given session.
|
||||||
|
@@ -19,8 +19,8 @@
|
|||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
|
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
|
||||||
<PackageVersion Include="Concentus" Version="2.2.2" />
|
<PackageVersion Include="Concentus" Version="2.2.2" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||||
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
<PackageVersion Include="DynamicData" Version="9.4.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
<PackageVersion Include="Humanizer" Version="2.14.1" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<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.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.116" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.116" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.29" />
|
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
|
||||||
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.29" />
|
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
|
||||||
<PackageVersion Include="Gommon" Version="2.7.1.1" />
|
<PackageVersion Include="Gommon" Version="2.7.2.1" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<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="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
||||||
|
1110
assets/locales.json
1110
assets/locales.json
File diff suppressed because it is too large
Load Diff
@@ -978,7 +978,7 @@
|
|||||||
0100416004C00000,"DOOM",gpu;slow;nvdec;online-broken,ingame,2024-09-23 15:40:07
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
0100CB900B498000,"Eternum Ex",,playable,2021-01-13 20:28:32
|
||||||
010092501EB2C000,"Europa (Demo)",gpu;crash;UE4,ingame,2024-04-23 10:47:12
|
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
|
01007BE0160D6000,"EVE ghost enemies",gpu,ingame,2023-01-14 03:13:30
|
||||||
@@ -1243,7 +1243,7 @@
|
|||||||
010003F00BD48000,"Friday the 13th: Killer Puzzle",,playable,2021-01-28 01:33:38
|
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
|
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
|
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
|
01007E6019872000,"FRONT MISSION 3: Remake",,playable,2025-07-30 12:12:02
|
||||||
0100861012474000,"Frontline Zed",,playable,2020-10-03 12:55:59
|
0100861012474000,"Frontline Zed",,playable,2020-10-03 12:55:59
|
||||||
0100B5300B49A000,"Frost",,playable,2022-07-27 12:00:36
|
0100B5300B49A000,"Frost",,playable,2022-07-27 12:00:36
|
||||||
@@ -1450,6 +1450,7 @@
|
|||||||
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
|
0100F7300ED2C000,"Hoggy2",,playable,2022-10-10 13:53:35
|
||||||
0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
|
0100F7E00C70E000,"Hogwarts Legacy",UE4;slow,ingame,2024-09-03 19:53:58
|
||||||
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
|
0100633007D48000,"Hollow Knight",nvdec,playable,2023-01-16 15:44:56
|
||||||
|
010013C00E930000,"Hollow Knight: Silksong",,playable,2025-09-04 17:23:22
|
||||||
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
|
0100F2100061E800,"Hollow0",UE4;gpu,ingame,2021-03-03 23:42:56
|
||||||
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
|
0100342009E16000,"Holy Potatoes! What The Hell?!",,playable,2020-07-03 10:48:56
|
||||||
010071B00C904000,"HoPiKo",,playable,2021-01-13 20:12:38
|
010071B00C904000,"HoPiKo",,playable,2021-01-13 20:12:38
|
||||||
@@ -1888,7 +1889,7 @@
|
|||||||
010097800EA20000,"Monster Energy Supercross - The Official Videogame 3",UE4;audout;nvdec;online,playable,2021-06-14 12:37:54
|
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
|
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
|
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
|
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
|
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
|
010042501329E000,"MONSTER HUNTER STORIES 2: WINGS OF RUIN Trial Version",demo,playable,2022-11-13 22:20:26
|
||||||
@@ -2313,7 +2314,7 @@
|
|||||||
010077B00BDD8000,"Professional Farmer: Nintendo Switch™ Edition",slow,playable,2020-12-16 13:38:19
|
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
|
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
|
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
|
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
|
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
|
01002980140F6000,"Project TRIANGLE STRATEGY™ Debut Demo",UE4;demo,playable,2022-10-24 21:40:27
|
||||||
@@ -2579,6 +2580,7 @@
|
|||||||
0100C610154CA000,"Shadowrun: Hong Kong - Extended Edition",gpu;Needs Update,ingame,2022-10-04 20:53:09
|
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
|
010000000EEF0000,"Shadows 2: Perfidia",,playable,2020-08-07 12:43:46
|
||||||
0100AD700CBBE000,"Shadows of Adam",,playable,2021-01-11 13:35:58
|
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
|
01002A800C064000,"Shadowverse Champions Battle",,playable,2022-10-02 22:59:29
|
||||||
01003B90136DA000,"Shadowverse: Champion's Battle",crash,nothing,2023-03-06 00:31:50
|
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
|
0100820013612000,"Shady Part of Me",,playable,2022-10-20 11:31:55
|
||||||
@@ -2977,6 +2979,7 @@
|
|||||||
0100EBA01548E000,"The Cruel King and the Great Hero",gpu;services,ingame,2022-12-02 07:02:08
|
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
|
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
|
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
|
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
|
01004A9006B84000,"The End Is Nigh",,playable,2020-06-01 11:26:45
|
||||||
0100CA100489C000,"The Escapists 2",nvdec,playable,2020-09-24 12:31:31
|
0100CA100489C000,"The Escapists 2",nvdec,playable,2020-09-24 12:31:31
|
||||||
|
|
@@ -182,6 +182,7 @@ namespace Ryujinx.Common
|
|||||||
"01001cc01b2d4000", // Goat Simulator 3
|
"01001cc01b2d4000", // Goat Simulator 3
|
||||||
"01003620068ea000", // Hand of Fate 2
|
"01003620068ea000", // Hand of Fate 2
|
||||||
"0100f7e00c70e000", // Hogwarts Legacy
|
"0100f7e00c70e000", // Hogwarts Legacy
|
||||||
|
"010013c00e930000", // Hollow Knight: Silksong
|
||||||
"010085500130a000", // Lego City: Undercover
|
"010085500130a000", // Lego City: Undercover
|
||||||
"010073c01af34000", // LEGO Horizon Adventures
|
"010073c01af34000", // LEGO Horizon Adventures
|
||||||
"0100d71004694000", // Minecraft
|
"0100d71004694000", // Minecraft
|
||||||
|
@@ -82,15 +82,7 @@ namespace Ryujinx.Graphics.Device
|
|||||||
{
|
{
|
||||||
uint alignedOffset = index * RegisterSize;
|
uint alignedOffset = index * RegisterSize;
|
||||||
|
|
||||||
Func<int> readCallback = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_readCallbacks), (nint)index);
|
return _readCallbacks[index]?.Invoke() ?? GetRefUnchecked<int>(alignedOffset);
|
||||||
if (readCallback != null)
|
|
||||||
{
|
|
||||||
return readCallback();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return GetRefUnchecked<int>(alignedOffset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -105,9 +97,9 @@ namespace Ryujinx.Graphics.Device
|
|||||||
uint alignedOffset = index * RegisterSize;
|
uint alignedOffset = index * RegisterSize;
|
||||||
DebugWrite(alignedOffset, data);
|
DebugWrite(alignedOffset, data);
|
||||||
|
|
||||||
GetRefIntAlignedUncheck(index) = data;
|
SetIntAlignedUncheck(index, data);
|
||||||
|
|
||||||
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (nint)index)?.Invoke(data);
|
_writeCallbacks[index]?.Invoke(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,11 +112,9 @@ namespace Ryujinx.Graphics.Device
|
|||||||
uint alignedOffset = index * RegisterSize;
|
uint alignedOffset = index * RegisterSize;
|
||||||
DebugWrite(alignedOffset, data);
|
DebugWrite(alignedOffset, data);
|
||||||
|
|
||||||
ref int storage = ref GetRefIntAlignedUncheck(index);
|
changed = SetIntAlignedUncheckChanged(index, data);
|
||||||
changed = storage != data;
|
|
||||||
storage = data;
|
|
||||||
|
|
||||||
Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (nint)index)?.Invoke(data);
|
_writeCallbacks[index]?.Invoke(data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -162,5 +152,24 @@ namespace Ryujinx.Graphics.Device
|
|||||||
{
|
{
|
||||||
return ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (nint)index);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
if (index < BlockSize)
|
if (index < BlockSize)
|
||||||
{
|
{
|
||||||
int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (nint)index);
|
int groupIndex = _registerToGroupMapping[index];
|
||||||
if (groupIndex != 0)
|
if (groupIndex != 0)
|
||||||
{
|
{
|
||||||
groupIndex--;
|
groupIndex--;
|
||||||
|
@@ -110,7 +110,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong size,
|
ulong size,
|
||||||
BufferStage stage,
|
BufferStage stage,
|
||||||
bool sparseCompatible,
|
bool sparseCompatible,
|
||||||
List<Buffer> baseBuffers)
|
RangeItem<Buffer>[] baseBuffers)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_physicalMemory = physicalMemory;
|
_physicalMemory = physicalMemory;
|
||||||
@@ -128,18 +128,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
List<IRegionHandle> baseHandles = null;
|
List<IRegionHandle> baseHandles = null;
|
||||||
|
|
||||||
if (baseBuffers.Count != 0)
|
if (baseBuffers.Length != 0)
|
||||||
{
|
{
|
||||||
baseHandles = new List<IRegionHandle>();
|
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
|
else
|
||||||
{
|
{
|
||||||
baseHandles.Add(buffer._memoryTracking);
|
baseHandles.Add(item.Value._memoryTracking);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="parent">Parent buffer</param>
|
/// <param name="parent">Parent buffer</param>
|
||||||
/// <param name="stage">Initial buffer stage</param>
|
/// <param name="stage">Initial buffer stage</param>
|
||||||
/// <param name="baseBuffers">Buffers to inherit state from</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;
|
_size = (int)parent.Size;
|
||||||
_systemMemoryType = context.Capabilities.MemoryType;
|
_systemMemoryType = context.Capabilities.MemoryType;
|
||||||
@@ -72,7 +73,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
BufferStage storageFlags = stage & BufferStage.StorageMask;
|
BufferStage storageFlags = stage & BufferStage.StorageMask;
|
||||||
|
|
||||||
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Count == 0)
|
if (parent.Size > DeviceLocalSizeThreshold && baseBuffers.Length == 0)
|
||||||
{
|
{
|
||||||
_desiredType = BufferBackingType.DeviceMemory;
|
_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.
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -81,13 +81,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
MemoryRange subRange = range.GetSubRange(index);
|
MemoryRange subRange = range.GetSubRange(index);
|
||||||
|
|
||||||
_buffers.Lock.EnterReadLock();
|
_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;
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
{
|
||||||
current.Value.Unmapped(subRange.Address, subRange.Size);
|
overlaps[i].Value.Unmapped(subRange.Address, subRange.Size);
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffers.Lock.ExitReadLock();
|
_buffers.Lock.ExitReadLock();
|
||||||
@@ -489,10 +487,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="stage">The type of usage that created the buffer</param>
|
/// <param name="stage">The type of usage that created the buffer</param>
|
||||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
|
||||||
{
|
{
|
||||||
_buffers.Lock.EnterWriteLock();
|
Buffer newBuffer = null;
|
||||||
(RangeItem<Buffer> first, RangeItem<Buffer> last) = _buffers.FindOverlaps(address, size);
|
|
||||||
|
|
||||||
if (first is not null)
|
_buffers.Lock.EnterWriteLock();
|
||||||
|
Span<RangeItem<Buffer>> overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||||
|
|
||||||
|
if (overlaps.Length != 0)
|
||||||
{
|
{
|
||||||
// The buffer already exists. We can just return the existing buffer
|
// The buffer already exists. We can just return the existing buffer
|
||||||
// if the buffer we need is fully contained inside the overlapping 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;
|
ulong endAddress = address + size;
|
||||||
|
|
||||||
if (first.Address > address || first.EndAddress < endAddress)
|
if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress)
|
||||||
{
|
{
|
||||||
bool anySparseCompatible = false;
|
bool anySparseCompatible = false;
|
||||||
|
|
||||||
@@ -515,52 +515,60 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
// sequential memory.
|
// sequential memory.
|
||||||
// Allowing for 2 pages (rather than just one) is necessary to catch cases where the
|
// 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.
|
// range crosses a page, and after alignment, ends having a size of 2 pages.
|
||||||
if (first == last &&
|
if (overlaps.Length == 1 &&
|
||||||
address >= first.Address &&
|
address >= overlaps[0].Address &&
|
||||||
endAddress - first.EndAddress <= BufferAlignmentSize * 2)
|
endAddress - overlaps[0].EndAddress <= BufferAlignmentSize * 2)
|
||||||
{
|
{
|
||||||
// Try to grow the buffer by 1.5x of its current size.
|
// 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.
|
// 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;
|
ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
|
||||||
|
|
||||||
size = Math.Max(size, growthSize);
|
size = Math.Max(size, growthSize);
|
||||||
endAddress = address + size;
|
endAddress = address + size;
|
||||||
|
|
||||||
(first, last) = _buffers.FindOverlaps(address, size);
|
overlaps = _buffers.FindOverlapsAsSpan(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
address = Math.Min(address, first.Address);
|
address = Math.Min(address, overlaps[0].Address);
|
||||||
endAddress = Math.Max(endAddress, last.EndAddress);
|
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
|
||||||
|
|
||||||
List<Buffer> overlaps = [];
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
|
|
||||||
RangeItem<Buffer> current = first;
|
|
||||||
while (current != last.Next)
|
|
||||||
{
|
{
|
||||||
anySparseCompatible |= current.Value.SparseCompatible;
|
anySparseCompatible |= overlaps[i].Value.SparseCompatible;
|
||||||
overlaps.Add(current.Value);
|
|
||||||
_buffers.Remove(current.Value);
|
|
||||||
|
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||||
|
|
||||||
|
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||||
|
|
||||||
|
_buffers.Lock.ExitWriteLock();
|
||||||
|
|
||||||
ulong newSize = endAddress - address;
|
ulong newSize = endAddress - address;
|
||||||
|
|
||||||
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps);
|
newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray);
|
||||||
|
}
|
||||||
_buffers.Add(newBuffer);
|
else
|
||||||
|
{
|
||||||
|
_buffers.Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No overlap, just create a new buffer.
|
_buffers.Lock.ExitWriteLock();
|
||||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
|
|
||||||
|
|
||||||
_buffers.Add(buffer);
|
// No overlap, just create a new buffer.
|
||||||
|
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buffers.Lock.ExitWriteLock();
|
if (newBuffer is not null)
|
||||||
|
{
|
||||||
|
_buffers.Lock.EnterWriteLock();
|
||||||
|
|
||||||
|
_buffers.Add(newBuffer);
|
||||||
|
|
||||||
|
_buffers.Lock.ExitWriteLock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -575,67 +583,74 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
|
||||||
{
|
{
|
||||||
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
|
bool sparseAligned = alignment >= SparseBufferAlignmentSize;
|
||||||
|
Buffer newBuffer = null;
|
||||||
|
|
||||||
_buffers.Lock.EnterWriteLock();
|
_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,
|
// If the buffer already exists, make sure if covers the entire range,
|
||||||
// and make sure it is properly aligned, otherwise sparse mapping may fail.
|
// and make sure it is properly aligned, otherwise sparse mapping may fail.
|
||||||
|
|
||||||
ulong endAddress = address + size;
|
ulong endAddress = address + size;
|
||||||
|
|
||||||
if (first.Address > address ||
|
if (overlaps[0].Address > address ||
|
||||||
first.EndAddress < endAddress ||
|
overlaps[0].EndAddress < endAddress ||
|
||||||
(first.Address & (alignment - 1)) != 0 ||
|
(overlaps[0].Address & (alignment - 1)) != 0 ||
|
||||||
(!first.Value.SparseCompatible && sparseAligned))
|
(!overlaps[0].Value.SparseCompatible && sparseAligned))
|
||||||
{
|
{
|
||||||
// We need to make sure the new buffer is properly aligned.
|
// We need to make sure the new buffer is properly aligned.
|
||||||
// However, after the range is aligned, it is possible that it
|
// However, after the range is aligned, it is possible that it
|
||||||
// overlaps more buffers, so try again after each extension
|
// overlaps more buffers, so try again after each extension
|
||||||
// and ensure we cover all overlaps.
|
// and ensure we cover all overlaps.
|
||||||
|
|
||||||
RangeItem<Buffer> oldFirst;
|
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
|
||||||
endAddress = Math.Max(endAddress, last.EndAddress);
|
int oldOverlapCount;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
address = Math.Min(address, first.Address);
|
address = Math.Min(address, overlaps[0].Address);
|
||||||
|
endAddress = Math.Max(endAddress, overlaps[^1].EndAddress);
|
||||||
|
|
||||||
address &= ~(alignment - 1);
|
address &= ~(alignment - 1);
|
||||||
|
|
||||||
oldFirst = first;
|
oldOverlapCount = overlaps.Length;
|
||||||
(first, last) = _buffers.FindOverlaps(address, endAddress - address);
|
overlaps = _buffers.FindOverlapsAsSpan(address, endAddress - address);
|
||||||
}
|
}
|
||||||
while (oldFirst != first);
|
while (oldOverlapCount != overlaps.Length);
|
||||||
|
|
||||||
ulong newSize = endAddress - address;
|
ulong newSize = endAddress - address;
|
||||||
|
|
||||||
List<Buffer> overlaps = [];
|
RangeItem<Buffer>[] overlapsArray = overlaps.ToArray();
|
||||||
|
|
||||||
RangeItem<Buffer> current = first;
|
_buffers.RemoveRange(overlaps[0], overlaps[^1]);
|
||||||
while (current != last.Next)
|
|
||||||
{
|
|
||||||
overlaps.Add(current.Value);
|
|
||||||
_buffers.Remove(current.Value);
|
|
||||||
|
|
||||||
current = current.Next;
|
_buffers.Lock.ExitWriteLock();
|
||||||
}
|
|
||||||
|
|
||||||
Buffer newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps);
|
newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray);
|
||||||
|
}
|
||||||
_buffers.Add(newBuffer);
|
else
|
||||||
|
{
|
||||||
|
_buffers.Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No overlap, just create a new buffer.
|
_buffers.Lock.ExitWriteLock();
|
||||||
Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
|
|
||||||
|
|
||||||
_buffers.Add(buffer);
|
// No overlap, just create a new buffer.
|
||||||
|
newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBuffer is not null)
|
||||||
|
{
|
||||||
|
_buffers.Lock.EnterWriteLock();
|
||||||
|
|
||||||
|
_buffers.Add(newBuffer);
|
||||||
|
|
||||||
|
_buffers.Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
_buffers.Lock.ExitWriteLock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -648,13 +663,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="stage">The type of usage that created the buffer</param>
|
/// <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="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
|
||||||
/// <param name="overlaps">Buffers overlapping the range</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);
|
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);
|
int dstOffset = (int)(buffer.Address - newBuffer.Address);
|
||||||
|
|
||||||
@@ -882,7 +897,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
MemoryRange subRange = range.GetSubRange(i);
|
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);
|
subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
|
||||||
|
|
||||||
@@ -930,7 +945,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (size != 0)
|
if (size != 0)
|
||||||
{
|
{
|
||||||
buffer = _buffers.FindOverlapFast(address, size).Value;
|
buffer = _buffers.FindOverlap(address, size).Value;
|
||||||
|
|
||||||
buffer.CopyFromDependantVirtualBuffers();
|
buffer.CopyFromDependantVirtualBuffers();
|
||||||
buffer.SynchronizeMemory(address, size);
|
buffer.SynchronizeMemory(address, size);
|
||||||
@@ -980,7 +995,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
if (size != 0)
|
if (size != 0)
|
||||||
{
|
{
|
||||||
Buffer buffer = _buffers.FindOverlapFast(address, size).Value;
|
Buffer buffer = _buffers.FindOverlap(address, size).Value;
|
||||||
|
|
||||||
if (copyBackVirtual)
|
if (copyBackVirtual)
|
||||||
{
|
{
|
||||||
|
@@ -81,8 +81,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
private BufferMigration _source;
|
private BufferMigration _source;
|
||||||
private BufferModifiedRangeList _migrationTarget;
|
private BufferModifiedRangeList _migrationTarget;
|
||||||
|
|
||||||
private List<RangeItem<BufferModifiedRange>> _overlaps;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the modified range list has any entries or not.
|
/// Whether the modified range list has any entries or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -108,7 +106,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
_context = context;
|
_context = context;
|
||||||
_parent = parent;
|
_parent = parent;
|
||||||
_flushAction = flushAction;
|
_flushAction = flushAction;
|
||||||
_overlaps = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,18 +117,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
public void ExcludeModifiedRegions(ulong address, ulong size, Action<ulong, ulong> action)
|
public void ExcludeModifiedRegions(ulong address, ulong size, Action<ulong, ulong> action)
|
||||||
{
|
{
|
||||||
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
// Slices a given region using the modified regions in the list. Calls the action for the new slices.
|
||||||
bool lockOwner = Lock.IsReadLockHeld;
|
Lock.EnterReadLock();
|
||||||
if (!lockOwner)
|
|
||||||
{
|
|
||||||
Lock.EnterReadLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||||
|
|
||||||
RangeItem<BufferModifiedRange> current = first;
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
{
|
||||||
BufferModifiedRange overlap = current.Value;
|
BufferModifiedRange overlap = overlaps[i].Value;
|
||||||
|
|
||||||
if (overlap.Address > address)
|
if (overlap.Address > address)
|
||||||
{
|
{
|
||||||
@@ -142,13 +134,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
// Remaining region is after this overlap.
|
// Remaining region is after this overlap.
|
||||||
size -= overlap.EndAddress - address;
|
size -= overlap.EndAddress - address;
|
||||||
address = overlap.EndAddress;
|
address = overlap.EndAddress;
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!lockOwner)
|
Lock.ExitReadLock();
|
||||||
{
|
|
||||||
Lock.ExitReadLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((long)size > 0)
|
if ((long)size > 0)
|
||||||
{
|
{
|
||||||
@@ -165,12 +153,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="size">Size of the modified region in bytes</param>
|
/// <param name="size">Size of the modified region in bytes</param>
|
||||||
public void SignalModified(ulong address, ulong size)
|
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 endAddress = address + size;
|
||||||
ulong syncNumber = _context.SyncNumber;
|
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)
|
if (first is null)
|
||||||
{
|
{
|
||||||
@@ -179,10 +166,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferModifiedRange buffPost = null;
|
|
||||||
bool extendsPost = false;
|
|
||||||
bool extendsPre = false;
|
|
||||||
|
|
||||||
if (first == last)
|
if (first == last)
|
||||||
{
|
{
|
||||||
if (first.Address == address && first.EndAddress == endAddress)
|
if (first.Address == address && first.EndAddress == endAddress)
|
||||||
@@ -196,14 +179,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
if (first.Address < address)
|
if (first.Address < address)
|
||||||
{
|
{
|
||||||
first.Value.Size = address - first.Address;
|
first.Value.Size = address - first.Address;
|
||||||
|
Update(first);
|
||||||
extendsPre = true;
|
|
||||||
|
|
||||||
if (first.EndAddress > endAddress)
|
if (first.EndAddress > endAddress)
|
||||||
{
|
{
|
||||||
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||||
first.Value.SyncNumber, first.Value.Parent);
|
first.Value.SyncNumber, first.Value.Parent));
|
||||||
extendsPost = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -212,6 +193,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
first.Value.Size = first.EndAddress - endAddress;
|
first.Value.Size = first.EndAddress - endAddress;
|
||||||
first.Value.Address = endAddress;
|
first.Value.Address = endAddress;
|
||||||
|
Update(first);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -219,11 +201,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extendsPre && extendsPost)
|
|
||||||
{
|
|
||||||
Add(buffPost);
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
Add(new BufferModifiedRange(address, size, syncNumber, this));
|
||||||
Lock.ExitWriteLock();
|
Lock.ExitWriteLock();
|
||||||
|
|
||||||
@@ -231,6 +208,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
BufferModifiedRange buffPre = null;
|
BufferModifiedRange buffPre = null;
|
||||||
|
BufferModifiedRange buffPost = null;
|
||||||
|
bool extendsPost = false;
|
||||||
|
bool extendsPre = false;
|
||||||
|
|
||||||
if (first.Address < address)
|
if (first.Address < address)
|
||||||
{
|
{
|
||||||
@@ -272,19 +252,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action<ulong, ulong> rangeAction)
|
||||||
{
|
{
|
||||||
Lock.EnterReadLock();
|
Lock.EnterReadLock();
|
||||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
Span<RangeItem<BufferModifiedRange>> overlaps = FindOverlapsAsSpan(address, size);
|
||||||
|
|
||||||
RangeItem<BufferModifiedRange> current = first;
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
{
|
||||||
BufferModifiedRange overlap = current.Value;
|
BufferModifiedRange overlap = overlaps[i].Value;
|
||||||
|
|
||||||
if (overlap.SyncNumber == syncNumber)
|
if (overlap.SyncNumber == syncNumber)
|
||||||
{
|
{
|
||||||
rangeAction(overlap.Address, overlap.Size);
|
rangeAction(overlap.Address, overlap.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Lock.ExitReadLock();
|
Lock.ExitReadLock();
|
||||||
@@ -300,22 +277,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
// We use the non-span method here because keeping the lock will cause a deadlock.
|
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||||
Lock.EnterReadLock();
|
Lock.EnterReadLock();
|
||||||
|
RangeItem<BufferModifiedRange>[] overlaps = FindOverlapsAsArray(address, size);
|
||||||
_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;
|
|
||||||
}
|
|
||||||
Lock.ExitReadLock();
|
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);
|
rangeAction(overlap.Address, overlap.Size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -329,7 +296,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
public bool HasRange(ulong address, ulong size)
|
public bool HasRange(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
Lock.EnterReadLock();
|
Lock.EnterReadLock();
|
||||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> _) = FindOverlaps(address, size);
|
RangeItem<BufferModifiedRange> first = FindOverlapFast(address, size);
|
||||||
bool result = first is not null;
|
bool result = first is not null;
|
||||||
Lock.ExitReadLock();
|
Lock.ExitReadLock();
|
||||||
return result;
|
return result;
|
||||||
@@ -386,9 +353,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong clampAddress = Math.Max(address, overlap.Address);
|
ulong clampAddress = Math.Max(address, overlap.Address);
|
||||||
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
ulong clampEnd = Math.Min(endAddress, overlap.EndAddress);
|
||||||
|
|
||||||
Lock.EnterWriteLock();
|
|
||||||
ClearPart(overlap, clampAddress, clampEnd);
|
ClearPart(overlap, clampAddress, clampEnd);
|
||||||
Lock.ExitWriteLock();
|
|
||||||
|
|
||||||
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction);
|
||||||
}
|
}
|
||||||
@@ -418,40 +383,24 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong endAddress = address + size;
|
ulong endAddress = address + size;
|
||||||
ulong currentSync = _context.SyncNumber;
|
ulong currentSync = _context.SyncNumber;
|
||||||
|
|
||||||
int rangeCount = 0;
|
|
||||||
|
|
||||||
List<RangeItem<BufferModifiedRange>> overlaps = [];
|
|
||||||
|
|
||||||
// Range list must be consistent for this operation
|
// Range list must be consistent for this operation
|
||||||
Lock.EnterReadLock();
|
|
||||||
if (_migrationTarget != null)
|
if (_migrationTarget != null)
|
||||||
{
|
|
||||||
rangeCount = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
rangeCount++;
|
|
||||||
overlaps.Add(current);
|
|
||||||
current = current.Next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Lock.ExitReadLock();
|
|
||||||
|
|
||||||
if (rangeCount == -1)
|
|
||||||
{
|
{
|
||||||
_migrationTarget!.WaitForAndFlushRanges(address, size);
|
_migrationTarget!.WaitForAndFlushRanges(address, size);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>[] overlaps = FindOverlapsAsArray(address, size);
|
||||||
|
|
||||||
|
int rangeCount = overlaps.Length;
|
||||||
|
|
||||||
if (rangeCount == 0)
|
if (rangeCount == 0)
|
||||||
{
|
{
|
||||||
|
Lock.ExitWriteLock();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +423,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
|
|
||||||
if (highestDiff == long.MinValue)
|
if (highestDiff == long.MinValue)
|
||||||
{
|
{
|
||||||
|
Lock.ExitWriteLock();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +432,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
|
_context.Renderer.WaitSync(currentSync + (ulong)highestDiff);
|
||||||
|
|
||||||
RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress);
|
RemoveRangesAndFlush(overlaps.ToArray(), rangeCount, highestDiff, currentSync, address, endAddress);
|
||||||
|
|
||||||
|
Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -520,6 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ranges._migrationTarget = this;
|
ranges._migrationTarget = this;
|
||||||
|
|
||||||
Lock.EnterWriteLock();
|
Lock.EnterWriteLock();
|
||||||
|
|
||||||
foreach (BufferModifiedRange range in inheritRanges)
|
foreach (BufferModifiedRange range in inheritRanges)
|
||||||
{
|
{
|
||||||
Add(range);
|
Add(range);
|
||||||
@@ -599,7 +553,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
ulong endAddress = address + size;
|
ulong endAddress = address + size;
|
||||||
Lock.EnterWriteLock();
|
Lock.EnterWriteLock();
|
||||||
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlaps(address, size);
|
(RangeItem<BufferModifiedRange> first, RangeItem<BufferModifiedRange> last) = FindOverlapsAsNodes(address, size);
|
||||||
|
|
||||||
if (first is null)
|
if (first is null)
|
||||||
{
|
{
|
||||||
@@ -607,22 +561,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferModifiedRange buffPost = null;
|
|
||||||
bool extendsPost = false;
|
|
||||||
bool extendsPre = false;
|
|
||||||
|
|
||||||
if (first == last)
|
if (first == last)
|
||||||
{
|
{
|
||||||
if (first.Address < address)
|
if (first.Address < address)
|
||||||
{
|
{
|
||||||
first.Value.Size = address - first.Address;
|
first.Value.Size = address - first.Address;
|
||||||
extendsPre = true;
|
Update(first);
|
||||||
|
|
||||||
if (first.EndAddress > endAddress)
|
if (first.EndAddress > endAddress)
|
||||||
{
|
{
|
||||||
buffPost = new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress,
|
||||||
first.Value.SyncNumber, first.Value.Parent);
|
first.Value.SyncNumber, first.Value.Parent));
|
||||||
extendsPost = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -631,6 +580,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
first.Value.Size = first.EndAddress - endAddress;
|
first.Value.Size = first.EndAddress - endAddress;
|
||||||
first.Value.Address = endAddress;
|
first.Value.Address = endAddress;
|
||||||
|
Update(first);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -638,16 +588,14 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extendsPre && extendsPost)
|
|
||||||
{
|
|
||||||
Add(buffPost);
|
|
||||||
}
|
|
||||||
|
|
||||||
Lock.ExitWriteLock();
|
Lock.ExitWriteLock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferModifiedRange buffPre = null;
|
BufferModifiedRange buffPre = null;
|
||||||
|
BufferModifiedRange buffPost = null;
|
||||||
|
bool extendsPost = false;
|
||||||
|
bool extendsPre = false;
|
||||||
|
|
||||||
if (first.Address < address)
|
if (first.Address < address)
|
||||||
{
|
{
|
||||||
|
@@ -122,7 +122,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
ulong originalVa = gpuVa;
|
ulong originalVa = gpuVa;
|
||||||
|
|
||||||
_virtualRanges.Lock.EnterWriteLock();
|
_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)
|
if (first is not null)
|
||||||
{
|
{
|
||||||
|
@@ -501,53 +501,13 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
using FileStream file = File.OpenRead(keysSource);
|
using FileStream file = File.OpenRead(keysSource);
|
||||||
|
|
||||||
switch (info.Extension)
|
if (info.Extension is ".keys")
|
||||||
{
|
{
|
||||||
case ".zip":
|
VerifyKeysFile(keysSource);
|
||||||
using (ZipArchive archive = ZipFile.OpenRead(keysSource))
|
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
||||||
{
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
||||||
private static void InstallKeysFromZip(ZipArchive archive, string installDirectory)
|
|
||||||
{
|
|
||||||
string temporaryDirectory = Path.Combine(installDirectory, "temp");
|
|
||||||
if (Directory.Exists(temporaryDirectory))
|
|
||||||
{
|
|
||||||
Directory.Delete(temporaryDirectory, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(temporaryDirectory);
|
|
||||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
|
||||||
{
|
|
||||||
if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
string extractDestination = Path.Combine(temporaryDirectory, entry.Name);
|
|
||||||
entry.ExtractToFile(extractDestination, overwrite: true);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
VerifyKeysFile(extractDestination);
|
|
||||||
File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
Directory.Delete(temporaryDirectory, true);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.Delete(temporaryDirectory, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
||||||
|
@@ -865,7 +865,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
lock (_threadingLock)
|
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.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||||
thread.Context.RequestInterrupt();
|
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.");
|
Logger.Warning?.Print(LogClass.Kernel, $"Failed to suspend thread {thread.ThreadUid} in time.");
|
||||||
}
|
}
|
||||||
|
@@ -13,16 +13,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
Monitor.Exit(mutex);
|
Monitor.Exit(mutex);
|
||||||
|
|
||||||
currentThread.Withholder = threadList;
|
|
||||||
|
|
||||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
|
||||||
|
|
||||||
currentThread.WithholderNode = threadList.AddLast(currentThread);
|
|
||||||
|
|
||||||
if (currentThread.TerminationRequested)
|
if (currentThread.TerminationRequested)
|
||||||
{
|
{
|
||||||
threadList.Remove(currentThread.WithholderNode);
|
|
||||||
|
|
||||||
currentThread.Reschedule(ThreadSchedState.Running);
|
currentThread.Reschedule(ThreadSchedState.Running);
|
||||||
|
|
||||||
currentThread.Withholder = null;
|
currentThread.Withholder = null;
|
||||||
@@ -31,6 +23,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
currentThread.Withholder = threadList;
|
||||||
|
|
||||||
|
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||||
|
|
||||||
|
threadList.AddLast(currentThread.WithholderNode);
|
||||||
|
|
||||||
if (timeout > 0)
|
if (timeout > 0)
|
||||||
{
|
{
|
||||||
context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
context.TimeManager.ScheduleFutureInvocation(currentThread, timeout);
|
||||||
|
@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
// even if they are not scheduled on guest cores.
|
// even if they are not scheduled on guest cores.
|
||||||
if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
|
if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
|
||||||
{
|
{
|
||||||
currentThread.SchedulerWaitEvent.WaitOne();
|
currentThread.SchedulerWaitEvent.Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -194,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.SiblingsPerCore[core] = SuggestedQueue(prio, core).AddFirst(thread);
|
SuggestedQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
|
||||||
|
|
||||||
_suggestedPrioritiesPerCore[core] |= 1L << prio;
|
_suggestedPrioritiesPerCore[core] |= 1L << prio;
|
||||||
}
|
}
|
||||||
@@ -223,7 +223,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddLast(thread);
|
ScheduledQueue(prio, core).AddLast(thread.SiblingsPerCore[core]);
|
||||||
|
|
||||||
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.SiblingsPerCore[core] = ScheduledQueue(prio, core).AddFirst(thread);
|
ScheduledQueue(prio, core).AddFirst(thread.SiblingsPerCore[core]);
|
||||||
|
|
||||||
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
_scheduledPrioritiesPerCore[core] |= 1L << prio;
|
||||||
}
|
}
|
||||||
@@ -251,7 +251,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
queue.Remove(thread.SiblingsPerCore[core]);
|
queue.Remove(thread.SiblingsPerCore[core]);
|
||||||
|
|
||||||
thread.SiblingsPerCore[core] = queue.AddLast(thread);
|
queue.AddLast(thread.SiblingsPerCore[core]);
|
||||||
|
|
||||||
return queue.First.Value;
|
return queue.First.Value;
|
||||||
}
|
}
|
||||||
|
@@ -318,11 +318,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
if (nextThread == null)
|
if (nextThread == null)
|
||||||
{
|
{
|
||||||
ActivateIdleThread();
|
ActivateIdleThread();
|
||||||
currentThread.SchedulerWaitEvent.WaitOne();
|
currentThread.SchedulerWaitEvent.Wait();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
|
nextThread.SchedulerWaitEvent.Set();
|
||||||
|
currentThread.SchedulerWaitEvent.Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -39,9 +39,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
public const int MaxWaitSyncObjects = 64;
|
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; }
|
public Thread HostThread { get; private set; }
|
||||||
|
|
||||||
@@ -93,6 +93,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
private LinkedListNode<KThread> _mutexWaiterNode;
|
private LinkedListNode<KThread> _mutexWaiterNode;
|
||||||
|
|
||||||
private readonly LinkedList<KThread> _pinnedWaiters;
|
private readonly LinkedList<KThread> _pinnedWaiters;
|
||||||
|
private LinkedListNode<KThread> _pinnedWaiterNode;
|
||||||
|
|
||||||
public KThread MutexOwner { get; private set; }
|
public KThread MutexOwner { get; private set; }
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private readonly Lock _activityOperationLock = new();
|
private readonly Lock _activityOperationLock = new();
|
||||||
|
|
||||||
internal readonly ManualResetEvent DebugHalt = new(false);
|
internal readonly ManualResetEventSlim DebugHalt = new(false);
|
||||||
|
|
||||||
public KThread(KernelContext context) : base(context)
|
public KThread(KernelContext context) : base(context)
|
||||||
{
|
{
|
||||||
@@ -144,8 +145,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
|
SiblingsPerCore = new LinkedListNode<KThread>[KScheduler.CpuCoresCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < SiblingsPerCore.Length; i++)
|
||||||
|
{
|
||||||
|
SiblingsPerCore[i] = new LinkedListNode<KThread>(this);
|
||||||
|
}
|
||||||
|
|
||||||
_mutexWaiters = [];
|
_mutexWaiters = [];
|
||||||
_pinnedWaiters = [];
|
_pinnedWaiters = [];
|
||||||
|
|
||||||
|
WithholderNode = new LinkedListNode<KThread>(this);
|
||||||
|
ProcessListNode = new LinkedListNode<KThread>(this);
|
||||||
|
_mutexWaiterNode = new LinkedListNode<KThread>(this);
|
||||||
|
_pinnedWaiterNode = new LinkedListNode<KThread>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Initialize(
|
public Result Initialize(
|
||||||
@@ -631,7 +642,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pinnedWaiters.AddLast(currentThread);
|
_pinnedWaiters.AddLast(_pinnedWaiterNode);
|
||||||
|
|
||||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||||
}
|
}
|
||||||
@@ -848,7 +859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
return KernelResult.ThreadTerminating;
|
return KernelResult.ThreadTerminating;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pinnedWaiters.AddLast(currentThread);
|
_pinnedWaiters.AddLast(_pinnedWaiterNode);
|
||||||
|
|
||||||
currentThread.Reschedule(ThreadSchedState.Paused);
|
currentThread.Reschedule(ThreadSchedState.Paused);
|
||||||
}
|
}
|
||||||
@@ -1262,7 +1273,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
if (_schedulerWaitEvent == null)
|
if (_schedulerWaitEvent == null)
|
||||||
{
|
{
|
||||||
ManualResetEvent schedulerWaitEvent = new(false);
|
ManualResetEventSlim schedulerWaitEvent = new(false);
|
||||||
|
|
||||||
if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
|
if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
|
||||||
{
|
{
|
||||||
@@ -1277,7 +1288,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private void ThreadStart()
|
private void ThreadStart()
|
||||||
{
|
{
|
||||||
_schedulerWaitEvent.WaitOne();
|
_schedulerWaitEvent.Wait();
|
||||||
DebugHalt.Reset();
|
DebugHalt.Reset();
|
||||||
KernelStatic.SetKernelContext(KernelContext, this);
|
KernelStatic.SetKernelContext(KernelContext, this);
|
||||||
|
|
||||||
|
@@ -31,11 +31,13 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
private readonly KEvent _friendInvitationStorageChannelEvent;
|
private readonly KEvent _friendInvitationStorageChannelEvent;
|
||||||
private readonly KEvent _notificationStorageChannelEvent;
|
private readonly KEvent _notificationStorageChannelEvent;
|
||||||
private readonly KEvent _healthWarningDisappearedSystemEvent;
|
private readonly KEvent _healthWarningDisappearedSystemEvent;
|
||||||
|
private readonly KEvent _unknownEvent;
|
||||||
|
|
||||||
private int _gpuErrorDetectedSystemEventHandle;
|
private int _gpuErrorDetectedSystemEventHandle;
|
||||||
private int _friendInvitationStorageChannelEventHandle;
|
private int _friendInvitationStorageChannelEventHandle;
|
||||||
private int _notificationStorageChannelEventHandle;
|
private int _notificationStorageChannelEventHandle;
|
||||||
private int _healthWarningDisappearedSystemEventHandle;
|
private int _healthWarningDisappearedSystemEventHandle;
|
||||||
|
private int _unknownEventHandle;
|
||||||
|
|
||||||
private bool _gamePlayRecordingState;
|
private bool _gamePlayRecordingState;
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
_friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
|
_friendInvitationStorageChannelEvent = new KEvent(system.KernelContext);
|
||||||
_notificationStorageChannelEvent = new KEvent(system.KernelContext);
|
_notificationStorageChannelEvent = new KEvent(system.KernelContext);
|
||||||
_healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
|
_healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext);
|
||||||
|
_unknownEvent = new KEvent(system.KernelContext);
|
||||||
|
|
||||||
_horizon = system.LibHacHorizonManager.AmClient;
|
_horizon = system.LibHacHorizonManager.AmClient;
|
||||||
}
|
}
|
||||||
@@ -648,6 +651,23 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.Applicati
|
|||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(210)] // 20.0.0+
|
||||||
|
// GetUnknownEvent() -> handle<copy>
|
||||||
|
public ResultCode GetUnknownEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (_unknownEventHandle == 0)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_unknownEvent.ReadableEvent, out _unknownEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_unknownEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandCmif(1001)] // 10.0.0+
|
[CommandCmif(1001)] // 10.0.0+
|
||||||
// PrepareForJit()
|
// PrepareForJit()
|
||||||
public ResultCode PrepareForJit(ServiceCtx context)
|
public ResultCode PrepareForJit(ServiceCtx context)
|
||||||
|
@@ -39,9 +39,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
|||||||
|
|
||||||
public ref AtomicStorage<T> GetCurrentAtomicEntryRef()
|
public ref AtomicStorage<T> GetCurrentAtomicEntryRef()
|
||||||
{
|
{
|
||||||
ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), 1);
|
ulong countAvailable = Math.Min(Math.Max(0, ReadCurrentCount()), 1);
|
||||||
|
|
||||||
if (countAvailaible == 0)
|
if (countAvailable == 0)
|
||||||
{
|
{
|
||||||
_storage[0] = default;
|
_storage[0] = default;
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible) % MaxEntries);
|
int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailable) % MaxEntries);
|
||||||
|
|
||||||
ref AtomicStorage<T> result = ref storageSpan[inputEntryIndex];
|
ref AtomicStorage<T> result = ref storageSpan[inputEntryIndex];
|
||||||
|
|
||||||
@@ -63,9 +63,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
|
|||||||
|
|
||||||
if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1)
|
if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1)
|
||||||
{
|
{
|
||||||
ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible);
|
ulong tempCount = Math.Min(ReadCurrentCount(), countAvailable);
|
||||||
|
|
||||||
countAvailaible = Math.Min(tempCount, 1);
|
countAvailable = Math.Min(tempCount, 1);
|
||||||
index = ReadCurrentIndex();
|
index = ReadCurrentIndex();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
||||||
|
{
|
||||||
|
struct NpadCondition
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0414 // Field is assigned but its value is never used
|
||||||
|
private uint _00;
|
||||||
|
private uint _04;
|
||||||
|
private NpadJoyHoldType _holdType;
|
||||||
|
private uint _0C;
|
||||||
|
#pragma warning restore CS0414 // Field is assigned but its value is never used
|
||||||
|
|
||||||
|
public static NpadCondition Create()
|
||||||
|
{
|
||||||
|
return new NpadCondition()
|
||||||
|
{
|
||||||
|
_00 = 0,
|
||||||
|
_04 = 1,
|
||||||
|
_holdType = NpadJoyHoldType.Horizontal,
|
||||||
|
_0C = 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -41,6 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
|
|||||||
public NpadLarkType LarkTypeRight;
|
public NpadLarkType LarkTypeRight;
|
||||||
public NpadLuciaType LuciaType;
|
public NpadLuciaType LuciaType;
|
||||||
public uint Unknown43EC;
|
public uint Unknown43EC;
|
||||||
|
public ulong SixAxisSensorPropertiesArray;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)]
|
||||||
private struct Reserved2Struct { }
|
private struct Reserved2Struct { }
|
||||||
|
@@ -53,6 +53,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
|
|||||||
[FieldOffset(0x3DC00)]
|
[FieldOffset(0x3DC00)]
|
||||||
public RingLifo<DebugMouseState> DebugMouse;
|
public RingLifo<DebugMouseState> DebugMouse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pad Condition.
|
||||||
|
/// </summary>
|
||||||
|
[FieldOffset(0x3e200)]
|
||||||
|
public NpadCondition Condition;
|
||||||
|
|
||||||
public static SharedMemory Create()
|
public static SharedMemory Create()
|
||||||
{
|
{
|
||||||
SharedMemory result = new()
|
SharedMemory result = new()
|
||||||
@@ -61,6 +67,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
|
|||||||
TouchScreen = RingLifo<TouchScreenState>.Create(),
|
TouchScreen = RingLifo<TouchScreenState>.Create(),
|
||||||
Mouse = RingLifo<MouseState>.Create(),
|
Mouse = RingLifo<MouseState>.Create(),
|
||||||
Keyboard = RingLifo<KeyboardState>.Create(),
|
Keyboard = RingLifo<KeyboardState>.Create(),
|
||||||
|
Condition = NpadCondition.Create(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Span<NpadState> npadsSpan = result.Npads.AsSpan();
|
Span<NpadState> npadsSpan = result.Npads.AsSpan();
|
||||||
|
@@ -50,7 +50,7 @@ namespace Ryujinx.HLE.HOS.Tamper
|
|||||||
Logger.Error?.Print(LogClass.TamperMachine, ex.ToString());
|
Logger.Error?.Print(LogClass.TamperMachine, ex.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Error?.Print(LogClass.TamperMachine, "There was a problem while compiling the Atmosphere cheat");
|
Logger.Error?.Print(LogClass.TamperMachine, $"There was a problem while compiling the Atmosphere cheat '{name}'");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ namespace Ryujinx.HLE.HOS.Tamper
|
|||||||
DebugLog.Emit(instruction, context);
|
DebugLog.Emit(instruction, context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat");
|
throw new TamperCompilationException($"Code type {codeType} not implemented in Atmosphere cheat compiler");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,7 +40,8 @@ namespace Ryujinx.HLE.HOS.Tamper.CodeEmitters
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the conditional begin instruction stored in the stack.
|
// Use the conditional begin instruction stored in the stack.
|
||||||
byte[] upperInstruction = context.CurrentBlock.BaseInstruction;
|
byte[] upperInstruction = context.CurrentBlock.BaseInstruction
|
||||||
|
?? throw new TamperCompilationException($"Base instruction in current block was null; termination type '{terminationType}'");
|
||||||
CodeType codeType = InstructionHelper.GetCodeType(upperInstruction);
|
CodeType codeType = InstructionHelper.GetCodeType(upperInstruction);
|
||||||
|
|
||||||
// Pop the current block of operations from the stack so control instructions
|
// Pop the current block of operations from the stack so control instructions
|
||||||
|
@@ -20,6 +20,18 @@ namespace Ryujinx.HLE
|
|||||||
{
|
{
|
||||||
public class Switch : IDisposable
|
public class Switch : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Currently running emulated Switch, if there is one.
|
||||||
|
/// <para>
|
||||||
|
/// Proper usage of this property null checks it before use, unless the caller is certain that the emulation is running.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// In case the emulation is running, there might be a way to directly pass the <see cref="Switch" /> instance, which is preferred.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// The instance is set to <c>this</c> on any <see cref="Switch" /> instantiation, and set to <c>null</c> on any <see cref="Switch" /> disposal.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
public static Switch Shared { get; private set; }
|
public static Switch Shared { get; private set; }
|
||||||
|
|
||||||
public HleConfiguration Configuration { get; }
|
public HleConfiguration Configuration { get; }
|
||||||
|
@@ -12,9 +12,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
/// <typeparam name="T">Type of the range.</typeparam>
|
/// <typeparam name="T">Type of the range.</typeparam>
|
||||||
public unsafe class NonOverlappingRangeList<T> : RangeListBase<T> where T : class, INonOverlappingRange
|
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();
|
public readonly ReaderWriterLockSlim Lock = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,8 +41,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
RangeItem<T> rangeItem = new(item);
|
RangeItem<T> rangeItem = new(item);
|
||||||
|
|
||||||
Insert(index, rangeItem);
|
Insert(index, rangeItem);
|
||||||
|
|
||||||
_quickAccess.Add(item.Address, rangeItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -71,22 +66,40 @@ namespace Ryujinx.Memory.Range
|
|||||||
Items[index + 1].Previous = rangeItem;
|
Items[index + 1].Previous = rangeItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (ulong addr in Items[index].QuickAccessAddresses)
|
|
||||||
{
|
|
||||||
_quickAccess.Remove(addr);
|
|
||||||
_fastQuickAccess.Remove(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Items[index] = rangeItem;
|
Items[index] = rangeItem;
|
||||||
|
|
||||||
_quickAccess[item.Address] = rangeItem;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an item's end address on the list. Address must be the same.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The RangeItem to be updated</param>
|
||||||
|
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||||
|
protected override bool Update(RangeItem<T> item)
|
||||||
|
{
|
||||||
|
int index = BinarySearch(item.Address);
|
||||||
|
|
||||||
|
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
Items[index - 1].Next = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < Count - 1)
|
||||||
|
{
|
||||||
|
Items[index + 1].Previous = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
Items[index] = rangeItem;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Insert(int index, RangeItem<T> item)
|
private void Insert(int index, RangeItem<T> item)
|
||||||
{
|
{
|
||||||
@@ -159,14 +172,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
|
|
||||||
if (index >= 0 && Items[index].Value.Equals(item))
|
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);
|
RemoveAt(index);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -193,36 +198,25 @@ namespace Ryujinx.Memory.Range
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int startIndex = BinarySearch(startItem.Address);
|
(int startIndex, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress);
|
||||||
int endIndex = BinarySearch(endItem.Address);
|
|
||||||
|
|
||||||
for (int i = startIndex; i <= endIndex; i++)
|
if (endIndex < Count)
|
||||||
{
|
{
|
||||||
_quickAccess.Remove(Items[i].Address);
|
Items[endIndex].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
||||||
foreach (ulong addr in Items[i].QuickAccessAddresses)
|
|
||||||
{
|
|
||||||
_quickAccess.Remove(addr);
|
|
||||||
_fastQuickAccess.Remove(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endIndex < Count - 1)
|
|
||||||
{
|
|
||||||
Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (startIndex > 0)
|
if (startIndex > 0)
|
||||||
{
|
{
|
||||||
Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null;
|
Items[startIndex - 1].Next = endIndex < Count ? Items[endIndex] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (endIndex < Count - 1)
|
if (endIndex < Count)
|
||||||
{
|
{
|
||||||
Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1);
|
Array.Copy(Items, endIndex, Items, startIndex, Count - endIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Count -= endIndex - startIndex + 1;
|
Count -= endIndex - startIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -243,13 +237,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
|
|
||||||
while (Items[endIndex] is not null && Items[endIndex].Address < address + size)
|
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)
|
if (endIndex == Count - 1)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -285,8 +272,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
Lock.EnterWriteLock();
|
Lock.EnterWriteLock();
|
||||||
Count = 0;
|
Count = 0;
|
||||||
_quickAccess.Clear();
|
|
||||||
_fastQuickAccess.Clear();
|
|
||||||
Lock.ExitWriteLock();
|
Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +293,7 @@ namespace Ryujinx.Memory.Range
|
|||||||
// So we need to return both the split 0-1 and 1-2 ranges.
|
// So we need to return both the split 0-1 and 1-2 ranges.
|
||||||
|
|
||||||
Lock.EnterWriteLock();
|
Lock.EnterWriteLock();
|
||||||
(RangeItem<T> first, RangeItem<T> last) = FindOverlaps(address, size);
|
(RangeItem<T> first, RangeItem<T> last) = FindOverlapsAsNodes(address, size);
|
||||||
list = new List<T>();
|
list = new List<T>();
|
||||||
|
|
||||||
if (first is null)
|
if (first is null)
|
||||||
@@ -400,11 +385,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public override RangeItem<T> FindOverlap(ulong address, ulong size)
|
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);
|
int index = BinarySearchLeftEdge(address, address + size);
|
||||||
|
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -412,12 +392,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Items[index].Address < address)
|
|
||||||
{
|
|
||||||
_quickAccess.TryAdd(address, Items[index]);
|
|
||||||
Items[index].QuickAccessAddresses.Add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Items[index];
|
return Items[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,11 +404,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public override RangeItem<T> FindOverlapFast(ulong address, ulong size)
|
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);
|
int index = BinarySearch(address, address + size);
|
||||||
|
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -442,17 +411,6 @@ namespace Ryujinx.Memory.Range
|
|||||||
return null;
|
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];
|
return Items[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,18 +421,8 @@ namespace Ryujinx.Memory.Range
|
|||||||
/// <param name="size">Size in bytes of the range</param>
|
/// <param name="size">Size in bytes of the range</param>
|
||||||
/// <returns>The first and last overlapping items, or null if none are found</returns>
|
/// <returns>The first and last overlapping items, or null if none are found</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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);
|
(int index, int endIndex) = BinarySearchEdges(address, address + size);
|
||||||
|
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
@@ -482,13 +430,45 @@ namespace Ryujinx.Memory.Range
|
|||||||
return (null, null);
|
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]);
|
result = [];
|
||||||
Items[index].QuickAccessAddresses.Add(address);
|
}
|
||||||
|
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()
|
public override IEnumerator<T> GetEnumerator()
|
||||||
|
@@ -81,12 +81,73 @@ namespace Ryujinx.Memory.Range
|
|||||||
{
|
{
|
||||||
if (Items[index].Value.Equals(item))
|
if (Items[index].Value.Equals(item))
|
||||||
{
|
{
|
||||||
|
RangeItem<T> rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next };
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
Items[index - 1].Next = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < Count - 1)
|
||||||
|
{
|
||||||
|
Items[index + 1].Previous = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (ulong address in Items[index].QuickAccessAddresses)
|
foreach (ulong address in Items[index].QuickAccessAddresses)
|
||||||
{
|
{
|
||||||
_quickAccess.Remove(address);
|
_quickAccess.Remove(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
Items[index] = new RangeItem<T>(item);
|
Items[index] = rangeItem;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Items[index].Address > item.Address)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an item's end address on the list. Address must be the same.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The RangeItem to be updated</param>
|
||||||
|
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||||
|
protected override bool Update(RangeItem<T> item)
|
||||||
|
{
|
||||||
|
int index = BinarySearch(item.Address);
|
||||||
|
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
while (index < Count)
|
||||||
|
{
|
||||||
|
if (Items[index].Equals(item))
|
||||||
|
{
|
||||||
|
RangeItem<T> rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next };
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
Items[index - 1].Next = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < Count - 1)
|
||||||
|
{
|
||||||
|
Items[index + 1].Previous = rangeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ulong address in item.QuickAccessAddresses)
|
||||||
|
{
|
||||||
|
_quickAccess.Remove(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
Items[index] = rangeItem;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ namespace Ryujinx.Memory.Range
|
|||||||
return u1 == u2;
|
return u1 == u2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetHashCode(ulong value) => (int)(value >> 5);
|
public int GetHashCode(ulong value) => (int)(value << 5);
|
||||||
|
|
||||||
public static readonly AddressEqualityComparer Comparer = new();
|
public static readonly AddressEqualityComparer Comparer = new();
|
||||||
}
|
}
|
||||||
@@ -63,6 +63,13 @@ namespace Ryujinx.Memory.Range
|
|||||||
/// <returns>True if the item was located and updated, false otherwise</returns>
|
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||||
protected abstract bool Update(T item);
|
protected abstract bool Update(T item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an item's end address on the list. Address must be the same.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The RangeItem to be updated</param>
|
||||||
|
/// <returns>True if the item was located and updated, false otherwise</returns>
|
||||||
|
protected abstract bool Update(RangeItem<T> item);
|
||||||
|
|
||||||
public abstract bool Remove(T item);
|
public abstract bool Remove(T item);
|
||||||
|
|
||||||
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);
|
public abstract void RemoveRange(RangeItem<T> startItem, RangeItem<T> endItem);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Memory.Tracking
|
namespace Ryujinx.Memory.Tracking
|
||||||
@@ -79,12 +80,10 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
{
|
{
|
||||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||||
regions.Lock.EnterReadLock();
|
regions.Lock.EnterReadLock();
|
||||||
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(va, size);
|
Span<RangeItem<VirtualRegion>> overlaps = regions.FindOverlapsAsSpan(va, size);
|
||||||
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
RangeItem<VirtualRegion> current = first;
|
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
{
|
||||||
VirtualRegion region = current.Value;
|
VirtualRegion region = overlaps[i].Value;
|
||||||
|
|
||||||
// If the region has been fully remapped, signal that it has been mapped again.
|
// If the region has been fully remapped, signal that it has been mapped again.
|
||||||
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
|
bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size);
|
||||||
@@ -94,7 +93,6 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
|
|
||||||
region.UpdateProtection();
|
region.UpdateProtection();
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
regions.Lock.ExitReadLock();
|
regions.Lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
@@ -118,15 +116,11 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
{
|
{
|
||||||
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
NonOverlappingRangeList<VirtualRegion> regions = type == 0 ? _virtualRegions : _guestVirtualRegions;
|
||||||
regions.Lock.EnterReadLock();
|
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;
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
{
|
||||||
VirtualRegion region = current.Value;
|
overlaps[i].Value.SignalMappingChanged(false);
|
||||||
|
|
||||||
region.SignalMappingChanged(false);
|
|
||||||
current = current.Next;
|
|
||||||
}
|
}
|
||||||
regions.Lock.ExitReadLock();
|
regions.Lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
@@ -182,11 +176,15 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
{
|
{
|
||||||
if (region.Guest)
|
if (region.Guest)
|
||||||
{
|
{
|
||||||
|
_guestVirtualRegions.Lock.EnterWriteLock();
|
||||||
_guestVirtualRegions.Remove(region);
|
_guestVirtualRegions.Remove(region);
|
||||||
|
_guestVirtualRegions.Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_virtualRegions.Lock.EnterWriteLock();
|
||||||
_virtualRegions.Remove(region);
|
_virtualRegions.Remove(region);
|
||||||
|
_virtualRegions.Lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,21 +297,13 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
lock (TrackingLock)
|
lock (TrackingLock)
|
||||||
{
|
{
|
||||||
NonOverlappingRangeList<VirtualRegion> regions = guest ? _guestVirtualRegions : _virtualRegions;
|
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.
|
// We use the non-span method here because keeping the lock will cause a deadlock.
|
||||||
regions.Lock.EnterReadLock();
|
regions.Lock.EnterReadLock();
|
||||||
(RangeItem<VirtualRegion> first, RangeItem<VirtualRegion> last) = regions.FindOverlaps(address, size);
|
RangeItem<VirtualRegion>[] overlaps = regions.FindOverlapsAsArray(address, size);
|
||||||
|
|
||||||
RangeItem<VirtualRegion> current = first;
|
|
||||||
while (last != null && current != last.Next)
|
|
||||||
{
|
|
||||||
overlaps.Add(current);
|
|
||||||
current = current.Next;
|
|
||||||
}
|
|
||||||
regions.Lock.ExitReadLock();
|
regions.Lock.ExitReadLock();
|
||||||
|
|
||||||
if (first is null && !precise)
|
if (overlaps.Length == 0 && !precise)
|
||||||
{
|
{
|
||||||
if (_memoryManager.IsRangeMapped(address, size))
|
if (_memoryManager.IsRangeMapped(address, size))
|
||||||
{
|
{
|
||||||
@@ -334,7 +324,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
size += (ulong)_pageSize;
|
size += (ulong)_pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < overlaps.Count; i++)
|
for (int i = 0; i < overlaps.Length; i++)
|
||||||
{
|
{
|
||||||
VirtualRegion region = overlaps[i].Value;
|
VirtualRegion region = overlaps[i].Value;
|
||||||
|
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using Avalonia.Platform.Storage;
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using Gommon;
|
|
||||||
using LibHac;
|
using LibHac;
|
||||||
using LibHac.Account;
|
using LibHac.Account;
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
@@ -411,7 +410,7 @@ namespace Ryujinx.Ava.Common
|
|||||||
|
|
||||||
public static async Task ExtractAoc(IStorageProvider storageProvider, string updateFilePath, string updateName)
|
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]
|
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)
|
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]
|
Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle]
|
||||||
});
|
});
|
||||||
|
@@ -19,7 +19,7 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
private readonly Dictionary<LocaleKeys, string> _localeStrings;
|
||||||
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
private readonly ConcurrentDictionary<LocaleKeys, object[]> _dynamicValues;
|
||||||
private string _localeLanguageCode;
|
private string _localeLanguageCode;
|
||||||
|
public string CurrentLanguageCode => _localeLanguageCode;
|
||||||
public static LocaleManager Instance { get; } = new();
|
public static LocaleManager Instance { get; } = new();
|
||||||
public event Action LocaleChanged;
|
public event Action LocaleChanged;
|
||||||
|
|
||||||
|
@@ -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.")]
|
[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; }
|
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; }
|
public ushort GdbStubPort { get; set; }
|
||||||
|
|
||||||
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
|
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
|
||||||
|
@@ -1045,7 +1045,7 @@ namespace Ryujinx.Ava.Systems
|
|||||||
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
|
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)
|
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUi)
|
||||||
{
|
{
|
||||||
_viewModel.ShowMenuAndStatusBar = false;
|
_viewModel.ShowMenuAndStatusBar = false;
|
||||||
}
|
}
|
||||||
|
@@ -117,7 +117,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
|
|
||||||
using UniqueRef<IFile> npdmFile = new();
|
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))
|
if (ResultFs.PathNotFound.Includes(result))
|
||||||
{
|
{
|
||||||
|
@@ -98,7 +98,9 @@ namespace Ryujinx.Ava.Systems.PlayReport
|
|||||||
_ => FormattedValue.ForceReset
|
_ => FormattedValue.ForceReset
|
||||||
};
|
};
|
||||||
|
|
||||||
return $"{playStatus} in {locations}";
|
return locations.Reset
|
||||||
|
? FormattedValue.ForceReset
|
||||||
|
: $"{playStatus} in {locations}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
|
private static FormattedValue SuperSmashBrosUltimate_Mode(SparseMultiValue values)
|
||||||
|
@@ -7,6 +7,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.Systems.Update.Client;
|
using Ryujinx.Systems.Update.Client;
|
||||||
using Ryujinx.Systems.Update.Common;
|
using Ryujinx.Systems.Update.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.Systems
|
namespace Ryujinx.Ava.Systems
|
||||||
@@ -14,16 +15,38 @@ namespace Ryujinx.Ava.Systems
|
|||||||
internal static partial class Updater
|
internal static partial class Updater
|
||||||
{
|
{
|
||||||
private static VersionResponse _versionResponse;
|
private static VersionResponse _versionResponse;
|
||||||
|
private static UpdateClient _updateClient;
|
||||||
|
|
||||||
private static UpdateClient CreateUpdateClient()
|
private static async Task<Return<VersionResponse>> QueryLatestVersionAsync()
|
||||||
=> UpdateClient.Builder()
|
{
|
||||||
|
_updateClient ??= UpdateClient.Builder()
|
||||||
.WithServerEndpoint("https://update.ryujinx.app") // This is the default, and doesn't need to be provided; it's here for transparency.
|
.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) =>
|
.WithLogger((format, args, caller) =>
|
||||||
Logger.Info?.Print(
|
Logger.Info?.Print(
|
||||||
LogClass.Application,
|
LogClass.Application,
|
||||||
args.Length is 0 ? format : format.Format(args),
|
args.Length is 0 ? format : format.Format(args),
|
||||||
caller: caller)
|
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)
|
public static async Task<Optional<(Version Current, Version Incoming)>> CheckVersionAsync(bool showVersionUpToDate = false)
|
||||||
{
|
{
|
||||||
@@ -41,17 +64,13 @@ namespace Ryujinx.Ava.Systems
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
using UpdateClient updateClient = CreateUpdateClient();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_versionResponse = await updateClient.QueryLatestAsync(ReleaseInformation.IsCanaryBuild
|
_versionResponse = await QueryLatestVersionAsync().Then(x => x.Unwrap());
|
||||||
? ReleaseChannel.Canary
|
|
||||||
: ReleaseChannel.Stable);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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;
|
_running = false;
|
||||||
return default;
|
return default;
|
||||||
|
@@ -71,12 +71,12 @@
|
|||||||
Command="{Binding OpenTitleUpdateManager}"
|
Command="{Binding OpenTitleUpdateManager}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuManageTitleUpdates}"
|
Header="{ext:Locale GameListContextMenuManageTitleUpdates}"
|
||||||
Icon="{ext:Icon fa-solid fa-code-compare}" />
|
Icon="{ext:Icon fa-solid fa-diagram-predecessor}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenDownloadableContentManager}"
|
Command="{Binding OpenDownloadableContentManager}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuManageDlc}"
|
Header="{ext:Locale GameListContextMenuManageDlc}"
|
||||||
Icon="{ext:Icon fa-solid fa-download}" />
|
Icon="{ext:Icon fa-solid fa-puzzle-piece}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenCheatManager}"
|
Command="{Binding OpenCheatManager}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
@@ -92,12 +92,12 @@
|
|||||||
Command="{Binding OpenModsDirectory}"
|
Command="{Binding OpenModsDirectory}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuOpenModsDirectory}"
|
Header="{ext:Locale GameListContextMenuOpenModsDirectory}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenSdModsDirectory}"
|
Command="{Binding OpenSdModsDirectory}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuOpenSdModsDirectory}"
|
Header="{ext:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder}"
|
Icon="{ext:Icon fa-solid fa-folder-closed}"
|
||||||
ToolTip.Tip="{ext:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
ToolTip.Tip="{ext:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -128,12 +128,12 @@
|
|||||||
Command="{Binding OpenPtcDirectory}"
|
Command="{Binding OpenPtcDirectory}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
Header="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenShaderCacheDirectory}"
|
Command="{Binding OpenShaderCacheDirectory}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Header="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
Header="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder}" />
|
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="{ext:Locale GameListContextMenuExtractData}" Icon="{ext:Icon fa-solid fa-file-export}">
|
<MenuItem Header="{ext:Locale GameListContextMenuExtractData}" Icon="{ext:Icon fa-solid fa-file-export}">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
x:Class="Ryujinx.Ava.RyujinxApp"
|
x:Class="Ryujinx.Ava.RyujinxApp"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:sty="using:FluentAvalonia.Styling">
|
xmlns:sty="using:FluentAvalonia.Styling"
|
||||||
|
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
<NativeMenu.Menu>
|
<NativeMenu.Menu>
|
||||||
<NativeMenu>
|
<NativeMenu>
|
||||||
<NativeMenuItem Header="About Ryujinx" Click="AboutRyujinx_OnClick" />
|
<NativeMenuItem Header="{ext:Locale MenuBarHelpAbout}" Click="AboutRyujinx_OnClick" />
|
||||||
</NativeMenu>
|
</NativeMenu>
|
||||||
</NativeMenu.Menu>
|
</NativeMenu.Menu>
|
||||||
</Application>
|
</Application>
|
||||||
|
@@ -562,7 +562,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartGamesWithoutUI
|
public bool StartGamesWithoutUi
|
||||||
{
|
{
|
||||||
get => ConfigurationState.Instance.UI.StartNoUI;
|
get => ConfigurationState.Instance.UI.StartNoUI;
|
||||||
set
|
set
|
||||||
@@ -975,8 +975,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle);
|
string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallTitle);
|
||||||
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
|
string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallMessage);
|
||||||
|
|
||||||
bool alreadyKesyInstalled = ContentManager.AreKeysAlredyPresent(systemDirectory);
|
if (ContentManager.AreKeysAlredyPresent(systemDirectory))
|
||||||
if (alreadyKesyInstalled)
|
|
||||||
{
|
{
|
||||||
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage);
|
dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogKeysInstallerKeysInstallSubMessage);
|
||||||
}
|
}
|
||||||
@@ -994,7 +993,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (result == UserResult.Yes)
|
if (result == UserResult.Yes)
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, $"Installing Keys");
|
Logger.Info?.Print(LogClass.Application, $"Installing keys from {filename}");
|
||||||
|
|
||||||
Thread thread = new(() =>
|
Thread thread = new(() =>
|
||||||
{
|
{
|
||||||
@@ -1204,17 +1203,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
_rendererWaitEvent.Set();
|
_rendererWaitEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected)
|
private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected, LocaleKeys dirSelectDialogTitle)
|
||||||
{
|
{
|
||||||
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
Optional<IReadOnlyList<IStorageFolder>> result = await StorageProvider.OpenMultiFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
|
Title = LocaleManager.Instance[dirSelectDialogTitle]
|
||||||
AllowMultiple = true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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);
|
int numAdded = onDirsSelected(dirs, out int numRemoved);
|
||||||
|
|
||||||
string msg = string.Join("\n",
|
string msg = string.Join("\n",
|
||||||
@@ -1270,51 +1268,26 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TakeScreenshot()
|
public void TakeScreenshot() => AppHost.ScreenshotRequested = true;
|
||||||
{
|
|
||||||
AppHost.ScreenshotRequested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideUi()
|
public void HideUi() => ShowMenuAndStatusBar = false;
|
||||||
{
|
|
||||||
ShowMenuAndStatusBar = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleStartGamesInFullscreen()
|
public void ToggleStartGamesInFullscreen() => StartGamesInFullscreen = !StartGamesInFullscreen;
|
||||||
{
|
|
||||||
StartGamesInFullscreen = !StartGamesInFullscreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleStartGamesWithoutUI()
|
public void ToggleStartGamesWithoutUi() => StartGamesWithoutUi = !StartGamesWithoutUi;
|
||||||
{
|
|
||||||
StartGamesWithoutUI = !StartGamesWithoutUI;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ToggleShowConsole()
|
public void ToggleShowConsole() => ShowConsole = !ShowConsole;
|
||||||
{
|
|
||||||
ShowConsole = !ShowConsole;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetListMode()
|
public void SetListMode() => Glyph = Glyph.List;
|
||||||
{
|
|
||||||
Glyph = Glyph.List;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetGridMode()
|
public void SetGridMode() => Glyph = Glyph.Grid;
|
||||||
{
|
|
||||||
Glyph = Glyph.Grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetAspectRatio(AspectRatio aspectRatio)
|
public void SetAspectRatio(AspectRatio aspectRatio) => ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
|
||||||
{
|
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InstallFirmwareFromFile()
|
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>
|
FileTypeFilter = new List<FilePickerFileType>
|
||||||
{
|
{
|
||||||
new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes])
|
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()
|
public async Task InstallFirmwareFromFolder()
|
||||||
{
|
{
|
||||||
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
|
||||||
{
|
|
||||||
AllowMultiple = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.Count > 0)
|
if (result.HasValue)
|
||||||
{
|
{
|
||||||
await HandleFirmwareInstallation(result[0].Path.LocalPath);
|
await HandleFirmwareInstallation(result.Value.Path.LocalPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InstallKeysFromFile()
|
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>
|
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")
|
new("KEYS")
|
||||||
{
|
{
|
||||||
Patterns = ["*.keys"],
|
Patterns = ["*.keys"],
|
||||||
AppleUniformTypeIdentifiers = ["com.ryujinx.xci"],
|
AppleUniformTypeIdentifiers = ["com.ryujinx.xci"],
|
||||||
MimeTypes = ["application/keys"],
|
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()
|
public async Task InstallKeysFromFolder()
|
||||||
{
|
{
|
||||||
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync();
|
||||||
{
|
|
||||||
AllowMultiple = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
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()
|
public async Task OpenFile()
|
||||||
{
|
{
|
||||||
IReadOnlyList<IStorageFile> result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(new FilePickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
Title = LocaleManager.Instance[LocaleKeys.LoadApplicationFromFileDialogTitle],
|
||||||
AllowMultiple = false,
|
|
||||||
FileTypeFilter = new List<FilePickerFileType>
|
FileTypeFilter = new List<FilePickerFileType>
|
||||||
{
|
{
|
||||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
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))
|
out List<ApplicationData> applications))
|
||||||
{
|
{
|
||||||
await LoadApplication(applications[0]);
|
await LoadApplication(applications[0]);
|
||||||
@@ -1581,7 +1534,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
await LoadContentFromFolder(
|
await LoadContentFromFolder(
|
||||||
LocaleKeys.AutoloadDlcAddedMessage,
|
LocaleKeys.AutoloadDlcAddedMessage,
|
||||||
LocaleKeys.AutoloadDlcRemovedMessage,
|
LocaleKeys.AutoloadDlcRemovedMessage,
|
||||||
ApplicationLibrary.AutoLoadDownloadableContents);
|
ApplicationLibrary.AutoLoadDownloadableContents,
|
||||||
|
LocaleKeys.LoadDLCFromFolderDialogTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LoadTitleUpdatesFromFolder()
|
public async Task LoadTitleUpdatesFromFolder()
|
||||||
@@ -1589,23 +1543,23 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
await LoadContentFromFolder(
|
await LoadContentFromFolder(
|
||||||
LocaleKeys.AutoloadUpdateAddedMessage,
|
LocaleKeys.AutoloadUpdateAddedMessage,
|
||||||
LocaleKeys.AutoloadUpdateRemovedMessage,
|
LocaleKeys.AutoloadUpdateRemovedMessage,
|
||||||
ApplicationLibrary.AutoLoadTitleUpdates);
|
ApplicationLibrary.AutoLoadTitleUpdates,
|
||||||
|
LocaleKeys.LoadTitleUpdatesFromFolderDialogTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OpenFolder()
|
public async Task OpenFolder()
|
||||||
{
|
{
|
||||||
IReadOnlyList<IStorageFolder> result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
Optional<IStorageFolder> result = await StorageProvider.OpenSingleFolderPickerAsync(new FolderPickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
|
Title = LocaleManager.Instance[LocaleKeys.LoadUnpackedGameFromFolderDialogTitle]
|
||||||
AllowMultiple = false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.Count > 0)
|
if (result.TryGet(out IStorageFolder value))
|
||||||
{
|
{
|
||||||
ApplicationData applicationData = new()
|
ApplicationData applicationData = new()
|
||||||
{
|
{
|
||||||
Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath),
|
Name = Path.GetFileNameWithoutExtension(value.Path.LocalPath),
|
||||||
Path = result[0].Path.LocalPath,
|
Path = value.Path.LocalPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
await LoadApplication(applicationData);
|
await LoadApplication(applicationData);
|
||||||
@@ -1810,10 +1764,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (AppHost.Device.System.SearchingForAmiibo(out _) && IsGameRunning)
|
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],
|
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||||
AllowMultiple = false,
|
|
||||||
FileTypeFilter = new List<FilePickerFileType>
|
FileTypeFilter = new List<FilePickerFileType>
|
||||||
{
|
{
|
||||||
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||||
@@ -1822,9 +1775,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (result.Count > 0)
|
|
||||||
|
if (result.HasValue)
|
||||||
{
|
{
|
||||||
AppHost.Device.System.ScanAmiiboFromBin(result[0].Path.LocalPath);
|
AppHost.Device.System.ScanAmiiboFromBin(result.Value.Path.LocalPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,36 +39,29 @@
|
|||||||
Header="{ext:Locale MenuBarFileOpenUnpacked}"
|
Header="{ext:Locale MenuBarFileOpenUnpacked}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder-open}"
|
Icon="{ext:Icon fa-solid fa-folder-open}"
|
||||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||||
<MenuItem
|
|
||||||
Command="{Binding LoadDlcFromFolder}"
|
|
||||||
Header="{ext:Locale MenuBarFileLoadDlcFromFolder}"
|
|
||||||
Icon="{ext:Icon fa-solid fa-download}"
|
|
||||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding LoadTitleUpdatesFromFolder}"
|
Command="{Binding LoadTitleUpdatesFromFolder}"
|
||||||
Header="{ext:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
|
Header="{ext:Locale MenuBarFileLoadTitleUpdatesFromFolder}"
|
||||||
Icon="{ext:Icon fa-solid fa-code-compare}"
|
Icon="{ext:Icon fa-solid fa-diagram-predecessor}"
|
||||||
|
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding LoadDlcFromFolder}"
|
||||||
|
Header="{ext:Locale MenuBarFileLoadDlcFromFolder}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-puzzle-piece}"
|
||||||
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
IsEnabled="{Binding EnableNonGameRunningControls}" />
|
||||||
<MenuItem Header="{ext:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}" Icon="{ext:Icon fa-solid fa-microchip}">
|
|
||||||
<MenuItem
|
|
||||||
Name="MiiAppletMenuItem"
|
|
||||||
Header="{ext:Locale MenuBarFileOpenAppletOpenMiiApplet}"
|
|
||||||
Icon="{ext:Icon fa-solid fa-person}"
|
|
||||||
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
|
||||||
</MenuItem>
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenRyujinxFolder}"
|
Command="{Binding OpenRyujinxFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
Header="{ext:Locale MenuBarFileOpenEmuFolder}"
|
||||||
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||||
<MenuItem
|
|
||||||
Command="{Binding OpenScreenshotsFolder}"
|
|
||||||
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
|
|
||||||
Icon="{ext:Icon fa-solid fa-desktop}" />
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding OpenLogsFolder}"
|
Command="{Binding OpenLogsFolder}"
|
||||||
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
Header="{ext:Locale MenuBarFileOpenLogsFolder}"
|
||||||
Icon="{ext:Icon fa-solid fa-file-lines}" />
|
Icon="{ext:Icon fa-solid fa-terminal}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenScreenshotsFolder}"
|
||||||
|
Header="{ext:Locale MenuBarFileOpenScreenshotsFolder}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-image}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Name="CloseRyujinxMenuItem"
|
Name="CloseRyujinxMenuItem"
|
||||||
@@ -99,14 +92,14 @@
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Padding="0"
|
Padding="0"
|
||||||
Command="{Binding ToggleStartGamesWithoutUI}"
|
Command="{Binding ToggleStartGamesWithoutUi}"
|
||||||
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}"
|
Header="{ext:Locale MenuBarOptionsStartGamesWithoutUI}"
|
||||||
Classes="withCheckbox">
|
Classes="withCheckbox">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<CheckBox
|
<CheckBox
|
||||||
MinWidth="{DynamicResource CheckBoxSize}"
|
MinWidth="{DynamicResource CheckBoxSize}"
|
||||||
MinHeight="{DynamicResource CheckBoxSize}"
|
MinHeight="{DynamicResource CheckBoxSize}"
|
||||||
IsChecked="{Binding StartGamesWithoutUI, Mode=TwoWay}"
|
IsChecked="{Binding StartGamesWithoutUi, Mode=TwoWay}"
|
||||||
Padding="0" />
|
Padding="0" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -132,11 +125,6 @@
|
|||||||
Icon="{ext:Icon fa-solid fa-globe}"
|
Icon="{ext:Icon fa-solid fa-globe}"
|
||||||
Classes="withCheckbox">
|
Classes="withCheckbox">
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
|
||||||
Name="ToggleFileTypesMenuItem"
|
|
||||||
Padding="-10,0,0,0"
|
|
||||||
Header="{ext:Locale MenuBarShowFileTypes}" />
|
|
||||||
<Separator />
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Name="OpenSettingsMenuItem"
|
Name="OpenSettingsMenuItem"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
@@ -222,20 +210,25 @@
|
|||||||
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
|
<MenuItem Command="{Binding InstallFirmwareFromFile}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromFile}" Icon="{ext:Icon fa-solid fa-file-code}" />
|
||||||
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
<MenuItem Command="{Binding InstallFirmwareFromFolder}" Header="{ext:Locale MenuBarActionsInstallFirmwareFromDirectory}" Icon="{ext:Icon fa-solid fa-folder-closed}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
|
<MenuItem Header="{ext:Locale MenuBarActionsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}" Icon="{ext:Icon fa-solid fa-clipboard}">
|
||||||
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" />
|
<MenuItem Name="InstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsInstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered, Converter={x:Static BoolConverters.Not}}" Icon="{ext:Icon fa-solid fa-square-plus}" />
|
||||||
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsUninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" />
|
<MenuItem Name="UninstallFileTypesMenuItem" Header="{ext:Locale MenuBarActionsUninstallFileTypes}" IsEnabled="{Binding AreMimeTypesRegistered}" Icon="{ext:Icon fa-solid fa-square-minus}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBarActionsXCITrimmer}" Icon="{ext:Icon fa-solid fa-scissors}" />
|
<MenuItem Header="{ext:Locale MenuBarActionsTools}" Icon="{ext:Icon fa-solid fa-toolbox}">
|
||||||
|
<MenuItem
|
||||||
|
Name="MiiAppletMenuItem" Header="{ext:Locale MenuBarActionsOpenMiiEditor}" Icon="{ext:Icon fa-solid fa-face-grin-wide}" ToolTip.Tip="{ext:Locale MenuBarActionsOpenMiiEditorToolTip}" />
|
||||||
|
<MenuItem Name="XciTrimmerMenuItem" Header="{ext:Locale MenuBarActionsXCITrimmer}" Icon="{ext:Icon fa-solid fa-scissors}" />
|
||||||
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">
|
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarView}">
|
||||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarViewWindow}">
|
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarViewWindow}" Icon="{ext:Icon fa-solid fa-window-restore}">
|
||||||
<MenuItem Name="WindowSize720PMenuItem" Header="{ext:Locale MenuBarViewWindow720}" CommandParameter="1280 720" />
|
<MenuItem Name="WindowSize720PMenuItem" Header="{ext:Locale MenuBarViewWindow720}" CommandParameter="1280 720" />
|
||||||
<MenuItem Name="WindowSize1080PMenuItem" Header="{ext:Locale MenuBarViewWindow1080}" CommandParameter="1920 1080" />
|
<MenuItem Name="WindowSize1080PMenuItem" Header="{ext:Locale MenuBarViewWindow1080}" CommandParameter="1920 1080" />
|
||||||
<MenuItem Name="WindowSize1440PMenuItem" Header="{ext:Locale MenuBarViewWindow1440}" CommandParameter="2560 1440" />
|
<MenuItem Name="WindowSize1440PMenuItem" Header="{ext:Locale MenuBarViewWindow1440}" CommandParameter="2560 1440" />
|
||||||
<MenuItem Name="WindowSize2160PMenuItem" Header="{ext:Locale MenuBarViewWindow2160}" CommandParameter="3840 2160" />
|
<MenuItem Name="WindowSize2160PMenuItem" Header="{ext:Locale MenuBarViewWindow2160}" CommandParameter="3840 2160" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Name="ToggleFileTypesMenuItem" Header="{ext:Locale MenuBarShowFileTypes}" Icon="{ext:Icon fa-solid fa-tags}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelp}">
|
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelp}">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -258,12 +251,6 @@
|
|||||||
IsEnabled="{Binding IsRyuLdnEnabled}"/>
|
IsEnabled="{Binding IsRyuLdnEnabled}"/>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelpFaqAndGuides}" Icon="{ext:Icon fa-solid fa-question}" >
|
<MenuItem VerticalAlignment="Center" Header="{ext:Locale MenuBarHelpFaqAndGuides}" Icon="{ext:Icon fa-solid fa-question}" >
|
||||||
<MenuItem
|
|
||||||
Name="FaqMenuItem"
|
|
||||||
Header="{ext:Locale MenuBarHelpFaq}"
|
|
||||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
|
||||||
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}"
|
|
||||||
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Name="SetupGuideMenuItem"
|
Name="SetupGuideMenuItem"
|
||||||
Header="{ext:Locale MenuBarHelpSetup}"
|
Header="{ext:Locale MenuBarHelpSetup}"
|
||||||
@@ -276,6 +263,12 @@
|
|||||||
Icon="{ext:Icon fa-brands fa-gitlab}"
|
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||||
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}"
|
CommandParameter="{x:Static common:SharedConstants.MultiplayerWikiUrl}"
|
||||||
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
|
ToolTip.Tip="{ext:Locale MenuBarHelpMultiplayerTooltip}" />
|
||||||
|
<MenuItem
|
||||||
|
Name="FaqMenuItem"
|
||||||
|
Header="{ext:Locale MenuBarHelpFaq}"
|
||||||
|
Icon="{ext:Icon fa-brands fa-gitlab}"
|
||||||
|
CommandParameter="{x:Static common:SharedConstants.FaqWikiUrl}"
|
||||||
|
ToolTip.Tip="{ext:Locale MenuBarHelpFaqTooltip}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
@@ -61,6 +61,14 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
WindowSize1080PMenuItem.Command =
|
WindowSize1080PMenuItem.Command =
|
||||||
WindowSize1440PMenuItem.Command =
|
WindowSize1440PMenuItem.Command =
|
||||||
WindowSize2160PMenuItem.Command = Commands.Create<string>(ChangeWindowSize);
|
WindowSize2160PMenuItem.Command = Commands.Create<string>(ChangeWindowSize);
|
||||||
|
|
||||||
|
LocaleManager.Instance.LocaleChanged += OnLocaleChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLocaleChanged()
|
||||||
|
{
|
||||||
|
ChangeLanguageMenuItem.ItemsSource = GenerateLanguageMenuItems();
|
||||||
|
Menu.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<CheckBox> GenerateToggleFileTypeItems() =>
|
private IEnumerable<CheckBox> GenerateToggleFileTypeItems() =>
|
||||||
@@ -80,6 +88,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
const string LocalePath = "Ryujinx/Assets/Locale.json";
|
const string LocalePath = "Ryujinx/Assets/Locale.json";
|
||||||
|
|
||||||
string languageJson = EmbeddedResources.ReadAllText(LocalePath);
|
string languageJson = EmbeddedResources.ReadAllText(LocalePath);
|
||||||
|
string currentLanguageCode = LocaleManager.Instance.CurrentLanguageCode;
|
||||||
|
|
||||||
LocalesJson locales = JsonHelper.Deserialize(languageJson, LocalesJsonContext.Default.LocalesJson);
|
LocalesJson locales = JsonHelper.Deserialize(languageJson, LocalesJsonContext.Default.LocalesJson);
|
||||||
|
|
||||||
@@ -105,7 +114,7 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
Padding = new Thickness(15, 0, 0, 0),
|
Padding = new Thickness(15, 0, 0, 0),
|
||||||
Margin = new Thickness(3, 0, 3, 0),
|
Margin = new Thickness(3, 0, 3, 0),
|
||||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||||
Header = languageName,
|
Header = language == currentLanguageCode ? $"{languageName} ✔" : languageName,
|
||||||
Command = Commands.Create(() => MainWindowViewModel.ChangeLanguage(language))
|
Command = Commands.Create(() => MainWindowViewModel.ChangeLanguage(language))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -115,7 +115,7 @@
|
|||||||
Margin="10, 0, 148, 0"
|
Margin="10, 0, 148, 0"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Content="Filters"
|
Content="{ext:Locale LdnGameListFiltersHeading}"
|
||||||
DockPanel.Dock="Right">
|
DockPanel.Dock="Right">
|
||||||
<DropDownButton.Flyout>
|
<DropDownButton.Flyout>
|
||||||
<Flyout Placement="Bottom">
|
<Flyout Placement="Bottom">
|
||||||
@@ -124,10 +124,10 @@
|
|||||||
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
|
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
|
||||||
<TextBlock Text="{ext:Locale LdnGameListOnlyShowPublicGames}" />
|
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowPublicGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
|
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
|
||||||
<TextBlock Text="{ext:Locale LdnGameListOnlyShowJoinableGames}" />
|
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowJoinableGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Flyout>
|
</Flyout>
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
Margin="10, 5, 20, 5"
|
Margin="10, 5, 20, 5"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Content="Filters"
|
Content="{ext:Locale LdnGameListFiltersHeading}"
|
||||||
DockPanel.Dock="Right">
|
DockPanel.Dock="Right">
|
||||||
<DropDownButton.Flyout>
|
<DropDownButton.Flyout>
|
||||||
<Flyout Placement="Bottom">
|
<Flyout Placement="Bottom">
|
||||||
@@ -222,10 +222,10 @@
|
|||||||
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
<TextBlock Text="{ext:Locale CompatibilityListOnlyShowOwnedGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
|
<CheckBox IsChecked="{Binding OnlyShowPublicGames}">
|
||||||
<TextBlock Text="{ext:Locale LdnGameListOnlyShowPublicGames}" />
|
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowPublicGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
|
<CheckBox IsChecked="{Binding OnlyShowJoinableGames}">
|
||||||
<TextBlock Text="{ext:Locale LdnGameListOnlyShowJoinableGames}" />
|
<TextBlock Text="{ext:Locale LdnGameListFiltersOnlyShowJoinableGames}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Flyout>
|
</Flyout>
|
||||||
|
Reference in New Issue
Block a user