From 333651d346db30d6db0f157150d12209ca9120f9 Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 6 Sep 2019 16:58:50 +0200 Subject: [PATCH] Implement Bluetooth, Btm, Hid and Nsd services and calls. (#761) - Implement `btdrv` service (IBluetoothDriver). Implement call `InitializeBluetoothLe` for initialize events of `bt` service according to RE. - Implement `bt` service (IBluetoothUser). Implement call `RegisterBleEvent` according to RE. - Add a placeholder for the `btm` service (close #750). - Implement `btm:u` service (IBtmUser) (close #751). Implement call `GetCore` according to RE (close #752). - Implement `IBtmUserCore` and calls `AcquireBleScanEvent`, `AcquireBleConnectionEvent`, `AcquireBleServiceDiscoveryEvent` and `AcquireBleMtuConfigEvent` according to RE. - Implement `SetPalmaBoostMode` in `IHidServer` according to RE. - Add stub for `SetIsPalmaAllConnectable` in `IHidServer` because we will not support Palma devices soon. - Implement `nsd:a` and `nsd:u` service (IManager) (close #755). Implement call `ResolveEx` according to RE (close #756). Implement calls `GetSettingName`, `GetEnvironmentIdentifier`, `GetDeviceId`, `DeleteSettings`, `Resolve`, `ReadSaveDataFromFsForTest`, `WriteSaveDataToFsForTest` and `DeleteSaveDataOfFsForTest` according to RE. --- Ryujinx.Common/Logging/LogClass.cs | 4 +- .../Bluetooth/BluetoothEventManager.cs | 25 ++ .../Services/Bluetooth/IBluetoothDriver.cs | 91 ++++++ .../HOS/Services/Bluetooth/IBluetoothUser.cs | 29 ++ Ryujinx.HLE/HOS/Services/Btm/IBtm.cs | 8 + Ryujinx.HLE/HOS/Services/Btm/IBtmUser.cs | 17 ++ Ryujinx.HLE/HOS/Services/Btm/IBtmUserCore.cs | 128 +++++++++ Ryujinx.HLE/HOS/Services/Btm/ResultCode.cs | 10 + Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs | 21 ++ Ryujinx.HLE/HOS/Services/Nsd/FqdnResolver.cs | 131 +++++++++ Ryujinx.HLE/HOS/Services/Nsd/IManager.cs | 267 ++++++++++++++++++ Ryujinx.HLE/HOS/Services/Nsd/NsdSettings.cs | 9 + Ryujinx.HLE/HOS/Services/Nsd/ResultCode.cs | 19 ++ 13 files changed, 758 insertions(+), 1 deletion(-) create mode 100644 Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothEventManager.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs create mode 100644 Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs create mode 100644 Ryujinx.HLE/HOS/Services/Btm/IBtm.cs create mode 100644 Ryujinx.HLE/HOS/Services/Btm/IBtmUser.cs create mode 100644 Ryujinx.HLE/HOS/Services/Btm/IBtmUserCore.cs create mode 100644 Ryujinx.HLE/HOS/Services/Btm/ResultCode.cs create mode 100644 Ryujinx.HLE/HOS/Services/Nsd/FqdnResolver.cs create mode 100644 Ryujinx.HLE/HOS/Services/Nsd/IManager.cs create mode 100644 Ryujinx.HLE/HOS/Services/Nsd/NsdSettings.cs create mode 100644 Ryujinx.HLE/HOS/Services/Nsd/ResultCode.cs diff --git a/Ryujinx.Common/Logging/LogClass.cs b/Ryujinx.Common/Logging/LogClass.cs index 66a83b37..b056f383 100644 --- a/Ryujinx.Common/Logging/LogClass.cs +++ b/Ryujinx.Common/Logging/LogClass.cs @@ -20,6 +20,7 @@ namespace Ryujinx.Common.Logging ServiceApm, ServiceAudio, ServiceBsd, + ServiceBtm, ServiceCaps, ServiceFriend, ServiceFs, @@ -31,6 +32,7 @@ namespace Ryujinx.Common.Logging ServiceNfp, ServiceNifm, ServiceNs, + ServiceNsd, ServiceNv, ServicePctl, ServicePl, @@ -44,4 +46,4 @@ namespace Ryujinx.Common.Logging ServiceTime, ServiceVi } -} +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothEventManager.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothEventManager.cs new file mode 100644 index 00000000..9b7ca4c1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/BluetoothEventManager.cs @@ -0,0 +1,25 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + static class BluetoothEventManager + { + public static KEvent InitializeBleDebugEvent; + public static int InitializeBleDebugEventHandle; + + public static KEvent UnknownBleDebugEvent; + public static int UnknownBleDebugEventHandle; + + public static KEvent RegisterBleDebugEvent; + public static int RegisterBleDebugEventHandle; + + public static KEvent InitializeBleEvent; + public static int InitializeBleEventHandle; + + public static KEvent UnknownBleEvent; + public static int UnknownBleEventHandle; + + public static KEvent RegisterBleEvent; + public static int RegisterBleEventHandle; + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs new file mode 100644 index 00000000..4cee67cd --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothDriver.cs @@ -0,0 +1,91 @@ +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Set; +using System; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("btdrv")] + class IBluetoothDriver : IpcService + { + private string _unknownLowEnergy; + + public IBluetoothDriver(ServiceCtx context) { } + + [Command(46)] + // InitializeBluetoothLe() -> handle + public ResultCode InitializeBluetoothLe(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + if (BluetoothEventManager.InitializeBleDebugEventHandle == 0) + { + BluetoothEventManager.InitializeBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleDebugEvent.ReadableEvent, out BluetoothEventManager.InitializeBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleDebugEventHandle == 0) + { + BluetoothEventManager.UnknownBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleDebugEvent.ReadableEvent, out BluetoothEventManager.UnknownBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleDebugEventHandle == 0) + { + BluetoothEventManager.RegisterBleDebugEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleDebugEvent.ReadableEvent, out BluetoothEventManager.RegisterBleDebugEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + } + else + { + _unknownLowEnergy = "low_energy"; + + if (BluetoothEventManager.InitializeBleEventHandle == 0) + { + BluetoothEventManager.InitializeBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.InitializeBleEvent.ReadableEvent, out BluetoothEventManager.InitializeBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.UnknownBleEventHandle == 0) + { + BluetoothEventManager.UnknownBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.UnknownBleEvent.ReadableEvent, out BluetoothEventManager.UnknownBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + if (BluetoothEventManager.RegisterBleEventHandle == 0) + { + BluetoothEventManager.RegisterBleEvent = new KEvent(context.Device.System); + + if (context.Process.HandleTable.GenerateHandle(BluetoothEventManager.RegisterBleEvent.ReadableEvent, out BluetoothEventManager.RegisterBleEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs new file mode 100644 index 00000000..d070e18f --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Bluetooth/IBluetoothUser.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Services.Set; + +namespace Ryujinx.HLE.HOS.Services.Bluetooth +{ + [Service("bt")] + class IBluetoothUser : IpcService + { + public IBluetoothUser(ServiceCtx context) { } + + [Command(9)] + // RegisterBleEvent(pid) -> handle + public ResultCode RegisterBleEvent(ServiceCtx context) + { + NxSettings.Settings.TryGetValue("bluetooth_debug!skip_boot", out object debugMode); + + if ((bool)debugMode) + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleDebugEventHandle); + } + else + { + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(BluetoothEventManager.RegisterBleEventHandle); + } + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Btm/IBtm.cs b/Ryujinx.HLE/HOS/Services/Btm/IBtm.cs new file mode 100644 index 00000000..947fee14 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Btm/IBtm.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Btm +{ + [Service("btm")] + class IBtm : IpcService + { + public IBtm(ServiceCtx context) { } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Btm/IBtmUser.cs b/Ryujinx.HLE/HOS/Services/Btm/IBtmUser.cs new file mode 100644 index 00000000..133ef0cc --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Btm/IBtmUser.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Btm +{ + [Service("btm:u")] + class IBtmUser : IpcService + { + public IBtmUser(ServiceCtx context) { } + + [Command(0)] // 5.0.0+ + // GetCore() -> object + public ResultCode GetCore(ServiceCtx context) + { + MakeObject(context, new IBtmUserCore()); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Btm/IBtmUserCore.cs b/Ryujinx.HLE/HOS/Services/Btm/IBtmUserCore.cs new file mode 100644 index 00000000..14b7b5f3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Btm/IBtmUserCore.cs @@ -0,0 +1,128 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; + +namespace Ryujinx.HLE.HOS.Services.Btm +{ + class IBtmUserCore : IpcService + { + public KEvent _bleScanEvent; + public int _bleScanEventHandle; + + public KEvent _bleConnectionEvent; + public int _bleConnectionEventHandle; + + public KEvent _bleServiceDiscoveryEvent; + public int _bleServiceDiscoveryEventHandle; + + public KEvent _bleMtuConfigEvent; + public int _bleMtuConfigEventHandle; + + public IBtmUserCore() { } + + [Command(0)] // 5.0.0+ + // AcquireBleScanEvent() -> (byte<1>, handle) + public ResultCode AcquireBleScanEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleScanEventHandle == 0) + { + _bleScanEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleScanEvent.ReadableEvent, out _bleScanEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleScanEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(17)] // 5.0.0+ + // AcquireBleConnectionEvent() -> (byte<1>, handle) + public ResultCode AcquireBleConnectionEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleConnectionEventHandle == 0) + { + _bleConnectionEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleConnectionEvent.ReadableEvent, out _bleConnectionEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleConnectionEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(26)] // 5.0.0+ + // AcquireBleServiceDiscoveryEvent() -> (byte<1>, handle) + public ResultCode AcquireBleServiceDiscoveryEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleServiceDiscoveryEventHandle == 0) + { + _bleServiceDiscoveryEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleServiceDiscoveryEvent.ReadableEvent, out _bleServiceDiscoveryEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleServiceDiscoveryEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + + [Command(33)] // 5.0.0+ + // AcquireBleMtuConfigEvent() -> (byte<1>, handle) + public ResultCode AcquireBleMtuConfigEvent(ServiceCtx context) + { + KernelResult result = KernelResult.Success; + + if (_bleMtuConfigEventHandle == 0) + { + _bleMtuConfigEvent = new KEvent(context.Device.System); + + result = context.Process.HandleTable.GenerateHandle(_bleMtuConfigEvent.ReadableEvent, out _bleMtuConfigEventHandle); + + if (result != KernelResult.Success) + { + // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not. + Logger.PrintError(LogClass.ServiceBsd, "Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_bleMtuConfigEventHandle); + + context.ResponseData.Write(result == KernelResult.Success ? 1 : 0); + + return ResultCode.Success; + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Btm/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Btm/ResultCode.cs new file mode 100644 index 00000000..b222fdc8 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Btm/ResultCode.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Btm +{ + enum ResultCode + { + ModuleId = 143, + ErrorCodeShift = 9, + + Success = 0 + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs index 7d77ff32..9adc08c1 100644 --- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs +++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs @@ -1413,6 +1413,27 @@ namespace Ryujinx.HLE.HOS.Services.Hid return ResultCode.Success; } + [Command(522)] // 5.1.0+ + // SetIsPalmaAllConnectable(nn::applet::AppletResourceUserId, bool, pid) + public ResultCode SetIsPalmaAllConnectable(ServiceCtx context) + { + long appletResourceUserId = context.RequestData.ReadInt64(); + long unknownBool = context.RequestData.ReadInt64(); + + Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknownBool }); + + return ResultCode.Success; + } + + [Command(525)] // 5.1.0+ + // SetPalmaBoostMode(bool) + public ResultCode SetPalmaBoostMode(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + [Command(1000)] // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId) public ResultCode SetNpadCommunicationMode(ServiceCtx context) diff --git a/Ryujinx.HLE/HOS/Services/Nsd/FqdnResolver.cs b/Ryujinx.HLE/HOS/Services/Nsd/FqdnResolver.cs new file mode 100644 index 00000000..10b6433a --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nsd/FqdnResolver.cs @@ -0,0 +1,131 @@ +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nsd +{ + class FqdnResolver + { + private const string _dummyAddress = "unknown.dummy.nintendo.net"; + + private NsdSettings _nsdSettings; + + public FqdnResolver(NsdSettings nsdSettings) + { + _nsdSettings = nsdSettings; + } + + public ResultCode GetSettingName(ServiceCtx context, out string settingName) + { + if (_nsdSettings.TestMode) + { + settingName = ""; + + return ResultCode.NotImplemented; + } + else + { + settingName = ""; + + if (true) // TODO: Determine field (struct + 0x2C) + { + settingName = _nsdSettings.Environment; + + return ResultCode.Success; + } + + return ResultCode.NullOutputObject; + } + } + + public ResultCode GetEnvironmentIdentifier(ServiceCtx context, out string identifier) + { + if (_nsdSettings.TestMode) + { + identifier = "rre"; + + return ResultCode.NotImplemented; + } + else + { + identifier = _nsdSettings.Environment; + } + + return ResultCode.Success; + } + + public ResultCode Resolve(ServiceCtx context, string address, out string resolvedAddress) + { + if (address != "api.sect.srv.nintendo.net" || address != "conntest.nintendowifi.net") + { + // TODO: Load Environment from the savedata. + address = address.Replace("%", _nsdSettings.Environment); + + resolvedAddress = ""; + + if (_nsdSettings == null) + { + return ResultCode.SettingsNotInitialized; + } + + if (!_nsdSettings.Initialized) + { + return ResultCode.SettingsNotLoaded; + } + + switch (address) + { + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb-api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "api.accounts.nintendo.com": // dp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + case "e97b8a9d672e4ce4845ec6947cd66ef6-sb.accounts.nintendo.com": // lp1 environment + resolvedAddress = "e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com"; + break; + case "accounts.nintendo.com": // lp1 environment + resolvedAddress = "e0d67c509fb203858ebcb2fe3f88c2aa.baas.nintendo.com"; + break; + /* + // TODO: Determine fields of the struct. + case "": // + 0xEB8 || + 0x2BE8 + resolvedAddress = ""; // + 0xEB8 + 0x300 || + 0x2BE8 + 0x300 + break; + */ + default: + resolvedAddress = address; + break; + } + } + else + { + resolvedAddress = address; + } + + return ResultCode.Success; + } + + public ResultCode ResolveEx(ServiceCtx context, out ResultCode resultCode, out string resolvedAddress) + { + (long inputPosition, long inputSize) = context.Request.GetBufferType0x21(); + + byte[] addressBuffer = context.Memory.ReadBytes(inputPosition, inputSize); + string address = Encoding.UTF8.GetString(addressBuffer); + + resultCode = Resolve(context, address, out resolvedAddress); + + if (resultCode != ResultCode.Success) + { + resolvedAddress = _dummyAddress; + } + + if (_nsdSettings.TestMode) + { + return ResultCode.Success; + } + else + { + return resultCode; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nsd/IManager.cs b/Ryujinx.HLE/HOS/Services/Nsd/IManager.cs new file mode 100644 index 00000000..5dac7cf4 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nsd/IManager.cs @@ -0,0 +1,267 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Services.Set; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Nsd +{ + [Service("nsd:a")] // Max sessions: 5 + [Service("nsd:u")] // Max sessions: 20 + class IManager : IpcService + { + private NsdSettings _nsdSettings; + private FqdnResolver _fqdnResolver; + + private bool _isInitialized = false; + + public IManager(ServiceCtx context) + { + // TODO: Load nsd settings through the savedata 0x80000000000000B0 (nsdsave:/). + + NxSettings.Settings.TryGetValue("nsd!test_mode", out object testMode); + + _nsdSettings = new NsdSettings + { + Initialized = true, + TestMode = (bool)testMode + }; + + _fqdnResolver = new FqdnResolver(_nsdSettings); + + _isInitialized = true; + } + + [Command(10)] + // GetSettingName() -> buffer, 0x16> + public ResultCode GetSettingName(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetSettingName(context, out string settingName); + + if (result == ResultCode.Success) + { + byte[] settingNameBuffer = Encoding.UTF8.GetBytes(settingName + '\0'); + + context.Memory.WriteBytes(outputPosition, settingNameBuffer); + } + + return result; + } + + [Command(11)] + // GetEnvironmentIdentifier() -> buffer, 0x16> + public ResultCode GetEnvironmentIdentifier(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.GetEnvironmentIdentifier(context, out string identifier); + + if (result == ResultCode.Success) + { + byte[] identifierBuffer = Encoding.UTF8.GetBytes(identifier + '\0'); + + context.Memory.WriteBytes(outputPosition, identifierBuffer); + } + + return result; + } + + [Command(12)] + // GetDeviceId() -> bytes<0x10, 1> + public ResultCode GetDeviceId(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + return ResultCode.Success; + } + + [Command(13)] + // DeleteSettings(u32) + public ResultCode DeleteSettings(ServiceCtx context) + { + uint unknown = context.RequestData.ReadUInt32(); + + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + if (unknown > 1) + { + return ResultCode.InvalidArgument; + } + + if (unknown == 1) + { + NxSettings.Settings.TryGetValue("nsd!environment_identifier", out object environmentIdentifier); + + if ((string)environmentIdentifier == _nsdSettings.Environment) + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + else + { + // TODO: Mount the savedata file and return ResultCode. + } + } + else + { + // TODO: Call nn::fs::DeleteSystemFile() to delete the savedata file and return ResultCode. + } + + return ResultCode.Success; + } + + [Command(14)] + // ImportSettings(u32, buffer) -> buffer + public ResultCode ImportSettings(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(15)] + // Unknown(bytes<1>) + public ResultCode Unknown(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(20)] + // Resolve(buffer, 0x15>) -> buffer, 0x16> + public ResultCode Resolve(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + return result; + } + + [Command(21)] + // ResolveEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode ResolveEx(ServiceCtx context) + { + (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(); + + ResultCode result = _fqdnResolver.ResolveEx(context, out ResultCode errorCode, out string resolvedAddress); + + byte[] resolvedAddressBuffer = Encoding.UTF8.GetBytes(resolvedAddress + '\0'); + + context.Memory.WriteBytes(outputPosition, resolvedAddressBuffer); + + context.ResponseData.Write((int)errorCode); + + return result; + } + + [Command(30)] + // GetNasServiceSetting(buffer, 0x15>) -> buffer, 0x16> + public ResultCode GetNasServiceSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(31)] + // GetNasServiceSettingEx(buffer, 0x15>) -> (u32, buffer, 0x16>) + public ResultCode GetNasServiceSettingEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(40)] + // GetNasRequestFqdn() -> buffer, 0x16> + public ResultCode GetNasRequestFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(41)] + // GetNasRequestFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasRequestFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(42)] + // GetNasApiFqdn() -> buffer, 0x16> + public ResultCode GetNasApiFqdn(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(43)] + // GetNasApiFqdnEx() -> (u32, buffer, 0x16>) + public ResultCode GetNasApiFqdnEx(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(50)] + // GetCurrentSetting() -> buffer, 0x16> + public ResultCode GetCurrentSetting(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + + [Command(60)] + // ReadSaveDataFromFsForTest() -> buffer, 0x16> + public ResultCode ReadSaveDataFromFsForTest(ServiceCtx context) + { + if (!_isInitialized) + { + return ResultCode.ServiceNotInitialized; + } + + // TODO: Call nn::nsd::detail::fs::ReadSaveDataWithOffset() at offset 0 to write the + // whole savedata inside the buffer. + + Logger.PrintStub(LogClass.ServiceNsd); + + return ResultCode.Success; + } + + [Command(61)] + // WriteSaveDataToFsForTest(buffer, 0x15>) + public ResultCode WriteSaveDataToFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(62)] + // DeleteSaveDataOfFsForTest() + public ResultCode DeleteSaveDataOfFsForTest(ServiceCtx context) + { + // NOTE: Stubbed in system module. + + if (_isInitialized) + { + return ResultCode.NotImplemented; + } + else + { + return ResultCode.ServiceNotInitialized; + } + } + + [Command(63)] + // IsChangeEnvironmentIdentifierDisabled() -> bytes<1> + public ResultCode IsChangeEnvironmentIdentifierDisabled(ServiceCtx context) + { + throw new ServiceNotImplementedException(context); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nsd/NsdSettings.cs b/Ryujinx.HLE/HOS/Services/Nsd/NsdSettings.cs new file mode 100644 index 00000000..42739df0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nsd/NsdSettings.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nsd +{ + class NsdSettings + { + public bool Initialized; + public bool TestMode; + public string Environment = "lp1"; // or "dd1" if devkit. + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nsd/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Nsd/ResultCode.cs new file mode 100644 index 00000000..27584eb1 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nsd/ResultCode.cs @@ -0,0 +1,19 @@ +namespace Ryujinx.HLE.HOS.Services.Nsd +{ + enum ResultCode + { + ModuleId = 141, + ErrorCodeShift = 9, + + Success = 0, + + NotImplemented = ( 1 << ErrorCodeShift) | ModuleId, + InvalidObject1 = ( 3 << ErrorCodeShift) | ModuleId, + InvalidObject2 = ( 4 << ErrorCodeShift) | ModuleId, + NullOutputObject = ( 5 << ErrorCodeShift) | ModuleId, + SettingsNotLoaded = ( 6 << ErrorCodeShift) | ModuleId, + InvalidArgument = ( 8 << ErrorCodeShift) | ModuleId, + SettingsNotInitialized = ( 10 << ErrorCodeShift) | ModuleId, + ServiceNotInitialized = (400 << ErrorCodeShift) | ModuleId, + } +} \ No newline at end of file