Implement QLaunch

Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
This commit is contained in:
Jacobwasbeast 2025-02-08 21:10:51 -06:00
parent 81c6aeec28
commit 50926d6a2f
98 changed files with 3321 additions and 27 deletions

View File

@ -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,
}
}

View File

@ -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());
}
}
}

View File

@ -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
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
public EnabledDirtyHack[] Hacks { internal get; set; }
/// <summary>
/// The list of title ids found byApplicationLibrary.
/// </summary>
/// <remarks>This cannot be changed after <see cref="Switch"/> instantiation.</remarks>
internal readonly IImmutableList<RyuApplicationData> Titles;
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
LibHacHorizonManager libHacHorizonManager,
ContentManager contentManager,
AccountManager accountManager,
IImmutableList<RyuApplicationData> 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;

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<nn::account::profile::UserData, 0x19>)

View File

@ -125,5 +125,24 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
return ResultCode.Success;
}
[CommandCmif(250)]
// GetBaasAccountAdministrator(nn::account::Uid) -> object<nn::account::baas::IAdministrator>
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;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
{
class ISystemProcessCommonFunctions : IpcService
{
}
}

View File

@ -155,6 +155,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
[CommandCmif(31)]
[CommandCmif(32)]
// GetReaderLockAccessorEx(u32) -> object<nn::am::service::ILockAccessor>
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

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<copy>
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<nn::am::service::ILockAccessor>
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;
}
}
}

View File

@ -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)

View File

@ -35,5 +35,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
return ResultCode.Success;
}
[CommandCmif(450)]
// ISystemProcessCommonFunctions() -> object<nn::am::service::ISystemProcessCommonFunctions>
public ResultCode ISystemProcessCommonFunctions(ServiceCtx context)
{
MakeObject(context, new ISystemProcessCommonFunctions());
return ResultCode.Success;
}
}
}

View File

@ -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<copy>
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;
}
}
};

View File

@ -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;
}
}
}

View File

@ -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<copy>)
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<copy>)
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;
}
}
}

View File

@ -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<nn::btm::IBtmSystemCore>
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;
}
}
}

View File

@ -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<unknown, 5>, buffer<unknown, 5>) -> 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;
}
}
}

View File

@ -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<ButtonState> 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<ButtonState> 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();
}
}
}

View File

@ -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<T>(int expectedSize)
{
@ -48,6 +52,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350);
CheckTypeSizeOrThrow<RingLifo<DebugMouseState>>(0x350);
CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8);
CheckTypeSizeOrThrow<RingLifo<ButtonState>>(0x1B8);
CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000);
CheckTypeSizeOrThrow<SharedMemory>(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> inputConfig)

View File

@ -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<copy>
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<copy>
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<copy>
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<nn::hid::system::UniquePadId[], 0xa>)
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<copy>
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<nn::hid::system::PlayReportControllerUsage[], 0xa>)
public ResultCode GetPlayReportControllerUsages(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceHid);
context.ResponseData.Write(0L);
return ResultCode.Success;
}
[CommandCmif(542)]
// AcquirePlayReportRegisteredDeviceUpdateEvent() -> handle<copy>
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<nn::hid::system::RegisteredDevice[], 0xa>)
public ResultCode GetRegisteredDevicesOld(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceHid);
context.ResponseData.Write(0L);
return ResultCode.Success;
}
[CommandCmif(544)]
// AcquireConnectionTriggerTimeoutEvent() -> handle<copy>
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<copy>
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<nn::hid::system::UniquePadId[], 0xa>)
public ResultCode GetUniquePadIds(ServiceCtx context)
{
context.ResponseData.Write(0L);
return ResultCode.Success;
}
[CommandCmif(751)]
// AcquireJoyDetachOnBluetoothOffEventHandle(nn::applet::AppletResourceUserId, pid) -> handle<copy>
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();

View File

@ -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;
}
}

View File

@ -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
/// </summary>
[FieldOffset(0x3800)]
public RingLifo<KeyboardState> Keyboard;
/// <summary>
/// Home Button.
/// </summary>
[FieldOffset(0x4C00)]
public RingLifo<ButtonState> HomeButton;
/// <summary>
/// Sleep Button.
/// </summary>
[FieldOffset(0x4E00)]
public RingLifo<ButtonState> SleepButton;
/// <summary>
/// Capture Button.
/// </summary>
[FieldOffset(0x5000)]
public RingLifo<ButtonState> CaptureButton;
/// <summary>
/// Nintendo Pads.

View File

@ -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>(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<byte> 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<byte>(rawData);
}
public Span<T> CreateSpanFromBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, bool isOutput) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(CreateByteSpanFromBuffer(context, ipcBuff, isOutput));
}
public void WriteSpanToBuffer<T>(ServiceCtx context, IpcBuffDesc ipcBuff, Span<T> span) where T : unmanaged
{
Span<byte> rawData = MemoryMarshal.Cast<T, byte>(span);
context.Memory.Write(ipcBuff.Position, rawData);
}
}
}

View File

@ -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;
}
}
}

View File

@ -4,5 +4,14 @@ namespace Ryujinx.HLE.HOS.Services.Ldn
class IMonitorServiceCreator : IpcService
{
public IMonitorServiceCreator(ServiceCtx context) { }
[CommandCmif(0)]
// CreateMonitorService() -> object<nn::ldn::detail::IMonitorService>
public ResultCode CreateMonitorService(ServiceCtx context)
{
MakeObject(context, new IMonitorService());
return ResultCode.Success;
}
}
}

View File

@ -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<nn::lp2p::monitor::detail::ISfMonitorService>
public ResultCode CreateMonitorService(ServiceCtx context)
{
MakeObject(context, new ISfMonitorService(context));
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
return ResultCode.Success;
}
}
}

View File

@ -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;
}

View File

@ -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<nn::lp2p::GroupInfo, 0x32>
public ResultCode GetGroupInfo(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
return ResultCode.Success;
}
}
}

View File

@ -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
}
}

View File

@ -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<copy>
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;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.News
{
class INewsDataService : IpcService
{
public INewsDataService(ServiceCtx context) { }
}
}

View File

@ -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<unknown, 9>, buffer<unknown, 9>) -> (unknown<4>, buffer<unknown, 6>)
public ResultCode GetListV1(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.Service);
return ResultCode.Success;
}
[CommandCmif(1)]
// Count(buffer<nn::news::detail::Count>) -> 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<unknown, 9>, buffer<unknown, 9>)
public ResultCode UpdateIntegerValue(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.Service);
return ResultCode.Success;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<copy>
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;
}
}
}

View File

@ -8,5 +8,46 @@ namespace Ryujinx.HLE.HOS.Services.News
class IServiceCreator : IpcService
{
public IServiceCreator(ServiceCtx context) { }
[CommandCmif(0)]
// CreateNewsService() -> object<nn::news::detail::ipc::INewsService>
public ResultCode CreateNewsService(ServiceCtx context)
{
MakeObject(context, new INewsService(context));
return ResultCode.Success;
}
[CommandCmif(30900)]
[CommandCmif(1)]
// CreateNewlyArrivedEventHolder() -> object<nn::news::detail::ipc::INewlyArrivedEventHolder>
public ResultCode CreateNewlyArrivedEventHolder(ServiceCtx context)
{
MakeObject(context, new INewlyArrivedEventHolder(context));
return ResultCode.Success;
}
[CommandCmif(2)]
// CreateNewsDataService() -> object<nn::news::detail::ipc::INewsDataService>
public ResultCode CreateNewsDataService(ServiceCtx context)
{
MakeObject(context, new INewsDataService(context));
return ResultCode.Success;
}
[CommandCmif(3)]
// CreateNewsDatabaseService() -> object<nn::news::detail::ipc::INewsDatabaseService>
public ResultCode CreateNewsDatabaseService(ServiceCtx context)
{
MakeObject(context, new INewsDatabaseService(context));
return ResultCode.Success;
}
[CommandCmif(4)]
// CreateOverwriteEventHolder() -> object<nn::news::detail::ipc::IOverwriteEventHolder>
public ResultCode CreateOverwriteEventHolder(ServiceCtx context)
{
MakeObject(context, new IOverwriteEventHolder(context));
return ResultCode.Success;
}
}
}

View File

@ -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<nn::nifm::detail::IScanRequest>
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<nn::nifm::detail::IRequest>
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<nn::nifm::detail::sf::NetworkInterfaceInfo, 0xa>)
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<nn::nifm::detail::sf::NetworkProfileBasicInfo[], 0x6>)
public ResultCode EnumerateNetworkProfiles(ServiceCtx context)
{
context.ResponseData.Write(0);
Logger.Stub?.PrintStub(LogClass.ServiceNifm);
return ResultCode.Success;
}
[CommandCmif(8)]
// GetNetworkProfile(nn::util::Uuid) -> buffer<nn::nifm::detail::sf::NetworkProfileData, 0x1a>
public ResultCode GetNetworkProfile(ServiceCtx context)
{
ulong networkProfileDataPosition = context.Request.RecvListBuff[0].Position;
UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
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>());
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<nn::nifm::detail::sf::NetworkProfileData, 0x19>)
public ResultCode SetNetworkProfile(ServiceCtx context)
{
ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
NetworkProfileData networkProfile = context.Memory.Read<NetworkProfileData>(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<UInt128>();
Logger.Stub?.PrintStub(LogClass.ServiceNifm, new { uuid });
return ResultCode.Success;
}
[CommandCmif(11)]
// GetScanData(u32) -> (u32, buffer<nn::nifm::detail::sf::AccessPointData, 0xa>)
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::nifm::detail::sf::NetworkProfileData, 0x19>) -> (nn::util::Uuid, object<nn::nifm::detail::INetworkProfile>)
public ResultCode CreateTemporaryNetworkProfile(ServiceCtx context)
{
ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
NetworkProfileData networkProfile = context.Memory.Read<NetworkProfileData>(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<nn::nifm::ClientId, 0x19, 4>)
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<int>(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);
}
}
}

View File

@ -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::nifm::detail::sf::NetworkProfileData, 0x19>) -> nn::util::Uuid;
public ResultCode Update(ServiceCtx context)
{
ulong networkProfileDataPosition = context.Request.PtrBuff[0].Position;
NetworkProfileData networkProfile = context.Memory.Read<NetworkProfileData>(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<UInt128>();
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;
}
}
}

View File

@ -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<copy>)
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;
}
}
}

View File

@ -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<AlarmSetting, 6>)
public ResultCode ListAlarmSettings(ServiceCtx context)
{
context.ResponseData.Write(0);
Logger.Stub?.PrintStub(LogClass.ServiceNotif);
return ResultCode.Success;
}
[CommandCmif(1040)]
// OpenNotificationSystemEventAccessor() -> object<nn::notification::server::INotificationSystemEventAccessor>
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;
}
}
}

View File

@ -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<copy>
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;
}
}
}

View File

@ -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<copy>
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;
}
}
}

View File

@ -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<ApplicationRecord[], 6>)
// 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<ApplicationRecord> 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<ApplicationRecord>();
}
return ResultCode.Success;
}
[CommandCmif(2)]
// GetApplicationRecordUpdateSystemEvent() -> handle<copy>
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<unknown, 5>) -> buffer<unknown, 6>
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<ApplicationId> applicationIds = new();
for (ulong i = 0; i < inSize / sizeof(ulong); i++)
{
ulong position = inPosition + (i * sizeof(ulong));
applicationIds.Add(new(context.Memory.Read<ulong>(position)));
}
List<ApplicationView> 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<ApplicationView>();
}
return ResultCode.Success;
}
[CommandCmif(44)]
// GetSdCardMountStatusChangedEvent() -> handle<copy>
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<copy>
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<copy>
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<unknown, 6>)
@ -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<unknown, 5>) -> buffer<unknown, 6>
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<ApplicationId> applicationIds = new();
for (ulong i = 0; i < inSize / sizeof(ulong); i++)
{
ulong position = inPosition + (i * sizeof(ulong));
applicationIds.Add(new(context.Memory.Read<ulong>(position)));
}
List<ApplicationView> 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<ApplicationView>();
}
return ResultCode.Success;
}
[CommandCmif(1704)]
// GetApplicationView(buffer<unknown, 5>) -> buffer<unknown, 6>
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<ApplicationId> applicationIds = new();
for (ulong i = 0; i < inSize / sizeof(ulong); i++)
{
ulong position = inPosition + (i * sizeof(ulong));
applicationIds.Add(new(context.Memory.Read<ulong>(position)));
}
List<ApplicationView> views = new();
foreach (ApplicationId applicationId in applicationIds)
{
views.Add(new()
{
AppId = applicationId,
Unknown1 = 0x70000,
Flags = 0x401f17,
Unknown2 = new byte[0x40]
});
}
List<ApplicationViewWithPromotionInfo> 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<ApplicationViewWithPromotionInfo>();
}
return ResultCode.Success;
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Ns
{
class IDynamicRightsInterface : IpcService
{
}
}

View File

@ -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<unknown, 6>)
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<ApplicationId>();
#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());

View File

@ -8,16 +8,17 @@ namespace Ryujinx.HLE.HOS.Services.Ns
class IServiceGetterInterface : IpcService
{
public IServiceGetterInterface(ServiceCtx context) { }
[CommandCmif(7996)]
// GetApplicationManagerInterface() -> object<nn::ns::detail::IApplicationManagerInterface>
public ResultCode GetApplicationManagerInterface(ServiceCtx context)
[CommandCmif(7988)]
// GetDynamicRightsInterface() -> object<nn::ns::detail::GetDynamicRightsInterface>
public ResultCode GetDynamicRightsInterface(ServiceCtx context)
{
MakeObject(context, new IApplicationManagerInterface(context));
MakeObject(context, new IDynamicRightsInterface());
return ResultCode.Success;
}
[CommandCmif(7989)]
// GetReadOnlyApplicationControlDataInterface() -> object<nn::ns::detail::IReadOnlyApplicationControlDataInterface>
public ResultCode GetReadOnlyApplicationControlDataInterface(ServiceCtx context)
@ -27,6 +28,15 @@ namespace Ryujinx.HLE.HOS.Services.Ns
return ResultCode.Success;
}
[CommandCmif(7996)]
// GetApplicationManagerInterface() -> object<nn::ns::detail::IApplicationManagerInterface>
public ResultCode GetApplicationManagerInterface(ServiceCtx context)
{
MakeObject(context, new IApplicationManagerInterface(context));
return ResultCode.Success;
}
[CommandCmif(7997)]
// GetDownloadTaskInterface() -> object<nn::ns::detail::IDownloadTaskInterface>
public ResultCode GetDownloadTaskInterface(ServiceCtx context)

View File

@ -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<copy>
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;
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Ns.Types
{
enum ApplicationRecordType : byte
{
Installing = 2,
Installed = 3,
GameCardNotInserted = 5,
Archived = 11,
GameCard = 16,
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Ns.Types
{
public enum BackgroundNetworkUpdateState
{
None,
InProgress,
Ready
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Olsc
{
class IDaemonController : IpcService
{
}
}

View File

@ -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<nn::olsc::ITransferTaskListController>
public ResultCode GetTransferTaskListController(ServiceCtx context)
{
MakeObject(context, new ITransferTaskListController(context));
return ResultCode.Success;
}
[CommandCmif(2)]
// GetDaemonController() -> object<nn::olsc::IDaemonController>
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<nn::olsc::IOlscServiceForSystemService>
public ResultCode GetOlscServiceForSystemService(ServiceCtx context)
{
MakeObject(context, new IOlscServiceForSystemService(context));
return ResultCode.Success;
}
}
}

View File

@ -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<copy>
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;
}
}
}

View File

@ -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<nn::olsc::srv::INativeHandleHolder>
public ResultCode GetNativeHandleHolder(ServiceCtx context)
{
MakeObject(context, new INativeHandleHolder(context));
Logger.Stub?.PrintStub(LogClass.ServiceOlsc);
return ResultCode.Success;
}
[CommandCmif(9)]
// GetNativeHandleHolderEx() -> object<nn::olsc::srv::INativeHandleHolder>
public ResultCode GetNativeHandleHolderEx(ServiceCtx context)
{
MakeObject(context, new INativeHandleHolder(context));
Logger.Stub?.PrintStub(LogClass.ServiceOlsc);
return ResultCode.Success;
}
}
}

View File

@ -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<copy>
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<copy>
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<copy>
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()
{
/*

View File

@ -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<nn::settings::system::EulaVersion, 6>)
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<EulaVersion>() > 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<nn::settings::system::AccountNotificationSettings, 6>)
public ResultCode GetAccountNotificationSettings(ServiceCtx context)
{
var buffer = context.Request.ReceiveBuff[0];
Span<AccountNotificationSettings> elementsSpan = CreateSpanFromBuffer<AccountNotificationSettings>(context,buffer,true);
elementsSpan[0] = new AccountNotificationSettings
{
Uid = new Array16<byte>(),
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<nn::settings::SettingsName, 0x19>, buffer<nn::settings::SettingsItemKey, 0x19>) -> 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<nn::settings::system::SerialNumber, 0x16>
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<nn::settings::system::DeviceNickName, 0x16>
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;

View File

@ -0,0 +1,19 @@
using Ryujinx.Common.Memory;
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
struct AccountNotificationSettings
{
public Array16<byte> 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
}
}

View File

@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
[StructLayout(LayoutKind.Sequential)]
struct AccountSettings
{
public UserSelectorSettings UserSelectorSettings;
}
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum CmuMode : uint
{
None = 0,
ColorInvert = 1,
HighContrast = 2,
GrayScale = 3
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum ErrorReportSharePermission : uint
{
NotConfirmed = 0,
Granted = 1,
Denied = 2
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum HdmiContentType : uint
{
None = 0,
Graphics = 1,
Cinema = 2,
Photo = 3,
Game = 4
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum NotificationVolume : uint
{
Mute = 0,
Low = 1,
High = 2
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum PrimaryAlbumStorage : uint
{
Nand = 0,
SdCard = 1,
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum RgbRange : uint
{
Auto = 0,
Full = 1,
Limited = 2
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
enum TvResolution : uint
{
Auto = 0,
Resolution1080p = 1,
Resolution720p = 2,
Resolution480p = 3
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Settings.Types
{
[StructLayout(LayoutKind.Sequential)]
struct UserSelectorSettings
{
public UserSelectorFlag UserSelectorFlag;
}
}

View File

@ -49,6 +49,15 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService
return _applicationDisplayService.CreateStrayLayer(context);
}
[CommandCmif(3000)]
// ListDisplayModes(u64) -> (u64, buffer<nn::vi::DisplayModeInfo, 6>)
public ResultCode ListDisplayModes(ServiceCtx context)
{
Logger.Stub?.PrintStub(LogClass.ServiceVi);
return ResultCode.Success;
}
[CommandCmif(3200)]
// GetDisplayMode(u64) -> nn::vi::DisplayModeInfo

View File

@ -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++)
{

View File

@ -66,5 +66,10 @@ namespace Ryujinx.HLE.UI
/// Displays the player select dialog and returns the selected profile.
/// </summary>
UserProfile ShowPlayerSelectDialog();
/// <summary>
/// Gets the UI theme and returns true if is dark mode.
/// </summary>
bool IsDarkMode();
}
}

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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
{
}
}

View File

@ -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<nn::ovln::ISender>
public Result OpenSender(out ISender service)
{
service = new Sender();
Logger.Stub?.PrintStub(LogClass.ServiceOvln);
return Result.Success;
}
}
}

View File

@ -0,0 +1,9 @@
using LibHac;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Ovln
{
interface ISender : IServiceObject
{
}
}

View File

@ -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<RyuApplicationData> _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<RyuApplicationData> 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<RyuApplicationData>();
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(),

View File

@ -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": {

View File

@ -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<RyuApplicationData>(),
_userChannelPersistence,
renderer,
new SDL2HardwareDeviceDriver(),

View File

@ -563,5 +563,10 @@ namespace Ryujinx.Headless
{
return AccountSaveDataManager.GetLastUsedUser();
}
public bool IsDarkMode()
{
return true;
}
}
}

View File

@ -313,5 +313,10 @@ namespace Ryujinx.Ava.UI.Applet
}
return profile;
}
public bool IsDarkMode()
{
return ConfigurationState.Instance.UI.BaseStyle.Value == "Dark";
}
}
}

View File

@ -65,6 +65,11 @@
Header="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerApplet}"
Icon="{ext:Icon fa-solid fa-photo-film}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenPhotoViewerAppletToolTip}" />
<MenuItem
Name="QLaunchAppletMenuItem"
Header="{ext:Locale MenuBarFileOpenAppletOpenQLaunchApplet}"
Icon="{ext:Icon fa-solid fa-newspaper}"
ToolTip.Tip="{ext:Locale MenuBarFileOpenAppletOpenQLaunchAppletToolTip}" />
</MenuItem>
<Separator />
<MenuItem

View File

@ -41,6 +41,7 @@ namespace Ryujinx.Ava.UI.Views.Main
MiiAppletMenuItem.Command = Commands.Create(OpenMiiApplet);
PhotoViewerAppletMenuItem.Command = Commands.Create(OpenPhotoViewerApplet);
QLaunchAppletMenuItem.Command = Commands.Create(OpenSystemApplet);
CloseRyujinxMenuItem.Command = Commands.Create(CloseWindow);
OpenSettingsMenuItem.Command = Commands.Create(OpenSettings);
PauseEmulationMenuItem.Command = Commands.Create(() => ViewModel.AppHost?.Pause());
@ -165,6 +166,16 @@ namespace Ryujinx.Ava.UI.Views.Main
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
}
public AppletMetadata SystemApplet => new(ViewModel.ContentManager,"qlaunch", 0x0100000000001000);
public async Task OpenSystemApplet()
{
if (SystemApplet.CanStart(ViewModel.ContentManager, out var appData, out var nacpData))
{
await ViewModel.LoadApplication(appData, ViewModel.IsFullScreen || ViewModel.StartGamesInFullscreen, nacpData);
}
}
public async Task OpenCheatManagerForCurrentApp()
{