Compare commits

..

2 Commits

Author SHA1 Message Date
GreemDev
e4abc3a960 cleanup 2026-01-04 19:34:32 -06:00
GreemDev
8ccbf33327 Replace CommandLineState with a more user-friendly CLI experience. 2026-01-04 05:18:32 -06:00
132 changed files with 3216 additions and 6408 deletions

View File

@@ -50,7 +50,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -162,7 +162,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -215,7 +215,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH

View File

@@ -44,7 +44,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.31 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -161,7 +161,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH
@@ -217,7 +217,7 @@ jobs:
- name: Install gli - name: Install gli
run: | run: |
mkdir -p $HOME/.bin mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' 2.0.30 gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli chmod +x gli
mv gli $HOME/.bin/ mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH echo "$HOME/.bin" >> $GITHUB_PATH

3
.gitignore vendored
View File

@@ -72,9 +72,6 @@ ipch/
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
#.NET
.dotnet-home/
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*

View File

@@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.12" /> <PackageVersion Include="Avalonia" Version="11.3.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.12" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" /> <PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.12" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.4" /> <PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.4" /> <PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" /> <PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" /> <PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
@@ -22,7 +22,7 @@
<PackageVersion Include="Concentus" Version="2.2.2" /> <PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" /> <PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.1" /> <PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.0" /> <PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
<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" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
@@ -41,10 +41,10 @@
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
<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.129" /> <PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.126" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" /> <PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" /> <PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.8.0.1" /> <PackageVersion Include="Gommon" Version="2.8.0.4" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" /> <PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.11.1" /> <PackageVersion Include="Sep" Version="0.11.1" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
@@ -56,6 +56,7 @@
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" /> <PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
<PackageVersion Include="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -47,8 +47,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Apple", "src\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj", "{AC26EFF0-8593-4184-9A09-98E37EFFB32E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
@@ -571,8 +569,6 @@ Global
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU {D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,12 @@
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Start RenderDoc Frame Capture", "en_US": "Start RenderDoc Frame Capture",
"es_ES": "Iniciar una captura de fotograma de RenderDoc", "es_ES": "",
"fr_FR": "Démarrer une capture de trame RenderDoc", "fr_FR": "",
"he_IL": "", "he_IL": "",
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 시작", "ko_KR": "",
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
@@ -21,7 +21,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "启动 RenderDoc 帧捕获", "zh_CN": "",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -32,12 +32,12 @@
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "End RenderDoc Frame Capture", "en_US": "End RenderDoc Frame Capture",
"es_ES": "Detener la captura de fotograma de RenderDoc", "es_ES": "",
"fr_FR": "Arrêter la capture de trame RenderDoc", "fr_FR": "",
"he_IL": "", "he_IL": "",
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 종료", "ko_KR": "",
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
@@ -46,7 +46,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "结束 RenderDoc 帧捕获", "zh_CN": "",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -57,12 +57,12 @@
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Discard RenderDoc Frame Capture", "en_US": "Discard RenderDoc Frame Capture",
"es_ES": "Descartar la captura de fotograma de RenderDoc", "es_ES": "",
"fr_FR": "Supprimer la capture de trame RenderDoc", "fr_FR": "",
"he_IL": "", "he_IL": "",
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 폐기", "ko_KR": "",
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
@@ -71,7 +71,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "丢弃 RenderDoc 帧捕获", "zh_CN": "",
"zh_TW": "" "zh_TW": ""
} }
}, },
@@ -82,12 +82,12 @@
"de_DE": "", "de_DE": "",
"el_GR": "", "el_GR": "",
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.", "en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.", "es_ES": "",
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.", "fr_FR": "",
"he_IL": "", "he_IL": "",
"it_IT": "", "it_IT": "",
"ja_JP": "", "ja_JP": "",
"ko_KR": "현재 활성화된 RenderDoc 프레임 캡처를 종료하고 결과를 즉시 폐기합니다.", "ko_KR": "",
"no_NO": "", "no_NO": "",
"pl_PL": "", "pl_PL": "",
"pt_BR": "", "pt_BR": "",
@@ -96,7 +96,7 @@
"th_TH": "", "th_TH": "",
"tr_TR": "", "tr_TR": "",
"uk_UA": "", "uk_UA": "",
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。", "zh_CN": "",
"zh_TW": "" "zh_TW": ""
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -2050,9 +2050,7 @@
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26 010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29 0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07 0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
010057D00ECE4000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00 0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
010037A0170D2000,"NINTENDO 64™ Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06 0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07 0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11 01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
@@ -2640,7 +2638,6 @@
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33 0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46 0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39 0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07 0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26 01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34 01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
@@ -3310,7 +3307,6 @@
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02 0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27 0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29 01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
0100BFC01D976000,"Virtual Boy Nintendo Classics",services,nothing,2026-02-17 11:26:59
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59 0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46 0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22 0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
1 title_id game_name labels status last_updated
2050 010003C00B868000 Ninjin: Clash of Carrots online-broken playable 2024-07-10 05:12:26
2051 0100746010E4C000 NinNinDays playable 2022-11-20 15:17:29
2052 0100C9A00ECE6000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
010057D00ECE4000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2053 0100e0601c632000 Nintendo 64™ – Nintendo Switch Online: MATURE 17+ ingame 2025-02-03 22:27:00
010037A0170D2000 NINTENDO 64™ – Nintendo Switch Online 18+ ingame 2025-02-03 22:27:00
2054 0100D870045B6000 Nintendo Entertainment System™ - Nintendo Switch Online online playable 2022-07-01 15:45:06
2055 0100C4B0034B2000 Nintendo Labo Toy-Con 01 Variety Kit gpu ingame 2022-08-07 12:56:07
2056 01001E9003502000 Nintendo Labo Toy-Con 03 Vehicle Kit services;crash menus 2022-08-03 17:20:11
2638 0100B16009C10000 SINNER: Sacrifice for Redemption nvdec;UE4;vulkan-backend-bug playable 2022-08-12 20:37:33
2639 0100E9201410E000 Sir Lovelot playable 2021-04-05 16:21:46
2640 0100134011E32000 Skate City playable 2022-11-04 11:37:39
0100a8501b66e000 Skateboard Drifting with Maxwell Cat: The Game Simulator playable 2026-02-17 19:05:00
2641 0100B2F008BD8000 Skee-Ball playable 2020-11-16 04:44:07
2642 01001A900F862000 Skelattack playable 2021-06-09 15:26:26
2643 01008E700F952000 Skelittle: A Giant Party! playable 2021-06-09 19:08:34
3307 0100AFA011068000 Voxel Pirates playable 2022-09-28 22:55:02
3308 0100BFB00D1F4000 Voxel Sword playable 2022-08-30 14:57:27
3309 01004E90028A2000 Vroom in the night sky Needs Update;vulkan-backend-bug playable 2023-02-20 02:32:29
0100BFC01D976000 Virtual Boy – Nintendo Classics services nothing 2026-02-17 11:26:59
3310 0100C7C00AE6C000 VSR: Void Space Racing playable 2021-01-27 14:08:59
3311 0100B130119D0000 Waifu Uncovered crash ingame 2023-02-27 01:17:46
3312 0100E29010A4A000 Wanba Warriors playable 2020-10-04 17:56:22

View File

@@ -168,7 +168,7 @@ namespace ARMeilleure.Common
{ {
_allocated.Dispose(); _allocated.Dispose();
foreach (nint page in _pages.Values) foreach (IntPtr page in _pages.Values)
{ {
NativeAllocator.Instance.Free((void*)page); NativeAllocator.Instance.Free((void*)page);
} }

View File

@@ -1,16 +0,0 @@
namespace Ryujinx.Audio.Backends.Apple
{
class AppleAudioBuffer
{
public readonly ulong DriverIdentifier;
public readonly ulong SampleCount;
public ulong SamplePlayed;
public AppleAudioBuffer(ulong driverIdentifier, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
SampleCount = sampleCount;
SamplePlayed = 0;
}
}
}

View File

@@ -1,196 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.Versioning;
using Ryujinx.Audio.Backends.Apple.Native;
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.Apple
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
public sealed class AppleHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<AppleHardwareDeviceSession, byte> _sessions;
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
public AppleHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<AppleHardwareDeviceSession, byte>();
_supportSurroundConfiguration = TestSurroundSupport();
Volume = 1f;
}
private bool TestSurroundSupport()
{
try
{
AudioStreamBasicDescription format =
GetAudioFormat(SampleFormat.PcmFloat, Constants.TargetSampleRate, 6);
int result = AudioQueueNewOutput(
ref format,
nint.Zero,
nint.Zero,
nint.Zero,
nint.Zero,
0,
out nint testQueue);
if (result == 0)
{
AudioChannelLayout layout = new AudioChannelLayout
{
AudioChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A,
AudioChannelBitmap = 0,
NumberChannelDescriptions = 0
};
int layoutResult = AudioQueueSetProperty(
testQueue,
kAudioQueueProperty_ChannelLayout,
ref layout,
(uint)Marshal.SizeOf<AudioChannelLayout>());
if (layoutResult == 0)
{
AudioQueueDispose(testQueue, true);
return true;
}
AudioQueueDispose(testQueue, true);
}
return false;
}
catch
{
return false;
}
}
public static bool IsSupported => OperatingSystem.IsMacOSVersionAtLeast(10, 5);
public ManualResetEvent GetUpdateRequiredEvent()
=> _updateRequiredEvent;
public ManualResetEvent GetPauseEvent()
=> _pauseEvent;
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager,
SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
channelCount = 2;
}
if (sampleRate == 0)
{
sampleRate = Constants.TargetSampleRate;
}
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on Apple backend!");
}
AppleHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);
return session;
}
internal bool Unregister(AppleHardwareDeviceSession session)
=> _sessions.TryRemove(session, out _);
internal static AudioStreamBasicDescription GetAudioFormat(SampleFormat sampleFormat, uint sampleRate,
uint channelCount)
{
uint formatFlags;
uint bitsPerChannel;
switch (sampleFormat)
{
case SampleFormat.PcmInt8:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 8;
break;
case SampleFormat.PcmInt16:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 16;
break;
case SampleFormat.PcmInt32:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 32;
break;
case SampleFormat.PcmFloat:
formatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
bitsPerChannel = 32;
break;
default:
throw new ArgumentException($"Unsupported sample format {sampleFormat}");
}
uint bytesPerFrame = (bitsPerChannel / 8) * channelCount;
return new AudioStreamBasicDescription
{
SampleRate = sampleRate,
FormatID = kAudioFormatLinearPCM,
FormatFlags = formatFlags,
BytesPerPacket = bytesPerFrame,
FramesPerPacket = 1,
BytesPerFrame = bytesPerFrame,
ChannelsPerFrame = channelCount,
BitsPerChannel = bitsPerChannel,
Reserved = 0
};
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
foreach (AppleHardwareDeviceSession session in _sessions.Keys)
{
session.Dispose();
}
_pauseEvent.Dispose();
}
}
public bool SupportsDirection(Direction direction)
=> direction != Direction.Input;
public bool SupportsSampleRate(uint sampleRate) => true;
public bool SupportsSampleFormat(SampleFormat sampleFormat)
=> sampleFormat != SampleFormat.PcmInt24;
public bool SupportsChannelCount(uint channelCount)
=> channelCount != 6 || _supportSurroundConfiguration;
}
}

View File

@@ -1,285 +0,0 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.Versioning;
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
namespace Ryujinx.Audio.Backends.Apple
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
class AppleHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private const int NumBuffers = 3;
private readonly AppleHardwareDeviceDriver _driver;
private readonly ConcurrentQueue<AppleAudioBuffer> _queuedBuffers = new();
private readonly DynamicRingBuffer _ringBuffer = new();
private readonly ManualResetEvent _updateRequiredEvent;
private readonly AudioQueueOutputCallback _callbackDelegate;
private readonly GCHandle _gcHandle;
private nint _audioQueue;
private readonly nint[] _audioQueueBuffers = new nint[NumBuffers];
private readonly int[] _bufferBytesFilled = new int[NumBuffers];
private readonly int _bytesPerFrame;
private ulong _playedSampleCount;
private bool _started;
private float _volume = 1f;
private readonly object _lock = new();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void AudioQueueOutputCallback(
nint userData,
nint audioQueue,
nint buffer);
public AppleHardwareDeviceSession(
AppleHardwareDeviceDriver driver,
IVirtualMemoryManager memoryManager,
SampleFormat requestedSampleFormat,
uint requestedSampleRate,
uint requestedChannelCount)
: base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = driver.GetUpdateRequiredEvent();
_callbackDelegate = OutputCallback;
_bytesPerFrame = BackendHelper.GetSampleSize(requestedSampleFormat) * (int)requestedChannelCount;
_gcHandle = GCHandle.Alloc(this, GCHandleType.Normal);
SetupAudioQueue();
}
private void SetupAudioQueue()
{
lock (_lock)
{
AudioStreamBasicDescription format = AppleHardwareDeviceDriver.GetAudioFormat(
RequestedSampleFormat,
RequestedSampleRate,
RequestedChannelCount);
nint callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackDelegate);
nint userData = GCHandle.ToIntPtr(_gcHandle);
int result = AudioQueueNewOutput(
ref format,
callbackPtr,
userData,
nint.Zero,
nint.Zero,
0,
out _audioQueue);
if (result != 0)
{
throw new InvalidOperationException($"AudioQueueNewOutput failed: {result}");
}
uint framesPerBuffer = RequestedSampleRate / 100;
uint bufferSize = framesPerBuffer * (uint)_bytesPerFrame;
for (int i = 0; i < NumBuffers; i++)
{
AudioQueueAllocateBuffer(_audioQueue, bufferSize, out _audioQueueBuffers[i]);
_bufferBytesFilled[i] = 0;
PrimeBuffer(_audioQueueBuffers[i], i);
}
}
}
private unsafe void PrimeBuffer(nint bufferPtr, int bufferIndex)
{
AudioQueueBuffer* buffer = (AudioQueueBuffer*)bufferPtr;
int capacityBytes = (int)buffer->AudioDataBytesCapacity;
int framesPerBuffer = capacityBytes / _bytesPerFrame;
int availableFrames = _ringBuffer.Length / _bytesPerFrame;
int framesToRead = Math.Min(availableFrames, framesPerBuffer);
int bytesToRead = framesToRead * _bytesPerFrame;
Span<byte> dst = new((void*)buffer->AudioData, capacityBytes);
dst.Clear();
if (bytesToRead > 0)
{
Span<byte> audio = dst.Slice(0, bytesToRead);
_ringBuffer.Read(audio, 0, bytesToRead);
ApplyVolume(buffer->AudioData, bytesToRead);
}
buffer->AudioDataByteSize = (uint)capacityBytes;
_bufferBytesFilled[bufferIndex] = bytesToRead;
AudioQueueEnqueueBuffer(_audioQueue, bufferPtr, 0, nint.Zero);
}
private void OutputCallback(nint userData, nint audioQueue, nint bufferPtr)
{
if (!_started || bufferPtr == nint.Zero)
return;
int bufferIndex = Array.IndexOf(_audioQueueBuffers, bufferPtr);
if (bufferIndex < 0)
return;
int bytesPlayed = _bufferBytesFilled[bufferIndex];
if (bytesPlayed > 0)
{
ProcessPlayedSamples(bytesPlayed);
}
PrimeBuffer(bufferPtr, bufferIndex);
}
private void ProcessPlayedSamples(int bytesPlayed)
{
ulong samplesPlayed = GetSampleCount(bytesPlayed);
ulong remaining = samplesPlayed;
bool needUpdate = false;
while (remaining > 0 && _queuedBuffers.TryPeek(out AppleAudioBuffer buffer))
{
ulong needed = buffer.SampleCount - Interlocked.Read(ref buffer.SamplePlayed);
ulong take = Math.Min(needed, remaining);
ulong played = Interlocked.Add(ref buffer.SamplePlayed, take);
remaining -= take;
if (played == buffer.SampleCount)
{
_queuedBuffers.TryDequeue(out _);
needUpdate = true;
}
Interlocked.Add(ref _playedSampleCount, take);
}
if (needUpdate)
{
_updateRequiredEvent.Set();
}
}
private unsafe void ApplyVolume(nint dataPtr, int byteSize)
{
float volume = Math.Clamp(_volume * _driver.Volume, 0f, 1f);
if (volume >= 0.999f)
return;
int sampleCount = byteSize / BackendHelper.GetSampleSize(RequestedSampleFormat);
switch (RequestedSampleFormat)
{
case SampleFormat.PcmInt16:
short* s16 = (short*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s16[i] = (short)(s16[i] * volume);
break;
case SampleFormat.PcmFloat:
float* f32 = (float*)dataPtr;
for (int i = 0; i < sampleCount; i++)
f32[i] *= volume;
break;
case SampleFormat.PcmInt32:
int* s32 = (int*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s32[i] = (int)(s32[i] * volume);
break;
case SampleFormat.PcmInt8:
sbyte* s8 = (sbyte*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s8[i] = (sbyte)(s8[i] * volume);
break;
}
}
public override void QueueBuffer(AudioBuffer buffer)
{
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_queuedBuffers.Enqueue(new AppleAudioBuffer(buffer.DataPointer, GetSampleCount(buffer)));
}
public override void Start()
{
lock (_lock)
{
if (_started)
return;
_started = true;
AudioQueueStart(_audioQueue, nint.Zero);
}
}
public override void Stop()
{
lock (_lock)
{
if (!_started)
return;
_started = false;
AudioQueuePause(_audioQueue);
}
}
public override ulong GetPlayedSampleCount()
=> Interlocked.Read(ref _playedSampleCount);
public override float GetVolume() => _volume;
public override void SetVolume(float volume) => _volume = volume;
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
if (!_queuedBuffers.TryPeek(out AppleAudioBuffer driverBuffer))
return true;
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
public override void PrepareToClose() { }
public override void UnregisterBuffer(AudioBuffer buffer) { }
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Stop();
if (_audioQueue != nint.Zero)
{
AudioQueueStop(_audioQueue, true);
AudioQueueDispose(_audioQueue, true);
_audioQueue = nint.Zero;
}
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,102 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Audio.Backends.Apple.Native
{
public static partial class AudioToolbox
{
[StructLayout(LayoutKind.Sequential)]
internal struct AudioStreamBasicDescription
{
public double SampleRate;
public uint FormatID;
public uint FormatFlags;
public uint BytesPerPacket;
public uint FramesPerPacket;
public uint BytesPerFrame;
public uint ChannelsPerFrame;
public uint BitsPerChannel;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AudioChannelLayout
{
public uint AudioChannelLayoutTag;
public uint AudioChannelBitmap;
public uint NumberChannelDescriptions;
}
internal const uint kAudioFormatLinearPCM = 0x6C70636D;
internal const uint kAudioQueueProperty_ChannelLayout = 0x6171636c;
internal const uint kAudioChannelLayoutTag_MPEG_5_1_A = 0x650006;
internal const uint kAudioFormatFlagIsFloat = (1 << 0);
internal const uint kAudioFormatFlagIsSignedInteger = (1 << 2);
internal const uint kAudioFormatFlagIsPacked = (1 << 3);
internal const uint kAudioFormatFlagIsBigEndian = (1 << 1);
internal const uint kAudioFormatFlagIsAlignedHigh = (1 << 4);
internal const uint kAudioFormatFlagIsNonInterleaved = (1 << 5);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueNewOutput(
ref AudioStreamBasicDescription format,
nint callback,
nint userData,
nint callbackRunLoop,
nint callbackRunLoopMode,
uint flags,
out nint audioQueue);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueSetProperty(
nint audioQueue,
uint propertyID,
ref AudioChannelLayout layout,
uint layoutSize);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueDispose(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueAllocateBuffer(
nint audioQueue,
uint bufferByteSize,
out nint buffer);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueStart(nint audioQueue, nint startTime);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueuePause(nint audioQueue);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueStop(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueSetParameter(
nint audioQueue,
uint parameterID,
float value);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueEnqueueBuffer(
nint audioQueue,
nint buffer,
uint numPacketDescs,
nint packetDescs);
[StructLayout(LayoutKind.Sequential)]
internal struct AudioQueueBuffer
{
public uint AudioDataBytesCapacity;
public nint AudioData;
public uint AudioDataByteSize;
public nint UserData;
public uint PacketDescriptionCapacity;
public nint PacketDescriptions;
public uint PacketDescriptionCount;
}
internal const uint kAudioQueueParam_Volume = 1;
}
}

View File

@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,8 +10,7 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.OpenAL namespace Ryujinx.Audio.Backends.OpenAL
{ {
// ReSharper disable once InconsistentNaming public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
public sealed class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
{ {
private readonly ALDevice _device; private readonly ALDevice _device;
private readonly ALContext _context; private readonly ALContext _context;
@@ -149,7 +148,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
Dispose(true); Dispose(true);
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {

View File

@@ -9,8 +9,7 @@ using System.Threading;
namespace Ryujinx.Audio.Backends.OpenAL namespace Ryujinx.Audio.Backends.OpenAL
{ {
// ReSharper disable once InconsistentNaming class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
sealed class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
{ {
private readonly OpenALHardwareDeviceDriver _driver; private readonly OpenALHardwareDeviceDriver _driver;
private readonly int _sourceId; private readonly int _sourceId;
@@ -191,7 +190,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
} }
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing && _driver.Unregister(this)) if (disposing && _driver.Unregister(this))
{ {

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Backends.SDL3
using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl]<nint, SDL_AudioStream*, int, int, void>; using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl]<nint, SDL_AudioStream*, int, int, void>;
public sealed class SDL3HardwareDeviceDriver : IHardwareDeviceDriver public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
{ {
private readonly ManualResetEvent _updateRequiredEvent; private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent; private readonly ManualResetEvent _pauseEvent;
@@ -162,7 +162,7 @@ namespace Ryujinx.Audio.Backends.SDL3
Dispose(true); Dispose(true);
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {

View File

@@ -12,7 +12,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Backends.SDL3 namespace Ryujinx.Audio.Backends.SDL3
{ {
sealed unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
{ {
private readonly SDL3HardwareDeviceDriver _driver; private readonly SDL3HardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers; private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
@@ -223,7 +226,7 @@ namespace Ryujinx.Audio.Backends.SDL3
return driverBuffer.DriverIdentifier != buffer.DataPointer; return driverBuffer.DriverIdentifier != buffer.DataPointer;
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing && _driver.Unregister(this)) if (disposing && _driver.Unregister(this))
{ {

View File

@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
unsafe unsafe
{ {
int* frameCountPtr = &nativeFrameCount; int* frameCountPtr = &nativeFrameCount;
nint* arenasPtr = &arenas; IntPtr* arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr)); CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
frameCount = *frameCountPtr; frameCount = *frameCountPtr;

View File

@@ -10,7 +10,7 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.SoundIo namespace Ryujinx.Audio.Backends.SoundIo
{ {
public sealed class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
{ {
private readonly SoundIoContext _audioContext; private readonly SoundIoContext _audioContext;
private readonly SoundIoDeviceContext _audioDevice; private readonly SoundIoDeviceContext _audioDevice;
@@ -227,7 +227,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
} }
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {

View File

@@ -11,7 +11,7 @@ using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo namespace Ryujinx.Audio.Backends.SoundIo
{ {
sealed class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
{ {
private readonly SoundIoHardwareDeviceDriver _driver; private readonly SoundIoHardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers; private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
@@ -428,7 +428,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
} }
} }
private void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing && _driver.Unregister(this)) if (disposing && _driver.Unregister(this))
{ {

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Common
public uint MixesSize; public uint MixesSize;
public uint SinksSize; public uint SinksSize;
public uint PerformanceBufferSize; public uint PerformanceBufferSize;
public uint SplitterSize; public uint Unknown24;
public uint RenderInfoSize; public uint RenderInfoSize;
#pragma warning disable IDE0051, CS0169 // Remove unused field #pragma warning disable IDE0051, CS0169 // Remove unused field

View File

@@ -433,12 +433,8 @@ namespace Ryujinx.Audio.Renderer.Server
public ResultCode UpdateSplitter(SplitterContext context) public ResultCode UpdateSplitter(SplitterContext context)
{ {
long initialInputConsumed = _inputReader.Consumed;
if (context.Update(ref _inputReader)) if (context.Update(ref _inputReader))
{ {
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SplitterSize);
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
{ {
locale.Translations[langCode] = string.Empty; locale.Translations[langCode] = string.Empty;
Console.WriteLine( Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it..."); $"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
} }
else else
{ {
Console.WriteLine( Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!"); $"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
} }
} }

View File

@@ -1,4 +1,4 @@
namespace Ryujinx.Common.Configuration.Hid.Keyboard namespace Ryujinx.Common.Configuration.Hid.Keyboard
{ {
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<PhysicalKey> { } public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
} }

View File

@@ -1,142 +0,0 @@
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonStringEnumConverter<PhysicalKey>))]
public enum PhysicalKey
{
Unknown,
ShiftLeft,
ShiftRight,
ControlLeft,
ControlRight,
AltLeft,
AltRight,
WinLeft,
WinRight,
Menu,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
F25,
F26,
F27,
F28,
F29,
F30,
F31,
F32,
F33,
F34,
F35,
Up,
Down,
Left,
Right,
Enter,
Escape,
Space,
Tab,
BackSpace,
Insert,
Delete,
PageUp,
PageDown,
Home,
End,
CapsLock,
ScrollLock,
PrintScreen,
Pause,
NumLock,
Clear,
Keypad0,
Keypad1,
Keypad2,
Keypad3,
Keypad4,
Keypad5,
Keypad6,
Keypad7,
Keypad8,
Keypad9,
KeypadDivide,
KeypadMultiply,
KeypadSubtract,
KeypadAdd,
KeypadDecimal,
KeypadEnter,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
Number0,
Number1,
Number2,
Number3,
Number4,
Number5,
Number6,
Number7,
Number8,
Number9,
Tilde,
Grave,
Minus,
Plus,
BracketLeft,
BracketRight,
Semicolon,
Quote,
Comma,
Period,
Slash,
BackSlash,
Unbound,
Count,
}
}

View File

@@ -12,6 +12,8 @@ namespace Ryujinx.Common.Logging
{ {
public static class Logger public static class Logger
{ {
public static readonly TextWriter WriterProxy = new TextWriterProxy();
private static readonly Stopwatch _time; private static readonly Stopwatch _time;
private static readonly bool[] _enabledClasses; private static readonly bool[] _enabledClasses;

View File

@@ -0,0 +1,21 @@
using System;
using System.IO;
using System.Text;
namespace Ryujinx.Common.Logging
{
internal class TextWriterProxy : TextWriter
{
public override Encoding Encoding => Console.OutputEncoding;
public override void Write(string value)
{
if (value is null) return;
foreach (var line in value.Split(Console.Out.NewLine))
{
Logger.Info?.PrintMsg(LogClass.Application, line);
}
}
}
}

View File

@@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" /> <PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" />
<PackageReference Include="Humanizer" /> <PackageReference Include="Humanizer" />
<PackageReference Include="Gommon" /> <PackageReference Include="Gommon" />
</ItemGroup> </ItemGroup>

View File

@@ -184,7 +184,6 @@ namespace Ryujinx.Common
"01001b300b9be000", // Diablo III: Eternal Collection "01001b300b9be000", // Diablo III: Eternal Collection
"010027400cdc6000", // Divinity Original 2 - Definitive Edition "010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition "01008c8012920000", // Dying Light Platinum Edition
"0100d11013e6a000", // Eschatos
"01001cc01b2d4000", // Goat Simulator 3 "01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2 "01003620068ea000", // Hand of Fate 2
"0100f7e00c70e000", // Hogwarts Legacy "0100f7e00c70e000", // Hogwarts Legacy
@@ -194,15 +193,9 @@ namespace Ryujinx.Common
"0100d71004694000", // Minecraft "0100d71004694000", // Minecraft
"01007430037f6000", // Monopoly "01007430037f6000", // Monopoly
"0100853015e86000", // No Man's Sky "0100853015e86000", // No Man's Sky
"0100f85014ed0000", // No More Heroes
"0100463014ed4000", // No More Heroes 2
"0100e570094e8000", // Owlboy
"01007bb017812000", // Portal "01007bb017812000", // Portal
"0100abd01785c000", // Portal 2 "0100abd01785c000", // Portal 2
"01009f100bc52000", // Psikyo Collection 1
"01009d400c4a8000", // Psikyo Collection 2
"01008e200c5c2000", // Muse Dash "01008e200c5c2000", // Muse Dash
"01005ff002e2a000", // Rayman Legends
"01007820196a6000", // Red Dead Redemption "01007820196a6000", // Red Dead Redemption
"0100e8300a67a000", // Risk "0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5 "01002f7013224000", // Rune Factory 5

View File

@@ -22,11 +22,10 @@ namespace Ryujinx.Common.Utilities
} }
// "dumpable" attribute of the calling process // "dumpable" attribute of the calling process
private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4; private const int PR_SET_DUMPABLE = 4;
[LibraryImport("libc", SetLastError = true)] [DllImport("libc", SetLastError = true)]
private static partial int prctl(int option, int arg2); private static extern int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable) public static void SetCoreDumpable(bool dumpable)
{ {
@@ -37,13 +36,5 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0); Debug.Assert(result == 0);
} }
} }
// Use the below line to display dumpable status in the console:
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
public static bool IsCoreDumpable()
{
int result = prctl(PR_GET_DUMPABLE, 0);
return result == 1;
}
} }
} }

View File

@@ -30,9 +30,9 @@ namespace ARMeilleure.Common
/// <summary> /// <summary>
/// Base address for the page. /// Base address for the page.
/// </summary> /// </summary>
public readonly nint Address; public readonly IntPtr Address;
public AddressTablePage(bool isSparse, nint address) public AddressTablePage(bool isSparse, IntPtr address)
{ {
IsSparse = isSparse; IsSparse = isSparse;
Address = address; Address = address;
@@ -47,20 +47,20 @@ namespace ARMeilleure.Common
public readonly SparseMemoryBlock Block; public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent; private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit) public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit)
{ {
SparseMemoryBlock block = new(size, pageInit, null); SparseMemoryBlock block = new(size, pageInit, null);
_trackingEvent = (address, size, write) => _trackingEvent = (address, size, write) =>
{ {
ulong pointer = (ulong)block.Block.Pointer + address; ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((nint)pointer); ensureMapped((IntPtr)pointer);
return pointer; return pointer;
}; };
bool added = NativeSignalHandler.AddTrackedRegion( bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer, (nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (nint)block.Block.Size), (nuint)(block.Block.Pointer + (IntPtr)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent)); Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added) if (!added)
@@ -116,7 +116,7 @@ namespace ARMeilleure.Common
} }
/// <inheritdoc/> /// <inheritdoc/>
public nint Base public IntPtr Base
{ {
get get
{ {
@@ -124,7 +124,7 @@ namespace ARMeilleure.Common
lock (_pages) lock (_pages)
{ {
return (nint)GetRootPage(); return (IntPtr)GetRootPage();
} }
} }
} }
@@ -240,7 +240,7 @@ namespace ARMeilleure.Common
long index = Levels[^1].GetValue(address); long index = Levels[^1].GetValue(address);
EnsureMapped((nint)(page + index)); EnsureMapped((IntPtr)(page + index));
return ref page[index]; return ref page[index];
} }
@@ -284,7 +284,7 @@ namespace ARMeilleure.Common
/// Ensure the given pointer is mapped in any overlapping sparse reservations. /// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary> /// </summary>
/// <param name="ptr">Pointer to be mapped</param> /// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(nint ptr) private void EnsureMapped(IntPtr ptr)
{ {
if (Sparse) if (Sparse)
{ {
@@ -299,7 +299,7 @@ namespace ARMeilleure.Common
{ {
SparseMemoryBlock sparse = reserved.Block; SparseMemoryBlock sparse = reserved.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size) if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
{ {
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer)); sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
@@ -319,15 +319,15 @@ namespace ARMeilleure.Common
/// </summary> /// </summary>
/// <param name="level">Level to get the fill value for</param> /// <param name="level">Level to get the fill value for</param>
/// <returns>The fill value</returns> /// <returns>The fill value</returns>
private nint GetFillValue(int level) private IntPtr GetFillValue(int level)
{ {
if (_fillBottomLevel != null && level == Levels.Length - 2) if (_fillBottomLevel != null && level == Levels.Length - 2)
{ {
return (nint)_fillBottomLevelPtr; return (IntPtr)_fillBottomLevelPtr;
} }
else else
{ {
return nint.Zero; return IntPtr.Zero;
} }
} }
@@ -379,7 +379,7 @@ namespace ARMeilleure.Common
/// <param name="fill">Fill value</param> /// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param> /// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns> /// <returns>Allocated block</returns>
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
{ {
int size = sizeof(T) * length; int size = sizeof(T) * length;
@@ -405,7 +405,7 @@ namespace ARMeilleure.Common
} }
} }
page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset); page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset);
_sparseReservedOffset += (ulong)size; _sparseReservedOffset += (ulong)size;
@@ -413,7 +413,7 @@ namespace ARMeilleure.Common
} }
else else
{ {
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size); IntPtr address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
page = new AddressTablePage(false, address); page = new AddressTablePage(false, address);
Span<T> span = new((void*)page.Address, length); Span<T> span = new((void*)page.Address, length);

View File

@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel; bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
nint hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0; IntPtr hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size)) if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
{ {

View File

@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Vulkan.MoltenVK
public static void Initialize() public static void Initialize()
{ {
nint configSize = (nint)Marshal.SizeOf<MVKConfiguration>(); IntPtr configSize = (nint)Marshal.SizeOf<MVKConfiguration>();
vkGetMoltenVKConfigurationMVK(nint.Zero, out MVKConfiguration config, configSize); vkGetMoltenVKConfigurationMVK(nint.Zero, out MVKConfiguration config, configSize);

View File

@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray(); enabledExtensions = enabledExtensions.Append(ExtDebugUtils.ExtensionName).ToArray();
} }
nint appName = Marshal.StringToHGlobalAnsi(AppName); IntPtr appName = Marshal.StringToHGlobalAnsi(AppName);
ApplicationInfo applicationInfo = new() ApplicationInfo applicationInfo = new()
{ {
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.Vulkan
internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api) internal static DeviceInfo[] GetSuitablePhysicalDevices(Vk api)
{ {
nint appName = Marshal.StringToHGlobalAnsi(AppName); IntPtr appName = Marshal.StringToHGlobalAnsi(AppName);
ApplicationInfo applicationInfo = new() ApplicationInfo applicationInfo = new()
{ {

View File

@@ -488,8 +488,6 @@ namespace Ryujinx.HLE.FileSystem
if (keyPaths.Length is 0) if (keyPaths.Length is 0)
throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files."); throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files.");
List<string> failedFiles = new();
foreach (string filePath in keyPaths) foreach (string filePath in keyPaths)
{ {
try try
@@ -499,18 +497,15 @@ namespace Ryujinx.HLE.FileSystem
catch (Exception e) catch (Exception e)
{ {
Logger.Error?.Print(LogClass.Application, e.Message); Logger.Error?.Print(LogClass.Application, e.Message);
failedFiles.Add(Path.GetFileName(filePath));
continue; continue;
} }
string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath)); string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath));
File.Copy(filePath, destPath, true); if (File.Exists(destPath))
} File.Delete(destPath);
if (failedFiles.Count > 0) File.Copy(filePath, destPath, true);
{
throw new InvalidOperationException($"Failed to install the following key files: {string.Join(", ", failedFiles)}");
} }
return; return;
@@ -523,6 +518,8 @@ namespace Ryujinx.HLE.FileSystem
FileInfo info = new(keysSource); FileInfo info = new(keysSource);
using FileStream file = File.OpenRead(keysSource);
if (info.Extension is not ".keys") if (info.Extension is not ".keys")
throw new InvalidFirmwarePackageException("Input file extension is not .keys"); throw new InvalidFirmwarePackageException("Input file extension is not .keys");
@@ -537,6 +534,10 @@ namespace Ryujinx.HLE.FileSystem
string dest = Path.Combine(installDirectory, info.Name); string dest = Path.Combine(installDirectory, info.Name);
if (File.Exists(dest))
File.Delete(dest);
// overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189
File.Copy(keysSource, dest, true); File.Copy(keysSource, dest, true);
} }
@@ -1058,7 +1059,7 @@ namespace Ryujinx.HLE.FileSystem
} }
} }
public static bool AreKeysAlreadyPresent(string pathToCheck) public static bool AreKeysAlredyPresent(string pathToCheck)
{ {
string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"]; string[] fileNames = ["prod.keys", "title.keys", "console.keys", "dev.keys"];
foreach (string file in fileNames) foreach (string file in fileNames)

View File

@@ -20,7 +20,6 @@ using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv;
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
using Ryujinx.HLE.HOS.Services.Pcv.Bpc; using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
@@ -67,8 +66,6 @@ namespace Ryujinx.HLE.HOS
internal List<NfpDevice> NfpDevices { get; private set; } internal List<NfpDevice> NfpDevices { get; private set; }
internal List<NfcDevice> NfcDevices { get; private set; }
internal SmRegistry SmRegistry { get; private set; } internal SmRegistry SmRegistry { get; private set; }
internal ServerBase SmServer { get; private set; } internal ServerBase SmServer { get; private set; }
@@ -135,7 +132,6 @@ namespace Ryujinx.HLE.HOS
PerformanceState = new PerformanceState(); PerformanceState = new PerformanceState();
NfpDevices = []; NfpDevices = [];
NfcDevices = [];
// Note: This is not really correct, but with HLE of services, the only memory // Note: This is not really correct, but with HLE of services, the only memory
// region used that is used is Application, so we can use the other ones for anything. // region used that is used is Application, so we can use the other ones for anything.
@@ -246,21 +242,21 @@ namespace Ryujinx.HLE.HOS
public void InitializeServices() public void InitializeServices()
{ {
SmRegistry = new SmRegistry(); SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "Sm", () => new IUserInterface(KernelContext, SmRegistry)); SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
// Wait until SM server thread is done with initialization, // Wait until SM server thread is done with initialization,
// only then doing connections to SM is safe. // only then doing connections to SM is safe.
SmServer.InitDone.WaitOne(); SmServer.InitDone.WaitOne();
BsdServer = new ServerBase(KernelContext, "Bsd"); BsdServer = new ServerBase(KernelContext, "BsdServer");
FsServer = new ServerBase(KernelContext, "Fs"); FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "Hid"); HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "Nv"); NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
TimeServer = new ServerBase(KernelContext, "Time"); TimeServer = new ServerBase(KernelContext, "TimeServer");
ViServer = new ServerBase(KernelContext, "Vi:u"); ViServer = new ServerBase(KernelContext, "ViServerU");
ViServerM = new ServerBase(KernelContext, "Vi:m"); ViServerM = new ServerBase(KernelContext, "ViServerM");
ViServerS = new ServerBase(KernelContext, "Vi:s"); ViServerS = new ServerBase(KernelContext, "ViServerS");
LdnServer = new ServerBase(KernelContext, "Ldn"); LdnServer = new ServerBase(KernelContext, "LdnServer");
StartNewServices(); StartNewServices();
} }
@@ -286,7 +282,7 @@ namespace Ryujinx.HLE.HOS
ProcessCreationFlags.Is64Bit | ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem; ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new(service.Name, 1, 0, 0x8000000, 1, Flags, 0, 0); ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
uint[] defaultCapabilities = uint[] defaultCapabilities =
[ [
@@ -376,15 +372,6 @@ namespace Ryujinx.HLE.HOS
} }
} }
public void ScanSkylander(int nfcDeviceId, byte[] data)
{
if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag)
{
NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound;
NfcDevices[nfcDeviceId].Data = data;
}
}
public bool SearchingForAmiibo(out int nfpDeviceId) public bool SearchingForAmiibo(out int nfpDeviceId)
{ {
nfpDeviceId = default; nfpDeviceId = default;
@@ -402,53 +389,6 @@ namespace Ryujinx.HLE.HOS
return false; return false;
} }
public bool SearchingForSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.SearchingForTag)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public bool HasSkylander(out int nfcDeviceId)
{
nfcDeviceId = default;
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
nfcDeviceId = i;
return true;
}
}
return false;
}
public void RemoveSkylander()
{
for (int i = 0; i < NfcDevices.Count; i++)
{
if (NfcDevices[i].State == NfcDeviceState.TagFound)
{
NfcDevices[i].State = NfcDeviceState.Initialized;
NfcDevices[i].SignalDeactivate();
Thread.Sleep(100); // NOTE: Simulate skylander scanning delay.
}
}
}
public void SignalDisplayResolutionChange() public void SignalDisplayResolutionChange()
{ {
DisplayResolutionChangeEvent.ReadableEvent.Signal(); DisplayResolutionChangeEvent.ReadableEvent.Signal();

View File

@@ -56,7 +56,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_activeCount = 0; _activeCount = 0;
JoyHold = NpadJoyHoldType.Vertical; JoyHold = NpadJoyHoldType.Vertical;
SixAxisActive = false;
} }
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player) internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
@@ -581,29 +580,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return needUpdateRight; return needUpdateRight;
} }
public bool isAtRest(int playerNumber)
{
ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[playerNumber].InternalState;
if (currentNpad.StyleSet == NpadStyleTag.None)
{
return true; // it will always be at rest because it cannot move.
}
ref SixAxisSensorState storage = ref GetSixAxisSensorLifo(ref currentNpad, false).GetCurrentEntryRef();
float acceleration = Math.Abs(storage.Acceleration.X)
+ Math.Abs(storage.Acceleration.Y)
+ Math.Abs(storage.Acceleration.Z);
float angularVelocity = Math.Abs(storage.AngularVelocity.X)
+ Math.Abs(storage.AngularVelocity.Y)
+ Math.Abs(storage.AngularVelocity.Z);
// TODO: check against config deadzone and add sensitivity setting
return ((acceleration <= 1.0F) && (angularVelocity <= 1.0F));
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index) private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{ {

View File

@@ -602,33 +602,19 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
[CommandCmif(82)] [CommandCmif(82)]
// IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAtRest // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
public ResultCode IsSixAxisSensorAtRest(ServiceCtx context) public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
{ {
int sixAxisSensorHandle = context.RequestData.ReadInt32(); int sixAxisSensorHandle = context.RequestData.ReadInt32();
// 4 byte struct w/ 4-byte alignment
// uint typeValue = (uint) sixAxisSensorHandle; // 0x0 0x4 TypeValue
// uint npadStyleIndex = (uint) sixAxisSensorHandle & 0xff; // 0x0 0x1 NpadStyleIndex
int playerNumber = (sixAxisSensorHandle << 8) & 0xff; // 0x1 0x1 PlayerNumber
// uint deviceIdx= ((uint) sixAxisSensorHandle << 16) & 0xff; // 0x2 0x1 DeviceIdx
// uint unknown = ((uint) sixAxisSensorHandle << 24) & 0xff;
// 32bit sign extension padding -> if = 0, + offset, else - offset
// npadStyleIndex = ((npadStyleIndex & 0x8000) == 0) ? npadStyleIndex | 0xFFFF0000 : npadStyleIndex & 0xFFFF0000;
// playerNumber = ((playerNumber & 0x8000) == 0) ? playerNumber | 0xFFFF0000 : playerNumber & 0xFFFF0000;
// deviceIdx = ((deviceIdx & 0x8000) == 0) ? deviceIdx | 0xFFFF0000 : deviceIdx & 0xFFFF0000;
// unknown = ((unknown & 0x8000) == 0) ? unknown | 0xFFFF0000 : unknown & 0xFFFF0000;
context.RequestData.BaseStream.Position += 4; // Padding context.RequestData.BaseStream.Position += 4; // Padding
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
// TODO: link to context.Device.Hid.Npads.SixAxisActive when properly implemented bool isAtRest = true;
// We currently do not support stopping or starting SixAxisTracking.
context.ResponseData.Write(isAtRest);
context.ResponseData.Write(context.Device.Hid.Npads.isAtRest(playerNumber));
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
return ResultCode.Success; return ResultCode.Success;
} }
@@ -643,7 +629,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor); context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor }); Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -5,7 +5,6 @@ using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Ldn.Types; using Ryujinx.HLE.HOS.Services.Ldn.Types;
@@ -15,7 +14,6 @@ using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
@@ -489,23 +487,6 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
return ResultCode.Success; return ResultCode.Success;
} }
[CommandCmif(106)] // 20.0.0+
// SetProtocol
public ResultCode SetProtocol(ServiceCtx context)
{
uint protocolValue = context.RequestData.ReadUInt32();
// On NX only input value 1 or 3 is allowed, with an error being thrown otherwise.
if (protocolValue != 1 && protocolValue != 3)
{
throw new ArgumentException($"{GetType().FullName}: Protocol value is not 1 or 3!! Protocol value: {protocolValue}");
}
Logger.Stub?.PrintStub(LogClass.ServiceLdn, $"Protocol value: {protocolValue}");
return ResultCode.Success;
}
[CommandCmif(200)] [CommandCmif(200)]
// OpenAccessPoint() // OpenAccessPoint()
public ResultCode OpenAccessPoint(ServiceCtx context) public ResultCode OpenAccessPoint(ServiceCtx context)

View File

@@ -1,19 +1,8 @@
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{ {
[Service("nfc:mf:u")] [Service("nfc:mf:u")]
class IUserManager : IpcService class IUserManager : IpcService
{ {
public IUserManager(ServiceCtx context) { } public IUserManager(ServiceCtx context) { }
[CommandCmif(0)]
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
public ResultCode CreateUserInterface(ServiceCtx context)
{
MakeObject(context, new IMifare());
return ResultCode.Success;
}
} }
} }

View File

@@ -1,477 +0,0 @@
using Ryujinx.Common.Memory;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.Horizon.Common;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class IMifare : IpcService
{
private State _state;
private KEvent _availabilityChangeEvent;
private CancellationTokenSource _cancelTokenSource;
public IMifare()
{
_state = State.NonInitialized;
}
[CommandCmif(0)]
public ResultCode Initialize(ServiceCtx context)
{
_state = State.Initialized;
NfcDevice devicePlayer1 = new()
{
NpadIdType = NpadIdType.Player1,
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
State = NfcDeviceState.Initialized,
};
context.Device.System.NfcDevices.Add(devicePlayer1);
return ResultCode.Success;
}
[CommandCmif(1)]
public ResultCode Finalize(ServiceCtx context)
{
if (_state == State.Initialized)
{
_cancelTokenSource?.Cancel();
// NOTE: All events are destroyed here.
context.Device.System.NfcDevices.Clear();
_state = State.NonInitialized;
}
return ResultCode.Success;
}
[CommandCmif(2)]
public ResultCode GetListDevices(ServiceCtx context)
{
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
ulong outputSize = context.Request.RecvListBuff[0].Size;
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle);
}
context.ResponseData.Write(context.Device.System.NfcDevices.Count);
return ResultCode.Success;
}
[CommandCmif(3)]
public ResultCode StartDetection(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag;
break;
}
}
_cancelTokenSource = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
{
if (_cancelTokenSource.Token.IsCancellationRequested)
{
break;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
context.Device.System.NfcDevices[i].SignalActivate();
Thread.Sleep(125); // NOTE: Simulate skylander scanning delay.
break;
}
}
}
}, _cancelTokenSource.Token);
return ResultCode.Success;
}
[CommandCmif(4)]
public ResultCode StopDetection(ServiceCtx context)
{
_cancelTokenSource?.Cancel();
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized;
Array.Clear(context.Device.System.NfcDevices[i].Data);
context.Device.System.NfcDevices[i].SignalDeactivate();
break;
}
}
return ResultCode.Success;
}
[CommandCmif(5)]
public ResultCode ReadMifare(ServiceCtx context)
{
if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] readBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, readBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareReadBlockParameter>(readBlockParameter);
var list = new List<NfcMifareReadBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
NfcMifareReadBlockData blockData = new()
{
SectorNumber = list[p].SectorNumber,
Reserved = new Array7<byte>(),
};
byte[] data = new byte[16];
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Read:
case NfcMifareCommand.NfcMifareCommand_AuthA:
if (IsCurrentBlockKeyBlock(list[p].SectorNumber))
{
Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4);
}
else
{
Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16);
}
data.CopyTo(blockData.Data.AsSpan());
context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf<NfcMifareReadBlockData>())), blockData);
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(6)]
public ResultCode WriteMifare(ServiceCtx context)
{
if (context.Request.SendBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] writeBlockParameter = new byte[inputSize];
context.Memory.Read(inputPosition, writeBlockParameter);
var span = MemoryMarshal.Cast<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
var list = new List<NfcMifareWriteBlockParameter>(span.Length);
foreach (var item in span)
list.Add(item);
Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay.
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
return ResultCode.TagNotFound;
}
else
{
for (int p = 0; p < list.Count; p++)
{
switch (list[p].SectorKey.MifareCommand)
{
case NfcMifareCommand.NfcMifareCommand_Write:
case NfcMifareCommand.NfcMifareCommand_AuthA:
list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16));
break;
}
}
}
}
}
return ResultCode.Success;
}
[CommandCmif(7)]
public ResultCode GetTagInfo(ServiceCtx context)
{
ResultCode resultCode = ResultCode.Success;
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfcDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound)
{
TagInfo tagInfo = new()
{
UuidLength = 4,
Reserved1 = new Array21<byte>(),
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
Reserved2 = new Array6<byte>(),
};
byte[] uuid = new byte[4];
Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4);
uuid.CopyTo(tagInfo.Uuid.AsSpan());
context.Memory.Write(outputPosition, tagInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(8)]
public ResultCode AttachActivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(9)]
public ResultCode AttachDeactivateEvent(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(10)]
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_state);
return ResultCode.Success;
}
[CommandCmif(11)]
public ResultCode GetDeviceState(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized)
{
throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}");
}
context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State);
return ResultCode.Success;
}
}
context.ResponseData.Write((uint)NfcDeviceState.Unavailable);
return ResultCode.DeviceNotFound;
}
[CommandCmif(12)]
public ResultCode GetNpadId(ServiceCtx context)
{
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfcDevices.Count; i++)
{
if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle)
{
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle));
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(13)]
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
{
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
return ResultCode.Success;
}
private bool IsCurrentBlockKeyBlock(byte block)
{
return ((block + 1) % 4) == 0;
}
}
}

View File

@@ -1,21 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
class NfcDevice
{
public KEvent ActivateEvent;
public KEvent DeactivateEvent;
public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
public NfcDeviceState State = NfcDeviceState.Unavailable;
public PlayerIndex Handle;
public NpadIdType NpadIdType;
public byte[] Data;
}
}

View File

@@ -1,14 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcMifareCommand : byte
{
NfcMifareCommand_Read = 0x30,
NfcMifareCommand_AuthA = 0x60,
NfcMifareCommand_AuthB = 0x61,
NfcMifareCommand_Write = 0xA0,
NfcMifareCommand_Transfer = 0xB0,
NfcMifareCommand_Decrement = 0xC0,
NfcMifareCommand_Increment = 0xC1,
NfcMifareCommand_Store = 0xC2,
}
}

View File

@@ -1,13 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockData
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
}
}

View File

@@ -1,13 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x18)]
struct NfcMifareReadBlockParameter
{
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -1,14 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x28)]
struct NfcMifareWriteBlockParameter
{
public Array16<byte> Data;
public byte SectorNumber;
public Array7<byte> Reserved;
public NfcSectorKey SectorKey;
}
}

View File

@@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcProtocol : byte
{
NfcProtocol_None = 0b_0000_0000,
NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A
NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B
NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa
NfcProtocol_All = 0xFF,
}
}

View File

@@ -1,16 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct NfcSectorKey
{
public NfcMifareCommand MifareCommand;
public byte Unknown;
public Array6<byte> Reserved1;
public Array6<byte> SectorKey;
public Array2<byte> Reserved2;
}
}

View File

@@ -1,15 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcTagType : byte
{
NfcTagType_None = 0b_0000_0000,
NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz
NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN
NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa
NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire
NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire
NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV
NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders
NfcTagType_All = 0xFF,
}
}

View File

@@ -1,13 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum NfcDeviceState : byte
{
Initialized = 0,
SearchingForTag = 1,
TagFound = 2,
TagRemoved = 3,
TagMounted = 4,
Unavailable = 5,
Finalized = 6,
}
}

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
enum State
{
NonInitialized,
Initialized,
}
}

View File

@@ -1,16 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
{
[StructLayout(LayoutKind.Sequential, Size = 0x58)]
struct TagInfo
{
public Array10<byte> Uuid;
public byte UuidLength;
public Array21<byte> Reserved1;
public uint Protocol;
public uint TagType;
public Array6<byte> Reserved2;
}
}

View File

@@ -1,17 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
{
public enum ResultCode
{
ModuleId = 161,
ErrorCodeShift = 9,
Success = 0,
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1
WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1
WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1
NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1
TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1
MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1
}
}

View File

@@ -79,15 +79,9 @@ namespace Ryujinx.HLE.HOS.Services
ProcessCreationFlags.Is64Bit | ProcessCreationFlags.Is64Bit |
ProcessCreationFlags.PoolPartitionSystem; ProcessCreationFlags.PoolPartitionSystem;
ProcessCreationInfo creationInfo = new(Name, 1, 0, 0x8000000, 1, Flags, 0, 0); ProcessCreationInfo creationInfo = new("Service", 1, 0, 0x8000000, 1, Flags, 0, 0);
KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, () => KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, Main);
{
var currentThread = KernelStatic.GetCurrentThread();
currentThread.HostThread.Name = $"{{{Name}}}";
Main();
});
} }
private void AddPort(int serverPortHandle, Func<IpcService> objectFactory) private void AddPort(int serverPortHandle, Func<IpcService> objectFactory)

View File

@@ -17,12 +17,13 @@ namespace Ryujinx.HLE.HOS.Services.Sm
private static readonly Dictionary<string, Type> _services; private static readonly Dictionary<string, Type> _services;
private readonly SmRegistry _registry; private readonly SmRegistry _registry;
private ServerBase _commonServer; private readonly ServerBase _commonServer;
private bool _isInitialized; private bool _isInitialized;
public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true) public IUserInterface(KernelContext context, SmRegistry registry) : base(registerTipc: true)
{ {
_commonServer = new ServerBase(context, "CommonServer");
_registry = registry; _registry = registry;
} }
@@ -96,11 +97,6 @@ namespace Ryujinx.HLE.HOS.Services.Sm
IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter); IpcService service = GetServiceInstance(type, context, serviceAttribute.Parameter);
if (_commonServer is null)
{
_commonServer = new ServerBase(context.Device.System.KernelContext, "Common");
}
service.TrySetServer(_commonServer); service.TrySetServer(_commonServer);
service.Server.AddSessionObj(session.ServerSession, service); service.Server.AddSessionObj(session.ServerSession, service);
} }
@@ -257,7 +253,7 @@ namespace Ryujinx.HLE.HOS.Services.Sm
public override void DestroyAtExit() public override void DestroyAtExit()
{ {
_commonServer?.Dispose(); _commonServer.Dispose();
base.DestroyAtExit(); base.DestroyAtExit();
} }

View File

@@ -21,7 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Impl
public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; } public bool Blocking { get => Socket.Blocking; set => Socket.Blocking = value; }
public nint Handle => nint.Zero; public nint Handle => IntPtr.Zero;
public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint; public IPEndPoint RemoteEndPoint => Socket.RemoteEndPoint as IPEndPoint;

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
public ISslService(ServiceCtx context) { } public ISslService(ServiceCtx context) { }
[CommandCmif(0)] [CommandCmif(0)]
// CreateContext(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContext> // CreateContext(nn::ssl::sf::SslVersion, u64, pid) -> object<nn::ssl::sf::ISslContext>
public ResultCode CreateContext(ServiceCtx context) public ResultCode CreateContext(ServiceCtx context)
{ {
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
@@ -126,18 +126,14 @@ namespace Ryujinx.HLE.HOS.Services.Ssl
} }
[CommandCmif(100)] [CommandCmif(100)]
// CreateContextForSystem(nn::ssl::sf::SslVersion, u64 pid_placeholder, pid) -> object<nn::ssl::sf::ISslContextForSystem> // CreateContextForSystem(u64 pid, nn::ssl::sf::SslVersion, u64)
public ResultCode CreateContextForSystem(ServiceCtx context) public ResultCode CreateContextForSystem(ServiceCtx context)
{ {
ulong pid = context.RequestData.ReadUInt64();
SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32(); SslVersion sslVersion = (SslVersion)context.RequestData.ReadUInt32();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong pidPlaceholder = context.RequestData.ReadUInt64(); ulong pidPlaceholder = context.RequestData.ReadUInt64();
#pragma warning restore IDE0059
// Note: We use ISslContext here instead of ISslContextForSystem class because Ryujinx implements both in one class. Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { pid, sslVersion, pidPlaceholder });
MakeObject(context, new ISslContext(context.Request.HandleDesc.PId, sslVersion));
Logger.Stub?.PrintStub(LogClass.ServiceSsl, new { sslVersion });
return ResultCode.Success; return ResultCode.Success;
} }

View File

@@ -20,7 +20,5 @@ namespace Ryujinx.HLE.HOS.SystemState
SimplifiedChinese, SimplifiedChinese,
TraditionalChinese, TraditionalChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
} }

View File

@@ -23,9 +23,7 @@ namespace Ryujinx.HLE.HOS.SystemState
"es-419", "es-419",
"zh-Hans", "zh-Hans",
"zh-Hant", "zh-Hant",
"pt-BR", "pt-BR"
"pl",
"th"
]; ];
internal long DesiredKeyboardLayout { get; private set; } internal long DesiredKeyboardLayout { get; private set; }

View File

@@ -18,7 +18,5 @@ namespace Ryujinx.HLE.HOS.SystemState
TraditionalChinese, TraditionalChinese,
SimplifiedChinese, SimplifiedChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
} }

View File

@@ -33,20 +33,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc
[CmifCommand(10100)] // 1.0.0-5.1.0 [CmifCommand(10100)] // 1.0.0-5.1.0
[CmifCommand(10102)] // 6.0.0-9.2.0 [CmifCommand(10102)] // 6.0.0-9.2.0
[CmifCommand(10104)] // 10.0.0+ [CmifCommand(10104)] // 10.0.0+
public Result SaveReportOld([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid) public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, Uid.Null);
return Result.Success;
}
[CmifCommand(10106)] // 21.0.0+
public Result SaveReport([Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
{ {
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0) if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{ {
@@ -61,20 +48,7 @@ namespace Ryujinx.Horizon.Prepo.Ipc
[CmifCommand(10101)] // 1.0.0-5.1.0 [CmifCommand(10101)] // 1.0.0-5.1.0
[CmifCommand(10103)] // 6.0.0-9.2.0 [CmifCommand(10103)] // 6.0.0-9.2.0
[CmifCommand(10105)] // 10.0.0+ [CmifCommand(10105)] // 10.0.0+
public Result SaveReportWithUserOld(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid) public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid)
{
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{
return PrepoResult.PermissionDenied;
}
ProcessPlayReport(PlayReportKind.Normal, gameRoomBuffer, reportBuffer, pid, userId, true);
return Result.Success;
}
[CmifCommand(10107)] // 21.0.0+
public Result SaveReportWithUser(Uid userId, [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer)] ReadOnlySpan<byte> gameRoomBuffer, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> reportBuffer, [ClientProcessId] ulong pid, bool optInCheckEnabled)
{ {
if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0) if ((_permissionLevel & PrepoServicePermissionLevel.User) == 0)
{ {

View File

@@ -1,19 +1,12 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using System; using System;
using System.Buffers.Binary;
using System.IO;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
namespace Ryujinx.Horizon.Sdk.Ns namespace Ryujinx.Horizon.Sdk.Ns
{ {
public struct ApplicationControlProperty public struct ApplicationControlProperty
{ {
/// <summary> public Array16<ApplicationTitle> Title;
/// Use <see cref="Title"/> to access titles instead of accessing them directly.
/// </summary>
public Array16<ApplicationTitle> TitleBlock;
public Array37<byte> Isbn; public Array37<byte> Isbn;
public StartupUserAccountValue StartupUserAccount; public StartupUserAccountValue StartupUserAccount;
public UserAccountSwitchLockValue UserAccountSwitchLock; public UserAccountSwitchLockValue UserAccountSwitchLock;
@@ -65,10 +58,7 @@ namespace Ryujinx.Horizon.Sdk.Ns
public RepairFlagValue RepairFlag; public RepairFlagValue RepairFlag;
public byte ProgramIndex; public byte ProgramIndex;
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag; public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
public byte ApplicationErrorCodePrefix; public Array4<byte> Reserved3214;
public TitleCompressionValue TitleCompression;
public byte AcdIndex;
public byte ApparentPlatform;
public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration; public ApplicationNeighborDetectionClientConfiguration NeighborDetectionClientConfiguration;
public ApplicationJitConfiguration JitConfiguration; public ApplicationJitConfiguration JitConfiguration;
public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors; public RequiredAddOnContentsSetBinaryDescriptor RequiredAddOnContentsSetBinaryDescriptors;
@@ -84,47 +74,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0'); public readonly string DisplayVersionString => Encoding.UTF8.GetString(DisplayVersion.AsSpan()).TrimEnd('\0');
public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0'); public readonly string ApplicationErrorCodeCategoryString => Encoding.UTF8.GetString(ApplicationErrorCodeCategory.AsSpan()).TrimEnd('\0');
public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0'); public readonly string BcatPassphraseString => Encoding.UTF8.GetString(BcatPassphrase.AsSpan()).TrimEnd('\0');
private const int TitleCount = 32;
private const int TitleEntrySize = 0x300;
/// <summary>
/// Returns the resolved title entries. When <see cref="TitleCompression"/> is
/// <see cref="TitleCompressionValue.Enable"/>, the raw <see cref="TitleBlock"/> bytes are
/// decompressed (raw deflate) from 0x3000 into 0x6000 bytes yielding up to 32 entries.
/// Otherwise the 16 uncompressed entries from <see cref="TitleBlock"/> are returned directly.
/// </summary>
public readonly ApplicationTitle[] Title
{
get
{
var titles = new ApplicationTitle[TitleCount];
if (TitleCompression != TitleCompressionValue.Enable)
{
TitleBlock.AsSpan().CopyTo(titles);
return titles;
}
ReadOnlySpan<byte> titleBytes = MemoryMarshal.AsBytes(TitleBlock.AsSpan());
ushort compressedBlobSize = BinaryPrimitives.ReadUInt16LittleEndian(titleBytes);
ReadOnlySpan<byte> compressedBlob = titleBytes.Slice(2, compressedBlobSize);
byte[] decompressed = new byte[TitleCount * TitleEntrySize];
using (var compressedStream = new MemoryStream(compressedBlob.ToArray()))
using (var deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
deflateStream.ReadExactly(decompressed, 0, decompressed.Length);
}
MemoryMarshal.Cast<byte, ApplicationTitle>(decompressed).CopyTo(titles);
return titles;
}
}
public struct ApplicationTitle public struct ApplicationTitle
{ {
@@ -181,8 +130,6 @@ namespace Ryujinx.Horizon.Sdk.Ns
TraditionalChinese = 13, TraditionalChinese = 13,
SimplifiedChinese = 14, SimplifiedChinese = 14,
BrazilianPortuguese = 15, BrazilianPortuguese = 15,
Polish = 16,
Thai = 17,
} }
public enum Organization public enum Organization
@@ -355,11 +302,5 @@ namespace Ryujinx.Horizon.Sdk.Ns
Deny = 0, Deny = 0,
Allow = 1, Allow = 1,
} }
public enum TitleCompressionValue : byte
{
Disable = 0,
Enable = 1,
}
} }
} }

View File

@@ -8,10 +8,8 @@ namespace Ryujinx.Horizon.Sdk.Prepo
{ {
interface IPrepoService : IServiceObject interface IPrepoService : IServiceObject
{ {
Result SaveReportOld(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid); Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReport(ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled); Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportWithUserOld(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid);
Result SaveReportWithUser(Uid userId, ReadOnlySpan<byte> gameRoomBuffer, ReadOnlySpan<byte> reportBuffer, ulong pid, bool optInCheckEnabled);
Result RequestImmediateTransmission(); Result RequestImmediateTransmission();
Result GetTransmissionStatus(out int status); Result GetTransmissionStatus(out int status);
Result GetSystemSessionId(out ulong systemSessionId); Result GetSystemSessionId(out ulong systemSessionId);

View File

@@ -9,14 +9,12 @@ namespace Ryujinx.Horizon
private readonly Action<ServiceTable> _entrypoint; private readonly Action<ServiceTable> _entrypoint;
private readonly ServiceTable _serviceTable; private readonly ServiceTable _serviceTable;
private readonly HorizonOptions _options; private readonly HorizonOptions _options;
public readonly string Name;
internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options, string name) internal ServiceEntry(Action<ServiceTable> entrypoint, ServiceTable serviceTable, HorizonOptions options)
{ {
_entrypoint = entrypoint; _entrypoint = entrypoint;
_serviceTable = serviceTable; _serviceTable = serviceTable;
_options = options; _options = options;
Name = name;
} }
public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext) public void Start(ISyscallApi syscallApi, IVirtualMemoryManager addressSpace, IThreadContext threadContext)

View File

@@ -37,7 +37,7 @@ namespace Ryujinx.Horizon
void RegisterService<T>() where T : IService void RegisterService<T>() where T : IService
{ {
entries.Add(new ServiceEntry(T.Main, this, options, typeof(T).Name)); entries.Add(new ServiceEntry(T.Main, this, options));
} }
RegisterService<ArpMain>(); RegisterService<ArpMain>();

View File

@@ -9,20 +9,10 @@ using static SDL.SDL3;
namespace Ryujinx.Input.SDL3 namespace Ryujinx.Input.SDL3
{ {
public unsafe class SDL3GamepadDriver : IGamepadDriver public unsafe class SDL3GamepadDriver : IGamepadDriver
{ {
private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping; private readonly Dictionary<SDL_JoystickID, string> _gamepadsInstanceIdsMapping;
private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds; private readonly Dictionary<SDL_JoystickID, string> _gamepadsIds;
/// <summary>
/// Unlinked joy-cons
/// </summary>
private readonly Dictionary<SDL_JoystickID, string> _joyConsIds;
/// <summary>
/// Linked joy-cons, remove dual joy-con from <c>_gamepadsIds</c> when a linked joy-con is removed
/// </summary>
private readonly Dictionary<SDL_JoystickID,string> _linkedJoyConsIds;
private readonly Lock _lock = new(); private readonly Lock _lock = new();
public ReadOnlySpan<string> GamepadsIds public ReadOnlySpan<string> GamepadsIds
@@ -31,11 +21,7 @@ namespace Ryujinx.Input.SDL3
{ {
lock (_lock) lock (_lock)
{ {
List<string> temp = []; return _gamepadsIds.Values.ToArray();
temp.AddRange(_gamepadsIds.Values);
temp.AddRange(_joyConsIds.Values);
temp.AddRange(_linkedJoyConsIds.Values);
return temp.ToArray();
} }
} }
} }
@@ -49,8 +35,6 @@ namespace Ryujinx.Input.SDL3
{ {
_gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>(); _gamepadsInstanceIdsMapping = new Dictionary<SDL_JoystickID, string>();
_gamepadsIds = []; _gamepadsIds = [];
_joyConsIds = [];
_linkedJoyConsIds = [];
SDL3Driver.Instance.Initialize(); SDL3Driver.Instance.Initialize();
SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected; SDL3Driver.Instance.OnJoyStickConnected += HandleJoyStickConnected;
@@ -108,7 +92,7 @@ namespace Ryujinx.Input.SDL3
int guidIndex = 0; int guidIndex = 0;
id = guidIndex + "-" + guidString; id = guidIndex + "-" + guidString;
while (_gamepadsIds.ContainsValue(id) || _joyConsIds.ContainsValue(id) || _linkedJoyConsIds.ContainsValue(id)) while (_gamepadsIds.ContainsValue(id))
{ {
id = (++guidIndex) + "-" + guidString; id = (++guidIndex) + "-" + guidString;
} }
@@ -120,47 +104,16 @@ namespace Ryujinx.Input.SDL3
private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId) private void HandleJoyStickDisconnected(SDL_JoystickID joystickInstanceId)
{ {
bool joyConPairDisconnected = false; bool joyConPairDisconnected = false;
string fakeId = null;
if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id)) if (!_gamepadsInstanceIdsMapping.Remove(joystickInstanceId, out string id))
return; return;
lock (_lock) lock (_lock)
{ {
if (!_linkedJoyConsIds.ContainsKey(joystickInstanceId)) _gamepadsIds.Remove(joystickInstanceId);
if (!SDL3JoyConPair.IsCombinable(_gamepadsIds))
{ {
if (!_joyConsIds.Remove(joystickInstanceId)) _gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
{
_gamepadsIds.Remove(joystickInstanceId);
}
}
else
{
foreach (string matchId in _gamepadsIds.Values)
{
if (matchId.Contains(id))
{
fakeId = matchId;
break;
}
}
string leftId = fakeId!.Split('_')[0];
string rightId = fakeId!.Split('_')[1];
if (leftId == id)
{
_linkedJoyConsIds.Remove(GetInstanceIdFromId(rightId));
_joyConsIds.Add(GetInstanceIdFromId(rightId), rightId);
}
else
{
_linkedJoyConsIds.Remove(GetInstanceIdFromId(leftId));
_joyConsIds.Add(GetInstanceIdFromId(leftId), leftId);
}
_linkedJoyConsIds.Remove(joystickInstanceId);
_gamepadsIds.Remove(GetInstanceIdFromId(fakeId));
joyConPairDisconnected = true; joyConPairDisconnected = true;
} }
} }
@@ -168,14 +121,13 @@ namespace Ryujinx.Input.SDL3
OnGamepadDisconnected?.Invoke(id); OnGamepadDisconnected?.Invoke(id);
if (joyConPairDisconnected) if (joyConPairDisconnected)
{ {
OnGamepadDisconnected?.Invoke(fakeId); OnGamepadDisconnected?.Invoke(SDL3JoyConPair.Id);
} }
} }
private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId) private void HandleJoyStickConnected(SDL_JoystickID joystickInstanceId)
{ {
bool joyConPairConnected = false; bool joyConPairConnected = false;
string fakeId = null;
if (SDL_IsGamepad(joystickInstanceId)) if (SDL_IsGamepad(joystickInstanceId))
{ {
@@ -197,40 +149,27 @@ namespace Ryujinx.Input.SDL3
{ {
lock (_lock) lock (_lock)
{ {
if (!SDL3JoyCon.IsJoyCon(joystickInstanceId))
_gamepadsIds.Add(joystickInstanceId, id);
if (SDL3JoyConPair.IsCombinable(_gamepadsIds))
{ {
_gamepadsIds.Add(joystickInstanceId, id); // TODO - It appears that you can only have one joy con pair connected at a time?
} // This was also the behavior before SDL3
else _gamepadsIds.Remove(GetInstanceIdFromId(SDL3JoyConPair.Id));
{ uint fakeInstanceID = uint.MaxValue;
if (SDL3JoyConPair.IsCombinable(joystickInstanceId, _joyConsIds, out SDL_JoystickID match)) while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceID, SDL3JoyConPair.Id))
{ {
_joyConsIds.Remove(match, out string matchId); fakeInstanceID--;
_linkedJoyConsIds.Add(joystickInstanceId, id);
_linkedJoyConsIds.Add(match, matchId);
uint fakeInstanceId = uint.MaxValue;
fakeId = SDL3JoyCon.IsLeftJoyCon(joystickInstanceId)
? $"{id}_{matchId}"
: $"{matchId}_{id}";
while (!_gamepadsIds.TryAdd((SDL_JoystickID)fakeInstanceId, fakeId))
{
fakeInstanceId--;
}
_gamepadsInstanceIdsMapping.Add((SDL_JoystickID)fakeInstanceId, fakeId);
joyConPairConnected = true;
}
else
{
_joyConsIds.Add(joystickInstanceId, id);
} }
joyConPairConnected = true;
} }
} }
OnGamepadConnected?.Invoke(id); OnGamepadConnected?.Invoke(id);
if (joyConPairConnected) if (joyConPairConnected)
{ {
OnGamepadConnected?.Invoke(fakeId); OnGamepadConnected?.Invoke(SDL3JoyConPair.Id);
} }
} }
} }
@@ -254,22 +193,10 @@ namespace Ryujinx.Input.SDL3
{ {
OnGamepadDisconnected?.Invoke(gamepad.Value); OnGamepadDisconnected?.Invoke(gamepad.Value);
} }
foreach (var gamepad in _joyConsIds)
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
foreach (var gamepad in _linkedJoyConsIds)
{
OnGamepadDisconnected?.Invoke(gamepad.Value);
}
lock (_lock) lock (_lock)
{ {
_gamepadsIds.Clear(); _gamepadsIds.Clear();
_joyConsIds.Clear();
_linkedJoyConsIds.Clear();
} }
SDL3Driver.Instance.Dispose(); SDL3Driver.Instance.Dispose();
@@ -288,27 +215,11 @@ namespace Ryujinx.Input.SDL3
public IGamepad GetGamepad(string id) public IGamepad GetGamepad(string id)
{ {
// joy-con pair ids is the combined ids of its parts which are split using a '_' if (id == SDL3JoyConPair.Id)
if (id.Contains('_'))
{ {
lock (_lock) lock (_lock)
{ {
string leftId = id.Split('_')[0]; return SDL3JoyConPair.GetGamepad(_gamepadsIds);
string rightId = id.Split('_')[1];
SDL_JoystickID leftInstanceId = GetInstanceIdFromId(leftId);
SDL_JoystickID rightInstanceId = GetInstanceIdFromId(rightId);
SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad(leftInstanceId);
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad(rightInstanceId);
if (leftGamepadHandle == null || rightGamepadHandle == null)
{
return null;
}
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, leftId),
new SDL3JoyCon(rightGamepadHandle, rightId));
} }
} }
@@ -321,7 +232,7 @@ namespace Ryujinx.Input.SDL3
return null; return null;
} }
if (SDL3JoyCon.IsJoyCon(instanceId)) if (SDL_GetGamepadName(gamepadHandle).StartsWith(SDL3JoyCon.Prefix))
{ {
return new SDL3JoyCon(gamepadHandle, id); return new SDL3JoyCon(gamepadHandle, id);
} }
@@ -338,22 +249,6 @@ namespace Ryujinx.Input.SDL3
yield return GetGamepad(gamepad.Value); yield return GetGamepad(gamepad.Value);
} }
} }
lock (_joyConsIds)
{
foreach (var gamepad in _joyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
lock (_linkedJoyConsIds)
{
foreach (var gamepad in _linkedJoyConsIds)
{
yield return GetGamepad(gamepad.Value);
}
}
} }
} }
} }

View File

@@ -24,10 +24,10 @@ namespace Ryujinx.Input.SDL3
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _leftButtonsDriverMapping = new() private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _leftButtonsDriverMapping = new()
{ {
{GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK}, {GamepadButtonInputId.LeftStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
{GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST}, {GamepadButtonInputId.DpadUp, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST}, {GamepadButtonInputId.DpadDown, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH}, {GamepadButtonInputId.DpadLeft, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH}, {GamepadButtonInputId.DpadRight, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START}, {GamepadButtonInputId.Minus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1}, {GamepadButtonInputId.LeftShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE1},
{GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, {GamepadButtonInputId.LeftTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
@@ -37,10 +37,10 @@ namespace Ryujinx.Input.SDL3
private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _rightButtonsDriverMapping = new() private readonly Dictionary<GamepadButtonInputId, SDL_GamepadButton> _rightButtonsDriverMapping = new()
{ {
{GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK}, {GamepadButtonInputId.RightStick, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_LEFT_STICK},
{GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH}, {GamepadButtonInputId.A, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST},
{GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST}, {GamepadButtonInputId.B, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH},
{GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_EAST}, {GamepadButtonInputId.X, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_SOUTH},
{GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_NORTH}, {GamepadButtonInputId.Y, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_WEST},
{GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START}, {GamepadButtonInputId.Plus, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_START},
{GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, {GamepadButtonInputId.RightShoulder, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
{GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, {GamepadButtonInputId.RightTrigger, SDL_GamepadButton.SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
@@ -398,15 +398,5 @@ namespace Ryujinx.Input.SDL3
return SDL_GetGamepadButton(_gamepadHandle, button); return SDL_GetGamepadButton(_gamepadHandle, button);
} }
public static bool IsJoyCon(SDL_JoystickID gamepadsId)
{
return SDL_GetGamepadNameForID(gamepadsId) is LeftName or RightName;
}
public static bool IsLeftJoyCon(SDL_JoystickID gamepadsId)
{
return SDL_GetGamepadNameForID(gamepadsId) is LeftName;
}
} }
} }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Input.SDL3
public const string Id = "JoyConPair"; public const string Id = "JoyConPair";
string IGamepad.Id => Id; string IGamepad.Id => Id;
public string Name => "Nintendo Switch Dual Joy-Con (L/R)"; public string Name => "* Nintendo Switch Joy-Con (L/R)";
public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true }; public bool IsConnected => left is { IsConnected: true } && right is { IsConnected: true };
public void Dispose() public void Dispose()
@@ -96,23 +96,44 @@ namespace Ryujinx.Input.SDL3
right.SetTriggerThreshold(triggerThreshold); right.SetTriggerThreshold(triggerThreshold);
} }
public static bool IsCombinable(SDL_JoystickID joyCon1, Dictionary<SDL_JoystickID, string> joyConIds, out SDL_JoystickID match) public static bool IsCombinable(Dictionary<SDL_JoystickID, string> gamepadsIds)
{ {
bool isLeft = SDL3JoyCon.IsLeftJoyCon(joyCon1); (int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
string matchName = isLeft ? SDL3JoyCon.RightName : SDL3JoyCon.LeftName; return leftIndex >= 0 && rightIndex >= 0;
match = 0; }
foreach (var joyConId in joyConIds.Keys) private static (int leftIndex, int rightIndex) DetectJoyConPair(Dictionary<SDL_JoystickID, string> gamepadsIds)
{
Dictionary<string, SDL_JoystickID> gamepadNames = gamepadsIds
.Where(gamepadId => gamepadId.Value != Id && SDL_GetGamepadNameForID(gamepadId.Key) is SDL3JoyCon.LeftName or SDL3JoyCon.RightName)
.Select(gamepad => (SDL_GetGamepadNameForID(gamepad.Key), gamepad.Key))
.ToDictionary();
SDL_JoystickID idx;
int leftIndex = gamepadNames.TryGetValue(SDL3JoyCon.LeftName, out idx) ? (int)idx : -1;
int rightIndex = gamepadNames.TryGetValue(SDL3JoyCon.RightName, out idx) ? (int)idx : -1;
return (leftIndex, rightIndex);
}
public unsafe static IGamepad GetGamepad(Dictionary<SDL_JoystickID, string> gamepadsIds)
{
(int leftIndex, int rightIndex) = DetectJoyConPair(gamepadsIds);
if (leftIndex <= 0 || rightIndex <= 0)
{ {
if (SDL_GetGamepadNameForID(joyConId) == matchName) return null;
{
match = joyConId;
return true;
}
} }
return false; SDL_Gamepad* leftGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)leftIndex);
SDL_Gamepad* rightGamepadHandle = SDL_OpenGamepad((SDL_JoystickID)rightIndex);
if (leftGamepadHandle == null || rightGamepadHandle == null)
{
return null;
}
return new SDL3JoyConPair(new SDL3JoyCon(leftGamepadHandle, gamepadsIds[(SDL_JoystickID)leftIndex]),
new SDL3JoyCon(rightGamepadHandle, gamepadsIds[(SDL_JoystickID)rightIndex]));
} }
} }
} }

View File

@@ -8,15 +8,25 @@ using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using SDL; using SDL;
using static SDL.SDL3; using static SDL.SDL3;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Input.SDL3 namespace Ryujinx.Input.SDL3
{ {
class SDL3Keyboard : IKeyboard class SDL3Keyboard : IKeyboard
{ {
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, Key From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not Key.Unbound;
}
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
#pragma warning disable IDE0052 // Remove unread private member
private readonly SDL3KeyboardDriver _driver;
#pragma warning restore IDE0052
private StandardKeyboardInputConfig _configuration; private StandardKeyboardInputConfig _configuration;
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping; private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private static readonly SDL_Keycode[] _keysDriverMapping = private static readonly SDL_Keycode[] _keysDriverMapping =
@@ -161,8 +171,9 @@ namespace Ryujinx.Input.SDL3
SDL_Keycode.SDLK_0 SDL_Keycode.SDLK_0
]; ];
public SDL3Keyboard(string id, string name) public SDL3Keyboard(SDL3KeyboardDriver driver, string id, string name)
{ {
_driver = driver;
Id = id; Id = id;
Name = name; Name = name;
_buttonsUserMapping = []; _buttonsUserMapping = [];
@@ -184,9 +195,9 @@ namespace Ryujinx.Input.SDL3
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe static int ToSDL3Scancode(ConfigPhysicalKey key) private unsafe static int ToSDL3Scancode(Key key)
{ {
if (key is >= ConfigPhysicalKey.Unknown and <= ConfigPhysicalKey.Menu) if (key is >= Key.Unknown and <= Key.Menu)
{ {
return -1; return -1;
} }
@@ -194,18 +205,18 @@ namespace Ryujinx.Input.SDL3
return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null); return (int)SDL_GetScancodeFromKey(_keysDriverMapping[(int)key], null);
} }
private static SDL_Keymod GetKeyboardModifierMask(ConfigPhysicalKey key) private static SDL_Keymod GetKeyboardModifierMask(Key key)
{ {
return key switch return key switch
{ {
ConfigPhysicalKey.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT, Key.ShiftLeft => SDL_Keymod.SDL_KMOD_LSHIFT,
ConfigPhysicalKey.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT, Key.ShiftRight => SDL_Keymod.SDL_KMOD_RSHIFT,
ConfigPhysicalKey.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL, Key.ControlLeft => SDL_Keymod.SDL_KMOD_LCTRL,
ConfigPhysicalKey.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL, Key.ControlRight => SDL_Keymod.SDL_KMOD_RCTRL,
ConfigPhysicalKey.AltLeft => SDL_Keymod.SDL_KMOD_LALT, Key.AltLeft => SDL_Keymod.SDL_KMOD_LALT,
ConfigPhysicalKey.AltRight => SDL_Keymod.SDL_KMOD_RALT, Key.AltRight => SDL_Keymod.SDL_KMOD_RALT,
ConfigPhysicalKey.WinLeft => SDL_Keymod.SDL_KMOD_LGUI, Key.WinLeft => SDL_Keymod.SDL_KMOD_LGUI,
ConfigPhysicalKey.WinRight => SDL_Keymod.SDL_KMOD_RGUI, Key.WinRight => SDL_Keymod.SDL_KMOD_RGUI,
// NOTE: Menu key isn't supported by SDL3. // NOTE: Menu key isn't supported by SDL3.
_ => SDL_Keymod.SDL_KMOD_NONE _ => SDL_Keymod.SDL_KMOD_NONE
}; };
@@ -221,9 +232,9 @@ namespace Ryujinx.Input.SDL3
rawKeyboardState = SDL_GetKeyboardState(null); rawKeyboardState = SDL_GetKeyboardState(null);
} }
bool[] keysState = new bool[(int)ConfigPhysicalKey.Count]; bool[] keysState = new bool[(int)Key.Count];
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++) for (Key key = 0; key < Key.Count; key++)
{ {
int index = ToSDL3Scancode(key); int index = ToSDL3Scancode(key);
if (index == -1) if (index == -1)
@@ -253,6 +264,36 @@ namespace Ryujinx.Input.SDL3
return value * ConvertRate; return value * ConvertRate;
} }
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed((Key)stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
{
stickX -= 1;
}
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
public GamepadStateSnapshot GetMappedStateSnapshot() public GamepadStateSnapshot GetMappedStateSnapshot()
{ {
KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot(); KeyboardStateSnapshot rawState = GetKeyboardStateSnapshot();
@@ -265,9 +306,9 @@ namespace Ryujinx.Input.SDL3
return result; return result;
} }
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping) foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{ {
if (!entry.IsValid) if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
{ {
continue; continue;
} }
@@ -279,8 +320,8 @@ namespace Ryujinx.Input.SDL3
} }
} }
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick); (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick); (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
@@ -316,15 +357,38 @@ namespace Ryujinx.Input.SDL3
{ {
_configuration = (StandardKeyboardInputConfig)configuration; _configuration = (StandardKeyboardInputConfig)configuration;
// First clear the buttons mapping
_buttonsUserMapping.Clear(); _buttonsUserMapping.Clear();
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration)); // Then configure left joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Finally configure right joycon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
} }
} }
public void SetLed(uint packedRgb) public void SetLed(uint packedRgb)
{ {
Logger.Debug?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard"); Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL3Keyboard");
} }
public void SetTriggerThreshold(float triggerThreshold) public void SetTriggerThreshold(float triggerThreshold)

View File

@@ -50,7 +50,7 @@ namespace Ryujinx.Input.SDL3
return null; return null;
} }
return new SDL3Keyboard(_keyboardIdentifers[0], "All keyboards"); return new SDL3Keyboard(this, _keyboardIdentifers[0], "All keyboards");
} }
public IEnumerable<IGamepad> GetGamepads() public IEnumerable<IGamepad> GetGamepads()

View File

@@ -1,5 +1,3 @@
using Ryujinx.Common.Logging;
namespace Ryujinx.Input.Assigner namespace Ryujinx.Input.Assigner
{ {
/// <summary> /// <summary>
@@ -10,42 +8,22 @@ namespace Ryujinx.Input.Assigner
private readonly IKeyboard _keyboard; private readonly IKeyboard _keyboard;
private KeyboardStateSnapshot _keyboardState; private KeyboardStateSnapshot _keyboardState;
private Button? _pressedButton;
public KeyboardKeyAssigner(IKeyboard keyboard) public KeyboardKeyAssigner(IKeyboard keyboard)
{ {
_keyboard = keyboard; _keyboard = keyboard;
} }
public void Initialize() public void Initialize() { }
{
_pressedButton = null;
}
public void ReadInput() public void ReadInput()
{ {
_keyboardState = _keyboard.GetKeyboardStateSnapshot(); _keyboardState = _keyboard.GetKeyboardStateSnapshot();
if (_pressedButton is not null)
{
return;
}
Button? buttonFromState = GetPressedButtonFromState();
Button? buttonFromBufferedPress = buttonFromState is null ? GetPressedButtonFromBufferedPress() : null;
_pressedButton = buttonFromState ?? buttonFromBufferedPress;
if (_pressedButton is not null)
{
string source = buttonFromState is not null ? "state" : "buffered-press";
Logger.Debug?.Print(LogClass.UI, $"Keyboard assigner registered key={_pressedButton.Value.AsHidType<Key>()}, source={source}, cancelPressed={ShouldCancel()}");
}
} }
public bool IsAnyButtonPressed() public bool IsAnyButtonPressed()
{ {
return _pressedButton is not null; return GetPressedButton() is not null;
} }
public bool ShouldCancel() public bool ShouldCancel()
@@ -55,53 +33,18 @@ namespace Ryujinx.Input.Assigner
public Button? GetPressedButton() public Button? GetPressedButton()
{ {
return !ShouldCancel() ? _pressedButton : null; Button? keyPressed = null;
}
private Button? GetPressedButtonFromState()
{
Key aliasedKey = GetAliasedPressedKey();
if (aliasedKey != Key.Unknown)
{
return new Button(aliasedKey);
}
for (Key key = Key.Unknown; key < Key.Count; key++) for (Key key = Key.Unknown; key < Key.Count; key++)
{ {
if (_keyboardState.IsPressed(key)) if (_keyboardState.IsPressed(key))
{ {
return new Button(key); keyPressed = new(key);
break;
} }
} }
return null; return !ShouldCancel() ? keyPressed : null;
}
private Button? GetPressedButtonFromBufferedPress()
{
return _keyboard.TryConsumePressedKey(out Key key) ? new Button(key) : null;
}
private Key GetAliasedPressedKey()
{
// On some layouts (for example AltGr on Windows), Right Alt is reported as Ctrl+Alt.
// Prefer AltRight in that case so the binding reflects the physical key used.
if (_keyboardState.IsPressed(Key.ControlLeft) && _keyboardState.IsPressed(Key.AltRight))
{
return Key.AltRight;
}
// On some Copilot keyboards, the key in the right-control position is reported as
// ShiftLeft+Win+F23. Prefer ControlRight so the binding reflects that physical key.
if (_keyboardState.IsPressed(Key.ShiftLeft) &&
_keyboardState.IsPressed(Key.F23) &&
(_keyboardState.IsPressed(Key.WinLeft) || _keyboardState.IsPressed(Key.WinRight)))
{
return Key.ControlRight;
}
return Key.Unknown;
} }
} }
} }

View File

@@ -2,7 +2,6 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using System; using System;
@@ -235,9 +234,7 @@ namespace Ryujinx.Input.HLE
_gamepad?.Dispose(); _gamepad?.Dispose();
Id = config.Id; Id = config.Id;
_gamepad = config is StandardKeyboardInputConfig && GamepadDriver is IKeyboardModeDriver keyboardModeDriver _gamepad = GamepadDriver.GetGamepad(Id);
? keyboardModeDriver.GetKeyboard(Id, KeyboardInputMode.Physical)
: GamepadDriver.GetGamepad(Id);
UpdateUserConfiguration(config); UpdateUserConfiguration(config);

View File

@@ -36,7 +36,6 @@ namespace Ryujinx.Input.HLE
private bool _isDisposed; private bool _isDisposed;
private List<InputConfig> _inputConfig; private List<InputConfig> _inputConfig;
private List<InputConfig> _requestedInputConfig;
private bool _enableKeyboard; private bool _enableKeyboard;
private bool _enableMouse; private bool _enableMouse;
private Switch _device; private Switch _device;
@@ -53,7 +52,6 @@ namespace Ryujinx.Input.HLE
_gamepadDriver = gamepadDriver; _gamepadDriver = gamepadDriver;
_mouseDriver = mouseDriver; _mouseDriver = mouseDriver;
_inputConfig = []; _inputConfig = [];
_requestedInputConfig = [];
_gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; _gamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _gamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
@@ -91,14 +89,14 @@ namespace Ryujinx.Input.HLE
} }
} }
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
} }
} }
private void HandleOnGamepadConnected(string id) private void HandleOnGamepadConnected(string id)
{ {
// Force input reload // Force input reload
ReloadConfiguration(_requestedInputConfig, _enableKeyboard, _enableMouse); ReloadConfiguration(_inputConfig, _enableKeyboard, _enableMouse);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -129,13 +127,11 @@ namespace Ryujinx.Input.HLE
{ {
lock (_lock) lock (_lock)
{ {
_requestedInputConfig = inputConfig?.ToList() ?? [];
NpadController[] oldControllers = _controllers.ToArray(); NpadController[] oldControllers = _controllers.ToArray();
List<InputConfig> validInputs = []; List<InputConfig> validInputs = [];
foreach (InputConfig inputConfigEntry in _requestedInputConfig) foreach (InputConfig inputConfigEntry in inputConfig)
{ {
NpadController controller; NpadController controller;
int index = (int)inputConfigEntry.PlayerIndex; int index = (int)inputConfigEntry.PlayerIndex;
@@ -151,17 +147,7 @@ namespace Ryujinx.Input.HLE
controller = new(_cemuHookClient); controller = new(_cemuHookClient);
} }
InputConfig activeConfig = inputConfigEntry; bool isValid = DriverConfigurationUpdate(ref controller, inputConfigEntry);
bool isValid = DriverConfigurationUpdate(ref controller, activeConfig);
if (!isValid &&
enableKeyboard &&
inputConfigEntry is StandardControllerInputConfig &&
TryGetKeyboardFallback(inputConfigEntry, out StandardKeyboardInputConfig fallbackConfig))
{
activeConfig = fallbackConfig;
isValid = DriverConfigurationUpdate(ref controller, activeConfig);
}
if (!isValid) if (!isValid)
{ {
@@ -171,7 +157,7 @@ namespace Ryujinx.Input.HLE
else else
{ {
_controllers[index] = controller; _controllers[index] = controller;
validInputs.Add(activeConfig); validInputs.Add(inputConfigEntry);
} }
} }
@@ -183,7 +169,7 @@ namespace Ryujinx.Input.HLE
oldControllers[i] = null; oldControllers[i] = null;
} }
_inputConfig = validInputs; _inputConfig = inputConfig;
_enableKeyboard = enableKeyboard; _enableKeyboard = enableKeyboard;
_enableMouse = enableMouse; _enableMouse = enableMouse;
@@ -191,79 +177,6 @@ namespace Ryujinx.Input.HLE
} }
} }
private bool TryGetKeyboardFallback(InputConfig inputConfig, out StandardKeyboardInputConfig fallbackConfig)
{
fallbackConfig = null;
ReadOnlySpan<string> keyboardIds = _keyboardDriver.GamepadsIds;
if (keyboardIds.IsEmpty)
{
return false;
}
string keyboardId = keyboardIds[0];
using IGamepad keyboard = _keyboardDriver.GetGamepad(keyboardId);
if (keyboard == null)
{
return false;
}
fallbackConfig = new StandardKeyboardInputConfig
{
Version = InputConfig.CurrentVersion,
Backend = InputBackendType.WindowKeyboard,
Id = keyboardId,
Name = keyboard.Name,
PlayerIndex = inputConfig.PlayerIndex,
ControllerType = inputConfig.ControllerType,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey>
{
DpadUp = PhysicalKey.Up,
DpadDown = PhysicalKey.Down,
DpadLeft = PhysicalKey.Left,
DpadRight = PhysicalKey.Right,
ButtonMinus = PhysicalKey.Minus,
ButtonL = PhysicalKey.E,
ButtonZl = PhysicalKey.Q,
ButtonSl = PhysicalKey.Unbound,
ButtonSr = PhysicalKey.Unbound,
},
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{
StickUp = PhysicalKey.W,
StickDown = PhysicalKey.S,
StickLeft = PhysicalKey.A,
StickRight = PhysicalKey.D,
StickButton = PhysicalKey.F,
},
RightJoycon = new RightJoyconCommonConfig<PhysicalKey>
{
ButtonA = PhysicalKey.Z,
ButtonB = PhysicalKey.X,
ButtonX = PhysicalKey.C,
ButtonY = PhysicalKey.V,
ButtonPlus = PhysicalKey.Plus,
ButtonR = PhysicalKey.U,
ButtonZr = PhysicalKey.O,
ButtonSl = PhysicalKey.Unbound,
ButtonSr = PhysicalKey.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey>
{
StickUp = PhysicalKey.I,
StickDown = PhysicalKey.K,
StickLeft = PhysicalKey.J,
StickRight = PhysicalKey.L,
StickButton = PhysicalKey.H,
},
};
return true;
}
public void UnblockInputUpdates() public void UnblockInputUpdates()
{ {
lock (_lock) lock (_lock)
@@ -421,7 +334,7 @@ namespace Ryujinx.Input.HLE
} }
} }
public InputConfig GetPlayerInputConfigByIndex(int index) internal InputConfig GetPlayerInputConfigByIndex(int index)
{ {
lock (_lock) lock (_lock)
{ {

View File

@@ -1,6 +1,5 @@
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input namespace Ryujinx.Input
{ {
@@ -34,26 +33,15 @@ namespace Ryujinx.Input
{ {
if (_keyState is null) if (_keyState is null)
{ {
_keyState = new bool[(int)ConfigPhysicalKey.Count]; _keyState = new bool[(int)Key.Count];
} }
for (ConfigPhysicalKey key = 0; key < ConfigPhysicalKey.Count; key++) for (Key key = 0; key < Key.Count; key++)
{ {
_keyState[(int)key] = keyboard.IsPressed((Key)(int)key); _keyState[(int)key] = keyboard.IsPressed(key);
} }
return new KeyboardStateSnapshot(_keyState); return new KeyboardStateSnapshot(_keyState);
} }
/// <summary>
/// Try to consume a recently pressed key.
/// </summary>
/// <param name="key">The pressed key, if available.</param>
/// <returns>True if a key press was consumed.</returns>
bool TryConsumePressedKey(out Key key)
{
key = Key.Unknown;
return false;
}
} }
} }

View File

@@ -1,7 +0,0 @@
namespace Ryujinx.Input
{
public interface IKeyboardModeDriver : IGamepadDriver
{
IKeyboard GetKeyboard(string id, KeyboardInputMode mode);
}
}

View File

@@ -1,78 +0,0 @@
using Ryujinx.Common.Configuration.Hid.Keyboard;
using System.Collections.Generic;
using System.Numerics;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input
{
public static class KeyboardInputMappingHelper
{
public readonly record struct KeyboardButtonMapping(GamepadButtonInputId To, ConfigPhysicalKey From)
{
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound;
}
public static KeyboardButtonMapping[] BuildButtonMappings(StandardKeyboardInputConfig configuration) =>
[
// Left JoyCon
new(GamepadButtonInputId.LeftStick, configuration.LeftJoyconStick.StickButton),
new(GamepadButtonInputId.DpadUp, configuration.LeftJoycon.DpadUp),
new(GamepadButtonInputId.DpadDown, configuration.LeftJoycon.DpadDown),
new(GamepadButtonInputId.DpadLeft, configuration.LeftJoycon.DpadLeft),
new(GamepadButtonInputId.DpadRight, configuration.LeftJoycon.DpadRight),
new(GamepadButtonInputId.Minus, configuration.LeftJoycon.ButtonMinus),
new(GamepadButtonInputId.LeftShoulder, configuration.LeftJoycon.ButtonL),
new(GamepadButtonInputId.LeftTrigger, configuration.LeftJoycon.ButtonZl),
new(GamepadButtonInputId.SingleRightTrigger0, configuration.LeftJoycon.ButtonSr),
new(GamepadButtonInputId.SingleLeftTrigger0, configuration.LeftJoycon.ButtonSl),
// Right JoyCon
new(GamepadButtonInputId.RightStick, configuration.RightJoyconStick.StickButton),
new(GamepadButtonInputId.A, configuration.RightJoycon.ButtonA),
new(GamepadButtonInputId.B, configuration.RightJoycon.ButtonB),
new(GamepadButtonInputId.X, configuration.RightJoycon.ButtonX),
new(GamepadButtonInputId.Y, configuration.RightJoycon.ButtonY),
new(GamepadButtonInputId.Plus, configuration.RightJoycon.ButtonPlus),
new(GamepadButtonInputId.RightShoulder, configuration.RightJoycon.ButtonR),
new(GamepadButtonInputId.RightTrigger, configuration.RightJoycon.ButtonZr),
new(GamepadButtonInputId.SingleRightTrigger1, configuration.RightJoycon.ButtonSr),
new(GamepadButtonInputId.SingleLeftTrigger1, configuration.RightJoycon.ButtonSl),
];
public static (short X, short Y) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigPhysicalKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed(stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed(stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed(stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed(stickConfig.StickLeft))
{
stickX -= 1;
}
if (stickX == 0 && stickY == 0)
{
return (0, 0);
}
Vector2 stick = Vector2.Normalize(new Vector2(stickX, stickY));
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
}
}

View File

@@ -1,8 +0,0 @@
namespace Ryujinx.Input
{
public enum KeyboardInputMode
{
Semantic,
Physical,
}
}

View File

@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Input namespace Ryujinx.Input
{ {
@@ -26,8 +25,5 @@ namespace Ryujinx.Input
/// <returns>True if the given key is pressed</returns> /// <returns>True if the given key is pressed</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPressed(Key key) => KeysState[(int)key]; public bool IsPressed(Key key) => KeysState[(int)key];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsPressed(ConfigPhysicalKey key) => KeysState[(int)key];
} }
} }

View File

@@ -159,7 +159,7 @@ namespace Ryujinx.Memory.WindowsShared
{ {
SplitForMap((ulong)location, (ulong)size, srcOffset); SplitForMap((ulong)location, (ulong)size, srcOffset);
nint ptr = WindowsApi.MapViewOfFile3( IntPtr ptr = WindowsApi.MapViewOfFile3(
sharedMemory, sharedMemory,
WindowsApi.CurrentProcessHandle, WindowsApi.CurrentProcessHandle,
location, location,

View File

@@ -227,7 +227,7 @@ namespace Ryujinx.Tests.Memory
// Create some info to be used for managing the native writing loop. // Create some info to be used for managing the native writing loop.
int stateSize = Unsafe.SizeOf<NativeWriteLoopState>(); int stateSize = Unsafe.SizeOf<NativeWriteLoopState>();
nint statePtr = Marshal.AllocHGlobal(stateSize); IntPtr statePtr = Marshal.AllocHGlobal(stateSize);
Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize); Unsafe.InitBlockUnaligned((void*)statePtr, 0, (uint)stateSize);
ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr); ref NativeWriteLoopState writeLoopState = ref Unsafe.AsRef<NativeWriteLoopState>((void*)statePtr);

View File

@@ -24,7 +24,7 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId;
using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey; using Key = Ryujinx.Common.Configuration.Hid.Key;
namespace Ryujinx.Headless namespace Ryujinx.Headless
{ {
@@ -105,48 +105,48 @@ namespace Ryujinx.Headless
Backend = InputBackendType.WindowKeyboard, Backend = InputBackendType.WindowKeyboard,
Id = null, Id = null,
ControllerType = ControllerType.JoyconPair, ControllerType = ControllerType.JoyconPair,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey> LeftJoycon = new LeftJoyconCommonConfig<Key>
{ {
DpadUp = PhysicalKey.Up, DpadUp = Key.Up,
DpadDown = PhysicalKey.Down, DpadDown = Key.Down,
DpadLeft = PhysicalKey.Left, DpadLeft = Key.Left,
DpadRight = PhysicalKey.Right, DpadRight = Key.Right,
ButtonMinus = PhysicalKey.Minus, ButtonMinus = Key.Minus,
ButtonL = PhysicalKey.E, ButtonL = Key.E,
ButtonZl = PhysicalKey.Q, ButtonZl = Key.Q,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.W, StickUp = Key.W,
StickDown = PhysicalKey.S, StickDown = Key.S,
StickLeft = PhysicalKey.A, StickLeft = Key.A,
StickRight = PhysicalKey.D, StickRight = Key.D,
StickButton = PhysicalKey.F, StickButton = Key.F,
}, },
RightJoycon = new RightJoyconCommonConfig<PhysicalKey> RightJoycon = new RightJoyconCommonConfig<Key>
{ {
ButtonA = PhysicalKey.Z, ButtonA = Key.Z,
ButtonB = PhysicalKey.X, ButtonB = Key.X,
ButtonX = PhysicalKey.C, ButtonX = Key.C,
ButtonY = PhysicalKey.V, ButtonY = Key.V,
ButtonPlus = PhysicalKey.Plus, ButtonPlus = Key.Plus,
ButtonR = PhysicalKey.U, ButtonR = Key.U,
ButtonZr = PhysicalKey.O, ButtonZr = Key.O,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.I, StickUp = Key.I,
StickDown = PhysicalKey.K, StickDown = Key.K,
StickLeft = PhysicalKey.J, StickLeft = Key.J,
StickRight = PhysicalKey.L, StickRight = Key.L,
StickButton = PhysicalKey.H, StickButton = Key.H,
}, },
}; };
} }

View File

@@ -6,15 +6,15 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using ConfigKey = Ryujinx.Common.Configuration.Hid.Key;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
internal class AvaloniaKeyboard : IKeyboard internal class AvaloniaKeyboard : IKeyboard
{ {
private readonly List<KeyboardInputMappingHelper.KeyboardButtonMapping> _buttonsUserMapping; private readonly List<ButtonMappingEntry> _buttonsUserMapping;
private readonly AvaloniaKeyboardDriver _driver; private readonly AvaloniaKeyboardDriver _driver;
private readonly KeyboardInputMode _mode;
private StandardKeyboardInputConfig _configuration; private StandardKeyboardInputConfig _configuration;
private readonly Lock _userMappingLock = new(); private readonly Lock _userMappingLock = new();
@@ -24,12 +24,18 @@ namespace Ryujinx.Ava.Input
public bool IsConnected => true; public bool IsConnected => true;
public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None; public GamepadFeaturesFlag Features => GamepadFeaturesFlag.None;
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name, KeyboardInputMode mode)
private class ButtonMappingEntry(GamepadButtonInputId to, Key from)
{
public readonly GamepadButtonInputId To = to;
public readonly Key From = from;
}
public AvaloniaKeyboard(AvaloniaKeyboardDriver driver, string id, string name)
{ {
_buttonsUserMapping = []; _buttonsUserMapping = [];
_driver = driver; _driver = driver;
_mode = mode;
Id = id; Id = id;
Name = name; Name = name;
} }
@@ -51,18 +57,22 @@ namespace Ryujinx.Ava.Input
return result; return result;
} }
foreach (KeyboardInputMappingHelper.KeyboardButtonMapping entry in _buttonsUserMapping) foreach (ButtonMappingEntry entry in _buttonsUserMapping)
{ {
if (!entry.IsValid || result.IsPressed(entry.To)) if (entry.From == Key.Unknown || entry.From == Key.Unbound || entry.To == GamepadButtonInputId.Unbound)
{ {
continue; continue;
} }
result.SetPressed(entry.To, rawState.IsPressed(entry.From)); // NOTE: Do not touch state of the button already pressed.
if (!result.IsPressed(entry.To))
{
result.SetPressed(entry.To, rawState.IsPressed(entry.From));
}
} }
(short leftStickX, short leftStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.LeftJoyconStick); (short leftStickX, short leftStickY) = GetStickValues(ref rawState, _configuration.LeftJoyconStick);
(short rightStickX, short rightStickY) = KeyboardInputMappingHelper.GetStickValues(ref rawState, _configuration.RightJoyconStick); (short rightStickX, short rightStickY) = GetStickValues(ref rawState, _configuration.RightJoyconStick);
result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY)); result.SetStick(StickInputId.Left, ConvertRawStickValue(leftStickX), ConvertRawStickValue(leftStickY));
result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY)); result.SetStick(StickInputId.Right, ConvertRawStickValue(rightStickX), ConvertRawStickValue(rightStickY));
@@ -90,7 +100,7 @@ namespace Ryujinx.Ava.Input
{ {
try try
{ {
return _driver.IsPressed(key, _mode); return _driver.IsPressed(key);
} }
catch catch
{ {
@@ -98,19 +108,6 @@ namespace Ryujinx.Ava.Input
} }
} }
public bool TryConsumePressedKey(out Key key)
{
try
{
return _driver.TryConsumePressedKey(_mode, out key);
}
catch
{
key = Key.Unknown;
return false;
}
}
public void SetConfiguration(InputConfig configuration) public void SetConfiguration(InputConfig configuration)
{ {
lock (_userMappingLock) lock (_userMappingLock)
@@ -119,13 +116,37 @@ namespace Ryujinx.Ava.Input
_buttonsUserMapping.Clear(); _buttonsUserMapping.Clear();
_buttonsUserMapping.AddRange(KeyboardInputMappingHelper.BuildButtonMappings(_configuration)); #pragma warning disable IDE0055 // Disable formatting
// Left JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftStick, (Key)_configuration.LeftJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadUp, (Key)_configuration.LeftJoycon.DpadUp));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadDown, (Key)_configuration.LeftJoycon.DpadDown));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadLeft, (Key)_configuration.LeftJoycon.DpadLeft));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.DpadRight, (Key)_configuration.LeftJoycon.DpadRight));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Minus, (Key)_configuration.LeftJoycon.ButtonMinus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftShoulder, (Key)_configuration.LeftJoycon.ButtonL));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.LeftTrigger, (Key)_configuration.LeftJoycon.ButtonZl));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger0, (Key)_configuration.LeftJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger0, (Key)_configuration.LeftJoycon.ButtonSl));
// Right JoyCon
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightStick, (Key)_configuration.RightJoyconStick.StickButton));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.A, (Key)_configuration.RightJoycon.ButtonA));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.B, (Key)_configuration.RightJoycon.ButtonB));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.X, (Key)_configuration.RightJoycon.ButtonX));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Y, (Key)_configuration.RightJoycon.ButtonY));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.Plus, (Key)_configuration.RightJoycon.ButtonPlus));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightShoulder, (Key)_configuration.RightJoycon.ButtonR));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.RightTrigger, (Key)_configuration.RightJoycon.ButtonZr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleRightTrigger1, (Key)_configuration.RightJoycon.ButtonSr));
_buttonsUserMapping.Add(new ButtonMappingEntry(GamepadButtonInputId.SingleLeftTrigger1, (Key)_configuration.RightJoycon.ButtonSl));
#pragma warning restore IDE0055
} }
} }
public void SetLed(uint packedRgb) public void SetLed(uint packedRgb)
{ {
Logger.Debug?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard"); Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
} }
public void SetTriggerThreshold(float triggerThreshold) { } public void SetTriggerThreshold(float triggerThreshold) { }
@@ -141,9 +162,41 @@ namespace Ryujinx.Ava.Input
return value * ConvertRate; return value * ConvertRate;
} }
private static (short, short) GetStickValues(ref KeyboardStateSnapshot snapshot, JoyconConfigKeyboardStick<ConfigKey> stickConfig)
{
short stickX = 0;
short stickY = 0;
if (snapshot.IsPressed((Key)stickConfig.StickUp))
{
stickY += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickDown))
{
stickY -= 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickRight))
{
stickX += 1;
}
if (snapshot.IsPressed((Key)stickConfig.StickLeft))
{
stickX -= 1;
}
Vector2 stick = new(stickX, stickY);
stick = Vector2.Normalize(stick);
return ((short)(stick.X * short.MaxValue), (short)(stick.Y * short.MaxValue));
}
public void Clear() public void Clear()
{ {
_driver?.Clear(_mode); _driver?.Clear();
} }
public void Dispose() { } public void Dispose() { }

View File

@@ -1,35 +1,19 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Logging;
using Ryujinx.Input; using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using AvaKey = Avalonia.Input.Key;
using ConfigPhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using Key = Ryujinx.Input.Key; using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
internal class AvaloniaKeyboardDriver : IKeyboardModeDriver internal class AvaloniaKeyboardDriver : IGamepadDriver
{ {
private enum PhysicalKeySource
{
Direct,
ObservedFallback,
Unknown,
}
private static readonly string[] _keyboardIdentifers = ["0"]; private static readonly string[] _keyboardIdentifers = ["0"];
private readonly Control _control; private readonly Control _control;
private readonly HashSet<Key> _semanticPressedKeys; private readonly HashSet<AvaKey> _pressedKeys;
private readonly HashSet<ConfigPhysicalKey> _physicalPressedKeys;
private readonly Dictionary<Key, ConfigPhysicalKey> _observedPhysicalKeysBySemanticKey;
private readonly Queue<Key> _semanticPressedKeyQueue;
private readonly Queue<Key> _physicalPressedKeyQueue;
private readonly Lock _pressedKeyQueueLock;
private readonly KeyboardInputMode _defaultMode;
public event EventHandler<KeyEventArgs> KeyPressed; public event EventHandler<KeyEventArgs> KeyPressed;
public event EventHandler<KeyEventArgs> KeyRelease; public event EventHandler<KeyEventArgs> KeyRelease;
@@ -38,22 +22,13 @@ namespace Ryujinx.Ava.Input
public string DriverName => "AvaloniaKeyboardDriver"; public string DriverName => "AvaloniaKeyboardDriver";
public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers; public ReadOnlySpan<string> GamepadsIds => _keyboardIdentifers;
public AvaloniaKeyboardDriver(Control control, KeyboardInputMode defaultMode = KeyboardInputMode.Semantic) public AvaloniaKeyboardDriver(Control control)
{ {
_control = control; _control = control;
_semanticPressedKeys = []; _pressedKeys = [];
_physicalPressedKeys = [];
_observedPhysicalKeysBySemanticKey = [];
_semanticPressedKeyQueue = [];
_physicalPressedKeyQueue = [];
_pressedKeyQueueLock = new();
_defaultMode = defaultMode;
// Use routed handlers so keys consumed earlier in the Avalonia pipeline _control.KeyDown += OnKeyPress;
// can still be observed by the input driver. This is needed for keys like _control.KeyUp += OnKeyRelease;
// Caps Lock on macOS, which may not reach the plain CLR event path.
_control.AddHandler(InputElement.KeyDownEvent, OnKeyPress, RoutingStrategies.Tunnel, true);
_control.AddHandler(InputElement.KeyUpEvent, OnKeyRelease, RoutingStrategies.Tunnel, true);
_control.TextInput += Control_TextInput; _control.TextInput += Control_TextInput;
} }
@@ -75,18 +50,13 @@ namespace Ryujinx.Ava.Input
} }
public IGamepad GetGamepad(string id) public IGamepad GetGamepad(string id)
{
return GetKeyboard(id, _defaultMode);
}
public IKeyboard GetKeyboard(string id, KeyboardInputMode mode)
{ {
if (!_keyboardIdentifers[0].Equals(id)) if (!_keyboardIdentifers[0].Equals(id))
{ {
return null; return null;
} }
return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.KeyboardLayout_KeyboardInputMode], mode); return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
} }
public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")]; public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
@@ -95,179 +65,40 @@ namespace Ryujinx.Ava.Input
{ {
if (disposing) if (disposing)
{ {
_control.RemoveHandler(InputElement.KeyDownEvent, OnKeyPress); _control.KeyUp -= OnKeyPress;
_control.RemoveHandler(InputElement.KeyUpEvent, OnKeyRelease); _control.KeyDown -= OnKeyRelease;
_control.TextInput -= Control_TextInput;
} }
} }
protected void OnKeyPress(object sender, KeyEventArgs args) protected void OnKeyPress(object sender, KeyEventArgs args)
{ {
UpdateKeyStates(args, isPressed: true); _pressedKeys.Add(args.Key);
KeyPressed?.Invoke(this, args); KeyPressed?.Invoke(this, args);
} }
protected void OnKeyRelease(object sender, KeyEventArgs args) protected void OnKeyRelease(object sender, KeyEventArgs args)
{ {
UpdateKeyStates(args, isPressed: false); _pressedKeys.Remove(args.Key);
KeyRelease?.Invoke(this, args); KeyRelease?.Invoke(this, args);
} }
internal bool IsPressed(Key key, KeyboardInputMode mode) internal bool IsPressed(Key key)
{ {
if (key is Key.Unbound or Key.Unknown) if (key is Key.Unbound or Key.Unknown)
{ {
return false; return false;
} }
return mode == KeyboardInputMode.Physical AvaloniaKeyboardMappingHelper.TryGetAvaKey(key, out AvaKey nativeKey);
? _physicalPressedKeys.Contains((ConfigPhysicalKey)(int)key)
: _semanticPressedKeys.Contains(key);
}
internal void Clear(KeyboardInputMode mode) return _pressedKeys.Contains(nativeKey);
{
lock (_pressedKeyQueueLock)
{
if (mode == KeyboardInputMode.Physical)
{
_physicalPressedKeys.Clear();
_physicalPressedKeyQueue.Clear();
}
else
{
_semanticPressedKeys.Clear();
_semanticPressedKeyQueue.Clear();
}
}
} }
public void Clear() public void Clear()
{ {
lock (_pressedKeyQueueLock) _pressedKeys.Clear();
{
_semanticPressedKeys.Clear();
_physicalPressedKeys.Clear();
_semanticPressedKeyQueue.Clear();
_physicalPressedKeyQueue.Clear();
}
}
internal bool TryConsumePressedKey(KeyboardInputMode mode, out Key key)
{
lock (_pressedKeyQueueLock)
{
Queue<Key> queue = mode == KeyboardInputMode.Physical ? _physicalPressedKeyQueue : _semanticPressedKeyQueue;
if (queue.TryDequeue(out key))
{
return true;
}
}
key = Key.Unknown;
return false;
}
private static void UpdateKeyState(HashSet<Key> pressedKeys, Key key, bool isPressed)
{
if (key is Key.Unknown or Key.Unbound)
{
return;
}
if (isPressed)
{
pressedKeys.Add(key);
return;
}
pressedKeys.Remove(key);
}
private static void UpdateKeyState(HashSet<ConfigPhysicalKey> pressedKeys, ConfigPhysicalKey key, bool isPressed)
{
if (key is ConfigPhysicalKey.Unknown or ConfigPhysicalKey.Unbound)
{
return;
}
if (isPressed)
{
pressedKeys.Add(key);
return;
}
pressedKeys.Remove(key);
}
private void UpdateKeyStates(KeyEventArgs args, bool isPressed)
{
Key semanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.Key);
Key resolvedSemanticKey = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey, args.Key);
ConfigPhysicalKey physicalKey = GetPhysicalInputKey(args, semanticKey, out PhysicalKeySource physicalKeySource);
bool semanticWasPressed = _semanticPressedKeys.Contains(resolvedSemanticKey);
bool physicalWasPressed = _physicalPressedKeys.Contains(physicalKey);
bool bufferedSemanticPress = false;
bool bufferedPhysicalPress = false;
UpdateKeyState(_semanticPressedKeys, resolvedSemanticKey, isPressed);
UpdateKeyState(_physicalPressedKeys, physicalKey, isPressed);
if (isPressed)
{
lock (_pressedKeyQueueLock)
{
if (!semanticWasPressed && resolvedSemanticKey is not Key.Unknown and not Key.Unbound)
{
_semanticPressedKeyQueue.Enqueue(resolvedSemanticKey);
bufferedSemanticPress = true;
}
if (!physicalWasPressed && physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
{
_physicalPressedKeyQueue.Enqueue((Key)(int)physicalKey);
bufferedPhysicalPress = true;
}
}
}
if (isPressed &&
semanticKey is not Key.Unknown and not Key.Unbound &&
physicalKey is not ConfigPhysicalKey.Unknown and not ConfigPhysicalKey.Unbound)
{
_observedPhysicalKeysBySemanticKey[semanticKey] = physicalKey;
}
Logger.Trace?.Print(
LogClass.UI,
$"Keyboard {(isPressed ? "down" : "up")}: avaloniaKey={args.Key}, avaloniaPhysical={args.PhysicalKey}, keySymbol={FormatKeySymbol(args.KeySymbol)}, modifiers={args.KeyModifiers}, semantic={semanticKey}, resolvedSemantic={resolvedSemanticKey}, physical={physicalKey}, physicalSource={physicalKeySource}, bufferedSemantic={bufferedSemanticPress}, bufferedPhysical={bufferedPhysicalPress}, semanticPressed={_semanticPressedKeys.Count}, physicalPressed={_physicalPressedKeys.Count}");
}
private ConfigPhysicalKey GetPhysicalInputKey(KeyEventArgs args, Key semanticKey, out PhysicalKeySource source)
{
Key key = AvaloniaKeyboardMappingHelper.ToInputKey(args.PhysicalKey);
if (key is >= Key.Unknown and < Key.Count)
{
source = PhysicalKeySource.Direct;
return (ConfigPhysicalKey)(int)key;
}
if (semanticKey is not Key.Unknown and not Key.Unbound &&
_observedPhysicalKeysBySemanticKey.TryGetValue(semanticKey, out ConfigPhysicalKey observedPhysicalKey))
{
source = PhysicalKeySource.ObservedFallback;
return observedPhysicalKey;
}
source = PhysicalKeySource.Unknown;
return ConfigPhysicalKey.Unknown;
}
private static string FormatKeySymbol(string keySymbol)
{
return string.IsNullOrEmpty(keySymbol) ? "<none>" : keySymbol;
} }
public void Dispose() public void Dispose()

View File

@@ -2,7 +2,6 @@ using Ryujinx.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using AvaKey = Avalonia.Input.Key; using AvaKey = Avalonia.Input.Key;
using AvaPhysicalKey = Avalonia.Input.PhysicalKey;
namespace Ryujinx.Ava.Input namespace Ryujinx.Ava.Input
{ {
@@ -133,8 +132,7 @@ namespace Ryujinx.Ava.Input
AvaKey.D8, AvaKey.D8,
AvaKey.D9, AvaKey.D9,
AvaKey.OemTilde, AvaKey.OemTilde,
AvaKey.Oem102, AvaKey.OemTilde,AvaKey.OemMinus,
AvaKey.OemMinus,
AvaKey.OemPlus, AvaKey.OemPlus,
AvaKey.OemOpenBrackets, AvaKey.OemOpenBrackets,
AvaKey.OemCloseBrackets, AvaKey.OemCloseBrackets,
@@ -143,155 +141,13 @@ namespace Ryujinx.Ava.Input
AvaKey.OemComma, AvaKey.OemComma,
AvaKey.OemPeriod, AvaKey.OemPeriod,
AvaKey.OemQuestion, AvaKey.OemQuestion,
AvaKey.OemPipe, AvaKey.OemBackslash,
// NOTE: invalid // NOTE: invalid
AvaKey.None AvaKey.None
]; ];
private static readonly AvaPhysicalKey[] _physicalKeyMapping =
[
// NOTE: Invalid
AvaPhysicalKey.None,
AvaPhysicalKey.ShiftLeft,
AvaPhysicalKey.ShiftRight,
AvaPhysicalKey.ControlLeft,
AvaPhysicalKey.ControlRight,
AvaPhysicalKey.AltLeft,
AvaPhysicalKey.AltRight,
AvaPhysicalKey.MetaLeft,
AvaPhysicalKey.MetaRight,
AvaPhysicalKey.ContextMenu,
AvaPhysicalKey.F1,
AvaPhysicalKey.F2,
AvaPhysicalKey.F3,
AvaPhysicalKey.F4,
AvaPhysicalKey.F5,
AvaPhysicalKey.F6,
AvaPhysicalKey.F7,
AvaPhysicalKey.F8,
AvaPhysicalKey.F9,
AvaPhysicalKey.F10,
AvaPhysicalKey.F11,
AvaPhysicalKey.F12,
AvaPhysicalKey.F13,
AvaPhysicalKey.F14,
AvaPhysicalKey.F15,
AvaPhysicalKey.F16,
AvaPhysicalKey.F17,
AvaPhysicalKey.F18,
AvaPhysicalKey.F19,
AvaPhysicalKey.F20,
AvaPhysicalKey.F21,
AvaPhysicalKey.F22,
AvaPhysicalKey.F23,
AvaPhysicalKey.F24,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.None,
AvaPhysicalKey.ArrowUp,
AvaPhysicalKey.ArrowDown,
AvaPhysicalKey.ArrowLeft,
AvaPhysicalKey.ArrowRight,
AvaPhysicalKey.Enter,
AvaPhysicalKey.Escape,
AvaPhysicalKey.Space,
AvaPhysicalKey.Tab,
AvaPhysicalKey.Backspace,
AvaPhysicalKey.Insert,
AvaPhysicalKey.Delete,
AvaPhysicalKey.PageUp,
AvaPhysicalKey.PageDown,
AvaPhysicalKey.Home,
AvaPhysicalKey.End,
AvaPhysicalKey.CapsLock,
AvaPhysicalKey.ScrollLock,
AvaPhysicalKey.PrintScreen,
AvaPhysicalKey.Pause,
AvaPhysicalKey.NumLock,
AvaPhysicalKey.NumPadClear,
AvaPhysicalKey.NumPad0,
AvaPhysicalKey.NumPad1,
AvaPhysicalKey.NumPad2,
AvaPhysicalKey.NumPad3,
AvaPhysicalKey.NumPad4,
AvaPhysicalKey.NumPad5,
AvaPhysicalKey.NumPad6,
AvaPhysicalKey.NumPad7,
AvaPhysicalKey.NumPad8,
AvaPhysicalKey.NumPad9,
AvaPhysicalKey.NumPadDivide,
AvaPhysicalKey.NumPadMultiply,
AvaPhysicalKey.NumPadSubtract,
AvaPhysicalKey.NumPadAdd,
AvaPhysicalKey.NumPadDecimal,
AvaPhysicalKey.NumPadEnter,
AvaPhysicalKey.A,
AvaPhysicalKey.B,
AvaPhysicalKey.C,
AvaPhysicalKey.D,
AvaPhysicalKey.E,
AvaPhysicalKey.F,
AvaPhysicalKey.G,
AvaPhysicalKey.H,
AvaPhysicalKey.I,
AvaPhysicalKey.J,
AvaPhysicalKey.K,
AvaPhysicalKey.L,
AvaPhysicalKey.M,
AvaPhysicalKey.N,
AvaPhysicalKey.O,
AvaPhysicalKey.P,
AvaPhysicalKey.Q,
AvaPhysicalKey.R,
AvaPhysicalKey.S,
AvaPhysicalKey.T,
AvaPhysicalKey.U,
AvaPhysicalKey.V,
AvaPhysicalKey.W,
AvaPhysicalKey.X,
AvaPhysicalKey.Y,
AvaPhysicalKey.Z,
AvaPhysicalKey.Digit0,
AvaPhysicalKey.Digit1,
AvaPhysicalKey.Digit2,
AvaPhysicalKey.Digit3,
AvaPhysicalKey.Digit4,
AvaPhysicalKey.Digit5,
AvaPhysicalKey.Digit6,
AvaPhysicalKey.Digit7,
AvaPhysicalKey.Digit8,
AvaPhysicalKey.Digit9,
AvaPhysicalKey.Backquote,
AvaPhysicalKey.IntlBackslash,
AvaPhysicalKey.Minus,
AvaPhysicalKey.Equal,
AvaPhysicalKey.BracketLeft,
AvaPhysicalKey.BracketRight,
AvaPhysicalKey.Semicolon,
AvaPhysicalKey.Quote,
AvaPhysicalKey.Comma,
AvaPhysicalKey.Period,
AvaPhysicalKey.Slash,
AvaPhysicalKey.Backslash,
// NOTE: invalid
AvaPhysicalKey.None
];
private static readonly Dictionary<AvaKey, Key> _avaKeyMapping; private static readonly Dictionary<AvaKey, Key> _avaKeyMapping;
private static readonly Dictionary<AvaPhysicalKey, Key> _avaPhysicalKeyMapping;
static AvaloniaKeyboardMappingHelper() static AvaloniaKeyboardMappingHelper()
{ {
@@ -299,42 +155,21 @@ namespace Ryujinx.Ava.Input
// NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array. // NOTE: Avalonia.Input.Key is not contiguous and quite large, so use a dictionary instead of an array.
_avaKeyMapping = new Dictionary<AvaKey, Key>(); _avaKeyMapping = new Dictionary<AvaKey, Key>();
_avaPhysicalKeyMapping = new Dictionary<AvaPhysicalKey, Key>();
foreach (Key key in inputKeys) foreach (Key key in inputKeys)
{ {
if (TryGetAvaKey(key, out AvaKey avaKey)) if (TryGetAvaKey(key, out AvaKey index))
{ {
_avaKeyMapping[avaKey] = key; _avaKeyMapping[index] = key;
}
if (TryGetAvaPhysicalKey(key, out AvaPhysicalKey avaPhysicalKey))
{
_avaPhysicalKeyMapping[avaPhysicalKey] = key;
} }
} }
// Alias additional Avalonia key values to improve non-US layout support.
_avaKeyMapping[AvaKey.Oem1] = Key.Semicolon;
_avaKeyMapping[AvaKey.Oem2] = Key.Slash;
_avaKeyMapping[AvaKey.Oem3] = Key.Tilde;
_avaKeyMapping[AvaKey.Oem4] = Key.BracketLeft;
_avaKeyMapping[AvaKey.Oem5] = Key.BackSlash;
_avaKeyMapping[AvaKey.Oem6] = Key.BracketRight;
_avaKeyMapping[AvaKey.Oem7] = Key.Quote;
_avaKeyMapping[AvaKey.OemBackslash] = Key.Grave;
_avaKeyMapping[AvaKey.Oem102] = Key.Grave;
// Common alternates for non-US/JIS physical keys.
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlRo] = Key.BackSlash;
_avaPhysicalKeyMapping[AvaPhysicalKey.IntlYen] = Key.BackSlash;
} }
public static bool TryGetAvaKey(Key key, out AvaKey avaKey) public static bool TryGetAvaKey(Key key, out AvaKey avaKey)
{ {
avaKey = AvaKey.None; avaKey = AvaKey.None;
bool keyExist = key < Key.Count && (int)key < _keyMapping.Length; bool keyExist = (int)key < _keyMapping.Length;
if (keyExist) if (keyExist)
{ {
avaKey = _keyMapping[(int)key]; avaKey = _keyMapping[(int)key];
@@ -343,34 +178,9 @@ namespace Ryujinx.Ava.Input
return keyExist; return keyExist;
} }
public static bool TryGetAvaPhysicalKey(Key key, out AvaPhysicalKey avaPhysicalKey)
{
avaPhysicalKey = AvaPhysicalKey.None;
bool keyExist = key < Key.Count && (int)key < _physicalKeyMapping.Length;
if (keyExist)
{
avaPhysicalKey = _physicalKeyMapping[(int)key];
}
return keyExist;
}
public static Key ToInputKey(AvaKey key) public static Key ToInputKey(AvaKey key)
{ {
return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown); return _avaKeyMapping.GetValueOrDefault(key, Key.Unknown);
} }
public static Key ToInputKey(AvaPhysicalKey key)
{
return _avaPhysicalKeyMapping.GetValueOrDefault(key, Key.Unknown);
}
public static Key ToInputKey(AvaPhysicalKey physicalKey, AvaKey key)
{
Key inputKey = ToInputKey(key);
return inputKey != Key.Unknown ? inputKey : ToInputKey(physicalKey);
}
} }
} }

View File

@@ -1,5 +1,6 @@
using Avalonia; using Avalonia;
using Avalonia.Threading; using Avalonia.Threading;
using CommandLine;
using DiscordRPC; using DiscordRPC;
using Gommon; using Gommon;
using Projektanker.Icons.Avalonia; using Projektanker.Icons.Avalonia;
@@ -42,7 +43,6 @@ namespace Ryujinx.Ava
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; } public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; } public static string BackendThreadingArg { get; private set; }
public static bool CoreDumpArg { get; private set; }
private const uint MbIconwarning = 0x30; private const uint MbIconwarning = 0x30;
@@ -79,30 +79,38 @@ namespace Ryujinx.Ava
} }
} }
bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui"); PreviewerDetached = true;
bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps");
CoreDumpArg = coreDumpArg; if (ConsumeCommandLineArgument(ref args, "--no-gui")
|| ConsumeCommandLineArgument(ref args, "nogui"))
{
try
{
HeadlessRyujinx.Entrypoint(args);
return 0;
}
catch (Exception e)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Exception occurred when running Headless Ryujinx: {e.Message}\n{e.StackTrace}");
return 1;
}
}
if (!Initialize(args, out RyujinxOptions options))
{
Logger.Flush();
return 1;
}
// TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception. // TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception.
// This is undesirable and causes very odd behavior during development (the process stops responding, // This is undesirable and causes very odd behavior during development (the process stops responding,
// the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user. // the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user.
// This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be. // This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be.
if (!coreDumpArg) if (!options.CoreDumpsEnabled)
{ {
OsUtils.SetCoreDumpable(false); OsUtils.SetCoreDumpable(false);
} }
PreviewerDetached = true;
if (noGuiArg)
{
HeadlessRyujinx.Entrypoint(args);
return 0;
}
Initialize(args);
LoggerAdapter.Register(); LoggerAdapter.Register();
IconProvider.Current IconProvider.Current
@@ -140,13 +148,14 @@ namespace Ryujinx.Ava
return found; return found;
} }
private static void Initialize(string[] args) private static Result Initialize(string[] args, out RyujinxOptions options)
{ {
// Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched // Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched
DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now; DiscordIntegrationModule.EmulatorStartedAt = Timestamps.Now;
// Parse arguments // Parse arguments
CommandLineState.ParseArguments(args); Result res = RyujinxOptions.Read(args, out options);
if (!res) return res;
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
@@ -166,7 +175,7 @@ namespace Ryujinx.Ava
AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit(); AppDomain.CurrentDomain.ProcessExit += (_, _) => Exit();
// Setup base data directory. // Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg); AppDataManager.Initialize(options.EmuDataBaseDirPath);
// Initialize the configuration. // Initialize the configuration.
ConfigurationState.Initialize(); ConfigurationState.Initialize();
@@ -199,10 +208,12 @@ namespace Ryujinx.Ava
} }
} }
if (CommandLineState.LaunchPathArg != null) if (options.LaunchPath != null)
{ {
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.LaunchApplicationId, CommandLineState.StartFullscreenArg); MainWindow.DeferLoadApplication(options.LaunchPath, options.LaunchApplicationId, options.StartFullscreen);
} }
return Result.Success;
} }
public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false) public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false)
@@ -225,7 +236,6 @@ namespace Ryujinx.Ava
public static void ReloadConfig(bool isRunGameWithCustomConfig = false) public static void ReloadConfig(bool isRunGameWithCustomConfig = false)
{ {
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
@@ -276,74 +286,50 @@ namespace Ryujinx.Ava
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration; UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration;
// Check if graphics backend was overridden // Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend is not null) if (RyujinxOptions.Shared.GraphicsBackendOverride is not null)
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = CommandLineState.OverrideGraphicsBackend.ToLower() switch ConfigurationState.Instance.Graphics.GraphicsBackend.Value =
{ RyujinxOptions.Shared.GraphicsBackendOverride.Value;
"opengl" => GraphicsBackend.OpenGl,
"vulkan" => GraphicsBackend.Vulkan,
_ => ConfigurationState.Instance.Graphics.GraphicsBackend
};
// Check if backend threading was overridden // Check if backend threading was overridden
if (CommandLineState.OverrideBackendThreading is not null) if (RyujinxOptions.Shared.BackendThreadingOverride is not null)
ConfigurationState.Instance.Graphics.BackendThreading.Value = CommandLineState.OverrideBackendThreading.ToLower() switch ConfigurationState.Instance.Graphics.BackendThreading.Value =
{ RyujinxOptions.Shared.BackendThreadingOverride.Value;
"auto" => BackendThreading.Auto,
"off" => BackendThreading.Off, if (RyujinxOptions.Shared.BackendThreadingOverrideAfterReboot is not null)
"on" => BackendThreading.On, BackendThreadingArg = RyujinxOptions.Shared.BackendThreadingOverrideAfterReboot.Value.ToString();
_ => ConfigurationState.Instance.Graphics.BackendThreading
};
if (CommandLineState.OverrideBackendThreadingAfterReboot is not null)
{
BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot;
}
// Check if docked mode was overriden. // Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue) if (RyujinxOptions.Shared.DockedModeOverride.HasValue)
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value; ConfigurationState.Instance.System.EnableDockedMode.Value =
RyujinxOptions.Shared.DockedModeOverride.Value;
// Check if HideCursor was overridden. // Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null) if (RyujinxOptions.Shared.HideCursorOverride is not null)
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor.ToLower() switch ConfigurationState.Instance.HideCursor.Value = RyujinxOptions.Shared.HideCursorOverride.Value;
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor,
};
// Check if memoryManagerMode was overridden. // Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null) if (RyujinxOptions.Shared.MemoryManagerModeOverride is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result)) ConfigurationState.Instance.System.MemoryManagerMode.Value = RyujinxOptions.Shared.MemoryManagerModeOverride.Value;
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden. // Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null) if (RyujinxOptions.Shared.PptcOverride is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result)) if (Enum.TryParse(RyujinxOptions.Shared.PptcOverride, true, out bool result))
{ {
ConfigurationState.Instance.System.EnablePtc.Value = result; ConfigurationState.Instance.System.EnablePtc.Value = result;
} }
// Check if region was overridden. // Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null) if (RyujinxOptions.Shared.SystemRegionOverride is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Region result)) ConfigurationState.Instance.System.Region.Value = RyujinxOptions.Shared.SystemRegionOverride.Value;
{
ConfigurationState.Instance.System.Region.Value = result;
}
//Check if language was overridden. //Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null) if (RyujinxOptions.Shared.SystemLanguageOverride is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Language result)) ConfigurationState.Instance.System.Language.Value = RyujinxOptions.Shared.SystemLanguageOverride.Value;
{
ConfigurationState.Instance.System.Language.Value = result;
}
// Check if hardware-acceleration was overridden. // Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null) if (RyujinxOptions.Shared.HardwareAccelerationOverride is not null)
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value; UseHardwareAcceleration = RyujinxOptions.Shared.HardwareAccelerationOverride.Value;
} }
internal static void PrintSystemInfo() internal static void PrintSystemInfo()

View File

@@ -28,6 +28,11 @@
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode> <TrimMode>partial</TrimMode>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-arm64'">
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
<!-- <!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json. FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
@@ -49,7 +54,7 @@
<PackageReference Include="Svg.Controls.Avalonia" /> <PackageReference Include="Svg.Controls.Avalonia" />
<PackageReference Include="Svg.Controls.Skia.Avalonia" /> <PackageReference Include="Svg.Controls.Skia.Avalonia" />
<PackageReference Include="DynamicData" /> <PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" /> <PackageReference Include="FluentAvaloniaUI.NoAnim" />
<PackageReference Include="CommandLineParser" /> <PackageReference Include="CommandLineParser" />
<PackageReference Include="CommunityToolkit.Mvvm" /> <PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="DiscordRichPresence" /> <PackageReference Include="DiscordRichPresence" />
@@ -72,13 +77,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" /> <ProjectReference Include="..\Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" /> <ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" /> <ProjectReference Include="..\Ryujinx.Input.SDL3\Ryujinx.Input.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" /> <ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />

View File

@@ -6,7 +6,6 @@ using Avalonia.Threading;
using DiscordRPC; using DiscordRPC;
using LibHac.Common; using LibHac.Common;
using LibHac.Ns; using LibHac.Ns;
using Ryujinx.Audio.Backends.Apple;
using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.Dummy;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL3; using Ryujinx.Audio.Backends.SDL3;
@@ -950,9 +949,6 @@ namespace Ryujinx.Ava.Systems
AudioBackend.Dummy AudioBackend.Dummy
]; ];
if (OperatingSystem.IsMacOS())
availableBackends.Insert(0, AudioBackend.AudioToolbox);
AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value; AudioBackend preferredBackend = ConfigurationState.Instance.System.AudioBackend.Value;
if (preferredBackend is AudioBackend.SDL2) if (preferredBackend is AudioBackend.SDL2)
@@ -989,9 +985,6 @@ namespace Ryujinx.Ava.Systems
deviceDriver = currentBackend switch deviceDriver = currentBackend switch
{ {
#pragma warning disable CA1416 // Platform compatibility is enforced in AppleHardwareDeviceDriver.IsSupported, before any potentially platform-sensitive code can run.
AudioBackend.AudioToolbox => InitializeAudioBackend<AppleHardwareDeviceDriver>(AudioBackend.AudioToolbox, nextBackend),
#pragma warning restore CA1416
AudioBackend.SDL3 => InitializeAudioBackend<SDL3HardwareDeviceDriver>(AudioBackend.SDL3, nextBackend), AudioBackend.SDL3 => InitializeAudioBackend<SDL3HardwareDeviceDriver>(AudioBackend.SDL3, nextBackend),
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend), AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend), AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),

View File

@@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Name)) if (string.IsNullOrWhiteSpace(data.Name))
{ {
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title) foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{ {
if (!controlTitle.NameString.IsEmpty()) if (!controlTitle.NameString.IsEmpty())
{ {
@@ -1417,7 +1417,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
if (string.IsNullOrWhiteSpace(data.Developer)) if (string.IsNullOrWhiteSpace(data.Developer))
{ {
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title) foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
{ {
if (!controlTitle.PublisherString.IsEmpty()) if (!controlTitle.PublisherString.IsEmpty())
{ {

View File

@@ -9,7 +9,6 @@ namespace Ryujinx.Ava.Systems.Configuration
OpenAl, OpenAl,
SoundIo, SoundIo,
SDL3, SDL3,
AudioToolbox,
SDL2 = SDL3 SDL2 = SDL3
} }
} }

View File

@@ -13,8 +13,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
using RyuLogger = Ryujinx.Common.Logging.Logger; using RyuLogger = Ryujinx.Common.Logging.Logger;
namespace Ryujinx.Ava.Systems.Configuration namespace Ryujinx.Ava.Systems.Configuration
@@ -271,45 +269,45 @@ namespace Ryujinx.Ava.Systems.Configuration
Id = "0", Id = "0",
PlayerIndex = PlayerIndex.Player1, PlayerIndex = PlayerIndex.Player1,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey> LeftJoycon = new LeftJoyconCommonConfig<Key>
{ {
DpadUp = PhysicalKey.Up, DpadUp = Key.Up,
DpadDown = PhysicalKey.Down, DpadDown = Key.Down,
DpadLeft = PhysicalKey.Left, DpadLeft = Key.Left,
DpadRight = PhysicalKey.Right, DpadRight = Key.Right,
ButtonMinus = PhysicalKey.Minus, ButtonMinus = Key.Minus,
ButtonL = PhysicalKey.E, ButtonL = Key.E,
ButtonZl = PhysicalKey.Q, ButtonZl = Key.Q,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.W, StickUp = Key.W,
StickDown = PhysicalKey.S, StickDown = Key.S,
StickLeft = PhysicalKey.A, StickLeft = Key.A,
StickRight = PhysicalKey.D, StickRight = Key.D,
StickButton = PhysicalKey.F, StickButton = Key.F,
}, },
RightJoycon = new RightJoyconCommonConfig<PhysicalKey> RightJoycon = new RightJoyconCommonConfig<Key>
{ {
ButtonA = PhysicalKey.Z, ButtonA = Key.Z,
ButtonB = PhysicalKey.X, ButtonB = Key.X,
ButtonX = PhysicalKey.C, ButtonX = Key.C,
ButtonY = PhysicalKey.V, ButtonY = Key.V,
ButtonPlus = PhysicalKey.Plus, ButtonPlus = Key.Plus,
ButtonR = PhysicalKey.U, ButtonR = Key.U,
ButtonZr = PhysicalKey.O, ButtonZr = Key.O,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.I, StickUp = Key.I,
StickDown = PhysicalKey.K, StickDown = Key.K,
StickLeft = PhysicalKey.J, StickLeft = Key.J,
StickRight = PhysicalKey.L, StickRight = Key.L,
StickButton = PhysicalKey.H, StickButton = Key.H,
}, },
} }
]; ];

View File

@@ -8,8 +8,6 @@ using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE; using Ryujinx.HLE;
using System; using System;
using System.Linq; using System.Linq;
using Key = Ryujinx.Common.Configuration.Hid.Key;
using PhysicalKey = Ryujinx.Common.Configuration.Hid.PhysicalKey;
namespace Ryujinx.Ava.Systems.Configuration namespace Ryujinx.Ava.Systems.Configuration
{ {
@@ -287,45 +285,45 @@ namespace Ryujinx.Ava.Systems.Configuration
Name = "Keyboard", Name = "Keyboard",
PlayerIndex = PlayerIndex.Player1, PlayerIndex = PlayerIndex.Player1,
ControllerType = ControllerType.ProController, ControllerType = ControllerType.ProController,
LeftJoycon = new LeftJoyconCommonConfig<PhysicalKey> LeftJoycon = new LeftJoyconCommonConfig<Key>
{ {
DpadUp = PhysicalKey.Up, DpadUp = Key.Up,
DpadDown = PhysicalKey.Down, DpadDown = Key.Down,
DpadLeft = PhysicalKey.Left, DpadLeft = Key.Left,
DpadRight = PhysicalKey.Right, DpadRight = Key.Right,
ButtonMinus = PhysicalKey.Minus, ButtonMinus = Key.Minus,
ButtonL = PhysicalKey.E, ButtonL = Key.E,
ButtonZl = PhysicalKey.Q, ButtonZl = Key.Q,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
LeftJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> LeftJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.W, StickUp = Key.W,
StickDown = PhysicalKey.S, StickDown = Key.S,
StickLeft = PhysicalKey.A, StickLeft = Key.A,
StickRight = PhysicalKey.D, StickRight = Key.D,
StickButton = PhysicalKey.F, StickButton = Key.F,
}, },
RightJoycon = new RightJoyconCommonConfig<PhysicalKey> RightJoycon = new RightJoyconCommonConfig<Key>
{ {
ButtonA = PhysicalKey.Z, ButtonA = Key.Z,
ButtonB = PhysicalKey.X, ButtonB = Key.X,
ButtonX = PhysicalKey.C, ButtonX = Key.C,
ButtonY = PhysicalKey.V, ButtonY = Key.V,
ButtonPlus = PhysicalKey.Plus, ButtonPlus = Key.Plus,
ButtonR = PhysicalKey.U, ButtonR = Key.U,
ButtonZr = PhysicalKey.O, ButtonZr = Key.O,
ButtonSl = PhysicalKey.Unbound, ButtonSl = Key.Unbound,
ButtonSr = PhysicalKey.Unbound, ButtonSr = Key.Unbound,
}, },
RightJoyconStick = new JoyconConfigKeyboardStick<PhysicalKey> RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{ {
StickUp = PhysicalKey.I, StickUp = Key.I,
StickDown = PhysicalKey.K, StickDown = Key.K,
StickLeft = PhysicalKey.J, StickLeft = Key.J,
StickRight = PhysicalKey.L, StickRight = Key.L,
StickButton = PhysicalKey.H, StickButton = Key.H,
}, },
} }
]; ];

View File

@@ -24,8 +24,6 @@ namespace Ryujinx.Ava.Systems.Configuration.System
SimplifiedChinese, SimplifiedChinese,
TraditionalChinese, TraditionalChinese,
BrazilianPortuguese, BrazilianPortuguese,
Polish,
Thai,
} }
public static class LanguageEnumHelper public static class LanguageEnumHelper

View File

@@ -7,7 +7,6 @@ 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;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -47,12 +46,6 @@ namespace Ryujinx.Ava.Systems
return Return<VersionResponse>.Failure( return Return<VersionResponse>.Failure(
new MessageError("DNS resolution error occurred. Is your internet down?")); new MessageError("DNS resolution error occurred. Is your internet down?"));
} }
catch (HttpRequestException hre)
when (hre.StatusCode is HttpStatusCode.BadGateway)
{
return Return<VersionResponse>.Failure(
new MessageError("Could not connect to the update server, but it appears like you have internet. It seems like the update server is offline, try again later."));
}
} }
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)

View File

@@ -181,7 +181,7 @@ namespace Ryujinx.Ava.Systems
if (shouldRestart) if (shouldRestart)
{ {
List<string> arguments = CommandLineState.Arguments.ToList(); List<string> arguments = RyujinxOptions.Shared.InputArguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory; string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch. // On macOS we perform the update at relaunch.
@@ -218,7 +218,7 @@ namespace Ryujinx.Ava.Systems
WorkingDirectory = executableDirectory, WorkingDirectory = executableDirectory,
}; };
foreach (string argument in CommandLineState.Arguments) foreach (string argument in arguments)
{ {
processStart.ArgumentList.Add(argument); processStart.ArgumentList.Add(argument);
} }

View File

@@ -65,7 +65,7 @@ namespace Ryujinx.Ava.UI.Applet
private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e) private void AvaloniaDynamicTextInputHandler_KeyRelease(object sender, KeyEventArgs e)
{ {
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true)) if (!(KeyReleasedEvent?.Invoke(key)).GetValueOrDefault(true))
{ {
@@ -85,7 +85,7 @@ namespace Ryujinx.Ava.UI.Applet
private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e) private void AvaloniaDynamicTextInputHandler_KeyPressed(object sender, KeyEventArgs e)
{ {
HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.PhysicalKey, e.Key); HidKey key = (HidKey)AvaloniaKeyboardMappingHelper.ToInputKey(e.Key);
if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true)) if (!(KeyPressedEvent?.Invoke(key)).GetValueOrDefault(true))
{ {

View File

@@ -1,6 +1,5 @@
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Threading; using Avalonia.Threading;
using Ryujinx.Ava.Input;
using Ryujinx.Input; using Ryujinx.Input;
using Ryujinx.Input.Assigner; using Ryujinx.Input.Assigner;
using System; using System;
@@ -26,7 +25,6 @@ namespace Ryujinx.Ava.UI.Helpers
private bool _isWaitingForInput; private bool _isWaitingForInput;
private bool _shouldUnbind; private bool _shouldUnbind;
private IKeyboard _keyboard;
public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned; public event EventHandler<ButtonAssignedEventArgs> ButtonAssigned;
public ButtonKeyAssigner(ToggleButton toggleButton) public ButtonKeyAssigner(ToggleButton toggleButton)
@@ -36,9 +34,6 @@ namespace Ryujinx.Ava.UI.Helpers
public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null) public async void GetInputAndAssign(IButtonAssigner assigner, IKeyboard keyboard = null)
{ {
_keyboard = keyboard;
ClearKeyboardState(_keyboard);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
ToggledButton.IsChecked = true; ToggledButton.IsChecked = true;
@@ -87,7 +82,6 @@ namespace Ryujinx.Ava.UI.Helpers
_isWaitingForInput = false; _isWaitingForInput = false;
ToggledButton.IsChecked = false; ToggledButton.IsChecked = false;
ClearKeyboardState(_keyboard);
if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace) if (pressedButton.HasValue && pressedButton.Value.AsHidType<Key>() == Key.BackSpace)
{ {
@@ -104,15 +98,6 @@ namespace Ryujinx.Ava.UI.Helpers
_isWaitingForInput = false; _isWaitingForInput = false;
ToggledButton.IsChecked = false; ToggledButton.IsChecked = false;
_shouldUnbind = shouldUnbind; _shouldUnbind = shouldUnbind;
ClearKeyboardState(_keyboard);
}
private static void ClearKeyboardState(IKeyboard keyboard)
{
if (keyboard is AvaloniaKeyboard avaloniaKeyboard)
{
avaloniaKeyboard.Clear();
}
} }
} }
} }

View File

@@ -5,7 +5,6 @@ using Ryujinx.Common.Configuration.Hid.Controller;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Key = Ryujinx.Input.Key;
namespace Ryujinx.Ava.UI.Helpers namespace Ryujinx.Ava.UI.Helpers
{ {
@@ -13,6 +12,79 @@ namespace Ryujinx.Ava.UI.Helpers
{ {
public static readonly KeyValueConverter Instance = new(); public static readonly KeyValueConverter Instance = new();
private static readonly Dictionary<Key, LocaleKeys> _keysMap = new()
{
{ Key.Unknown, LocaleKeys.KeyUnknown },
{ Key.ShiftLeft, LocaleKeys.KeyShiftLeft },
{ Key.ShiftRight, LocaleKeys.KeyShiftRight },
{ Key.ControlLeft, LocaleKeys.KeyControlLeft },
{ Key.ControlRight, LocaleKeys.KeyControlRight },
{ Key.AltLeft, LocaleKeys.KeyAltLeft },
{ Key.AltRight, LocaleKeys.KeyAltRight },
{ Key.WinLeft, LocaleKeys.KeyWinLeft },
{ Key.WinRight, LocaleKeys.KeyWinRight },
{ Key.Up, LocaleKeys.KeyUp },
{ Key.Down, LocaleKeys.KeyDown },
{ Key.Left, LocaleKeys.KeyLeft },
{ Key.Right, LocaleKeys.KeyRight },
{ Key.Enter, LocaleKeys.KeyEnter },
{ Key.Escape, LocaleKeys.KeyEscape },
{ Key.Space, LocaleKeys.KeySpace },
{ Key.Tab, LocaleKeys.KeyTab },
{ Key.BackSpace, LocaleKeys.KeyBackSpace },
{ Key.Insert, LocaleKeys.KeyInsert },
{ Key.Delete, LocaleKeys.KeyDelete },
{ Key.PageUp, LocaleKeys.KeyPageUp },
{ Key.PageDown, LocaleKeys.KeyPageDown },
{ Key.Home, LocaleKeys.KeyHome },
{ Key.End, LocaleKeys.KeyEnd },
{ Key.CapsLock, LocaleKeys.KeyCapsLock },
{ Key.ScrollLock, LocaleKeys.KeyScrollLock },
{ Key.PrintScreen, LocaleKeys.KeyPrintScreen },
{ Key.Pause, LocaleKeys.KeyPause },
{ Key.NumLock, LocaleKeys.KeyNumLock },
{ Key.Clear, LocaleKeys.KeyClear },
{ Key.Keypad0, LocaleKeys.KeyKeypad0 },
{ Key.Keypad1, LocaleKeys.KeyKeypad1 },
{ Key.Keypad2, LocaleKeys.KeyKeypad2 },
{ Key.Keypad3, LocaleKeys.KeyKeypad3 },
{ Key.Keypad4, LocaleKeys.KeyKeypad4 },
{ Key.Keypad5, LocaleKeys.KeyKeypad5 },
{ Key.Keypad6, LocaleKeys.KeyKeypad6 },
{ Key.Keypad7, LocaleKeys.KeyKeypad7 },
{ Key.Keypad8, LocaleKeys.KeyKeypad8 },
{ Key.Keypad9, LocaleKeys.KeyKeypad9 },
{ Key.KeypadDivide, LocaleKeys.KeyKeypadDivide },
{ Key.KeypadMultiply, LocaleKeys.KeyKeypadMultiply },
{ Key.KeypadSubtract, LocaleKeys.KeyKeypadSubtract },
{ Key.KeypadAdd, LocaleKeys.KeyKeypadAdd },
{ Key.KeypadDecimal, LocaleKeys.KeyKeypadDecimal },
{ Key.KeypadEnter, LocaleKeys.KeyKeypadEnter },
{ Key.Number0, LocaleKeys.KeyNumber0 },
{ Key.Number1, LocaleKeys.KeyNumber1 },
{ Key.Number2, LocaleKeys.KeyNumber2 },
{ Key.Number3, LocaleKeys.KeyNumber3 },
{ Key.Number4, LocaleKeys.KeyNumber4 },
{ Key.Number5, LocaleKeys.KeyNumber5 },
{ Key.Number6, LocaleKeys.KeyNumber6 },
{ Key.Number7, LocaleKeys.KeyNumber7 },
{ Key.Number8, LocaleKeys.KeyNumber8 },
{ Key.Number9, LocaleKeys.KeyNumber9 },
{ Key.Tilde, LocaleKeys.KeyTilde },
{ Key.Grave, LocaleKeys.KeyGrave },
{ Key.Minus, LocaleKeys.KeyMinus },
{ Key.Plus, LocaleKeys.KeyPlus },
{ Key.BracketLeft, LocaleKeys.KeyBracketLeft },
{ Key.BracketRight, LocaleKeys.KeyBracketRight },
{ Key.Semicolon, LocaleKeys.KeySemicolon },
{ Key.Quote, LocaleKeys.KeyQuote },
{ Key.Comma, LocaleKeys.KeyComma },
{ Key.Period, LocaleKeys.KeyPeriod },
{ Key.Slash, LocaleKeys.KeySlash },
{ Key.BackSlash, LocaleKeys.KeyBackSlash },
{ Key.Unbound, LocaleKeys.KeyUnbound },
};
private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new() private static readonly Dictionary<GamepadInputId, LocaleKeys> _gamepadInputIdMap = new()
{ {
{ GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick }, { GamepadInputId.LeftStick, LocaleKeys.GamepadLeftStick },
@@ -38,38 +110,49 @@ namespace Ryujinx.Ava.UI.Helpers
{ GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0}, { GamepadInputId.SingleRightTrigger0, LocaleKeys.GamepadSingleRightTrigger0},
{ GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1}, { GamepadInputId.SingleLeftTrigger1, LocaleKeys.GamepadSingleLeftTrigger1},
{ GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1}, { GamepadInputId.SingleRightTrigger1, LocaleKeys.GamepadSingleRightTrigger1},
{ GamepadInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound}, { GamepadInputId.Unbound, LocaleKeys.KeyUnbound},
}; };
private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new() private static readonly Dictionary<StickInputId, LocaleKeys> _stickInputIdMap = new()
{ {
{ StickInputId.Left, LocaleKeys.StickLeft}, { StickInputId.Left, LocaleKeys.StickLeft},
{ StickInputId.Right, LocaleKeys.StickRight}, { StickInputId.Right, LocaleKeys.StickRight},
{ StickInputId.Unbound, LocaleKeys.KeyboardLayout_KeyUnbound}, { StickInputId.Unbound, LocaleKeys.KeyUnbound},
}; };
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
string keyString = string.Empty; string keyString = string.Empty;
LocaleKeys localeKey;
switch (value) switch (value)
{ {
case Key key: case Key key:
if (KeyboardLayoutLocaleHelper.TryGetSemanticLabel(key, out string localizedKeyLabel)) if (_keysMap.TryGetValue(key, out localeKey))
{ {
keyString = localizedKeyLabel; if (OperatingSystem.IsMacOS())
{
localeKey = localeKey switch
{
LocaleKeys.KeyControlLeft => LocaleKeys.KeyMacControlLeft,
LocaleKeys.KeyControlRight => LocaleKeys.KeyMacControlRight,
LocaleKeys.KeyAltLeft => LocaleKeys.KeyMacAltLeft,
LocaleKeys.KeyAltRight => LocaleKeys.KeyMacAltRight,
LocaleKeys.KeyWinLeft => LocaleKeys.KeyMacWinLeft,
LocaleKeys.KeyWinRight => LocaleKeys.KeyMacWinRight,
_ => localeKey
};
}
keyString = LocaleManager.Instance[localeKey];
} }
else else
{ {
keyString = key.ToString(); keyString = key.ToString();
} }
break;
case PhysicalKey physicalKey:
keyString = PhysicalKeyLabelHelper.GetDisplayString(physicalKey);
break; break;
case GamepadInputId gamepadInputId: case GamepadInputId gamepadInputId:
LocaleKeys localeKey;
if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey)) if (_gamepadInputIdMap.TryGetValue(gamepadInputId, out localeKey))
{ {
keyString = LocaleManager.Instance[localeKey]; keyString = LocaleManager.Instance[localeKey];

View File

@@ -1,28 +0,0 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.UI.Models;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class InputDeviceNameConverter : MarkupExtension, IValueConverter
{
public static readonly InputDeviceNameConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is ValueTuple<DeviceType, string, string> device ? device.Item3 : string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Instance;
}
}
}

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