mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2026-06-05 03:49:17 +00:00
Merge branch ryujinx:master into master
This commit is contained in:
@@ -41,7 +41,7 @@
|
|||||||
<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.126" />
|
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.129" />
|
||||||
<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.1" />
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -700,6 +700,56 @@
|
|||||||
"zh_TW": "掃描 Amiibo"
|
"zh_TW": "掃描 Amiibo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ID": "MenuBarActionsScanSkylander",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "فحص Skylander",
|
||||||
|
"de_DE": "Skylander scannen",
|
||||||
|
"el_GR": "Σάρωση Skylander",
|
||||||
|
"en_US": "Scan A Skylander",
|
||||||
|
"es_ES": "Escanear Skylander",
|
||||||
|
"fr_FR": "Scanner un Skylander",
|
||||||
|
"he_IL": "סרוק אמיבו",
|
||||||
|
"it_IT": "Scansiona un Skylander",
|
||||||
|
"ja_JP": "Skylander をスキャン",
|
||||||
|
"ko_KR": "Skylander 스캔",
|
||||||
|
"no_NO": "Skann en Skylander",
|
||||||
|
"pl_PL": "Skanuj Skylander",
|
||||||
|
"pt_BR": "Escanear um Skylander",
|
||||||
|
"ru_RU": "Сканировать Skylander",
|
||||||
|
"sv_SE": "Skanna en Skylander",
|
||||||
|
"th_TH": "สแกนหา Skylander",
|
||||||
|
"tr_TR": "Bir Skylander Tara",
|
||||||
|
"uk_UA": "Сканувати Skylander",
|
||||||
|
"zh_CN": "扫描 Skylander",
|
||||||
|
"zh_TW": "掃描 Skylander"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "MenuBarActionsRemoveSkylander",
|
||||||
|
"Translations": {
|
||||||
|
"ar_SA": "إزالة Skylander",
|
||||||
|
"de_DE": "Skylander entfernen",
|
||||||
|
"el_GR": "Αφαίρεση Skylander",
|
||||||
|
"en_US": "Remove Skylander",
|
||||||
|
"es_ES": "Eliminar Skylander",
|
||||||
|
"fr_FR": "Supprimer un Skylander",
|
||||||
|
"he_IL": "הסר Skylander",
|
||||||
|
"it_IT": "Rimuovi Skylander",
|
||||||
|
"ja_JP": "Skylander を削除",
|
||||||
|
"ko_KR": "Skylander 제거",
|
||||||
|
"no_NO": "Fjern Skylander",
|
||||||
|
"pl_PL": "Usuń Skylander",
|
||||||
|
"pt_BR": "Remover um Skylander",
|
||||||
|
"ru_RU": "Удалить Skylander",
|
||||||
|
"sv_SE": "Ta bort Skylander",
|
||||||
|
"th_TH": "ลบ Skylander",
|
||||||
|
"tr_TR": "Skylander'ı Kaldır",
|
||||||
|
"uk_UA": "Видалити Skylander",
|
||||||
|
"zh_CN": "移除 Skylander",
|
||||||
|
"zh_TW": "移除 Skylander"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ID": "MenuBarActionsScanAmiiboBin",
|
"ID": "MenuBarActionsScanAmiiboBin",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
|||||||
@@ -2050,7 +2050,9 @@
|
|||||||
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
|
||||||
@@ -2638,6 +2640,7 @@
|
|||||||
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
|
||||||
@@ -3307,6 +3310,7 @@
|
|||||||
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
|
||||||
|
|||||||
|
@@ -107,12 +107,12 @@ namespace Ryujinx.BuildValidationTasks
|
|||||||
{
|
{
|
||||||
locale.Translations[langCode] = string.Empty;
|
locale.Translations[langCode] = string.Empty;
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Console.WriteLine(
|
||||||
$"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ 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
|
||||||
@@ -193,9 +194,15 @@ 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
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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;
|
||||||
@@ -66,6 +67,8 @@ 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; }
|
||||||
@@ -132,6 +135,7 @@ 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.
|
||||||
@@ -372,6 +376,15 @@ 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;
|
||||||
@@ -389,6 +402,53 @@ 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();
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||||
|
{
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
NonInitialized,
|
||||||
|
Initialized,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,5 +20,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
|||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
|
Polish,
|
||||||
|
Thai,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ 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; }
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ namespace Ryujinx.HLE.HOS.SystemState
|
|||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
|
Polish,
|
||||||
|
Thai,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
public Array16<ApplicationTitle> Title;
|
/// <summary>
|
||||||
|
/// 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;
|
||||||
@@ -58,7 +65,10 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
|||||||
public RepairFlagValue RepairFlag;
|
public RepairFlagValue RepairFlag;
|
||||||
public byte ProgramIndex;
|
public byte ProgramIndex;
|
||||||
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
public RequiredNetworkServiceLicenseOnLaunchValue RequiredNetworkServiceLicenseOnLaunchFlag;
|
||||||
public Array4<byte> Reserved3214;
|
public byte ApplicationErrorCodePrefix;
|
||||||
|
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;
|
||||||
@@ -74,6 +84,47 @@ 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
|
||||||
{
|
{
|
||||||
@@ -130,6 +181,8 @@ 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
|
||||||
@@ -302,5 +355,11 @@ namespace Ryujinx.Horizon.Sdk.Ns
|
|||||||
Deny = 0,
|
Deny = 0,
|
||||||
Allow = 1,
|
Allow = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum TitleCompressionValue : byte
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
Enable = 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ namespace Ryujinx.Ava.Input
|
|||||||
AvaKey.OemComma,
|
AvaKey.OemComma,
|
||||||
AvaKey.OemPeriod,
|
AvaKey.OemPeriod,
|
||||||
AvaKey.OemQuestion,
|
AvaKey.OemQuestion,
|
||||||
AvaKey.OemBackslash,
|
AvaKey.OemPipe,
|
||||||
|
|
||||||
// NOTE: invalid
|
// NOTE: invalid
|
||||||
AvaKey.None
|
AvaKey.None
|
||||||
|
|||||||
@@ -28,11 +28,6 @@
|
|||||||
<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.
|
||||||
|
|||||||
@@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.Systems.AppLibrary
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(data.Name))
|
if (string.IsNullOrWhiteSpace(data.Name))
|
||||||
{
|
{
|
||||||
foreach (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
foreach (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 (ref readonly ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
foreach (ApplicationControlProperty.ApplicationTitle controlTitle in controlData.Title)
|
||||||
{
|
{
|
||||||
if (!controlTitle.PublisherString.IsEmpty())
|
if (!controlTitle.PublisherString.IsEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ namespace Ryujinx.Ava.Systems.Configuration.System
|
|||||||
SimplifiedChinese,
|
SimplifiedChinese,
|
||||||
TraditionalChinese,
|
TraditionalChinese,
|
||||||
BrazilianPortuguese,
|
BrazilianPortuguese,
|
||||||
|
Polish,
|
||||||
|
Thai,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class LanguageEnumHelper
|
public static class LanguageEnumHelper
|
||||||
|
|||||||
@@ -370,6 +370,39 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
||||||
|
|
||||||
|
public bool IsSkylanderRequested
|
||||||
|
{
|
||||||
|
get => field && _isGameRunning;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasSkylander
|
||||||
|
{
|
||||||
|
get => field && _isGameRunning;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShowSkylanderActions
|
||||||
|
{
|
||||||
|
get => field && _isGameRunning;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool ShowLoadProgress
|
public bool ShowLoadProgress
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -1864,6 +1897,46 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task OpenSkylanderWindow()
|
||||||
|
{
|
||||||
|
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
|
||||||
|
{
|
||||||
|
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||||
|
new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||||
|
FileTypeFilter = new List<FilePickerFileType>
|
||||||
|
{
|
||||||
|
new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats])
|
||||||
|
{
|
||||||
|
Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (result.HasValue)
|
||||||
|
{
|
||||||
|
// Open reading stream from the first file.
|
||||||
|
await using var stream = await result.Value.OpenReadAsync();
|
||||||
|
using var streamReader = new BinaryReader(stream);
|
||||||
|
// Reads all the content of file as a text.
|
||||||
|
byte[] data = new byte[1024];
|
||||||
|
var count = streamReader.Read(data, 0, 1024);
|
||||||
|
if (count < 1024)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppHost.Device.System.ScanSkylander(deviceId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveSkylander()
|
||||||
|
{
|
||||||
|
AppHost.Device.System.RemoveSkylander();
|
||||||
|
}
|
||||||
|
|
||||||
public void ReloadRenderDocApi()
|
public void ReloadRenderDocApi()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -184,6 +184,22 @@
|
|||||||
IsVisible="{Binding CanScanAmiiboBinaries}"
|
IsVisible="{Binding CanScanAmiiboBinaries}"
|
||||||
InputGesture="Ctrl + B"
|
InputGesture="Ctrl + B"
|
||||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenSkylanderWindow}"
|
||||||
|
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
|
||||||
|
Header="{ext:Locale MenuBarActionsScanSkylander}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-cube}"
|
||||||
|
IsVisible="{Binding ShowSkylanderActions}"
|
||||||
|
InputGesture="Ctrl + S"
|
||||||
|
IsEnabled="{Binding IsSkylanderRequested}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding RemoveSkylander}"
|
||||||
|
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
|
||||||
|
Header="{ext:Locale MenuBarActionsRemoveSkylander}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-cube}"
|
||||||
|
IsVisible="{Binding ShowSkylanderActions}"
|
||||||
|
InputGesture="Ctrl + D"
|
||||||
|
IsEnabled="{Binding HasSkylander}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding TakeScreenshot}"
|
Command="{Binding TakeScreenshot}"
|
||||||
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
||||||
|
|||||||
@@ -193,6 +193,20 @@ namespace Ryujinx.Ava.UI.Views.Main
|
|||||||
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem)
|
||||||
|
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
|
||||||
|
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem)
|
||||||
|
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
|
||||||
|
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InstallFileTypes()
|
private async Task InstallFileTypes()
|
||||||
{
|
{
|
||||||
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();
|
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Management;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
|||||||
{
|
{
|
||||||
internal WindowsSystemInfo()
|
internal WindowsSystemInfo()
|
||||||
{
|
{
|
||||||
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
|
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameFromRegistry()} ; {LogicalCoreCount} logical";
|
||||||
(RamTotal, RamAvailable) = GetMemoryStats();
|
(RamTotal, RamAvailable) = GetMemoryStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,25 +27,26 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
|||||||
return (0, 0);
|
return (0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetCpuNameWMI()
|
private static string GetCpuNameFromRegistry()
|
||||||
{
|
{
|
||||||
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
|
try
|
||||||
|
|
||||||
if (cpuObjs != null)
|
|
||||||
{
|
{
|
||||||
foreach (ManagementBaseObject cpuObj in cpuObjs)
|
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0");
|
||||||
{
|
|
||||||
return cpuObj["Name"].ToString().Trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
|
return key?.GetValue("ProcessorNameString")?.ToString()?.Trim();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, $"Registry CPU name lookup failed: {ex.Message}");
|
||||||
|
|
||||||
|
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")?.Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
private struct MemoryStatusEx
|
private struct MemoryStatusEx()
|
||||||
{
|
{
|
||||||
public uint Length;
|
public uint Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
|
||||||
public uint MemoryLoad;
|
public uint MemoryLoad;
|
||||||
public ulong TotalPhys;
|
public ulong TotalPhys;
|
||||||
public ulong AvailPhys;
|
public ulong AvailPhys;
|
||||||
@@ -55,33 +55,10 @@ namespace Ryujinx.Ava.Utilities.SystemInfo
|
|||||||
public ulong TotalVirtual;
|
public ulong TotalVirtual;
|
||||||
public ulong AvailVirtual;
|
public ulong AvailVirtual;
|
||||||
public ulong AvailExtendedVirtual;
|
public ulong AvailExtendedVirtual;
|
||||||
|
|
||||||
public MemoryStatusEx()
|
|
||||||
{
|
|
||||||
Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
|
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
|
||||||
|
|
||||||
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new ManagementObjectSearcher(scope, query).Get();
|
|
||||||
}
|
|
||||||
catch (PlatformNotSupportedException ex)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
|
||||||
}
|
|
||||||
catch (COMException ex)
|
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user