diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs
index a4117580e..744f12548 100644
--- a/src/Ryujinx.Common/Logging/LogClass.cs
+++ b/src/Ryujinx.Common/Logging/LogClass.cs
@@ -39,6 +39,7 @@ namespace Ryujinx.Common.Logging
ServiceFs,
ServiceHid,
ServiceIrs,
+ ServiceLbl,
ServiceLdn,
ServiceLdr,
ServiceLm,
@@ -50,11 +51,14 @@ namespace Ryujinx.Common.Logging
ServiceNgct,
ServiceNifm,
ServiceNim,
+ ServiceNotif,
ServiceNs,
ServiceNsd,
ServiceNtc,
ServiceNv,
+ ServiceNpns,
ServiceOlsc,
+ ServiceOvln,
ServicePctl,
ServicePcv,
ServicePl,
@@ -72,6 +76,6 @@ namespace Ryujinx.Common.Logging
TamperMachine,
UI,
Vic,
- XCIFileTrimmer
+ XCIFileTrimmer,
}
}
diff --git a/src/Ryujinx.Common/Utilities/UInt128Utils.cs b/src/Ryujinx.Common/Utilities/UInt128Utils.cs
index 23afaa3a1..e9f581b9f 100644
--- a/src/Ryujinx.Common/Utilities/UInt128Utils.cs
+++ b/src/Ryujinx.Common/Utilities/UInt128Utils.cs
@@ -16,5 +16,10 @@ namespace Ryujinx.Common.Utilities
public static UInt128 NextUInt128(this Random rand) =>
new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
+
+ public static UInt128 CreateRandom()
+ {
+ return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.NextInt64());
+ }
}
}
diff --git a/src/Ryujinx.HLE/HLEConfiguration.cs b/src/Ryujinx.HLE/HLEConfiguration.cs
index 0b7ae3974..2ec1d03f5 100644
--- a/src/Ryujinx.HLE/HLEConfiguration.cs
+++ b/src/Ryujinx.HLE/HLEConfiguration.cs
@@ -6,9 +6,12 @@ using Ryujinx.Graphics.GAL;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.HLE.UI;
using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
namespace Ryujinx.HLE
{
@@ -195,10 +198,17 @@ namespace Ryujinx.HLE
/// This cannot be changed after instantiation.
public EnabledDirtyHack[] Hacks { internal get; set; }
+ ///
+ /// The list of title ids found byApplicationLibrary.
+ ///
+ /// This cannot be changed after instantiation.
+ internal readonly IImmutableList Titles;
+
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager,
AccountManager accountManager,
+ IImmutableList titles,
UserChannelPersistence userChannelPersistence,
IRenderer gpuRenderer,
IHardwareDeviceDriver audioDeviceDriver,
@@ -231,6 +241,7 @@ namespace Ryujinx.HLE
LibHacHorizonManager = libHacHorizonManager;
AccountManager = accountManager;
ContentManager = contentManager;
+ Titles = titles;
UserChannelPersistence = userChannelPersistence;
GpuRenderer = gpuRenderer;
AudioDeviceDriver = audioDeviceDriver;
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IAdministrator.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IAdministrator.cs
new file mode 100644
index 000000000..9f2e29710
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IAdministrator.cs
@@ -0,0 +1,32 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
+{
+ class IAdministrator : IpcService
+ {
+ public IAdministrator(UserId userId)
+ {
+
+ }
+
+ [CommandCmif(0)]
+ // CheckAvailability()
+ public ResultCode CheckAvailability(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAcc);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(250)]
+ // IsLinkedWithNintendoAccount() -> bool
+ public ResultCode IsLinkedWithNintendoAccount(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAcc);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs
index 8510837b2..07ef2eb6e 100644
--- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IManagerForSystemService.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Common.Logging;
+
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IManagerForSystemService : IpcService
@@ -43,5 +45,13 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
return _managerServer.LoadIdTokenCache(context);
}
+
+ [CommandCmif(143)]
+ // GetNetworkServiceLicenseCacheEx()
+ public ResultCode GetNetworkServiceLicenseCacheEx(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAcc);
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs
index 5d5d0dd69..2ad350029 100644
--- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/AccountService/IProfileEditor.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Common.Logging;
+
namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
class IProfileEditor : IpcService
@@ -36,6 +38,14 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
{
return _profileServer.LoadImage(context);
}
+
+ [CommandCmif(30)]
+ // GetImageId()
+ public ResultCode GetImageId(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
[CommandCmif(100)]
// Store(nn::account::profile::ProfileBase, buffer)
diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs
index 74c135aed..bb959fbf8 100644
--- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/IAccountServiceForAdministrator.cs
@@ -125,5 +125,24 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return ResultCode.Success;
}
+
+ [CommandCmif(250)]
+ // GetBaasAccountAdministrator(nn::account::Uid) -> object
+ public ResultCode GetBaasAccountAdministrator(ServiceCtx context)
+ {
+ ResultCode resultCode = _applicationServiceServer.CheckUserId(context, out UserId userId);
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ MakeObject(context, new IAdministrator(userId));
+
+ // Doesn't occur in our case.
+ // return ResultCode.NullObject;
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemProcessCommonFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemProcessCommonFunctions.cs
new file mode 100644
index 000000000..637a50da7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemProcessCommonFunctions.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
+{
+ class ISystemProcessCommonFunctions : IpcService
+ {
+
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
index 3bb703724..84c2f5cca 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs
@@ -155,6 +155,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+
+ [CommandCmif(31)]
+ [CommandCmif(32)]
+ // GetReaderLockAccessorEx(u32) -> object
+ public ResultCode GetReaderLockAccessorEx(ServiceCtx context)
+ {
+ int lockId = context.RequestData.ReadInt32();
+
+ MakeObject(context, new ILockAccessor(lockId, context.Device.System));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ return ResultCode.Success;
+ }
[CommandCmif(50)] // 3.0.0+
// IsVrModeEnabled() -> b8
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICradleFirmwareUpdater.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICradleFirmwareUpdater.cs
new file mode 100644
index 000000000..27a3fce70
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICradleFirmwareUpdater.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy
+{
+ class ICradleFirmwareUpdater : IpcService
+ {
+ [CommandCmif(1)]
+ // Finish()
+ public ResultCode Finish(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // GetUpdateDeviceStatus()
+ public ResultCode GetUpdateDeviceStatus(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetUpdateProgress()
+ public ResultCode GetUpdateProgress(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs
index a26140871..6cf0e6819 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs
@@ -42,5 +42,13 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
Logger.Stub?.PrintStub(LogClass.ServiceAm);
return ResultCode.Success;
}
+
+ [CommandCmif(30)]
+ // OpenCradleFirmwareUpdater() -> ICradleFirmwareUpdater.
+ public ResultCode OpenCradleFirmwareUpdater(ServiceCtx context)
+ {
+ MakeObject(context, new ICradleFirmwareUpdater());
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs
index 78f47e0e9..fdac7fca3 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs
@@ -26,6 +26,23 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+ [CommandCmif(11)]
+ // LockForeground()
+ public ResultCode LockForeground(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ return ResultCode.Success;
+ }
+ [CommandCmif(12)]
+ // UnlockForeground()
+ public ResultCode UnlockForeground(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(21)]
// GetPopFromGeneralChannelEvent() -> handle
public ResultCode GetPopFromGeneralChannelEvent(ServiceCtx context)
@@ -44,5 +61,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+
+ [CommandCmif(31)]
+ // GetWriterLockAccessorEx(i32) -> object
+ public ResultCode GetWriterLockAccessorEx(ServiceCtx context)
+ {
+ int lockId = context.RequestData.ReadInt32();
+
+ MakeObject(context, new ILockAccessor(lockId, context.Device.System));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
index 71b4b482a..2250867c8 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs
@@ -276,6 +276,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+ [CommandCmif(60)]
+ // OverrideAutoSleepTimeAndDimmingTime()
+ public ResultCode OverrideAutoSleepTimeAndDimmingTime(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
+
[CommandCmif(62)]
// SetIdleTimeDetectionExtension(u32)
public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context)
@@ -356,6 +364,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+ [CommandCmif(72)]
+ // SetInputDetectionPolicy()
+ public ResultCode SetInputDetectionPolicy(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ return ResultCode.Success;
+ }
+
[CommandCmif(80)] // 4.0.0+
// SetWirelessPriorityMode(s32 wireless_priority_mode)
public ResultCode SetWirelessPriorityMode(ServiceCtx context)
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs
index b8741b22b..df0d72deb 100644
--- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs
@@ -35,5 +35,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
return ResultCode.Success;
}
+
+ [CommandCmif(450)]
+ // ISystemProcessCommonFunctions() -> object
+ public ResultCode ISystemProcessCommonFunctions(ServiceCtx context)
+ {
+ MakeObject(context, new ISystemProcessCommonFunctions());
+ return ResultCode.Success;
+ }
+
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/ILockAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/ILockAccessor.cs
new file mode 100644
index 000000000..dcf426213
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/ILockAccessor.cs
@@ -0,0 +1,94 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
+{
+ class ILockAccessor : IpcService
+ {
+ private readonly int _lockId;
+ private bool _isLocked;
+ private KEvent _lockEvent;
+ private int _lockEventHandle;
+
+ public ILockAccessor(int lockId, Horizon system)
+ {
+ _lockId = lockId;
+ _isLocked = false;
+ _lockEvent = new KEvent(system.KernelContext);
+ _lockEvent.ReadableEvent.Signal();
+ }
+
+ [CommandCmif(1)]
+ // TryLock(bool) -> (bool, IpcService)
+ public ResultCode TryLock(ServiceCtx context)
+ {
+ bool returnHandle = context.RequestData.ReadBoolean();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ if (_lockEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_lockEvent.ReadableEvent, out _lockEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ _isLocked = true;
+ _lockEvent.ReadableEvent.Signal();
+
+ context.ResponseData.Write(_isLocked);
+ if (returnHandle)
+ {
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_lockEventHandle);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // Unlock()
+ public ResultCode Unlock(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ _isLocked = false;
+ _lockEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetEvent() -> handle
+ public ResultCode GetEvent(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ if (_lockEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_lockEvent.ReadableEvent, out _lockEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_lockEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)]
+ // IsLocked() -> bool
+ public ResultCode IsLocked(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+
+ context.ResponseData.Write(_isLocked);
+
+ return ResultCode.Success;
+ }
+ }
+};
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
new file mode 100644
index 000000000..89dfad2ac
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
@@ -0,0 +1,191 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audctl")]
+ class IAudioController : IpcService
+ {
+ public IAudioController(ServiceCtx context) { }
+
+ [CommandCmif(9)]
+ // GetAudioOutputMode(s32) -> s32
+ public ResultCode GetAudioOutputMode(ServiceCtx context)
+ {
+ context.ResponseData.Write(0); // todo?
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10)]
+ // SetAudioOutputMode(s32, s32)
+ public ResultCode SetAudioOutputMode(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)]
+ // SetForceMutePolicy(u32)
+ public ResultCode SetForceMutePolicy(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(12)]
+ // GetForceMutePolicy() -> u32
+ public ResultCode GetForceMutePolicy(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(13)]
+ // GetOutputModeSetting(u32) -> u32
+ public ResultCode GetOutputModeSetting(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(18)] // 3.0.0+
+ // GetHeadphoneOutputLevelMode() -> u32
+ public ResultCode GetHeadphoneOutputLevelMode(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(31)] // 13.0.0+
+ // IsSpeakerAutoMuteEnabled() -> b8
+ public ResultCode IsSpeakerAutoMuteEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5000)]
+ // Unknown5000() -> bool // 19.0.0+
+ public ResultCode Unknown5000(ServiceCtx context)
+ {
+ MakeObject(context, new IAudioController(context));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10000)]
+ // NotifyAudioOutputTargetForPlayReport()
+ public ResultCode NotifyAudioOutputTargetForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10001)]
+ // NotifyAudioOutputChannelCountForPlayReport()
+ public ResultCode NotifyAudioOutputChannelCountForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10002)]
+ // NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport()
+ public ResultCode NotifyUnsupportedUsbOutputDeviceAttachedForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10100)]
+ // GetAudioVolumeDataForPlayReport()
+ public ResultCode GetAudioVolumeDataForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10101)]
+ // BindAudioVolumeUpdateEventForPlayReport()
+ public ResultCode BindAudioVolumeUpdateEventForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10102)]
+ // BindAudioOutputTargetUpdateEventForPlayReport()
+ public ResultCode BindAudioOutputTargetUpdateEventForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10103)]
+ // GetAudioOutputTargetForPlayReport()
+ public ResultCode GetAudioOutputTargetForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10104)]
+ // GetAudioOutputChannelCountForPlayReport()
+ public ResultCode GetAudioOutputChannelCountForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10105)]
+ // BindAudioOutputChannelCountUpdateEventForPlayReport()
+ public ResultCode BindAudioOutputChannelCountUpdateEventForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10106)]
+ // GetDefaultAudioOutputTargetForPlayReport()
+ public ResultCode GetDefaultAudioOutputTargetForPlayReport(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(50000)]
+ // SetAnalogInputBoostGainForPrototyping()
+ public ResultCode SetAnalogInputBoostGainForPrototyping(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmSystem/IBtmSystemCore.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmSystem/IBtmSystemCore.cs
new file mode 100644
index 000000000..304cfe03b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/BtmSystem/IBtmSystemCore.cs
@@ -0,0 +1,81 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Services.BluetoothManager.BtmSystem
+{
+ class IBtmSystemCore : IpcService
+ {
+ public KEvent _radioEvent;
+ public int _radioEventhandle;
+
+ public KEvent _gamepadPairingEvent;
+ public int _gamepadPairingEventHandle;
+
+ public IBtmSystemCore() { }
+
+ [CommandCmif(6)]
+ // IsRadioEnabled() -> b8
+ public ResultCode IsRadioEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(true);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceBtm);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)] // 3.0.0+
+ // AcquireRadioEvent() -> (byte<1>, handle)
+ public ResultCode AcquireRadioEvent(ServiceCtx context)
+ {
+ Result result = Result.Success;
+
+ if (_radioEventhandle == 0)
+ {
+ _radioEvent = new KEvent(context.Device.System.KernelContext);
+
+ result = context.Process.HandleTable.GenerateHandle(_radioEvent.ReadableEvent, out _radioEventhandle);
+
+ if (result != Result.Success)
+ {
+ // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not.
+ Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_radioEventhandle);
+
+ context.ResponseData.Write(result == Result.Success ? 1 : 0);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(8)] // 3.0.0+
+ // AcquireGamepadPairingEvent() -> (byte<1>, handle)
+ public ResultCode AcquireGamepadPairingEvent(ServiceCtx context)
+ {
+ Result result = Result.Success;
+
+ if (_gamepadPairingEventHandle == 0)
+ {
+ _gamepadPairingEvent = new KEvent(context.Device.System.KernelContext);
+
+ result = context.Process.HandleTable.GenerateHandle(_gamepadPairingEvent.ReadableEvent, out _gamepadPairingEventHandle);
+
+ if (result != Result.Success)
+ {
+ // NOTE: We use a Logging instead of an exception because the call return a boolean if succeed or not.
+ Logger.Error?.Print(LogClass.ServiceBsd, "Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gamepadPairingEventHandle);
+
+ context.ResponseData.Write(result == Result.Success ? 1 : 0);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs
index 0efab8763..d1b389c59 100644
--- a/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs
+++ b/src/Ryujinx.HLE/HOS/Services/BluetoothManager/IBtmSystem.cs
@@ -1,8 +1,28 @@
+using Ryujinx.HLE.HOS.Services.BluetoothManager.BtmSystem;
+
namespace Ryujinx.HLE.HOS.Services.BluetoothManager
{
[Service("btm:sys")]
class IBtmSystem : IpcService
{
public IBtmSystem(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // GetCoreImpl() -> object
+ public ResultCode GetCoreImpl(ServiceCtx context)
+ {
+ MakeObject(context, new IBtmSystemCore());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(6)]
+ // IsRadioEnabled() -> b8
+ public ResultCode IsRadioEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(true);
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs
index bd0880db7..abf000411 100644
--- a/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Erpt/IContext.cs
@@ -1,8 +1,20 @@
+using Ryujinx.Common.Logging;
+
namespace Ryujinx.HLE.HOS.Services.Erpt
{
[Service("erpt:c")]
class IContext : IpcService
{
public IContext(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // SubmitContext(buffer, buffer) -> u32
+ public ResultCode SubmitContext(ServiceCtx context)
+ {
+ Logger.Info?.PrintStub(LogClass.Service, $"ContextEntry size: {context.Request.SendBuff[0].Size}");
+ Logger.Info?.PrintStub(LogClass.Service, $"FieldList size: {context.Request.SendBuff[1].Size}");
+ context.ResponseData.Write(0);
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ButtonDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ButtonDevice.cs
new file mode 100644
index 000000000..1e555192b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/ButtonDevice.cs
@@ -0,0 +1,80 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Button;
+using System;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum ButtonDeviceType
+ {
+ HomeButton,
+ SleepButton,
+ CaptureButton,
+ }
+
+ public class ButtonDevice : BaseDevice
+ {
+ private ButtonDeviceType _type;
+ private KEvent _event;
+ private bool isDown;
+
+
+ public ButtonDevice(Switch device, bool active, ButtonDeviceType type) : base(device, active)
+ {
+ _type = type;
+ _event = new KEvent(device.System.KernelContext);
+ }
+
+ internal ref KEvent GetEvent()
+ {
+ return ref _event;
+ }
+
+ private ref RingLifo GetButtonStateLifo()
+ {
+ switch (_type)
+ {
+ case ButtonDeviceType.HomeButton:
+ return ref _device.Hid.SharedMemory.HomeButton;
+ case ButtonDeviceType.SleepButton:
+ return ref _device.Hid.SharedMemory.SleepButton;
+ case ButtonDeviceType.CaptureButton:
+ return ref _device.Hid.SharedMemory.CaptureButton;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(_type));
+ }
+ }
+
+ public void Update(bool state)
+ {
+ ref RingLifo lifo = ref GetButtonStateLifo();
+
+ if (!Active)
+ {
+ lifo.Clear();
+
+ return;
+ }
+
+ bool shouldSignal = state != isDown;
+ isDown = state;
+
+ if (!shouldSignal)
+ {
+ return;
+ }
+
+ ref ButtonState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ ButtonState newState = new()
+ {
+ SamplingNumber = previousEntry.SamplingNumber + 1,
+ Buttons = state ? 1UL : 0UL
+ };
+
+ lifo.Write(ref newState);
+
+ _event.ReadableEvent.Signal();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs
index 66b5a5cba..1a0280648 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs
@@ -4,6 +4,7 @@ using Ryujinx.Common.Memory;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Button;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
@@ -32,6 +33,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public DebugMouseDevice DebugMouse;
public KeyboardDevice Keyboard;
public NpadDevices Npads;
+ public ButtonDevice HomeButton;
+ public ButtonDevice SleepButton;
+ public ButtonDevice CaptureButton;
private static void CheckTypeSizeOrThrow(int expectedSize)
{
@@ -48,6 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
CheckTypeSizeOrThrow>(0x350);
CheckTypeSizeOrThrow>(0x350);
CheckTypeSizeOrThrow>(0x3D8);
+ CheckTypeSizeOrThrow>(0x1B8);
CheckTypeSizeOrThrow>(0x32000);
CheckTypeSizeOrThrow(Horizon.HidSize);
}
@@ -70,6 +75,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
DebugMouse = new DebugMouseDevice(_device, false);
Keyboard = new KeyboardDevice(_device, false);
Npads = new NpadDevices(_device, true);
+ HomeButton = new ButtonDevice(_device, true, ButtonDeviceType.HomeButton);
+ SleepButton = new ButtonDevice(_device, true, ButtonDeviceType.SleepButton);
+ CaptureButton = new ButtonDevice(_device, true, ButtonDeviceType.CaptureButton);
}
public void RefreshInputConfig(List inputConfig)
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs
index 5307292a5..1808db666 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs
@@ -1,13 +1,135 @@
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types;
+using Ryujinx.Horizon.Common;
+using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Service("hid:sys")]
class IHidSystemServer : IpcService
{
- public IHidSystemServer(ServiceCtx context) { }
+ private KEvent _deviceRegisteredEventForControllerSupport;
+ private int _deviceRegisteredEventForControllerSupportHandle;
+
+ private KEvent _connectionTriggerTimeoutEvent;
+ private int _connectionTriggerTimeoutEventHandle;
+
+ private KEvent _joyDetachOnBluetoothOffEvent;
+ private int _joyDetachOnBluetoothOffEventHandle;
+
+ private KEvent _playReportControllerUsageUpdateEvent;
+ private int _playReportControllerUsageUpdateEventHandle;
+
+ private KEvent _playReportRegisteredDeviceUpdateEvent;
+ private int _playReportRegisteredDeviceUpdateEventHandle;
+
+ private IHidServer _hidServer;
+
+ private int _homeButtonEventHandle;
+ private int _sleepButtonEventHandle;
+ private int _captureButtonEventHandle;
+
+ public IHidSystemServer(ServiceCtx context)
+ {
+ _deviceRegisteredEventForControllerSupport = new KEvent(context.Device.System.KernelContext);
+ _connectionTriggerTimeoutEvent = new KEvent(context.Device.System.KernelContext);
+ _joyDetachOnBluetoothOffEvent = new KEvent(context.Device.System.KernelContext);
+ _playReportControllerUsageUpdateEvent = new KEvent(context.Device.System.KernelContext);
+ _playReportRegisteredDeviceUpdateEvent = new KEvent(context.Device.System.KernelContext);
+ _hidServer = new IHidServer(context);
+ }
+
+ [CommandCmif(101)]
+ // AcquireHomeButtonEventHandle() -> handle
+ public ResultCode AcquireHomeButtonEventHandle(ServiceCtx context)
+ {
+ if (_homeButtonEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(context.Device.Hid.HomeButton.GetEvent().ReadableEvent, out _homeButtonEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_homeButtonEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(111)]
+ // ActivateHomeButton(nn::applet::AppletResourceUserId, pid)
+ public ResultCode ActivateHomeButton(ServiceCtx context)
+ {
+ context.Device.Hid.HomeButton.Active = true;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(121)]
+ // AcquireSleepButtonEventHandle() -> handle
+ public ResultCode AcquireSleepButtonEventHandle(ServiceCtx context)
+ {
+ if (_sleepButtonEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(context.Device.Hid.SleepButton.GetEvent().ReadableEvent, out _sleepButtonEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_sleepButtonEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(131)]
+ // ActivateSleepButton(nn::applet::AppletResourceUserId, pid)
+ public ResultCode ActivateSleepButton(ServiceCtx context)
+ {
+ context.Device.Hid.SleepButton.Active = true;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(141)]
+ // AcquireCaptureButtonEventHandle() -> handle
+ public ResultCode AcquireCaptureButtonEventHandle(ServiceCtx context)
+ {
+ if (_captureButtonEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(context.Device.Hid.CaptureButton.GetEvent().ReadableEvent, out _captureButtonEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_captureButtonEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(151)]
+ // ActivateCaptureButton(nn::applet::AppletResourceUserId, pid)
+ public ResultCode ActivateCaptureButton(ServiceCtx context)
+ {
+ context.Device.Hid.CaptureButton.Active = true;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(301)]
+ // ActivateNpadSystem(u32)
+ public ResultCode ActivateNpadSystem(ServiceCtx context)
+ {
+ uint npadSystem = context.RequestData.ReadUInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadSystem });
+
+ return ResultCode.Success;
+ }
[CommandCmif(303)]
// ApplyNpadSystemCommonPolicy(u64)
@@ -21,18 +143,23 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
[CommandCmif(306)]
- // GetLastActiveNpad(u32) -> u8, u8
+ // GetLastActiveNpad() -> nn::hid::NpadIdType
public ResultCode GetLastActiveNpad(ServiceCtx context)
{
+ // TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue.
+ context.RequestData.ReadUInt32();
+
context.ResponseData.Write((byte)context.Device.Hid.Npads.GetLastActiveNpadId());
return ResultCode.Success;
}
[CommandCmif(307)]
- // GetNpadSystemExtStyle() -> u64
+ // GetNpadSystemExtStyle(u32) -> (u64, u64)
public ResultCode GetNpadSystemExtStyle(ServiceCtx context)
{
+ context.RequestData.ReadUInt32();
+
foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers())
{
if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld)
@@ -41,7 +168,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid
}
}
- context.ResponseData.Write((ulong)context.Device.Hid.Npads.SupportedStyleSets);
+ // todo: wrong
+ // context.ResponseData.Write((ulong)context.Device.Hid.Npads.SupportedStyleSets);
+ context.ResponseData.Write((ulong)AppletFooterUiType.JoyDual);
+ context.ResponseData.Write((ulong)0);
return ResultCode.Success;
}
@@ -57,6 +187,203 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return resultCode;
}
+ [CommandCmif(321)]
+ // GetUniquePadsFromNpad(u32) -> (u64, buffer)
+ public ResultCode GetUniquePadsFromNpad(ServiceCtx context)
+ {
+ context.ResponseData.Write(0L);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(525)]
+ // IsJoyConAttachedOnAllRail() -> bool
+ public ResultCode IsJoyConAttachedOnAllRail(ServiceCtx context)
+ {
+ context.ResponseData.Write(true);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(540)]
+ // AcquirePlayReportControllerUsageUpdateEvent() -> handle
+ public ResultCode AcquirePlayReportControllerUsageUpdateEvent(ServiceCtx context)
+ {
+ if (_playReportControllerUsageUpdateEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_playReportControllerUsageUpdateEvent.ReadableEvent, out _playReportControllerUsageUpdateEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_playReportControllerUsageUpdateEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(541)]
+ // GetPlayReportControllerUsages() -> (u64, buffer)
+ public ResultCode GetPlayReportControllerUsages(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ context.ResponseData.Write(0L);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(542)]
+ // AcquirePlayReportRegisteredDeviceUpdateEvent() -> handle
+ public ResultCode AcquirePlayReportRegisteredDeviceUpdateEvent(ServiceCtx context)
+ {
+ if (_playReportRegisteredDeviceUpdateEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_playReportRegisteredDeviceUpdateEvent.ReadableEvent, out _playReportRegisteredDeviceUpdateEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_playReportRegisteredDeviceUpdateEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(543)]
+ // GetRegisteredDevicesOld() -> (u64, buffer)
+ public ResultCode GetRegisteredDevicesOld(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ context.ResponseData.Write(0L);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(544)]
+ // AcquireConnectionTriggerTimeoutEvent() -> handle
+ public ResultCode AcquireConnectionTriggerTimeoutEvent(ServiceCtx context)
+ {
+ if (_connectionTriggerTimeoutEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_connectionTriggerTimeoutEvent.ReadableEvent,
+ out _connectionTriggerTimeoutEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_connectionTriggerTimeoutEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(545)]
+ // SendConnectionTrigger(nn::bluetooth::Address)
+ public ResultCode SendConnectionTrigger(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(546)]
+ // AcquireDeviceRegisteredEventForControllerSupport() -> handle
+ public ResultCode AcquireDeviceRegisteredEventForControllerSupport(ServiceCtx context)
+ {
+ if (_deviceRegisteredEventForControllerSupportHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_deviceRegisteredEventForControllerSupport.ReadableEvent,
+ out _deviceRegisteredEventForControllerSupportHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_deviceRegisteredEventForControllerSupportHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(703)]
+ // GetUniquePadIds() -> (u64, buffer)
+ public ResultCode GetUniquePadIds(ServiceCtx context)
+ {
+ context.ResponseData.Write(0L);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(751)]
+ // AcquireJoyDetachOnBluetoothOffEventHandle(nn::applet::AppletResourceUserId, pid) -> handle
+ public ResultCode AcquireJoyDetachOnBluetoothOffEventHandle(ServiceCtx context)
+ {
+ if (_joyDetachOnBluetoothOffEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_joyDetachOnBluetoothOffEvent.ReadableEvent,
+ out _joyDetachOnBluetoothOffEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_joyDetachOnBluetoothOffEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(850)]
+ // IsUsbFullKeyControllerEnabled() -> bool
+ public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1120)]
+ // Unknown1120()
+ public ResultCode Unknown1120(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1131)]
+ // FinalizeUsbFirmwareUpdate()
+ public ResultCode FinalizeUsbFirmwareUpdate(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1132)]
+ // CheckUsbFirmwareUpdateRequired()
+ public ResultCode CheckUsbFirmwareUpdateRequired(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1135)]
+ // InitializeUsbFirmwareUpdateWithoutMemory()
+ public ResultCode InitializeUsbFirmwareUpdateWithoutMemory(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1153)]
+ // GetTouchScreenDefaultConfiguration() -> unknown
+ public ResultCode GetTouchScreenDefaultConfiguration(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
private ResultCode GetAppletFooterUiTypeImpl(ServiceCtx context, out AppletFooterUiType appletFooterUiType)
{
NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Button/ButtonState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Button/ButtonState.cs
new file mode 100644
index 000000000..96cd8f72b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Button/ButtonState.cs
@@ -0,0 +1,12 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Button
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct ButtonState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public ulong Buttons;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs
index 59d8f4489..98952f1d1 100644
--- a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs
@@ -1,4 +1,5 @@
using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Button;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
@@ -39,6 +40,24 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
///
[FieldOffset(0x3800)]
public RingLifo Keyboard;
+
+ ///
+ /// Home Button.
+ ///
+ [FieldOffset(0x4C00)]
+ public RingLifo HomeButton;
+
+ ///
+ /// Sleep Button.
+ ///
+ [FieldOffset(0x4E00)]
+ public RingLifo SleepButton;
+
+ ///
+ /// Capture Button.
+ ///
+ [FieldOffset(0x5000)]
+ public RingLifo CaptureButton;
///
/// Nintendo Pads.
diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs
index 1b95b6712..e775211ab 100644
--- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services
{
@@ -280,5 +281,54 @@ namespace Ryujinx.HLE.HOS.Services
_domainObjects.Clear();
}
+
+ public static byte[] StructToBytes(T structure) where T : struct
+ {
+ int size = Marshal.SizeOf(structure);
+ byte[] bytes = new byte[size];
+ IntPtr ptr = Marshal.AllocHGlobal(size);
+
+ try
+ {
+ Marshal.StructureToPtr(structure, ptr, false);
+ Marshal.Copy(ptr, bytes, 0, size);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ptr);
+ }
+
+ return bytes;
+ }
+
+ public Span CreateByteSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput)
+ {
+ byte[] rawData;
+
+ if (isOutput)
+ {
+ rawData = new byte[ipcBuff.Size];
+ }
+ else
+ {
+ rawData = new byte[ipcBuff.Size];
+
+ context.Memory.Read(ipcBuff.Position, rawData);
+ }
+
+ return new Span(rawData);
+ }
+
+ public Span CreateSpanFromBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) where T : unmanaged
+ {
+ return MemoryMarshal.Cast(CreateByteSpanFromBuffer(context, ipcBuff, isOutput));
+ }
+
+ public void WriteSpanToBuffer(ServiceCtx context, IpcBuffDesc ipcBuff, Span span) where T : unmanaged
+ {
+ Span rawData = MemoryMarshal.Cast(span);
+
+ context.Memory.Write(ipcBuff.Position, rawData);
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorService.cs
new file mode 100644
index 000000000..09ff483eb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorService.cs
@@ -0,0 +1,38 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Nfc.NfcManager;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn
+{
+ class IMonitorService : IpcService
+ {
+ public IMonitorService() { }
+
+ [CommandCmif(0)]
+ // GetStateForMonitor() -> u32
+ public ResultCode GetStateForMonitor(ServiceCtx context)
+ {
+ State state = State.Initialized;
+ context.ResponseData.Write((uint)state);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceLdn);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(100)]
+ // InitializeMonitor()
+ public ResultCode InitializeMonitor(ServiceCtx context)
+ {
+ Logger.Info?.PrintStub(LogClass.ServiceLdn);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(288)]
+ // ???()
+ public ResultCode Unknown288(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceLdn);
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs
index 4bea3945a..0307a73ae 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/IMonitorServiceCreator.cs
@@ -4,5 +4,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
class IMonitorServiceCreator : IpcService
{
public IMonitorServiceCreator(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateMonitorService() -> object
+ public ResultCode CreateMonitorService(ServiceCtx context)
+ {
+ MakeObject(context, new IMonitorService());
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfMonitorServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfMonitorServiceCreator.cs
new file mode 100644
index 000000000..64f0b5d2b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfMonitorServiceCreator.cs
@@ -0,0 +1,22 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Ldn.Lp2p.SfMonitorServiceCreator;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
+{
+ [Service("lp2p:m")] // 9.1.0+
+ class ISfMonitorServiceCreator : IpcService
+ {
+ public ISfMonitorServiceCreator(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateMonitorService(pid, u64, u64) -> object
+ public ResultCode CreateMonitorService(ServiceCtx context)
+ {
+ MakeObject(context, new ISfMonitorService(context));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceLdn);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
index d3a8bead2..d10f9c67b 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
@@ -1,6 +1,8 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Ldn.Types;
using Ryujinx.Horizon.Common;
using System;
@@ -47,7 +49,8 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
public ResultCode GetGroupInfo(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
-
+ GroupInfo info = new();
+ context.Memory.Write(context.Response.ReceiveBuff[0].Position,SpanHelpers.AsByteSpan(ref info).ToArray());
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/SfMonitorServiceCreator/ISfMonitorService.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/SfMonitorServiceCreator/ISfMonitorService.cs
new file mode 100644
index 000000000..270ebdecc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/SfMonitorServiceCreator/ISfMonitorService.cs
@@ -0,0 +1,27 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p.SfMonitorServiceCreator
+{
+ class ISfMonitorService : IpcService
+ {
+ public ISfMonitorService(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // Initialize()
+ public ResultCode Initialize(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceLdn);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(288)]
+ // GetGroupInfo() -> buffer
+ public ResultCode GetGroupInfo(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceLdn);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ldn/Types/GroupInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/GroupInfo.cs
new file mode 100644
index 000000000..7f919920f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ldn/Types/GroupInfo.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Ldn.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct GroupInfo
+ {
+ public fixed byte info[0x200]; // Fixed size buffer
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/INewlyArrivedEventHolder.cs b/src/Ryujinx.HLE/HOS/Services/News/INewlyArrivedEventHolder.cs
new file mode 100644
index 000000000..24c1811a6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/News/INewlyArrivedEventHolder.cs
@@ -0,0 +1,39 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+
+namespace Ryujinx.HLE.HOS.Services.News
+{
+ class INewlyArrivedEventHolder : IpcService
+ {
+ KEvent _newlyArrivedEvent;
+ int _newlyArrivedEventHandle;
+
+ public INewlyArrivedEventHolder(ServiceCtx context)
+ {
+ _newlyArrivedEvent = new KEvent(context.Device.System.KernelContext);
+ _newlyArrivedEventHandle = -1;
+ }
+
+ [CommandCmif(0)]
+ // Get() -> handle
+ public ResultCode Get(ServiceCtx context)
+ {
+ if (_newlyArrivedEventHandle == -1)
+ {
+ Result resultCode = context.Process.HandleTable.GenerateHandle(_newlyArrivedEvent.ReadableEvent, out _newlyArrivedEventHandle);
+
+ if (resultCode != Result.Success)
+ {
+ return (ResultCode)resultCode.ErrorCode;
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_newlyArrivedEventHandle);
+
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/INewsDataService.cs b/src/Ryujinx.HLE/HOS/Services/News/INewsDataService.cs
new file mode 100644
index 000000000..8e416f9b5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/News/INewsDataService.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.News
+{
+ class INewsDataService : IpcService
+ {
+ public INewsDataService(ServiceCtx context) { }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/INewsDatabaseService.cs b/src/Ryujinx.HLE/HOS/Services/News/INewsDatabaseService.cs
new file mode 100644
index 000000000..d4b56b1a2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/News/INewsDatabaseService.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Common.Logging;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.News
+{
+ class INewsDatabaseService : IpcService
+ {
+ public INewsDatabaseService(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // GetListV1(unknown<4>, buffer, buffer) -> (unknown<4>, buffer)
+ public ResultCode GetListV1(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // Count(buffer) -> u32
+ public ResultCode Count(ServiceCtx context)
+ {
+ // TODO: Implement news database count
+ context.ResponseData.Write(0);
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // UpdateIntegerValue(unknown<4>, buffer, buffer)
+ public ResultCode UpdateIntegerValue(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/INewsService.cs b/src/Ryujinx.HLE/HOS/Services/News/INewsService.cs
new file mode 100644
index 000000000..bce42adf8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/News/INewsService.cs
@@ -0,0 +1,30 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.News
+{
+ class INewsService : IpcService
+ {
+ public INewsService(ServiceCtx context) { }
+
+ [CommandCmif(30100)]
+ // GetSubscriptionStatus() -> u32
+ public ResultCode GetSubscriptionStatus(ServiceCtx context)
+ {
+ // TODO: Implement this properly
+ Logger.Stub?.PrintStub(LogClass.Service);
+ context.ResponseData.Write((uint)0);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(30200)]
+ // IsSystemUpdateRequired() -> bool
+ public ResultCode IsSystemUpdateRequired(ServiceCtx context)
+ {
+ // TODO: Implement this properly
+ Logger.Stub?.PrintStub(LogClass.Service);
+ context.ResponseData.Write(false);
+ return ResultCode.Success;
+ }
+
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/IOverwriteEventHolder.cs b/src/Ryujinx.HLE/HOS/Services/News/IOverwriteEventHolder.cs
new file mode 100644
index 000000000..3376975dc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/News/IOverwriteEventHolder.cs
@@ -0,0 +1,40 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.News
+{
+ class IOverwriteEventHolder : IpcService
+ {
+ KEvent _overwriteEvent;
+ int _overwriteEventHandle;
+ public IOverwriteEventHolder(ServiceCtx context)
+ {
+ _overwriteEvent = new KEvent(context.Device.System.KernelContext);
+ _overwriteEventHandle = -1;
+ }
+
+ [CommandCmif(0)]
+ // Get() -> handle
+ public ResultCode Get(ServiceCtx context)
+ {
+ if (_overwriteEventHandle == -1)
+ {
+ Result resultCode = context.Process.HandleTable.GenerateHandle(_overwriteEvent.ReadableEvent, out _overwriteEventHandle);
+
+ if (resultCode != Result.Success)
+ {
+ return (ResultCode)resultCode.ErrorCode;
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_overwriteEventHandle);
+
+ Logger.Stub?.PrintStub(LogClass.Service);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs
index 3d14629ca..bbb8180bc 100644
--- a/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs
+++ b/src/Ryujinx.HLE/HOS/Services/News/IServiceCreator.cs
@@ -8,5 +8,46 @@ namespace Ryujinx.HLE.HOS.Services.News
class IServiceCreator : IpcService
{
public IServiceCreator(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateNewsService() -> object
+ public ResultCode CreateNewsService(ServiceCtx context)
+ {
+ MakeObject(context, new INewsService(context));
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(30900)]
+ [CommandCmif(1)]
+ // CreateNewlyArrivedEventHolder() -> object
+ public ResultCode CreateNewlyArrivedEventHolder(ServiceCtx context)
+ {
+ MakeObject(context, new INewlyArrivedEventHolder(context));
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // CreateNewsDataService() -> object
+ public ResultCode CreateNewsDataService(ServiceCtx context)
+ {
+ MakeObject(context, new INewsDataService(context));
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // CreateNewsDatabaseService() -> object
+ public ResultCode CreateNewsDatabaseService(ServiceCtx context)
+ {
+ MakeObject(context, new INewsDatabaseService(context));
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)]
+ // CreateOverwriteEventHolder() -> object
+ public ResultCode CreateOverwriteEventHolder(ServiceCtx context)
+ {
+ MakeObject(context, new IOverwriteEventHolder(context));
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
index a5a822db3..76b7e5957 100644
--- a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IGeneralService.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
+using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Nifm.StaticService.GeneralService;
using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
using System;
@@ -17,6 +18,8 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
private UnicastIPAddressInformation _targetAddressInfoCache = null;
private string _cacheChosenInterface = null;
+ private readonly UInt128 _interfaceId = UInt128Utils.CreateRandom();
+
public IGeneralService()
{
_generalServiceDetail = new GeneralServiceDetail
@@ -43,6 +46,17 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success;
}
+ [CommandCmif(2)]
+ // CreateScanRequest() -> object
+ public ResultCode CreateScanRequest(ServiceCtx context)
+ {
+ MakeObject(context, new IScanRequest(context.Device.System));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(4)]
// CreateRequest(u32 version) -> object
public ResultCode CreateRequest(ServiceCtx context)
@@ -78,7 +92,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
NetworkProfileData networkProfile = new()
{
- Uuid = Random.Shared.NextUInt128(),
+ Uuid = _interfaceId,
};
networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress);
@@ -91,6 +105,111 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success;
}
+ [CommandCmif(6)]
+ // EnumerateNetworkInterfaces() -> (u32, buffer)
+ public ResultCode EnumerateNetworkInterfaces(ServiceCtx context)
+ {
+ context.ResponseData.Write(1); // account crashes if we don't have at least one interface
+
+ // TODO: write interface info
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)]
+ // EnumerateNetworkProfiles() -> (u32, buffer)
+ public ResultCode EnumerateNetworkProfiles(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(8)]
+ // GetNetworkProfile(nn::util::Uuid) -> buffer
+ public ResultCode GetNetworkProfile(ServiceCtx context)
+ {
+ ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
+ UInt128 uuid = context.RequestData.ReadStruct();
+
+ if (uuid != _interfaceId)
+ {
+ return ResultCode.NoInternetConnection;
+ }
+
+ (IPInterfaceProperties interfaceProperties, UnicastIPAddressInformation unicastAddress) = GetLocalInterface(context);
+
+ if (interfaceProperties == null || unicastAddress == null)
+ {
+ return ResultCode.NoInternetConnection;
+ }
+
+ Logger.Info?.Print(LogClass.ServiceNifm, $"Console's local IP is \"{unicastAddress.Address}\".");
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Unsafe.SizeOf());
+
+ NetworkProfileData networkProfile = new()
+ {
+ Uuid = _interfaceId,
+ };
+
+ networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress);
+ networkProfile.IpSettingData.DnsSetting = new DnsSetting(interfaceProperties);
+
+ "RyujinxNetwork"u8.CopyTo(networkProfile.Name.AsSpan());
+
+ context.Memory.Write(networkProfileDataPosition, networkProfile);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(9)]
+ // SetNetworkProfile(buffer)
+ public ResultCode SetNetworkProfile(ServiceCtx context)
+ {
+ ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
+
+ NetworkProfileData networkProfile = context.Memory.Read(networkProfileDataPosition);
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { networkProfile });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10)]
+ // RemoveNetworkProfile(nn::util::Uuid)
+ public ResultCode RemoveNetworkProfile(ServiceCtx context)
+ {
+ UInt128 uuid = context.RequestData.ReadStruct();
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { uuid });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)]
+ // GetScanData(u32) -> (u32, buffer)
+ public ResultCode GetScanData(ServiceCtx context)
+ {
+ SystemVersion version = context.Device.System.ContentManager.GetCurrentFirmwareVersion();
+ if (version.Major >= 4)
+ {
+ // TODO: write AccessPointData
+ }
+ else
+ {
+ // TOO: write AccessPointDataOld
+ }
+
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(12)]
// GetCurrentIpAddress() -> nn::nifm::IpV4Address
public ResultCode GetCurrentIpAddress(ServiceCtx context)
@@ -109,6 +228,24 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success;
}
+ [CommandCmif(14)]
+ // CreateTemporaryNetworkProfile(buffer) -> (nn::util::Uuid, object)
+ public ResultCode CreateTemporaryNetworkProfile(ServiceCtx context)
+ {
+ ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
+
+ NetworkProfileData networkProfile = context.Memory.Read(networkProfileDataPosition);
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { networkProfile });
+
+ var uuid = UInt128Utils.CreateRandom();
+ var networkProfileObject = new INetworkProfile(uuid, networkProfile);
+
+ context.ResponseData.WriteStruct(uuid);
+ MakeObject(context, networkProfileObject);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(15)]
// GetCurrentIpConfigInfo() -> (nn::nifm::IpAddressSetting, nn::nifm::DnsSetting)
public ResultCode GetCurrentIpConfigInfo(ServiceCtx context)
@@ -128,6 +265,17 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success;
}
+ [CommandCmif(17)]
+ // IsWirelessCommunicationEnabled() -> b8
+ public ResultCode IsWirelessCommunicationEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(true);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(18)]
// GetInternetConnectionStatus() -> nn::nifm::detail::sf::InternetConnectionStatus
public ResultCode GetInternetConnectionStatus(ServiceCtx context)
@@ -165,6 +313,30 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
return ResultCode.Success;
}
+ [CommandCmif(26)]
+ // SetExclusiveClient(buffer)
+ public ResultCode SetExclusiveClient(ServiceCtx context)
+ {
+ ulong position = context.Request.PtrBuff[0].Position;
+#pragma warning disable IDE0059 // Remove unnecessary value assignment
+ ulong size = context.Request.PtrBuff[0].Size;
+#pragma warning restore IDE0059
+
+ int clientId = context.Memory.Read(position);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(33)]
+ // ConfirmSystemAvailability()
+ public ResultCode ConfirmSystemAvailability(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+ return ResultCode.Success;
+ }
+
private (IPInterfaceProperties, UnicastIPAddressInformation) GetLocalInterface(ServiceCtx context)
{
if (!NetworkInterface.GetIsNetworkAvailable())
@@ -198,7 +370,8 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
{
NetworkChange.NetworkAddressChanged -= LocalInterfaceCacheHandler;
- GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
+ if (_generalServiceDetail.ClientId < GeneralServiceManager.Count)
+ GeneralServiceManager.Remove(_generalServiceDetail.ClientId);
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/INetworkProfile.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/INetworkProfile.cs
new file mode 100644
index 000000000..3020eb99f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/INetworkProfile.cs
@@ -0,0 +1,61 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Nifm.StaticService.Types;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
+{
+ class INetworkProfile : IpcService
+ {
+ public UInt128 Uuid { get; private set; }
+ public NetworkProfileData ProfileData;
+
+ public INetworkProfile(UInt128 uuid, NetworkProfileData profileData)
+ {
+ Uuid = uuid;
+ ProfileData = profileData;
+ }
+
+ [CommandCmif(0)]
+ // Update(buffer) -> nn::util::Uuid;
+ public ResultCode Update(ServiceCtx context)
+ {
+ ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
+ NetworkProfileData networkProfile = context.Memory.Read(networkProfileDataPosition);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { networkProfile });
+
+ ProfileData = networkProfile;
+
+ context.ResponseData.WriteStruct(Uuid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // Persist(nn::util::Uuid) -> nn::util::Uuid;
+ public ResultCode Persist1(ServiceCtx context)
+ {
+ UInt128 uuid = context.RequestData.ReadStruct();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { uuid });
+
+ Uuid = uuid;
+
+ context.ResponseData.WriteStruct(Uuid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // Persist() -> nn::util::Uuid;
+ public ResultCode Persist2(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+
+ context.ResponseData.WriteStruct(Uuid);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IScanRequest.cs b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IScanRequest.cs
new file mode 100644
index 000000000..50bd96f3e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nifm/StaticService/IScanRequest.cs
@@ -0,0 +1,66 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
+{
+ class IScanRequest : IpcService
+ {
+ private readonly KEvent _systemEvent;
+ private int _systemEventHandle;
+
+ public IScanRequest(Horizon system)
+ {
+ _systemEvent = new KEvent(system.KernelContext);
+ }
+
+ [CommandCmif(0)]
+ // Submit()
+ public ResultCode Submit(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+ _systemEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // IsProcessing() -> bool
+ public ResultCode IsProcessing(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+ context.ResponseData.Write(false);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // GetResult() -> u32
+ public ResultCode GetResult(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNifm);
+ context.ResponseData.Write(0);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetSystemEventReadableHandle() -> (handle)
+ public ResultCode GetSystemEventReadableHandle(ServiceCtx context)
+ {
+ if (_systemEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_systemEvent.ReadableEvent, out _systemEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_systemEventHandle);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs
index c5946be84..b20e40d3a 100644
--- a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationServicesForSystem.cs
@@ -1,8 +1,39 @@
+using Ryujinx.Common.Logging;
+
namespace Ryujinx.HLE.HOS.Services.Notification
{
[Service("notif:s")] // 9.0.0+
class INotificationServicesForSystem : IpcService
{
public INotificationServicesForSystem(ServiceCtx context) { }
+
+ [CommandCmif(520)]
+ // ListAlarmSettings() -> (s32, buffer)
+ public ResultCode ListAlarmSettings(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNotif);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1040)]
+ // OpenNotificationSystemEventAccessor() -> object
+ public ResultCode OpenNotificationSystemEventAccessor(ServiceCtx context)
+ {
+ MakeObject(context, new INotificationSystemEventAccessor(context));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1510)]
+ // GetPresentationSetting() -> unknown
+ public ResultCode GetPresentationSetting(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNotif);
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs
new file mode 100644
index 000000000..fa98bc8de
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Notification/INotificationSystemEventAccessor.cs
@@ -0,0 +1,36 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Notification
+{
+ class INotificationSystemEventAccessor : IpcService
+ {
+ private KEvent _event;
+ private int _eventHandle;
+
+ public INotificationSystemEventAccessor(ServiceCtx context)
+ {
+ _event = new KEvent(context.Device.System.KernelContext);
+ }
+
+ [CommandCmif(0)]
+ // GetEvent() -> handle
+ public ResultCode GetEvent(ServiceCtx context)
+ {
+ if (_eventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out _eventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_eventHandle);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs
index dc707e6f7..aa5cc1cca 100644
--- a/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Npns/INpnsSystem.cs
@@ -1,8 +1,51 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
namespace Ryujinx.HLE.HOS.Services.Npns
{
[Service("npns:s")]
class INpnsSystem : IpcService
{
- public INpnsSystem(ServiceCtx context) { }
+ public KEvent ListenEvent;
+ public int ListenHandle = 0;
+ public INpnsSystem(ServiceCtx context)
+ {
+ ListenEvent = new KEvent(context.Device.System.KernelContext);
+ }
+
+ [CommandCmif(2)]
+ // ListenTo(u64)
+ public ResultCode ListenTo(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNpns);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // GetReceiveEvent() -> handle
+ public ResultCode GetReceiveEvent(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNpns);
+ if (ListenHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(ListenEvent.ReadableEvent, out ListenHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(ListenHandle);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(8)]
+ // ListenToByName()
+ public ResultCode ListenToByName(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNpns);
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
index f510da594..6954a54cf 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/IApplicationManagerInterface.cs
@@ -1,12 +1,232 @@
using LibHac.Ns;
+using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
+using Ryujinx.Horizon.Common;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using ApplicationId = LibHac.ApplicationId;
namespace Ryujinx.HLE.HOS.Services.Ns
{
[Service("ns:am")]
class IApplicationManagerInterface : IpcService
{
- public IApplicationManagerInterface(ServiceCtx context) { }
+ private KEvent _applicationRecordUpdateSystemEvent;
+ private int _applicationRecordUpdateSystemEventHandle;
+
+ private KEvent _sdCardMountStatusChangedEvent;
+ private int _sdCardMountStatusChangedEventHandle;
+
+ private KEvent _gameCardUpdateDetectionEvent;
+ private int _gameCardUpdateDetectionEventHandle;
+
+ private KEvent _gameCardMountFailureEvent;
+ private int _gameCardMountFailureEventHandle;
+
+ public IApplicationManagerInterface(ServiceCtx context)
+ {
+ _applicationRecordUpdateSystemEvent = new KEvent(context.Device.System.KernelContext);
+ _sdCardMountStatusChangedEvent = new KEvent(context.Device.System.KernelContext);
+ _gameCardUpdateDetectionEvent = new KEvent(context.Device.System.KernelContext);
+ _gameCardMountFailureEvent = new KEvent(context.Device.System.KernelContext);
+ }
+
+
+ [CommandCmif(0)]
+ // ListApplicationRecord(s32) -> (s32, buffer)
+ // entry_offset -> (out_entrycount, ApplicationRecord[])
+ public ResultCode ListApplicationRecord(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceAm);
+ int entryOffset = context.RequestData.ReadInt32();
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ List records = new();
+ int sID = 24;
+ foreach (RyuApplicationData title in context.Device.Configuration.Titles)
+ {
+ records.Add(new ApplicationRecord()
+ {
+ AppId = title.AppId,
+ Type = ApplicationRecordType.Installed,
+ Unknown = 0,
+ Unknown2 = (byte)sID++
+ });
+ }
+
+ records.Sort((x, y) => (int)(x.AppId.Value - y.AppId.Value));
+ if (entryOffset > 0)
+ {
+ records = records.Skip(entryOffset - 1).ToList();
+ }
+
+ context.ResponseData.Write(records.Count);
+ foreach (var record in records)
+ {
+ context.Memory.Write(position, StructToBytes(record));
+ position += (ulong)Marshal.SizeOf();
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // GetApplicationRecordUpdateSystemEvent() -> handle
+ public ResultCode GetApplicationRecordUpdateSystemEvent(ServiceCtx context)
+ {
+ if (_applicationRecordUpdateSystemEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_applicationRecordUpdateSystemEvent.ReadableEvent, out _applicationRecordUpdateSystemEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_applicationRecordUpdateSystemEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetApplicationView(buffer) -> buffer
+ public ResultCode GetApplicationViewDeperecated(ServiceCtx context)
+ {
+ ulong inPosition = context.Request.SendBuff[0].Position;
+ ulong inSize = context.Request.SendBuff[0].Size;
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ List applicationIds = new();
+ for (ulong i = 0; i < inSize / sizeof(ulong); i++)
+ {
+ ulong position = inPosition + (i * sizeof(ulong));
+ applicationIds.Add(new(context.Memory.Read(position)));
+ }
+
+ List views = new();
+
+ foreach (ApplicationId applicationId in applicationIds)
+ {
+ views.Add(new()
+ {
+ AppId = applicationId,
+ Unknown1 = 0x70000,
+ Flags = 0x401f17,
+ Unknown2 = new byte[0x40]
+ });
+ }
+
+ context.ResponseData.Write(views.Count);
+ foreach (var view in views)
+ {
+ context.Memory.Write(outputPosition, StructToBytes(view));
+ outputPosition += (ulong)Marshal.SizeOf();
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(44)]
+ // GetSdCardMountStatusChangedEvent() -> handle
+ public ResultCode GetSdCardMountStatusChangedEvent(ServiceCtx context)
+ {
+ if (_sdCardMountStatusChangedEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_sdCardMountStatusChangedEvent.ReadableEvent, out _sdCardMountStatusChangedEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_sdCardMountStatusChangedEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ const long storageFreeAndTotalSpaceSize = 6999999999999L;
+ [CommandCmif(47)]
+ // GetTotalSpaceSize(u8 storage_id) -> u64
+ public ResultCode GetTotalSpaceSize(ServiceCtx context)
+ {
+ long storageId = context.RequestData.ReadByte();
+
+
+ context.ResponseData.Write(storageFreeAndTotalSpaceSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(48)]
+ // GetFreeSpaceSize(u8 storage_id) -> u64
+ public ResultCode GetFreeSpaceSize(ServiceCtx context)
+ {
+ long storageId = context.RequestData.ReadByte();
+
+ context.ResponseData.Write(storageFreeAndTotalSpaceSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(52)]
+ // GetGameCardUpdateDetectionEvent() -> handle
+ public ResultCode GetGameCardUpdateDetectionEvent(ServiceCtx context)
+ {
+ if (_gameCardUpdateDetectionEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_gameCardUpdateDetectionEvent.ReadableEvent, out _gameCardUpdateDetectionEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gameCardUpdateDetectionEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(55)]
+ // GetApplicationDesiredLanguage(u8) -> u8
+ public ResultCode GetApplicationDesiredLanguage(ServiceCtx context)
+ {
+ byte source = context.RequestData.ReadByte();
+ byte language = 0;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNs, new { source, language });
+
+ context.ResponseData.Write(language);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(70)]
+ // ResumeAll()
+ public ResultCode ResumeAll(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceNs);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(505)]
+ // GetGameCardMountFailureEvent() -> handle
+ public ResultCode GetGameCardMountFailureEvent(ServiceCtx context)
+ {
+ if (_gameCardMountFailureEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_gameCardMountFailureEvent.ReadableEvent, out _gameCardMountFailureEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gameCardMountFailureEventHandle);
+
+ return ResultCode.Success;
+ }
[CommandCmif(400)]
// GetApplicationControlData(u8, u64) -> (unknown<4>, buffer)
@@ -19,9 +239,113 @@ namespace Ryujinx.HLE.HOS.Services.Ns
ulong position = context.Request.ReceiveBuff[0].Position;
- ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+ var title = context.Device.Configuration.Titles.FirstOrDefault(x => x.AppId.Value == titleId);
+
+ ApplicationControlProperty nacp = title.Nacp;
context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
+ if (title.Icon?.Length > 0)
+ {
+ context.Memory.Write(position + 0x4000, title.Icon);
+
+ context.ResponseData.Write(0x4000 + title.Icon.Length);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(403)]
+ // GetMaxApplicationControlCacheCount() -> u32
+ public ResultCode GetMaxApplicationControlCacheCount(ServiceCtx context)
+ {
+ // TODO: Implement this method properly.
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1701)]
+ // GetApplicationView(buffer) -> buffer
+ public ResultCode GetApplicationView(ServiceCtx context)
+ {
+ ulong inPosition = context.Request.SendBuff[0].Position;
+ ulong inSize = context.Request.SendBuff[0].Size;
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ List applicationIds = new();
+ for (ulong i = 0; i < inSize / sizeof(ulong); i++)
+ {
+ ulong position = inPosition + (i * sizeof(ulong));
+ applicationIds.Add(new(context.Memory.Read(position)));
+ }
+
+ List views = new();
+
+ foreach (ApplicationId applicationId in applicationIds)
+ {
+ views.Add(new()
+ {
+ AppId = applicationId,
+ Unknown1 = 0x70000,
+ Flags = 0x401f17,
+ Unknown2 = new byte[0x40]
+ });
+ }
+
+ context.ResponseData.Write(views.Count);
+ foreach (var view in views)
+ {
+ context.Memory.Write(outputPosition, StructToBytes(view));
+ outputPosition += (ulong)Marshal.SizeOf();
+ }
+
+ return ResultCode.Success;
+ }
+ [CommandCmif(1704)]
+ // GetApplicationView(buffer) -> buffer
+ public ResultCode GetApplicationViewWithPromotionInfo(ServiceCtx context)
+ {
+ ulong inPosition = context.Request.SendBuff[0].Position;
+ ulong inSize = context.Request.SendBuff[0].Size;
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ List applicationIds = new();
+ for (ulong i = 0; i < inSize / sizeof(ulong); i++)
+ {
+ ulong position = inPosition + (i * sizeof(ulong));
+ applicationIds.Add(new(context.Memory.Read(position)));
+ }
+
+ List views = new();
+
+ foreach (ApplicationId applicationId in applicationIds)
+ {
+ views.Add(new()
+ {
+ AppId = applicationId,
+ Unknown1 = 0x70000,
+ Flags = 0x401f17,
+ Unknown2 = new byte[0x40]
+ });
+ }
+
+ List promotions = new();
+ foreach(ApplicationView view in views)
+ {
+ promotions.Add(new()
+ {
+ AppView = view,
+ Promotion = new()
+ });
+ }
+
+ context.ResponseData.Write(views.Count);
+ foreach (var promotion in promotions)
+ {
+ context.Memory.Write(outputPosition, StructToBytes(promotion));
+ outputPosition += (ulong)Marshal.SizeOf();
+ }
return ResultCode.Success;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IDynamicRightsInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IDynamicRightsInterface.cs
new file mode 100644
index 000000000..22e66ba20
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/IDynamicRightsInterface.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Ns
+{
+ class IDynamicRightsInterface : IpcService
+ {
+
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
index 73a164135..17bf2cc15 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/IReadOnlyApplicationControlDataInterface.cs
@@ -1,24 +1,47 @@
using LibHac.Common;
using LibHac.Ns;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
+using System;
+using System.Linq;
+using ApplicationId = LibHac.ApplicationId;
namespace Ryujinx.HLE.HOS.Services.Ns
{
class IReadOnlyApplicationControlDataInterface : IpcService
{
public IReadOnlyApplicationControlDataInterface(ServiceCtx context) { }
-
+
[CommandCmif(0)]
// GetApplicationControlData(u8, u64) -> (unknown<4>, buffer)
public ResultCode GetApplicationControlData(ServiceCtx context)
{
#pragma warning disable IDE0059 // Remove unnecessary value assignment
byte source = (byte)context.RequestData.ReadInt64();
- ulong titleId = context.RequestData.ReadUInt64();
+ ApplicationId titleId = context.RequestData.ReadStruct();
#pragma warning restore IDE0059
ulong position = context.Request.ReceiveBuff[0].Position;
-
+
ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
+ foreach (RyuApplicationData ryuApplicationData in context.Device.Configuration.Titles)
+ {
+ if (ryuApplicationData.AppId != titleId)
+ {
+ continue;
+ }
+
+ nacp = ryuApplicationData.Nacp;
+ // NOTE: this is hacky but it works and prevents a crash
+ nacp.Title[1] = ryuApplicationData.Nacp.Title[0];
+ if (ryuApplicationData.Icon?.Length > 0)
+ {
+ context.Memory.Write(position + 0x4000, ryuApplicationData.Icon);
+ context.ResponseData.Write(0x4000 + ryuApplicationData.Icon.Length);
+ }
+ break;
+ }
context.Memory.Write(position, SpanHelpers.AsByteSpan(ref nacp).ToArray());
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
index d4af1afa6..b58d1d85c 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/IServiceGetterInterface.cs
@@ -8,16 +8,17 @@ namespace Ryujinx.HLE.HOS.Services.Ns
class IServiceGetterInterface : IpcService
{
public IServiceGetterInterface(ServiceCtx context) { }
+
- [CommandCmif(7996)]
- // GetApplicationManagerInterface() -> object
- public ResultCode GetApplicationManagerInterface(ServiceCtx context)
+ [CommandCmif(7988)]
+ // GetDynamicRightsInterface() -> object
+ public ResultCode GetDynamicRightsInterface(ServiceCtx context)
{
- MakeObject(context, new IApplicationManagerInterface(context));
+ MakeObject(context, new IDynamicRightsInterface());
return ResultCode.Success;
}
-
+
[CommandCmif(7989)]
// GetReadOnlyApplicationControlDataInterface() -> object
public ResultCode GetReadOnlyApplicationControlDataInterface(ServiceCtx context)
@@ -27,6 +28,15 @@ namespace Ryujinx.HLE.HOS.Services.Ns
return ResultCode.Success;
}
+ [CommandCmif(7996)]
+ // GetApplicationManagerInterface() -> object
+ public ResultCode GetApplicationManagerInterface(ServiceCtx context)
+ {
+ MakeObject(context, new IApplicationManagerInterface(context));
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(7997)]
// GetDownloadTaskInterface() -> object
public ResultCode GetDownloadTaskInterface(ServiceCtx context)
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs
index 1108778c3..860e9f210 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/ISystemUpdateInterface.cs
@@ -1,8 +1,50 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
+using Ryujinx.Horizon.Common;
+using System;
+
namespace Ryujinx.HLE.HOS.Services.Ns
{
[Service("ns:su")]
class ISystemUpdateInterface : IpcService
{
- public ISystemUpdateInterface(ServiceCtx context) { }
+ KEvent _systemUpdateEvent;
+ int _systemUpdateEventHandle;
+ public ISystemUpdateInterface(ServiceCtx context)
+ {
+ _systemUpdateEvent = new KEvent(context.Device.System.KernelContext);
+ _systemUpdateEventHandle = -1;
+ }
+
+ [CommandCmif(0)]
+ // GetBackgroundNetworkUpdateState() -> u32
+ public ResultCode GetBackgroundNetworkUpdateState(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.Service);
+ context.ResponseData.Write((byte)BackgroundNetworkUpdateState.None);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(9)]
+ // GetSystemUpdateNotificationEventForContentDelivery() -> handle
+ public ResultCode GetSystemUpdateNotificationEventForContentDelivery(ServiceCtx context)
+ {
+ if (_systemUpdateEventHandle == -1)
+ {
+ Result resultCode = context.Process.HandleTable.GenerateHandle(_systemUpdateEvent.ReadableEvent, out _systemUpdateEventHandle);
+
+ if (resultCode != Result.Success)
+ {
+ return (ResultCode)resultCode.ErrorCode;
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_systemUpdateEventHandle);
+
+ Logger.Stub?.PrintStub(LogClass.Service);
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecord.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecord.cs
new file mode 100644
index 000000000..95a0c2565
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecord.cs
@@ -0,0 +1,21 @@
+using LibHac;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal struct ApplicationRecord
+ {
+ public ApplicationId AppId;
+ public ApplicationRecordType Type;
+ public byte Unknown;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
+ public byte[] Padding1;
+
+ public byte Unknown2;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
+ public byte[] Padding2;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecordType.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecordType.cs
new file mode 100644
index 000000000..cbfd02b2c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationRecordType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ enum ApplicationRecordType : byte
+ {
+ Installing = 2,
+ Installed = 3,
+ GameCardNotInserted = 5,
+ Archived = 11,
+ GameCard = 16,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationView.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationView.cs
new file mode 100644
index 000000000..d22f7b0f3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationView.cs
@@ -0,0 +1,15 @@
+using LibHac;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x50)]
+ struct ApplicationView
+ {
+ public ApplicationId AppId;
+ public uint Unknown1;
+ public uint Flags;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x40)]
+ public byte[] Unknown2;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationViewWithPromotionInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationViewWithPromotionInfo.cs
new file mode 100644
index 000000000..11d3d7078
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/ApplicationViewWithPromotionInfo.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x70)]
+ struct ApplicationViewWithPromotionInfo
+ {
+ public ApplicationView AppView;
+ public PromotionInfo Promotion;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/BackgroundNetworkUpdateState.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/BackgroundNetworkUpdateState.cs
new file mode 100644
index 000000000..0c30c5283
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/BackgroundNetworkUpdateState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ public enum BackgroundNetworkUpdateState
+ {
+ None,
+ InProgress,
+ Ready
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/PromotionInfo.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/PromotionInfo.cs
new file mode 100644
index 000000000..3d0314b12
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/PromotionInfo.cs
@@ -0,0 +1,26 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct PromotionInfo
+ {
+ [MarshalAs(UnmanagedType.I8)]
+ public long StartTimestamp;
+
+ [MarshalAs(UnmanagedType.I8)]
+ public long EndTimestamp;
+
+ [MarshalAs(UnmanagedType.I8)]
+ public long RemainingTime;
+
+ [MarshalAs(UnmanagedType.U4)]
+ public uint Reserved;
+
+ [MarshalAs(UnmanagedType.U1)]
+ public byte Flags;
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public byte[] Padding;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ns/Types/RyuApplicationData.cs b/src/Ryujinx.HLE/HOS/Services/Ns/Types/RyuApplicationData.cs
new file mode 100644
index 000000000..5af7140ee
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Ns/Types/RyuApplicationData.cs
@@ -0,0 +1,22 @@
+using LibHac;
+using LibHac.Ns;
+
+namespace Ryujinx.HLE.HOS.Services.Ns.Types
+{
+ // Used to store some internal data about applications.
+ public struct RyuApplicationData
+ {
+ public ApplicationId AppId;
+ public ApplicationControlProperty Nacp;
+ public string Path;
+ public byte[] Icon;
+
+ public RyuApplicationData(ApplicationId appId, ApplicationControlProperty nacp, string path, byte[] icon)
+ {
+ AppId = appId;
+ Nacp = nacp;
+ Path = path;
+ Icon = icon;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IDaemonController.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IDaemonController.cs
new file mode 100644
index 000000000..782a94dfd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IDaemonController.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Olsc
+{
+ class IDaemonController : IpcService
+ {
+
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs
index c6e372052..d8fc39e72 100644
--- a/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Olsc/IOlscServiceForSystemService.cs
@@ -1,8 +1,46 @@
+using Ryujinx.HLE.HOS.Services.Olsc.OlscServiceForSystemService;
+
namespace Ryujinx.HLE.HOS.Services.Olsc
{
[Service("olsc:s")] // 4.0.0+
class IOlscServiceForSystemService : IpcService
{
public IOlscServiceForSystemService(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // GetTransferTaskListController() -> object
+ public ResultCode GetTransferTaskListController(ServiceCtx context)
+ {
+ MakeObject(context, new ITransferTaskListController(context));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // GetDaemonController() -> object
+ public ResultCode GetDaemonController(ServiceCtx context)
+ {
+ MakeObject(context, new IDaemonController());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(200)]
+ // GetDataTransferPolicy(u64) -> (u8, u8)
+ public ResultCode GetDataTransferPolicy(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+ context.ResponseData.Write(0);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10000)]
+ // GetOlscServiceForSystemService() -> object
+ public ResultCode GetOlscServiceForSystemService(ServiceCtx context)
+ {
+ MakeObject(context, new IOlscServiceForSystemService(context));
+
+ return ResultCode.Success;
+ }
}
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/INativeHandleHolder.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/INativeHandleHolder.cs
new file mode 100644
index 000000000..e034046ff
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/INativeHandleHolder.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Olsc.OlscServiceForSystemService
+{
+ class INativeHandleHolder : IpcService
+ {
+ private KEvent _event;
+ private int _eventHandle;
+
+ public INativeHandleHolder(ServiceCtx context)
+ {
+ _event = new KEvent(context.Device.System.KernelContext);
+ }
+
+ [CommandCmif(0)]
+ // GetNativeHandle() -> handle
+ public ResultCode GetNativeHandle(ServiceCtx context)
+ {
+ if (_eventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_event.ReadableEvent, out _eventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_eventHandle);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/ITransferTaskListController.cs b/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/ITransferTaskListController.cs
new file mode 100644
index 000000000..11bc03668
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Olsc/OlscServiceForSystemService/ITransferTaskListController.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Olsc.OlscServiceForSystemService
+{
+ class ITransferTaskListController : IpcService
+ {
+ public ITransferTaskListController(ServiceCtx context) { }
+
+ [CommandCmif(5)]
+ // GetNativeHandleHolder() -> object
+ public ResultCode GetNativeHandleHolder(ServiceCtx context)
+ {
+ MakeObject(context, new INativeHandleHolder(context));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceOlsc);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(9)]
+ // GetNativeHandleHolderEx() -> object
+ public ResultCode GetNativeHandleHolderEx(ServiceCtx context)
+ {
+ MakeObject(context, new INativeHandleHolder(context));
+
+ Logger.Stub?.PrintStub(LogClass.ServiceOlsc);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
index 9b026a1c3..0cbfa562e 100644
--- a/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Pctl/ParentalControlServiceFactory/IParentalControlService.cs
@@ -1,5 +1,8 @@
using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Arp;
+using Ryujinx.Horizon.Common;
using System;
using static LibHac.Ns.ApplicationControlProperty;
@@ -22,11 +25,24 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
private bool _stereoVisionRestriction = false;
#pragma warning restore IDE0052, CS0414
+ private KEvent _synchronizationEvent;
+ private int _synchronizationEventHandle;
+
+ private KEvent _unlinkedEvent;
+ private int _unlinkedEventHandle;
+
+ private KEvent _playTimerRequestSuspensionEvent;
+ private int _playTimerRequestSuspensionEventHandle;
+
public IParentalControlService(ServiceCtx context, ulong pid, bool withInitialize, int permissionFlag)
{
_pid = pid;
_permissionFlag = permissionFlag;
+ _synchronizationEvent = new KEvent(context.Device.System.KernelContext);
+ _unlinkedEvent = new KEvent(context.Device.System.KernelContext);
+ _playTimerRequestSuspensionEvent = new KEvent(context.Device.System.KernelContext);
+
if (withInitialize)
{
Initialize(context);
@@ -97,6 +113,28 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
return ResultCode.Success;
}
+ [CommandCmif(1006)]
+ // IsRestrictionTemporaryUnlocked() -> b8
+ public ResultCode IsRestrictionTemporaryUnlocked(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1010)]
+ // IsRestrictedSystemSettingsEntered() -> b8
+ public ResultCode IsRestrictedSystemSettingsEntered(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(1017)] // 10.0.0+
// EndFreeCommunication()
public ResultCode EndFreeCommunication(ServiceCtx context)
@@ -106,6 +144,37 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
return ResultCode.Success;
}
+ [CommandCmif(1032)]
+ // GetSafetyLevel() -> u32
+ public ResultCode GetSafetyLevel(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1035)]
+ // GetCurrentSettings() -> nn::pctl::RestrictionSettings
+ public ResultCode GetCurrentSettings(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1039)]
+ // GetFreeCommunicationApplicationListCount() -> u32
+ public ResultCode GetFreeCommunicationApplicationListCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(1013)] // 4.0.0+
// ConfirmStereoVisionPermission()
public ResultCode ConfirmStereoVisionPermission(ServiceCtx context)
@@ -235,6 +304,125 @@ namespace Ryujinx.HLE.HOS.Services.Pctl.ParentalControlServiceFactory
return resultCode;
}
+ [CommandCmif(1403)]
+ // IsPairingActive() -> b8
+ public ResultCode IsPairingActive(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1432)]
+ // GetSynchronizationEvent() -> handle
+ public ResultCode GetSynchronizationEvent(ServiceCtx context)
+ {
+ if (_synchronizationEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_synchronizationEvent.ReadableEvent, out _synchronizationEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_synchronizationEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1451)]
+ // StartPlayTimer()
+ public ResultCode StartPlayTimer(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1452)]
+ // StopPlayTimer()
+ public ResultCode StopPlayTimer(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1455)]
+ // IsRestrictedByPlayTimer() -> b8
+ public ResultCode IsRestrictedByPlayTimer(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1456)]
+ // GetPlayTimerSettings() -> nn::pctl::PlayTimerSettings
+ public ResultCode GetPlayTimerSettings(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1457)]
+ // GetPlayTimerEventToRequestSuspension() -> handle
+ public ResultCode GetPlayTimerEventToRequestSuspension(ServiceCtx context)
+ {
+ if (_playTimerRequestSuspensionEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_playTimerRequestSuspensionEvent.ReadableEvent, out _playTimerRequestSuspensionEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_playTimerRequestSuspensionEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1458)] // 4.0.0+
+ // IsPlayTimerAlarmDisabled() -> b8
+ public ResultCode IsPlayTimerAlarmDisabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1473)]
+ // GetUnlinkedEvent() -> handle
+ public ResultCode GetUnlinkedEvent(ServiceCtx context)
+ {
+ if (_unlinkedEventHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_unlinkedEvent.ReadableEvent, out _unlinkedEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_unlinkedEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(145601)]
+ // GetPlayTimerSettings()
+ public ResultCode GetPlayTimerSettingsEx(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServicePctl);
+ return ResultCode.Success;
+ }
+
private ResultCode IsStereoVisionPermittedImpl()
{
/*
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs
index 4ad57bedc..df27bda67 100644
--- a/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/ISystemSettingsServer.cs
@@ -1,4 +1,5 @@
using LibHac;
+using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@@ -7,9 +8,15 @@ using LibHac.Ncm;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Services.Settings.Types;
+using Ryujinx.HLE.HOS.Services.Time.Clock;
using Ryujinx.HLE.HOS.SystemState;
using System;
using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Settings
@@ -85,12 +92,68 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
+
+ [CommandCmif(7)]
+ // GetLockScreenFlag() -> bool
+ public ResultCode GetLockScreenFlag(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+ return ResultCode.Success;
+ }
+ [CommandCmif(17)]
+ // GetAccountSettings() -> nn::settings::system::AccountSettings
+ public ResultCode GetAccountSettings(ServiceCtx context)
+ {
+ AccountSettings accountSettings = new AccountSettings
+ {
+ UserSelectorSettings = new UserSelectorSettings()
+ };
+ context.ResponseData.WriteStruct(accountSettings);
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(21)]
+ // GetEulaVersions() -> (u32, buffer)
+ public ResultCode GetEulaVersions(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ ulong bufferPosition = context.Request.ReceiveBuff[0].Position;
+ ulong bufferLen = context.Request.ReceiveBuff[0].Size;
+
+ if ((ulong)Unsafe.SizeOf() > bufferLen)
+ {
+ return ResultCode.NullEULAVersionBuffer;
+ }
+
+ var eulaVersion = new EulaVersion
+ {
+ Version = 0x10000,
+ RegionCode = 1,
+ ClockType = 1,
+ NetworkSystemClock = 0,
+ SteadyClock = new Time.Clock.SteadyClockTimePoint {
+ TimePoint = 0xc,
+ ClockSourceId = new UInt128(0x36a0328708bc18c1, 0x1608ea2b023284)
+ }
+ };
+
+ context.Memory.Write(bufferPosition, eulaVersion);
+
+ context.ResponseData.Write(1);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(23)]
// GetColorSetId() -> i32
public ResultCode GetColorSetId(ServiceCtx context)
{
- context.ResponseData.Write((int)context.Device.System.State.ThemeColor);
+ bool isDarkMode = context.Device.UIHandler.IsDarkMode();
+ context.ResponseData.Write(isDarkMode ? 1 : 0);
return ResultCode.Success;
}
@@ -99,12 +162,51 @@ namespace Ryujinx.HLE.HOS.Services.Settings
// GetColorSetId() -> i32
public ResultCode SetColorSetId(ServiceCtx context)
{
- int colorSetId = context.RequestData.ReadInt32();
-
- context.Device.System.State.ThemeColor = (ColorSet)colorSetId;
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
+
+ [CommandCmif(29)]
+ // GetNotificationSettings() -> nn::settings::system::NotificationSettings
+ public ResultCode GetNotificationSettings(ServiceCtx context)
+ {
+ NotificationSettings notificationSettings = new NotificationSettings
+ {
+ Flags = NotificationFlag.None,
+ Volume = NotificationVolume.Low,
+ HeadTime = new NotificationTime(),
+ TailTime = new NotificationTime()
+ };
+
+ context.ResponseData.WriteStruct(notificationSettings);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(31)]
+ // GetAccountNotificationSettings() -> (u32, buffer)
+ public ResultCode GetAccountNotificationSettings(ServiceCtx context)
+ {
+ var buffer = context.Request.ReceiveBuff[0];
+
+ Span elementsSpan = CreateSpanFromBuffer(context,buffer,true);
+ elementsSpan[0] = new AccountNotificationSettings
+ {
+ Uid = new Array16(),
+ Flags = 0x1F,
+ FriendPresenceOverlayPermission = 0x1,
+ FriendInvitationOverlayPermission = 0x1,
+ Reserved1 = 0,
+ Reserved2 = 0
+ };
+ int count = elementsSpan.Length;
+ Logger.Info?.PrintStub(LogClass.ServiceSet, $"AccountNotificationSettings: {count} settings found");
+ context.ResponseData.Write(1);
+ WriteSpanToBuffer(context, buffer, elementsSpan);
+ return ResultCode.Success;
+ }
+
[CommandCmif(37)]
// GetSettingsItemValueSize(buffer, buffer) -> u64
@@ -221,6 +323,29 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
+ [CommandCmif(39)]
+ // GetTvSettings() -> nn::settings::system::TvSettings
+ public ResultCode GetTvSettings(ServiceCtx context)
+ {
+ TvSettings tvSettings = new();
+
+ context.ResponseData.WriteStruct(tvSettings);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(47)]
+ // GetQuestFlag() -> bool
+ public ResultCode GetQuestFlag(ServiceCtx context)
+ {
+ // NOTE: Gets a flag determining whether the console is a kiosk unit (codenamed "Quest"). Used by qlaunch to determine whether to launch Retail Interactive Display Menu.
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
[CommandCmif(60)]
// IsUserSystemClockAutomaticCorrectionEnabled() -> bool
public ResultCode IsUserSystemClockAutomaticCorrectionEnabled(ServiceCtx context)
@@ -244,15 +369,50 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
+ [CommandCmif(63)]
+ // GetPrimaryAlbumStorage() -> s32
+ public ResultCode GetPrimaryAlbumStorage(ServiceCtx context)
+ {
+ context.ResponseData.Write((byte)PrimaryAlbumStorage.Nand);
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+ return ResultCode.Success;
+ }
+
[CommandCmif(68)]
// GetSerialNumber() -> buffer
public ResultCode GetSerialNumber(ServiceCtx context)
{
context.ResponseData.Write(Encoding.ASCII.GetBytes("RYU00000000000"));
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(71)]
+ // GetSleepSettings() -> SleepSettings
+ public ResultCode GetSleepSettings(ServiceCtx context)
+ {
+ SleepSettings sleepSettings = new();
+
+ context.ResponseData.WriteStruct(sleepSettings);
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(75)]
+ // GetInitialLaunchSettings() -> nn::settings::system::InitialLaunchSettings
+ public ResultCode GetInitialLaunchSettings(ServiceCtx context)
+ {
+ InitialLaunchSettings launchSettings = new InitialLaunchSettings();
+ launchSettings.Flags |= InitialLaunchFlag.InitialLaunchCompletionFlag;
+ launchSettings.Flags |= InitialLaunchFlag.InitialLaunchUserAdditionFlag;
+ launchSettings.Flags |= InitialLaunchFlag.InitialLaunchTimestampFlag;
+
+ context.ResponseData.WriteStruct(launchSettings);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
return ResultCode.Success;
}
-
+
[CommandCmif(77)]
// GetDeviceNickName() -> buffer
public ResultCode GetDeviceNickName(ServiceCtx context)
@@ -301,7 +461,7 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
-
+
[CommandCmif(90)]
// GetMiiAuthorId() -> nn::util::Uuid
public ResultCode GetMiiAuthorId(ServiceCtx context)
@@ -313,7 +473,85 @@ namespace Ryujinx.HLE.HOS.Services.Settings
return ResultCode.Success;
}
+
+ [CommandCmif(95)]
+ // GetAutoUpdateEnableFlag() -> bool
+ public ResultCode GetAutoUpdateEnableFlag(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(99)]
+ // GetBatteryPercentageFlag() -> u8
+ public ResultCode GetBatteryPercentageFlag(ServiceCtx context)
+ {
+ context.ResponseData.Write(100);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(124)]
+ // GetErrorReportSharePermission() -> s32
+ public ResultCode GetErrorReportSharePermission(ServiceCtx context)
+ {
+ context.ResponseData.Write(1);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(126)]
+ // GetAppletLaunchFlags() -> u32
+ public ResultCode GetAppletLaunchFlags(ServiceCtx context)
+ {
+ // NOTE: I do not know what this is used for but it is used by qlaunch.
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(136)]
+ // GetKeyboardLayout() -> s32
+ public ResultCode GetKeyboardLayout(ServiceCtx context)
+ {
+ context.ResponseData.Write((int)KeyboardLayout.Default);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(170)]
+ // GetChineseTraditionalInputMethod() -> s32
+ public ResultCode GetChineseTraditionalInputMethod(ServiceCtx context)
+ {
+ context.ResponseData.Write(0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(201)]
+ // GetFieldTestingFlag() -> bool
+ public ResultCode GetFieldTestingFlag(ServiceCtx context)
+ {
+ context.ResponseData.Write(false);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceSet);
+
+ return ResultCode.Success;
+ }
+
public byte[] GetFirmwareData(Switch device)
{
const ulong SystemVersionTitleId = 0x0100000000000809;
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountNotificationSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountNotificationSettings.cs
new file mode 100644
index 000000000..b90308133
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountNotificationSettings.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ struct AccountNotificationSettings
+ {
+ public Array16 Uid; // 0x0 - 0x10: Unique ID (16 bytes)
+
+ public uint Flags; // 0x10 - 0x4: Notification Flags
+
+ public byte FriendPresenceOverlayPermission; // 0x14 - 0x1: Friend presence overlay permission
+
+ public byte FriendInvitationOverlayPermission; // 0x15 - 0x1: Friend invitation overlay permission
+
+ public byte Reserved1; // 0x16 - 0x2: Reserved bytes
+ public byte Reserved2; // 0x16 - 0x2: Reserved bytes
+ }
+
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountSettings.cs
new file mode 100644
index 000000000..91a41f959
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/AccountSettings.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct AccountSettings
+ {
+ public UserSelectorSettings UserSelectorSettings;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/CmuMode.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/CmuMode.cs
new file mode 100644
index 000000000..eb84f0c1b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/CmuMode.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum CmuMode : uint
+ {
+ None = 0,
+ ColorInvert = 1,
+ HighContrast = 2,
+ GrayScale = 3
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/ConsoleSleepPlan.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/ConsoleSleepPlan.cs
new file mode 100644
index 000000000..c692f60b0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/ConsoleSleepPlan.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum ConsoleSleepPlan : uint
+ {
+ OneHour = 0,
+ TwoHour = 1,
+ ThreeHour = 2,
+ SixHour = 3,
+ TwelveHour = 4,
+ Never = 5
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/ErrorReportSharePermission.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/ErrorReportSharePermission.cs
new file mode 100644
index 000000000..de021e600
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/ErrorReportSharePermission.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum ErrorReportSharePermission : uint
+ {
+ NotConfirmed = 0,
+ Granted = 1,
+ Denied = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/EulaVersion.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/EulaVersion.cs
new file mode 100644
index 000000000..df1ceed10
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/EulaVersion.cs
@@ -0,0 +1,16 @@
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct EulaVersion
+ {
+ public uint Version;
+ public uint RegionCode;
+ public uint ClockType;
+ public uint Reserved;
+ public long NetworkSystemClock;
+ public SteadyClockTimePoint SteadyClock;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/HandheldSleepPlan.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/HandheldSleepPlan.cs
new file mode 100644
index 000000000..19548f79a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/HandheldSleepPlan.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum HandheldSleepPlan : uint
+ {
+ OneMin = 0,
+ ThreeMin = 1,
+ FiveMin = 2,
+ TenMin = 3,
+ ThirtyMin = 4,
+ Never = 5
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/HdmiContentType.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/HdmiContentType.cs
new file mode 100644
index 000000000..70a4b8b42
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/HdmiContentType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum HdmiContentType : uint
+ {
+ None = 0,
+ Graphics = 1,
+ Cinema = 2,
+ Photo = 3,
+ Game = 4
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchFlag.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchFlag.cs
new file mode 100644
index 000000000..4f6d3a739
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchFlag.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [Flags]
+ enum InitialLaunchFlag : uint
+ {
+ None = 0,
+ InitialLaunchCompletionFlag = 1 << 0,
+ InitialLaunchUserAdditionFlag = 1 << 8,
+ InitialLaunchTimestampFlag = 1 << 16
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchSettings.cs
new file mode 100644
index 000000000..abc0a7fdb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/InitialLaunchSettings.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Time.Clock;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 8)]
+ struct InitialLaunchSettings
+ {
+ public InitialLaunchFlag Flags;
+ public uint Reserved;
+ public SteadyClockTimePoint TimeStamp;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationFlag.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationFlag.cs
new file mode 100644
index 000000000..0d349324c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationFlag.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [Flags]
+ enum NotificationFlag : uint
+ {
+ None = 0,
+ RingtoneFlag = 1 << 0,
+ DownloadCompletionFlag = 1 << 1,
+ EnablesNews = 1 << 8,
+ IncomingLampFlag = 1 << 9
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationSettings.cs
new file mode 100644
index 000000000..d8e3bea96
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationSettings.cs
@@ -0,0 +1,13 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 8)]
+ struct NotificationSettings
+ {
+ public NotificationFlag Flags;
+ public NotificationVolume Volume;
+ public NotificationTime HeadTime;
+ public NotificationTime TailTime;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationTime.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationTime.cs
new file mode 100644
index 000000000..207465a7f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationTime.cs
@@ -0,0 +1,11 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ struct NotificationTime
+ {
+ public uint Hour;
+ public uint Minute;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationVolume.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationVolume.cs
new file mode 100644
index 000000000..94590e958
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/NotificationVolume.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum NotificationVolume : uint
+ {
+ Mute = 0,
+ Low = 1,
+ High = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/PrimaryAlbumStorage.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PrimaryAlbumStorage.cs
new file mode 100644
index 000000000..49e54374f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/PrimaryAlbumStorage.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum PrimaryAlbumStorage : uint
+ {
+ Nand = 0,
+ SdCard = 1,
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/RgbRange.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/RgbRange.cs
new file mode 100644
index 000000000..a8409f647
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/RgbRange.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum RgbRange : uint
+ {
+ Auto = 0,
+ Full = 1,
+ Limited = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/SleepSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/SleepSettings.cs
new file mode 100644
index 000000000..dd417d658
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/SleepSettings.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ struct SleepSettings
+ {
+ public uint Flags;
+ public HandheldSleepPlan HandheldSleepPlan;
+ public ConsoleSleepPlan ConsoleSleepPlan;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvFlag.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvFlag.cs
new file mode 100644
index 000000000..943bb4163
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvFlag.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [Flags]
+ enum TvFlag : uint
+ {
+ None = 0,
+ Allows4k = 1 << 0,
+ Allows3d = 1 << 1,
+ AllowsCec = 1 << 2,
+ PreventsScreenBurnIn = 1 << 3
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvResolution.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvResolution.cs
new file mode 100644
index 000000000..ef852bbde
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvResolution.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ enum TvResolution : uint
+ {
+ Auto = 0,
+ Resolution1080p = 1,
+ Resolution720p = 2,
+ Resolution480p = 3
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvSettings.cs
new file mode 100644
index 000000000..d781de01e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/TvSettings.cs
@@ -0,0 +1,17 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 4)]
+ struct TvSettings
+ {
+ public TvFlag Flags;
+ public TvResolution TvResolution;
+ public HdmiContentType HdmiContentType;
+ public RgbRange RgbRange;
+ public CmuMode CmuMode;
+ public uint TvUnderscan;
+ public uint TvGamma;
+ public uint ContrastRatio;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorFlag.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorFlag.cs
new file mode 100644
index 000000000..3f7e1c966
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorFlag.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [Flags]
+ enum UserSelectorFlag : uint
+ {
+ None = 0,
+ SkipsIfSingleUser = 1 << 0,
+ Unknown = 1U << 31
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorSettings.cs b/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorSettings.cs
new file mode 100644
index 000000000..d223d99c0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Settings/Types/UserSelectorSettings.cs
@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Settings.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct UserSelectorSettings
+ {
+ public UserSelectorFlag UserSelectorFlag;
+ }
+}
\ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs
index fa6e77298..ba12a6885 100644
--- a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/ApplicationDisplayService/ISystemDisplayService.cs
@@ -49,6 +49,15 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
return _applicationDisplayService.CreateStrayLayer(context);
}
+
+ [CommandCmif(3000)]
+ // ListDisplayModes(u64) -> (u64, buffer)
+ public ResultCode ListDisplayModes(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceVi);
+
+ return ResultCode.Success;
+ }
[CommandCmif(3200)]
// GetDisplayMode(u64) -> nn::vi::DisplayModeInfo
diff --git a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
index 56bcae1ee..3bec26386 100644
--- a/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Vi/RootService/IApplicationDisplayService.cs
@@ -126,7 +126,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
ulong displayInfoBuffer = context.Request.ReceiveBuff[0].Position;
// TODO: Determine when more than one display is needed.
- ulong displayCount = 1;
+ ulong displayCount = 2;
for (int i = 0; i < (int)displayCount; i++)
{
diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
index 8ccb5cf89..54bc3ca7f 100644
--- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs
+++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs
@@ -66,5 +66,10 @@ namespace Ryujinx.HLE.UI
/// Displays the player select dialog and returns the selected profile.
///
UserProfile ShowPlayerSelectDialog();
+
+ ///
+ /// Gets the UI theme and returns true if is dark mode.
+ ///
+ bool IsDarkMode();
}
}
diff --git a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs
index 91beec20d..24482764a 100644
--- a/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs
+++ b/src/Ryujinx.Horizon/Bcat/Ipc/ServiceCreator/BcatService.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Bcat.Types;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Bcat;
@@ -16,5 +17,13 @@ namespace Ryujinx.Horizon.Bcat.Ipc
return Result.Success;
}
+
+ [CmifCommand(30300)]
+ // RegisterSystemApplicationDeliveryTasks
+ public Result RegisterSystemApplicationDeliveryTasks()
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceBcat);
+ return Result.Success;
+ }
}
}
diff --git a/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs
index 2c97476c8..9960f6bac 100644
--- a/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs
+++ b/src/Ryujinx.Horizon/Lbl/Ipc/LblController.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Lbl;
using Ryujinx.Horizon.Sdk.Sf;
@@ -8,6 +9,15 @@ namespace Ryujinx.Horizon.Lbl.Ipc
{
private bool _vrModeEnabled;
private float _currentBrightnessSettingForVrMode;
+
+ [CmifCommand(1)]
+ // LoadCurrentSetting()
+ public Result LoadCurrentSetting()
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceLbl);
+
+ return Result.Success;
+ }
[CmifCommand(17)]
public Result SetBrightnessReflectionDelayLevel(float unknown0, float unknown1)
diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/Sender.cs b/src/Ryujinx.Horizon/Ovln/Ipc/Sender.cs
new file mode 100644
index 000000000..20f07d067
--- /dev/null
+++ b/src/Ryujinx.Horizon/Ovln/Ipc/Sender.cs
@@ -0,0 +1,10 @@
+using Ryujinx.Horizon.Common;
+using Ryujinx.Horizon.Sdk.Ovln;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Ovln.Ipc
+{
+ partial class Sender : ISender
+ {
+ }
+}
diff --git a/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs
index 04ef5d4b1..a95e7f2e5 100644
--- a/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs
+++ b/src/Ryujinx.Horizon/Ovln/Ipc/SenderService.cs
@@ -1,8 +1,21 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Ovln;
+using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Ovln.Ipc
{
partial class SenderService : ISenderService
{
+ [CmifCommand(0)]
+ // OpenSender() -> object
+ public Result OpenSender(out ISender service)
+ {
+ service = new Sender();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceOvln);
+
+ return Result.Success;
+ }
}
}
diff --git a/src/Ryujinx.Horizon/Sdk/Ovln/ISender.cs b/src/Ryujinx.Horizon/Sdk/Ovln/ISender.cs
new file mode 100644
index 000000000..8e9ba9ebd
--- /dev/null
+++ b/src/Ryujinx.Horizon/Sdk/Ovln/ISender.cs
@@ -0,0 +1,9 @@
+using LibHac;
+using Ryujinx.Horizon.Sdk.Sf;
+
+namespace Ryujinx.Horizon.Sdk.Ovln
+{
+ interface ISender : IServiceObject
+ {
+ }
+}
diff --git a/src/Ryujinx/AppHost.cs b/src/Ryujinx/AppHost.cs
index 25f451858..46dac3582 100644
--- a/src/Ryujinx/AppHost.cs
+++ b/src/Ryujinx/AppHost.cs
@@ -40,6 +40,7 @@ using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using Ryujinx.Input.HLE;
@@ -47,6 +48,7 @@ using SkiaSharp;
using SPB.Graphics.Vulkan;
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
@@ -54,6 +56,7 @@ using System.Threading;
using System.Threading.Tasks;
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
using AntiAliasing = Ryujinx.Common.Configuration.AntiAliasing;
+using ApplicationId = LibHac.ApplicationId;
using InputManager = Ryujinx.Input.HLE.InputManager;
using IRenderer = Ryujinx.Graphics.GAL.IRenderer;
using Key = Ryujinx.Input.Key;
@@ -124,6 +127,7 @@ namespace Ryujinx.Ava
private bool _dialogShown;
private readonly bool _isFirmwareTitle;
+ private List _titles = new();
private readonly Lock _lockObject = new();
@@ -141,6 +145,8 @@ namespace Ryujinx.Ava
public string ApplicationPath { get; private set; }
public ulong ApplicationId { get; private set; }
public bool ScreenshotRequested { get; set; }
+
+ public IImmutableList Titles { get => _titles.ToImmutableList(); }
public AppHost(
RendererHost renderer,
@@ -919,12 +925,19 @@ namespace Ryujinx.Ava
// Initialize Configuration.
MemoryConfiguration memoryConfiguration = ConfigurationState.Instance.System.DramSize.Value;
+
+ _titles = new List();
+ foreach (ApplicationData App in _viewModel.Applications)
+ {
+ _titles.Add(new(new ApplicationId(App.Id),App.ControlHolder.Value,App.Path,App.Icon));
+ }
Device = new Switch(new HLEConfiguration(
VirtualFileSystem,
_viewModel.LibHacHorizonManager,
ContentManager,
_accountManager,
+ Titles.ToImmutableList(),
_userChannelPersistence,
renderer,
InitializeAudio(),
diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json
index e89b872b5..28bebb46d 100644
--- a/src/Ryujinx/Assets/locales.json
+++ b/src/Ryujinx/Assets/locales.json
@@ -172,6 +172,56 @@
"zh_TW": ""
}
},
+ {
+ "ID": "MenuBarFileOpenAppletOpenQLaunchApplet",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "QLaunch Applet",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
+ {
+ "ID": "MenuBarFileOpenAppletOpenQLaunchAppletToolTip",
+ "Translations": {
+ "ar_SA": "",
+ "de_DE": "",
+ "el_GR": "",
+ "en_US": "Open QLaunch Applet in Standalone mode",
+ "es_ES": "",
+ "fr_FR": "",
+ "he_IL": "",
+ "it_IT": "",
+ "ja_JP": "",
+ "ko_KR": "",
+ "no_NO": "",
+ "pl_PL": "",
+ "pt_BR": "",
+ "ru_RU": "",
+ "sv_SE": "",
+ "th_TH": "",
+ "tr_TR": "",
+ "uk_UA": "",
+ "zh_CN": "",
+ "zh_TW": ""
+ }
+ },
{
"ID": "SettingsTabInputDirectMouseAccess",
"Translations": {
diff --git a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
index aa8238fc6..e873f4c74 100644
--- a/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
+++ b/src/Ryujinx/Headless/HeadlessRyujinx.Init.cs
@@ -16,9 +16,11 @@ using Ryujinx.Graphics.Metal;
using Ryujinx.Graphics.OpenGL;
using Ryujinx.Graphics.Vulkan;
using Ryujinx.HLE;
+using Ryujinx.HLE.HOS.Services.Ns.Types;
using Ryujinx.Input;
using Silk.NET.Vulkan;
using System;
+using System.Collections.Immutable;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
@@ -333,6 +335,7 @@ namespace Ryujinx.Headless
_libHacHorizonManager,
_contentManager,
_accountManager,
+ new ImmutableArray(),
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),
diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs
index c9d672af4..0645f341c 100644
--- a/src/Ryujinx/Headless/Windows/WindowBase.cs
+++ b/src/Ryujinx/Headless/Windows/WindowBase.cs
@@ -563,5 +563,10 @@ namespace Ryujinx.Headless
{
return AccountSaveDataManager.GetLastUsedUser();
}
+
+ public bool IsDarkMode()
+ {
+ return true;
+ }
}
}
diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
index c75e532ec..17fd1e44f 100644
--- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
+++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs
@@ -313,5 +313,10 @@ namespace Ryujinx.Ava.UI.Applet
}
return profile;
}
+
+ public bool IsDarkMode()
+ {
+ return ConfigurationState.Instance.UI.BaseStyle.Value == "Dark";
+ }
}
}
diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
index 24d71b495..4f2119f9a 100644
--- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
+++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml
@@ -65,6 +65,11 @@
Header="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerApplet}"
Icon="{ext:Icon fa-solid fa-photo-film}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerAppletToolTip}" />
+