diff --git a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs index 0b90252bc..8033034e4 100644 --- a/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs +++ b/src/ARMeilleure/Signal/NativeSignalHandlerGenerator.cs @@ -8,7 +8,7 @@ namespace ARMeilleure.Signal { public static class NativeSignalHandlerGenerator { - public const int MaxTrackedRanges = 16; + public const int MaxTrackedRanges = 256; private const int StructAddressOffset = 0; private const int StructWriteOffset = 4; diff --git a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs index 5bda957bc..1ec3353ca 100644 --- a/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs +++ b/src/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -25,9 +25,6 @@ namespace Ryujinx.HLE.HOS.Applets case AppletId.LibAppletShop: case AppletId.LibAppletOff: return new BrowserApplet(); - case AppletId.MiiEdit: - Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet"); - return new DummyApplet(system); case AppletId.Cabinet: return new CabinetApplet(system); } diff --git a/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs index 7aacec764..2d8690314 100644 --- a/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs +++ b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs @@ -576,6 +576,35 @@ namespace Ryujinx.HLE.HOS.Applets } } } + + public bool IsLaunchedAsReal(ulong pid) + { + RealApplet applet = null; + lock (_lock) + { + if (_applets.TryGetValue(pid, out applet)) + { + if (_applets.Count > 1) + { + return true; + } + else + { + if (applet==_homeMenu||applet==_overlayDisp) + { + return false; + } + } + } + } + + return false; + } + + public bool HasAnyApplets() + { + return _applets.Count > 0; + } } internal class ButtonPressTracker diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs index 373899b7b..f18d03870 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KBufferDescriptorTable { - private const int MaxInternalBuffersCount = 8; + private const int MaxInternalBuffersCount = 256; private readonly List _sendBufferDescriptors; private readonly List _receiveBufferDescriptors; diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs index 9e0dd09f9..8a5940c28 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs @@ -88,8 +88,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService // OpenLibraryAppletSelfAccessor() -> object public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context) { - MakeObject(context, new ILibraryAppletSelfAccessor(context)); - + if (context.Device.System.WindowSystem.IsLaunchedAsReal(_pid)) + { + MakeObject(context, new ILibraryRealAppletSelfAccessor(context, _pid)); + } + else + { + MakeObject(context, new ILibraryAppletSelfAccessor(context)); + } + return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryRealAppletAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryRealAppletAccessor.cs new file mode 100644 index 000000000..9676b3873 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryRealAppletAccessor.cs @@ -0,0 +1,323 @@ +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator +{ + class ILibraryRealAppletAccessor : DisposableIpcService + { + private readonly KernelContext _kernelContext; + private readonly ulong _callerPid; + + private readonly RealAppletId _appletId; + private RealApplet _applet; + + private readonly AppletChannel _inChannel; + private readonly AppletChannel _outChannel; + private readonly AppletChannel _interactiveInChannel; + private readonly AppletChannel _interactiveOutChannel; + private readonly AppletChannel _contextChannel; + + private readonly KEvent _stateChangedEvent; + private readonly KEvent _normalOutDataEvent; + private readonly KEvent _interactiveOutDataEvent; + + private int _stateChangedEventHandle; + private int _normalOutDataEventHandle; + private int _interactiveOutDataEventHandle; + + private int _indirectLayerHandle; + + private ResultCode StartAppletProcess(Horizon system) + { + // TODO: use ns + var programId = RealApplet.GetProgramIdFromAppletId(_appletId); + + string contentPath = system.ContentManager.GetInstalledContentPath(programId, StorageId.BuiltInSystem, NcaContentType.Program); + + if (contentPath.Length == 0) + { + return ResultCode.AppletLaunchFailed; + } + + if (contentPath.StartsWith("@SystemContent")) + { + contentPath = FileSystem.VirtualFileSystem.SwitchPathToSystemPath(contentPath); + } + + if (!system.Device.Processes.LoadNca(contentPath, out var Process)) + { + return ResultCode.AppletLaunchFailed; + } + + _applet = system.WindowSystem.TrackProcess(Process.ProcessId, _callerPid, false); + _applet.AppletStateChanged += OnAppletStateChanged; + + _applet.AppletState.LaunchableEvent.ReadableEvent.Signal(); + + return ResultCode.Success; + } + + public ILibraryRealAppletAccessor(RealAppletId appletId, Horizon system, ulong callerPid) + { + _kernelContext = system.KernelContext; + + _stateChangedEvent = new KEvent(system.KernelContext); + _normalOutDataEvent = new KEvent(system.KernelContext); + _interactiveOutDataEvent = new KEvent(system.KernelContext); + + _callerPid = callerPid; + _appletId = appletId; + + _inChannel = new AppletChannel(); + _outChannel = new AppletChannel(); + _interactiveInChannel = new AppletChannel(); + _interactiveOutChannel = new AppletChannel(); + _contextChannel = new AppletChannel(); + + _outChannel.DataAvailable += OnNormalOutData; + _interactiveOutChannel.DataAvailable += OnInteractiveOutData; + } + + private void OnAppletStateChanged(object sender, EventArgs e) + { + _stateChangedEvent.WritableEvent.Signal(); + } + + private void OnNormalOutData(object sender, EventArgs e) + { + _normalOutDataEvent.WritableEvent.Signal(); + } + + private void OnInteractiveOutData(object sender, EventArgs e) + { + _interactiveOutDataEvent.WritableEvent.Signal(); + } + + [CommandCmif(0)] + // GetAppletStateChangedEvent() -> handle + public ResultCode GetAppletStateChangedEvent(ServiceCtx context) + { + if (_stateChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out _stateChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangedEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // Start() + public ResultCode Start(ServiceCtx context) + { + var result = StartAppletProcess(context.Device.System); + if (result != ResultCode.Success) + { + return result; + } + + return (ResultCode)_applet.Start(_inChannel, _outChannel, _interactiveInChannel, _interactiveOutChannel, _contextChannel); + } + + [CommandCmif(20)] + // RequestExit() + public ResultCode RequestExit(ServiceCtx context) + { + _applet.ProcessHandle.SetActivity(false); + _applet.AppletState.OnExitRequested(); + _applet?.ProcessHandle.Terminate(); + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(25)] + // Terminate() + public ResultCode Terminate(ServiceCtx context) + { + _applet.ProcessHandle.SetActivity(false); + _applet.AppletState.OnExitRequested(); + _applet?.ProcessHandle.Terminate(); + Logger.Stub?.PrintStub(LogClass.ServiceAm); + return ResultCode.Success; + } + + [CommandCmif(30)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + if (_applet == null) + { + return ResultCode.LibraryAppletTerminated; + } + + return (ResultCode)_applet.GetResult(); + } + + [CommandCmif(60)] + // PresetLibraryAppletGpuTimeSliceZero() + public ResultCode PresetLibraryAppletGpuTimeSliceZero(ServiceCtx context) + { + // NOTE: This call reset two internal fields to 0 and one internal field to "true". + // It seems to be used only with software keyboard inline. + // Since we doesn't support applets for now, it's fine to stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // PushInData(object) + public ResultCode PushInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _inChannel.PushData(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // PopOutData() -> object + public ResultCode PopOutData(ServiceCtx context) + { + if (_outChannel.TryPopData(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _normalOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(103)] + // PushInteractiveInData(object) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _interactiveInChannel.PushData(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(104)] + // PopInteractiveOutData() -> object + public ResultCode PopInteractiveOutData(ServiceCtx context) + { + if (_interactiveOutChannel.TryPopData(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _interactiveOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(105)] + // GetPopOutDataEvent() -> handle + public ResultCode GetPopOutDataEvent(ServiceCtx context) + { + if (_normalOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out _normalOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_normalOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(106)] + // GetPopInteractiveOutDataEvent() -> handle + public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context) + { + if (_interactiveOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out _interactiveOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_interactiveOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(110)] + // NeedsToExitProcess() + public ResultCode NeedsToExitProcess(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(150)] + // RequestForAppletToGetForeground() + public ResultCode RequestForAppletToGetForeground(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(160)] // 2.0.0+ + // GetIndirectLayerConsumerHandle() -> u64 indirect_layer_consumer_handle + public ResultCode GetIndirectLayerConsumerHandle(ServiceCtx context) + { + if (_applet == null) + { + return ResultCode.LibraryAppletTerminated; + } + + _indirectLayerHandle = _applet.AppletState.IndirectLayerHandles.Add(_applet); + + context.ResponseData.Write((ulong)_indirectLayerHandle); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (_stateChangedEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_stateChangedEventHandle); + } + + if (_normalOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle); + } + + if (_interactiveOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle); + } + + _applet?.AppletState.IndirectLayerHandles.Delete(_indirectLayerHandle); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs new file mode 100644 index 000000000..fc06582e0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryRealAppletSelfAccessor.cs @@ -0,0 +1,274 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class ILibraryRealAppletSelfAccessor : DisposableIpcService + { + private readonly KernelContext _kernelContext; + private readonly RealApplet _applet; + + private readonly KEvent _normalInDataEvent; + private readonly KEvent _interactiveInDataEvent; + private int _normalInDataEventHandle; + private int _interactiveInDataEventHandle; + + public ILibraryRealAppletSelfAccessor(ServiceCtx context, ulong pid) + { + var system = context.Device.System; + + _kernelContext = system.KernelContext; + _applet = system.WindowSystem.GetByAruId(pid); + _normalInDataEvent = new KEvent(system.KernelContext); + _interactiveInDataEvent = new KEvent(system.KernelContext); + + _applet.InChannel.DataAvailable += OnNormalInData; + _applet.InteractiveInChannel.DataAvailable += OnInteractiveInData; + } + + private void OnNormalInData(object sender, EventArgs e) + { + _normalInDataEvent.WritableEvent.Signal(); + } + + private void OnInteractiveInData(object sender, EventArgs e) + { + _interactiveInDataEvent.WritableEvent.Signal(); + } + + [CommandCmif(0)] + // PopInData() -> object + public ResultCode PopInData(ServiceCtx context) + { + byte[] appletData; + + if (!_applet.InChannel.TryPopData(out appletData)) + { + return ResultCode.NotAvailable; + } + + if (appletData.Length == 0) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new IStorage(appletData)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + public ResultCode PushOutData(ServiceCtx context) + { + if (_applet != null) + { + IStorage data = GetObject(context, 0); + _applet.OutChannel.PushData(data.Data); + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + public ResultCode PopInteractiveInData(ServiceCtx context) + { + byte[] appletData; + + if (!_applet.InteractiveInChannel.TryPopData(out appletData)) + { + return ResultCode.NotAvailable; + } + + if (appletData.Length == 0) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new IStorage(appletData)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + public ResultCode PushInteractiveOutData(ServiceCtx context) + { + if (_applet != null) + { + IStorage data = GetObject(context, 0); + _applet.InteractiveOutChannel.PushData(data.Data); + } + + return ResultCode.Success; + } + + [CommandCmif(5)] + public ResultCode GetPopInDataEvent(ServiceCtx context) + { + if (_normalInDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_normalInDataEvent.ReadableEvent, out _normalInDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_normalInDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(6)] + public ResultCode GetPopInteractiveInDataEvent(ServiceCtx context) + { + if (_interactiveInDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_interactiveInDataEvent.ReadableEvent, out _interactiveInDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_interactiveInDataEventHandle); + + return ResultCode.Success; + } + + + [CommandCmif(10)] + public ResultCode ExitProcessAndReturn(ServiceCtx context) + { + _applet.ProcessHandle.Terminate(); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo + public ResultCode GetLibraryAppletInfo(ServiceCtx context) + { + LibraryAppletInfo libraryAppletInfo = new(); + + libraryAppletInfo.AppletId = (AppletId)_applet.AppletId; + libraryAppletInfo.LibraryAppletMode = _applet.LibraryAppletMode; + + context.ResponseData.WriteStruct(libraryAppletInfo); + + return ResultCode.Success; + } + + [CommandCmif(12)] + // GetMainAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo + public ResultCode GetMainAppletIdentityInfo(ServiceCtx context) + { + AppletIdentifyInfo appletIdentifyInfo = new() + { + AppletId = AppletId.QLaunch, + TitleId = 0x0100000000001000, + }; + + context.ResponseData.WriteStruct(appletIdentifyInfo); + + return ResultCode.Success; + } + + [CommandCmif(13)] + // CanUseApplicationCore() -> bool + public ResultCode CanUseApplicationCore(ServiceCtx context) + { + context.ResponseData.Write(false); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // GetCallerAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo + public ResultCode GetCallerAppletIdentityInfo(ServiceCtx context) + { + context.ResponseData.WriteStruct(GetCallerIdentity(_applet)); + + return ResultCode.Success; + } + + [CommandCmif(30)] + // UnpopInData(nn::am::service::IStorage) + public ResultCode UnpopInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _applet.InChannel.InsertFrontData(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(50)] + // ReportVisibleError(nn::err::ErrorCode) + public ResultCode ReportVisibleError(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + return ResultCode.Success; + } + + [CommandCmif(150)] + // ShouldSetGpuTimeSliceManually() -> bool + public ResultCode ShouldSetGpuTimeSliceManually(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + context.ResponseData.Write(false); + return ResultCode.Success; + } + + [CommandCmif(160)] + // GetLibraryAppletInfoEx() -> u64 usually 0 + public ResultCode GetLibraryAppletInfoEx(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + context.ResponseData.Write((ulong)0); + return ResultCode.Success; + } + + private static AppletIdentifyInfo GetCallerIdentity(RealApplet applet) + { + if (applet.CallerApplet != null) + { + return new AppletIdentifyInfo + { + AppletId = (AppletId)applet.CallerApplet.AppletId, + TitleId = applet.CallerApplet.ProcessHandle.TitleId, + }; + } + else + { + return new AppletIdentifyInfo + { + AppletId = AppletId.QLaunch, + TitleId = 0x0100000000001000 + }; + } + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (_normalInDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_normalInDataEventHandle); + } + + if (_interactiveInDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_interactiveInDataEventHandle); + } + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs index 26141c076..f0f09cbf4 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs @@ -124,6 +124,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys return ResultCode.Success; } + [CommandCmif(7)] [CommandCmif(24)] [CommandCmif(26)] diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs index 7fda13737..7dc3a1ef4 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs @@ -22,7 +22,15 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys int libraryAppletMode = context.RequestData.ReadInt32(); #pragma warning restore IDE0059 - MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System, _pid)); + if (ShouldBeReal(context.Device.System,appletId)) + { + RealAppletId realAppletId = (RealAppletId)appletId; + MakeObject(context, new ILibraryRealAppletAccessor(realAppletId, context.Device.System, _pid)); + } + else + { + MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System, _pid)); + } return ResultCode.Success; } @@ -95,5 +103,10 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys return ResultCode.Success; } + + public bool ShouldBeReal(Horizon horizon, AppletId appletId) + { + return horizon.Device.Configuration.HostUIHandler.IsAppletReal((RealAppletId)appletId); + } } } diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index 54bc3ca7f..734359513 100644 --- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -1,6 +1,7 @@ using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.Horizon.Sdk.Applet; namespace Ryujinx.HLE.UI { @@ -71,5 +72,10 @@ namespace Ryujinx.HLE.UI /// Gets the UI theme and returns true if is dark mode. /// bool IsDarkMode(); + + /// + /// Gets weather or not the applet is real in the system + /// + bool IsAppletReal(RealAppletId appletId); } } diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index e4b94ae4d..4ff1f0fb5 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -5647,6 +5647,31 @@ "zh_TW": "日誌" } }, + { + "ID": "SettingsTabRealApplets", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Real Applets", + "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": "SettingsTabLoggingLogging", "Translations": { diff --git a/src/Ryujinx/Headless/Options.cs b/src/Ryujinx/Headless/Options.cs index 1b10145fe..bc058ee15 100644 --- a/src/Ryujinx/Headless/Options.cs +++ b/src/Ryujinx/Headless/Options.cs @@ -150,6 +150,24 @@ namespace Ryujinx.Headless if (NeedsOverride(nameof(IgnoreControllerApplet))) IgnoreControllerApplet = configurationState.System.IgnoreApplet; + if (NeedsOverride(nameof(MissingAppletsAsReal))) + MissingAppletsAsReal = configurationState.System.MissingAppletsAsReal; + + if (NeedsOverride(nameof(SoftwareKeyboardIsReal))) + SoftwareKeyboardIsReal = configurationState.System.SoftwareKeyboardIsReal; + + if (NeedsOverride(nameof(BrowserIsReal))) + BrowserIsReal = configurationState.System.BrowserIsReal; + + if (NeedsOverride(nameof(ControllerIsReal))) + ControllerIsReal = configurationState.System.ControllerIsReal; + + if (NeedsOverride(nameof(PlayerSelectIsReal))) + PlayerSelectIsReal = configurationState.System.PlayerSelectIsReal; + + if (NeedsOverride(nameof(CabinetIsReal))) + CabinetIsReal = configurationState.System.CabinetIsReal; + return; bool NeedsOverride(string argKey) => originalArgs.None(arg => arg.TrimStart('-').EqualsIgnoreCase(OptionName(argKey))); @@ -411,8 +429,26 @@ namespace Ryujinx.Headless public bool IgnoreMissingServices { get; set; } [Option("ignore-controller-applet", Required = false, Default = false, HelpText = "Enable ignoring the controller applet when your game loses connection to your controller.")] - public bool IgnoreControllerApplet { get; set; } - + public bool IgnoreControllerApplet { get; set; } + + [Option("missing-applets-as-real", Required = false, Default = false, HelpText = "Runs the missing applets as real applets.")] + public bool MissingAppletsAsReal { get; set; } + + [Option("is-softwarekeyboard-real", Required = false, Default = false, HelpText = "Runs the software keyboard applets as real")] + public bool SoftwareKeyboardIsReal { get; set; } + + [Option("is-browser-real", Required = false, Default = false, HelpText = "Runs the browser applets as real")] + public bool BrowserIsReal { get; set; } + + [Option("is-controller-real", Required = false, Default = false, HelpText = "Runs the controller applets as real")] + public bool ControllerIsReal { get; set; } + + [Option("is-playerselect-real", Required = false, Default = false, HelpText = "Runs the player select applets as real")] + public bool PlayerSelectIsReal { get; set; } + + [Option("is-cabinet-real", Required = false, Default = false, HelpText = "Runs the cabinet applets as real")] + public bool CabinetIsReal { get; set; } + // Values [Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)] diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 0645f341c..9feca8f03 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -13,6 +13,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.Loaders.Processes; using Ryujinx.HLE.UI; +using Ryujinx.Horizon.Sdk.Applet; using Ryujinx.Input; using Ryujinx.Input.HLE; using Ryujinx.Input.SDL2; @@ -568,5 +569,10 @@ namespace Ryujinx.Headless { return true; } + + public bool IsAppletReal(RealAppletId appletId) + { + return false; + } } } diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 174aadeb0..7bec0d16f 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -172,5 +172,11 @@ + + + SettingsRealApplets.axaml + Code + + diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 17fd1e44f..b733c7912 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -15,6 +15,7 @@ using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.UI; +using Ryujinx.Horizon.Sdk.Applet; using System; using System.Collections.ObjectModel; using System.Linq; @@ -318,5 +319,36 @@ namespace Ryujinx.Ava.UI.Applet { return ConfigurationState.Instance.UI.BaseStyle.Value == "Dark"; } + + public bool IsMissingAppletsAsReal() + { + return ConfigurationState.Instance.System.MissingAppletsAsReal; + } + + public bool IsAppletReal(RealAppletId appletId) + { + bool softwareKeyboardReal = ConfigurationState.Instance.System.SoftwareKeyboardIsReal; + bool browserReal = ConfigurationState.Instance.System.BrowserIsReal; + bool controllerReal = ConfigurationState.Instance.System.ControllerIsReal; + bool playerSelectReal = ConfigurationState.Instance.System.PlayerSelectIsReal; + bool cabinetReal = ConfigurationState.Instance.System.CabinetIsReal; + bool missingAppletsAsReal = ConfigurationState.Instance.System.MissingAppletsAsReal; + + switch (appletId) + { + case RealAppletId.LibraryAppletSwkbd: + return softwareKeyboardReal; + case RealAppletId.LibraryAppletWeb: + return browserReal; + case RealAppletId.LibraryAppletController: + return controllerReal; + case RealAppletId.LibraryAppletPlayerSelect: + return playerSelectReal; + case RealAppletId.LibraryAppletCabinet: + return cabinetReal; + default: + return missingAppletsAsReal; + } + } } } diff --git a/src/Ryujinx/UI/Helpers/Converters/BoolToIndexConverter.cs b/src/Ryujinx/UI/Helpers/Converters/BoolToIndexConverter.cs new file mode 100644 index 000000000..31c64a645 --- /dev/null +++ b/src/Ryujinx/UI/Helpers/Converters/BoolToIndexConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace Ryujinx.Ava.UI.Helpers +{ + public class BoolToIndexConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool boolValue) + { + return boolValue ? 0 : 1; + } + return 1; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int index) + { + return index == 0; + } + return false; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/SettingsRealAppsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsRealAppsViewModel.cs new file mode 100644 index 000000000..748d2e37a --- /dev/null +++ b/src/Ryujinx/UI/ViewModels/SettingsRealAppsViewModel.cs @@ -0,0 +1,21 @@ +using Avalonia.Data.Converters; +using CommunityToolkit.Mvvm.ComponentModel; +using Gommon; +using Ryujinx.Ava.Utilities.Configuration; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.ViewModels +{ + public partial class SettingsRealAppsViewModel : BaseModel + { + private readonly SettingsViewModel _baseViewModel; + + public SettingsRealAppsViewModel() {} + + public SettingsRealAppsViewModel(SettingsViewModel settingsVm) + { + _baseViewModel = settingsVm; + } + } +} diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index bd649602c..fa3b24b35 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -190,6 +190,12 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableInternetAccess { get; set; } public bool EnableFsIntegrityChecks { get; set; } public bool IgnoreMissingServices { get; set; } + public bool MissingAppletsAsReal { get; set; } + public bool SoftwareKeyboardIsReal { get; set; } + public bool BrowserIsReal { get; set; } + public bool ControllerIsReal { get; set; } + public bool PlayerSelectIsReal { get; set; } + public bool CabinetIsReal { get; set; } public MemoryConfiguration DramSize { get; set; } public bool EnableShaderCache { get; set; } public bool EnableTextureRecompression { get; set; } @@ -518,6 +524,13 @@ namespace Ryujinx.Ava.UI.ViewModels DramSize = config.System.DramSize; IgnoreMissingServices = config.System.IgnoreMissingServices; IgnoreApplet = config.System.IgnoreApplet; + + MissingAppletsAsReal = config.System.MissingAppletsAsReal; + SoftwareKeyboardIsReal = config.System.SoftwareKeyboardIsReal; + BrowserIsReal = config.System.BrowserIsReal; + ControllerIsReal = config.System.ControllerIsReal; + PlayerSelectIsReal = config.System.PlayerSelectIsReal; + CabinetIsReal = config.System.CabinetIsReal; // CPU EnablePptc = config.System.EnablePtc; @@ -622,6 +635,12 @@ namespace Ryujinx.Ava.UI.ViewModels config.System.DramSize.Value = DramSize; config.System.IgnoreMissingServices.Value = IgnoreMissingServices; config.System.IgnoreApplet.Value = IgnoreApplet; + config.System.MissingAppletsAsReal.Value = MissingAppletsAsReal; + config.System.SoftwareKeyboardIsReal.Value = SoftwareKeyboardIsReal; + config.System.BrowserIsReal.Value = BrowserIsReal; + config.System.ControllerIsReal.Value = ControllerIsReal; + config.System.PlayerSelectIsReal.Value = PlayerSelectIsReal; + config.System.CabinetIsReal.Value = CabinetIsReal; // CPU config.System.EnablePtc.Value = EnablePptc; diff --git a/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml b/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml new file mode 100644 index 000000000..8cc879950 --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml.cs b/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml.cs new file mode 100644 index 000000000..dbc1c959d --- /dev/null +++ b/src/Ryujinx/UI/Views/Settings/SettingsRealApplets.axaml.cs @@ -0,0 +1,15 @@ +using Avalonia.Controls; +using Avalonia.Data.Converters; +using System; +using System.Globalization; + +namespace Ryujinx.Ava.UI.Views.Settings +{ + public partial class SettingsRealAppsView : UserControl + { + public SettingsRealAppsView() + { + InitializeComponent(); + } + } +} diff --git a/src/Ryujinx/UI/Windows/SettingsWindow.axaml b/src/Ryujinx/UI/Windows/SettingsWindow.axaml index 1c7e9cf58..e58cb24cc 100644 --- a/src/Ryujinx/UI/Windows/SettingsWindow.axaml +++ b/src/Ryujinx/UI/Windows/SettingsWindow.axaml @@ -38,6 +38,7 @@ + + public ulong[] DirtyHacks { get; set; } + /// + /// Runs missing applets as real ones + /// + public bool MissingAppletsAsReal { get; set; } + + /// + /// Launches the SoftwareKeyboard as a real applet + /// + public bool SoftwareKeyboardIsReal { get; set; } + + /// + /// Launches the Browser as a real applet + /// + public bool BrowserIsReal { get; set; } + + /// + /// Launches the Controller as a real applet + /// + public bool ControllerIsReal { get; set; } + + /// + /// Launches the PlayerSelect as a real applet + /// + public bool PlayerSelectIsReal { get; set; } + + /// + /// Launches the Cabinet as a real applet + /// + public bool CabinetIsReal { get; set; } + /// /// Loads a configuration file from disk /// diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 33ae72f14..c136ce512 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -100,6 +100,13 @@ namespace Ryujinx.Ava.Utilities.Configuration System.IgnoreApplet.Value = cff.IgnoreApplet; System.UseHypervisor.Value = cff.UseHypervisor; + System.MissingAppletsAsReal.Value = cff.MissingAppletsAsReal; + System.SoftwareKeyboardIsReal.Value = cff.SoftwareKeyboardIsReal; + System.BrowserIsReal.Value = cff.BrowserIsReal; + System.ControllerIsReal.Value = cff.ControllerIsReal; + System.PlayerSelectIsReal.Value = cff.PlayerSelectIsReal; + System.CabinetIsReal.Value = cff.CabinetIsReal; + UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn; UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn; UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn; @@ -416,6 +423,7 @@ namespace Ryujinx.Ava.Utilities.Configuration // This was accidentally enabled by default when it was PRed. That is not what we want, // so as a compromise users who want to use it will simply need to re-enable it once after updating. cff.IgnoreApplet = false; + cff.MissingAppletsAsReal = false; }), (60, static cff => cff.StartNoUI = false), (61, static cff => diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 2d6e2aa7f..48a528250 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -383,6 +383,36 @@ namespace Ryujinx.Ava.Utilities.Configuration /// public ReactiveObject UseHypervisor { get; private set; } + /// + /// Launches missing applets as real apps + /// + public ReactiveObject MissingAppletsAsReal { get; private set; } + + /// + /// Launches the SoftwareKeyboard as a real applet + /// + public ReactiveObject SoftwareKeyboardIsReal { get; set; } + + /// + /// Launches the Browser as a real applet + /// + public ReactiveObject BrowserIsReal { get; set; } + + /// + /// Launches the Controller as a real applet + /// + public ReactiveObject ControllerIsReal { get; set; } + + /// + /// Launches the PlayerSelect as a real applet + /// + public ReactiveObject PlayerSelectIsReal { get; set; } + + /// + /// Launches the Cabinet as a real applet + /// + public ReactiveObject CabinetIsReal { get; set; } + public SystemSection() { Language = new ReactiveObject(); @@ -423,6 +453,19 @@ namespace Ryujinx.Ava.Utilities.Configuration AudioVolume.LogChangesToValue(nameof(AudioVolume)); UseHypervisor = new ReactiveObject(); UseHypervisor.LogChangesToValue(nameof(UseHypervisor)); + + MissingAppletsAsReal = new ReactiveObject(); + MissingAppletsAsReal.LogChangesToValue(nameof(MissingAppletsAsReal)); + SoftwareKeyboardIsReal = new ReactiveObject(); + SoftwareKeyboardIsReal.LogChangesToValue(nameof(SoftwareKeyboardIsReal)); + BrowserIsReal = new ReactiveObject(); + BrowserIsReal.LogChangesToValue(nameof(BrowserIsReal)); + ControllerIsReal = new ReactiveObject(); + ControllerIsReal.LogChangesToValue(nameof(ControllerIsReal)); + PlayerSelectIsReal = new ReactiveObject(); + PlayerSelectIsReal.LogChangesToValue(nameof(PlayerSelectIsReal)); + CabinetIsReal = new ReactiveObject(); + CabinetIsReal.LogChangesToValue(nameof(CabinetIsReal)); } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index 1fe04d40a..d65710419 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -141,6 +141,12 @@ namespace Ryujinx.Ava.Utilities.Configuration LdnServer = Multiplayer.LdnServer, ShowDirtyHacks = Hacks.ShowDirtyHacks, DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(), + MissingAppletsAsReal = System.MissingAppletsAsReal, + SoftwareKeyboardIsReal = System.SoftwareKeyboardIsReal, + BrowserIsReal = System.BrowserIsReal, + ControllerIsReal = System.ControllerIsReal, + PlayerSelectIsReal = System.PlayerSelectIsReal, + CabinetIsReal = System.CabinetIsReal, }; return configurationFile; @@ -199,6 +205,12 @@ namespace Ryujinx.Ava.Utilities.Configuration System.DramSize.Value = MemoryConfiguration.MemoryConfiguration4GiB; System.IgnoreMissingServices.Value = false; System.IgnoreApplet.Value = false; + System.MissingAppletsAsReal.Value = false; + System.SoftwareKeyboardIsReal.Value = false; + System.BrowserIsReal.Value = false; + System.ControllerIsReal.Value = false; + System.PlayerSelectIsReal.Value = false; + System.CabinetIsReal.Value = false; System.UseHypervisor.Value = true; Multiplayer.LanInterfaceId.Value = "0"; Multiplayer.Mode.Value = MultiplayerMode.Disabled;