From ffcc13e1cc599fa8f48b47622bb2a755851cd271 Mon Sep 17 00:00:00 2001 From: Jacobwasbeast Date: Sun, 9 Feb 2025 08:22:59 -0600 Subject: [PATCH] Implement application launching Co-authored-by: Alula <6276139+alula@users.noreply.github.com> --- src/Ryujinx.HLE/HOS/Applets/EventObserver.cs | 227 ++++++++ src/Ryujinx.HLE/HOS/Applets/IRealApplet.cs | 31 + src/Ryujinx.HLE/HOS/Applets/ProcessHolder.cs | 18 + src/Ryujinx.HLE/HOS/Applets/RealApplet.cs | 254 ++++++++ .../HOS/Applets/Types/ActivityState.cs | 10 + .../HOS/Applets/Types/SuspendMode.cs | 9 + .../HOS/Applets/Types/UserDataTag.cs | 8 + src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs | 524 +++++++++++++++++ src/Ryujinx.HLE/HOS/Horizon.cs | 47 +- .../HOS/Kernel/Process/KProcess.cs | 5 + .../ISystemAppletProxy.cs | 2 +- .../SystemAppletProxy/IApplicationAccessor.cs | 107 ++++ .../SystemAppletProxy/IApplicationCreator.cs | 49 +- .../SystemAppletProxy/ICommonStateGetter.cs | 21 +- .../SystemAppletProxy/IHomeMenuFunctions.cs | 7 +- .../SystemAppletProxy/IWindowController.cs | 2 +- .../SystemAppletProxy/Types/FocusState.cs | 1 + .../HOS/Services/Am/AppletAE/AppletChannel.cs | 85 +++ .../HOS/Services/Am/AppletAE/RealAppletId.cs | 71 +++ .../Am/AppletAE/Types/FocusHandlingMode.cs | 9 + .../Am/AppletAE/Types/SystemButtonType.cs | 13 + src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs | 1 + .../HOS/Services/Fs/IFileSystemProxy.cs | 9 + src/Ryujinx.HLE/HOS/Services/ServerBase.cs | 3 + .../HOS/SystemState/AppletStateMgr.cs | 545 +++++++++++++++++- .../Loaders/Processes/ProcessLoader.cs | 108 +++- .../Loaders/Processes/ProcessResult.cs | 1 + .../Sdk/OsTypes/EventClearMode.cs | 2 +- src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs | 2 +- .../Sdk/OsTypes/Impl/MultiWaitImpl.cs | 2 +- .../Sdk/OsTypes/InitializationState.cs | 2 +- .../Sdk/OsTypes/InterProcessEventType.cs | 2 +- src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs | 2 +- .../Sdk/OsTypes/MultiWaitHolder.cs | 2 +- .../Sdk/OsTypes/MultiWaitHolderBase.cs | 4 +- .../Sdk/OsTypes/MultiWaitHolderOfHandle.cs | 2 +- .../MultiWaitHolderOfInterProcessEvent.cs | 26 + src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs | 2 +- .../Sdk/OsTypes/SystemEventType.cs | 2 +- src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs | 2 +- 40 files changed, 2170 insertions(+), 49 deletions(-) create mode 100644 src/Ryujinx.HLE/HOS/Applets/EventObserver.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/IRealApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/ProcessHolder.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/RealApplet.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Types/ActivityState.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Types/SuspendMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/Types/UserDataTag.cs create mode 100644 src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationAccessor.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletChannel.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/RealAppletId.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/FocusHandlingMode.cs create mode 100644 src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/SystemButtonType.cs create mode 100644 src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfInterProcessEvent.cs diff --git a/src/Ryujinx.HLE/HOS/Applets/EventObserver.cs b/src/Ryujinx.HLE/HOS/Applets/EventObserver.cs new file mode 100644 index 000000000..b7fb73cdd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/EventObserver.cs @@ -0,0 +1,227 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets.Types; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon; +using Ryujinx.Horizon.Sdk.OsTypes; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.HLE.HOS.Applets +{ + public class EventObserver + { + private readonly object _lock = new(); + + private Horizon _system; + private WindowSystem _windowSystem; + + // Guest event handle to wake up the event loop + internal SystemEventType _wakeupEvent; + internal MultiWaitHolder _wakeupHolder; + private KWritableEvent _wakeupEventObj; + + // List of owned process holders + private readonly LinkedList _processHolders = new(); + + // Multi-wait objects for new tasks + private readonly MultiWait _multiWait; + private readonly MultiWait _deferredWaitList; + + // Processing thread + private KThread _thread; + private readonly CancellationTokenSource _cts = new(); + private bool _initialized; + + public EventObserver(Horizon system, WindowSystem windowSystem) + { + _system = system; + _windowSystem = windowSystem; + + _multiWait = new(); + _deferredWaitList = new(); + + _windowSystem.SetEventObserver(this); + } + + public void Dispose() + { + if (!_initialized) + return; + + // Signal and wait for the thread to finish + _cts.Cancel(); + // Os.SignalSystemEvent(ref _wakeupEvent); + _wakeupEventObj?.Signal(); + // _thread.Join(); + + _processHolders.Clear(); + } + + private void Initialize() + { + if (_initialized) + { + return; + } + _initialized = true; + + var holderProcess = _system.ViServer.SelfProcess; + _thread = new KThread(_system.KernelContext); + _thread.SetName("am:EventObserver"); + _thread.Initialize( + 0, + 0, + 0, + 1, + 3, + holderProcess, + ThreadType.Kernel, + ThreadFunc); + _thread.Owner.HandleTable.GenerateHandle(_thread, out int threadHandle).AbortOnFailure(); + _thread.SetEntryArguments(0, threadHandle); + _thread.Start(); + } + + internal void TrackAppletProcess(RealApplet applet) + { + Initialize(); + + if (applet.ProcessHandle == null) + { + return; + } + + _thread.Owner.HandleTable.GenerateHandle(applet.ProcessHandle, out int processHandle).AbortOnFailure(); + ProcessHolder holder = new(applet, applet.ProcessHandle, processHandle); + holder.UserData = UserDataTag.AppletProcess; + + // lock (_lock) + { + _processHolders.AddLast(holder); + _deferredWaitList.LinkMultiWaitHolder(holder); + } + + RequestUpdate(); + } + + public void RequestUpdate() + { + Initialize(); + + // Os.SignalSystemEvent(ref _wakeupEvent); + // lock (_lock) + { + _wakeupEventObj?.Signal(); + } + } + + public void LinkDeferred() + { + // lock (_lock) + { + _multiWait.MoveAllFrom(_deferredWaitList); + } + } + + private MultiWaitHolder WaitSignaled() + { + while (true) + { + LinkDeferred(); + + if (_cts.Token.IsCancellationRequested) + { + return null; + } + + var selected = _multiWait.WaitAny(); + // Logger.Warning?.Print(LogClass.ServiceAm, $"*** Selected={selected}"); + if (selected != _wakeupHolder) + { + selected.UnlinkFromMultiWaitHolder(); + } + + return selected; + } + } + + private void Process(MultiWaitHolder holder) + { + switch (holder.UserData) + { + case UserDataTag.WakeupEvent: + OnWakeupEvent(holder); + break; + case UserDataTag.AppletProcess: + OnProcessEvent((ProcessHolder)holder); + break; + } + } + + private void OnWakeupEvent(MultiWaitHolder holder) + { + // Os.ClearSystemEvent(ref _wakeupEvent); + Logger.Warning?.Print(LogClass.ServiceAm, "*** Wakeup event"); + // lock (_lock) + { + _wakeupEventObj?.Clear(); + } + _windowSystem.Update(); + } + + private void OnProcessEvent(ProcessHolder holder) + { + var applet = holder.Applet; + var process = holder.ProcessHandle; + Logger.Warning?.Print(LogClass.ServiceAm, $"*** Applet={applet.AppletId}, Process={process.Pid}, State={process.State}"); + + // lock (_lock) + { + if (process.State == ProcessState.Exited) + { + _processHolders.Remove(holder); + } + else + { + process.ClearIfNotExited(); + _deferredWaitList.LinkMultiWaitHolder(holder); + } + + applet.IsProcessRunning = process.IsRunning(); + } + _windowSystem.Update(); + } + + private void ThreadFunc() + { + HorizonStatic.Register( + default, + _system.KernelContext.Syscall, + null, + _thread.ThreadContext, + (int)_thread.ThreadContext.GetX(1)); + + // lock (_lock) + { + Os.CreateSystemEvent(out _wakeupEvent, EventClearMode.ManualClear, true).AbortOnFailure(); + _wakeupEventObj = _thread.Owner.HandleTable.GetObject(Os.GetWritableHandleOfSystemEvent(ref _wakeupEvent)); + + _wakeupHolder = new MultiWaitHolderOfInterProcessEvent(_wakeupEvent.InterProcessEvent); + _wakeupHolder.UserData = UserDataTag.WakeupEvent; + _multiWait.LinkMultiWaitHolder(_wakeupHolder); + } + + while (!_cts.Token.IsCancellationRequested) + { + var holder = WaitSignaled(); + if (holder == null) + { + break; + } + + Process(holder); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/IRealApplet.cs b/src/Ryujinx.HLE/HOS/Applets/IRealApplet.cs new file mode 100644 index 000000000..b207f808e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/IRealApplet.cs @@ -0,0 +1,31 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.UI; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets +{ + interface IRealApplet + { + event EventHandler AppletStateChanged; + + ResultCode Start(AppletChannel inChannel, + AppletChannel outChannel, + AppletChannel interactiveInChannel, + AppletChannel interactiveOutChannel, + AppletChannel contextChannel); + + ResultCode GetResult(); + + bool DrawTo(RenderingSurfaceInfo surfaceInfo, IVirtualMemoryManager destination, ulong position) + { + return false; + } + + static T ReadStruct(ReadOnlySpan data) where T : unmanaged + { + return MemoryMarshal.Cast(data)[0]; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/ProcessHolder.cs b/src/Ryujinx.HLE/HOS/Applets/ProcessHolder.cs new file mode 100644 index 000000000..2cb150c87 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/ProcessHolder.cs @@ -0,0 +1,18 @@ +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.Horizon.Sdk.OsTypes; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class ProcessHolder : MultiWaitHolderOfHandle + { + public RealApplet Applet { get; private set; } + public KProcess ProcessHandle { get; private set; } + + public ProcessHolder(RealApplet applet, KProcess kProcess, int processHandle) + : base(processHandle) + { + Applet = applet; + ProcessHandle = kProcess; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/RealApplet.cs b/src/Ryujinx.HLE/HOS/Applets/RealApplet.cs new file mode 100644 index 000000000..cb0c7f169 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/RealApplet.cs @@ -0,0 +1,254 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.HLE.Loaders.Processes; +using Ryujinx.Horizon.Sdk.Applet; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class RealApplet : IRealApplet + { + // 0x02 010000000000100C OverlayApplet (overlayDisp) + // 0x03 0100000000001000 SystemAppletMenu (qlaunch) + // 0x04 0100000000001012 SystemApplication (starter) + // 0x0A 0100000000001001 LibraryAppletAuth (auth) + // 0x0B 0100000000001002 LibraryAppletCabinet (cabinet) + // 0x0C 0100000000001003 LibraryAppletController (controller) + // 0x0D 0100000000001004 LibraryAppletDataErase (dataErase) + // 0x0E 0100000000001005 LibraryAppletError (error) + // 0x0F 0100000000001006 LibraryAppletNetConnect (netConnect) + // 0x10 0100000000001007 LibraryAppletPlayerSelect (playerSelect) + // 0x11 0100000000001008 LibraryAppletSwkbd (swkbd) + // 0x12 0100000000001009 LibraryAppletMiiEdit (miiEdit) + // 0x13 010000000000100A LibraryAppletWeb (web) + // 0x14 010000000000100B LibraryAppletShop (shop) + // 0x15 010000000000100D LibraryAppletPhotoViewer (photoViewer) + // 0x16 010000000000100E LibraryAppletSet (set) + // 0x17 010000000000100F LibraryAppletOfflineWeb (offlineWeb) + // 0x18 0100000000001010 LibraryAppletLoginShare (loginShare) + // 0x19 0100000000001011 LibraryAppletWifiWebAuth (wifiWebAuth) + // 0x1A 0100000000001013 LibraryAppletMyPage (myPage) + // 0x1B 010000000000101A LibraryAppletGift (gift) + // 0x1C 010000000000101C LibraryAppletUserMigration (userMigration) + // 0x1D 010000000000101D [9.0.0+] LibraryAppletPreomiaSys (EncounterSys) + // 0x1E 0100000000001020 [9.0.0+] LibraryAppletStory (story) + // 0x1F 010070000E3C0000 [9.0.0+] LibraryAppletPreomiaUsr (EncounterUsr) + // 0x20 010086000E49C000 [9.0.0+] LibraryAppletPreomiaUsrDummy (EncounterUsrDummy) + // 0x21 0100000000001038 [10.0.0+] LibraryAppletSample (sample) + // 0x22 0100000000001007 [13.0.0+] LibraryAppletPromoteQualification (playerSelect) + // 0x32 010000000000100F [17.0.0+] LibraryAppletOfflineWeb (offlineWeb) + // 0x33 010000000000100F [17.0.0+] LibraryAppletOfflineWeb (offlineWeb) + // 0x35 [17.0.0+] 0100000000001010 ([16.0.0-16.1.0] 0100000000001042) [17.0.0+] LibraryAppletLoginShare (loginShare) ([16.0.0-16.1.0] ) + // 0x36 [17.0.0+] 0100000000001010 ([16.0.0-16.1.0] 0100000000001042) [17.0.0+] LibraryAppletLoginShare (loginShare) ([16.0.0-16.1.0] ) + // 0x37 [17.0.0+] 0100000000001010 ([16.0.0-16.1.0] 0100000000001042) [17.0.0+] LibraryAppletLoginShare (loginShare) ([16.0.0-16.1.0] ) + private static readonly Dictionary _appletTitles = new Dictionary + { + { RealAppletId.SystemAppletMenu, 0x0100000000001000 }, + { RealAppletId.LibraryAppletAuth, 0x0100000000001001 }, + { RealAppletId.LibraryAppletCabinet, 0x0100000000001002 }, + { RealAppletId.LibraryAppletController, 0x0100000000001003 }, + { RealAppletId.LibraryAppletDataErase, 0x0100000000001004 }, + { RealAppletId.LibraryAppletError, 0x0100000000001005 }, + { RealAppletId.LibraryAppletNetConnect, 0x0100000000001006 }, + { RealAppletId.LibraryAppletPlayerSelect, 0x0100000000001007 }, + { RealAppletId.LibraryAppletSwkbd, 0x0100000000001008 }, + { RealAppletId.LibraryAppletMiiEdit, 0x0100000000001009 }, + { RealAppletId.LibraryAppletWeb, 0x010000000000100A }, + { RealAppletId.LibraryAppletShop, 0x010000000000100B }, + { RealAppletId.OverlayApplet, 0x010000000000100C }, + { RealAppletId.LibraryAppletPhotoViewer, 0x010000000000100D }, + { RealAppletId.LibraryAppletSet, 0x010000000000100E }, + { RealAppletId.LibraryAppletOfflineWeb, 0x010000000000100F }, + { RealAppletId.LibraryAppletLoginShare, 0x0100000000001010 }, + { RealAppletId.LibraryAppletWifiWebAuth, 0x0100000000001011 }, + { RealAppletId.SystemApplication, 0x0100000000001012 }, + { RealAppletId.LibraryAppletMyPage, 0x0100000000001013 }, + { RealAppletId.LibraryAppletGift, 0x010000000000101A }, + { RealAppletId.LibraryAppletUserMigration, 0x010000000000101C }, + { RealAppletId.LibraryAppletPreomiaSys, 0x010000000000101D }, + { RealAppletId.LibraryAppletStory, 0x0100000000001020 }, + { RealAppletId.LibraryAppletPreomiaUsr, 0x010070000E3C0000 }, + { RealAppletId.LibraryAppletPreomiaUsrDummy,0x010086000E49C000 }, + { RealAppletId.LibraryAppletSample, 0x0100000000001038 }, + { RealAppletId.LibraryAppletPromoteQualification, 0x0100000000001007 }, + { RealAppletId.LibraryAppletOfflineWebFw17, 0x0100000000001010 }, + { RealAppletId.LibraryAppletOfflineWeb2Fw17, 0x0100000000001010 }, + }; + + internal static RealAppletId GetAppletIdFromProgramId(ulong programId) + { + foreach (var applet in _appletTitles) + { + if (applet.Value == programId) + { + return applet.Key; + } + } + + return RealAppletId.Application; + } + + internal static ulong GetProgramIdFromAppletId(RealAppletId appletId) + { + return _appletTitles[appletId]; + } + + private readonly object _lock = new(); + public object Lock => _lock; + + private readonly Horizon _system; + internal RealAppletId AppletId { get; private set; } + internal LibraryAppletMode LibraryAppletMode { get; private set; } + internal ulong AppletResourceUserId { get; private set; } + + internal AppletChannel InChannel { get; private set; } + internal AppletChannel OutChannel { get; private set; } + internal AppletChannel InteractiveInChannel { get; private set; } + internal AppletChannel InteractiveOutChannel { get; private set; } + internal AppletChannel ContextChannel { get; private set; } + internal RealApplet CallerApplet = null; + internal LinkedList ChildApplets = new(); + internal bool IsCompleted = false; + + internal bool IsActivityRunnable = false; + internal bool IsInteractable = true; + internal bool WindowVisible = true; + internal bool ExitLocked = false; + + internal AppletStateMgr AppletState { get; private set; } + public event EventHandler AppletStateChanged; + internal AppletProcessLaunchReason LaunchReason = default; + internal ResultCode TerminateResult = ResultCode.Success; + + internal KProcess ProcessHandle; + internal bool IsProcessRunning; + + public RealApplet(ulong pid, bool isApplication, Horizon system) + { + _system = system; + AppletState = new AppletStateMgr(system, isApplication); + ProcessHandle = _system.KernelContext.Processes[pid]; + AppletResourceUserId = ProcessHandle.Pid; + AppletId = GetAppletIdFromProgramId(ProcessHandle.TitleId); + } + + public void RegisterChild(RealApplet applet) + { + if (applet == null) + { + return; + } + + if (applet == this) + { + throw new InvalidOperationException("Cannot register self as child."); + } + + lock (_lock) + { + ChildApplets.AddLast(applet); + } + } + + public ResultCode Start(AppletChannel inChannel, + AppletChannel outChannel, + AppletChannel interactiveInChannel, + AppletChannel interactiveOutChannel, + AppletChannel contextChannel) + { + InChannel = inChannel; + OutChannel = outChannel; + InteractiveInChannel = interactiveInChannel; + InteractiveOutChannel = interactiveOutChannel; + ContextChannel = contextChannel; + return ResultCode.Success; + } + + public ResultCode StartApplication() + { + InChannel = new AppletChannel(); + OutChannel = new AppletChannel(); + InteractiveInChannel = new AppletChannel(); + InteractiveOutChannel = new AppletChannel(); + ContextChannel = new AppletChannel(); + + // ProcessHandle = _system.KernelContext.Processes[pid]; + // AppletResourceUserId = ProcessHandle.Pid; + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return TerminateResult; + } + + public void InvokeAppletStateChanged() + { + AppletStateChanged?.Invoke(this, null); + } + + internal void UpdateSuspensionStateLocked(bool forceMessage) + { + // Remove any forced resumption. + AppletState.RemoveForceResumeIfPossible(); + + // Check if we're runnable. + bool currActivityRunnable = AppletState.IsRunnable(); + bool prevActivityRunnable = IsActivityRunnable; + bool wasChanged = currActivityRunnable != prevActivityRunnable; + + if (wasChanged) + { + if (currActivityRunnable) + { + ProcessHandle.SetActivity(false); + } + else + { + ProcessHandle.SetActivity(true); + AppletState.RequestResumeNotification(); + } + + IsActivityRunnable = currActivityRunnable; + } + + if (AppletState.ForcedSuspend) + { + // TODO: why is this allowed? + return; + } + + // Signal if the focus state was changed or the process state was changed. + if (AppletState.UpdateRequestedFocusState() || wasChanged || forceMessage) + { + AppletState.SignalEventIfNeeded(); + } + } + + internal void SetInteractibleLocked(bool interactible) + { + if (IsInteractable == interactible) + { + return; + } + + IsInteractable = interactible; + + // _hidRegistration.EnableAppletToGetInput(interactible && !lifecycle_manager.GetExitRequested()); + } + + internal void OnProcessTerminatedLocked() + { + IsProcessRunning = false; + ProcessHandle = null; + InvokeAppletStateChanged(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Types/ActivityState.cs b/src/Ryujinx.HLE/HOS/Applets/Types/ActivityState.cs new file mode 100644 index 000000000..7a1792b6d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Types/ActivityState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Applets.Types +{ + public enum ActivityState + { + ForegroundVisible = 0, + ForegroundObscured = 1, + BackgroundVisible = 2, + BackgroundObscured = 3, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Types/SuspendMode.cs b/src/Ryujinx.HLE/HOS/Applets/Types/SuspendMode.cs new file mode 100644 index 000000000..bd04712c7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Types/SuspendMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets.Types +{ + public enum SuspendMode + { + NoOverride = 0, + ForceResume = 1, + ForceSuspend = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/Types/UserDataTag.cs b/src/Ryujinx.HLE/HOS/Applets/Types/UserDataTag.cs new file mode 100644 index 000000000..1227cc52a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/Types/UserDataTag.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Applets.Types +{ + enum UserDataTag : uint + { + WakeupEvent, + AppletProcess, + } +} diff --git a/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs new file mode 100644 index 000000000..7e4db975e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Applets/WindowSystem.cs @@ -0,0 +1,524 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Process; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using System.Collections.Generic; +using System.Linq; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Common; +using Ryujinx.HLE.HOS.Applets.Types; + +namespace Ryujinx.HLE.HOS.Applets +{ + public class WindowSystem + { + private Horizon _system; + private readonly object _lock = new(); + EventObserver _eventObserver = null; + + // Foreground roots + RealApplet _homeMenu = null; + RealApplet _overlayDisp = null; + RealApplet _application = null; + + // Home menu state + bool _homeMenuForegroundLocked = false; + RealApplet _foregroundRequestedApplet = null; + + + // aruid -> applet map + Dictionary _applets = new(); + List _rootApplets = new(); + + internal ButtonPressTracker ButtonPressTracker { get; } + + public WindowSystem(Horizon system) + { + _system = system; + ButtonPressTracker = new ButtonPressTracker(system); + } + + void Dispose() + { + // SetWindowSystem(null); + } + + internal void SetEventObserver(EventObserver eventObserver) + { + _eventObserver = eventObserver; + // SetWindowSystem(this); + } + + internal void Update() + { + lock (_lock) + { + PruneTerminatedAppletsLocked(); + + if (LockHomeMenuIntoForegroundLocked()) + { + return; + } + + if (_foregroundRequestedApplet == null && _rootApplets.Count != 0) + { + _foregroundRequestedApplet = _rootApplets.Last(); + } + + foreach (var applet in _rootApplets) + { + UpdateAppletStateLocked(applet, _foregroundRequestedApplet == applet); + } + } + } + + internal RealApplet TrackProcess(ulong pid, ulong callerPid, bool isApplication) + { + lock (_lock) + { + if (_applets.TryGetValue(pid, out var applet)) + { + Logger.Info?.Print(LogClass.ServiceAm, $"TrackProcess() called on existing applet {pid} - caller {callerPid}"); + return applet; + } + + Logger.Info?.Print(LogClass.ServiceAm, $"Tracking process {pid} as {(isApplication ? "application" : "applet")} - caller {callerPid}"); + if (_system.KernelContext.Processes.TryGetValue(pid, out var _process)) + { + applet = new RealApplet(pid, isApplication, _system); + + if (callerPid == 0) + { + _rootApplets.Add(applet); + } + else + { + var callerApplet = _applets[callerPid]; + applet.CallerApplet = callerApplet; + callerApplet.RegisterChild(applet); + } + + TrackApplet(applet, isApplication); + return applet; + } + + return null; + } + } + + private void TrackApplet(RealApplet applet, bool isApplication) + { + if (_applets.ContainsKey(applet.AppletResourceUserId)) + { + return; + } + + if (applet.AppletId == RealAppletId.SystemAppletMenu) + { + _homeMenu = applet; + _foregroundRequestedApplet = applet; + } + else if (applet.AppletId == RealAppletId.OverlayApplet) + { + _overlayDisp = applet; + } + else if (isApplication) + { + _application = applet; + } + + _applets[applet.AppletResourceUserId] = applet; + _eventObserver.TrackAppletProcess(applet); + + if (_applets.Count == 1 || applet.AppletId == RealAppletId.SystemAppletMenu || applet.AppletId == RealAppletId.OverlayApplet) + { + SetupFirstApplet(applet); + } + + // _foregroundRequestedApplet = applet; + // applet.AppletState.SetFocusState(FocusState.InFocus); + + _eventObserver.RequestUpdate(); + } + + private void SetupFirstApplet(RealApplet applet) + { + if (applet.AppletId == RealAppletId.SystemAppletMenu) + { + //applet.AppletState.SetFocusHandlingMode(false); + applet.AppletState.SetOutOfFocusSuspendingEnabled(false); + RequestHomeMenuToGetForeground(); + } + else if (applet.AppletId == RealAppletId.OverlayApplet) + { + applet.AppletState.SetOutOfFocusSuspendingEnabled(false); + applet.AppletState.SetFocusState(FocusState.OutOfFocus); + } + else + { + applet.AppletState.SetFocusState(FocusState.InFocus); + _foregroundRequestedApplet = applet; + RequestApplicationToGetForeground(); + } + + applet.UpdateSuspensionStateLocked(true); + } + + internal RealApplet GetByAruId(ulong aruid) + { + // lock (_lock) + { + if (_applets.TryGetValue(aruid, out RealApplet applet)) + { + return applet; + } + + return null; + } + } + + internal RealApplet GetMainApplet() + { + // lock (_lock) + { + if (_application != null) + { + if (_applets.TryGetValue(_application.AppletResourceUserId, out RealApplet applet)) + { + return applet; + } + } + + return null; + } + } + + internal void RequestHomeMenuToGetForeground() + { + // lock (_lock) + { + _foregroundRequestedApplet = _homeMenu; + } + + _eventObserver.RequestUpdate(); + } + + internal void RequestApplicationToGetForeground() + { + // lock (_lock) + { + _foregroundRequestedApplet = _application; + } + + _eventObserver.RequestUpdate(); + } + + internal void RequestLockHomeMenuIntoForeground() + { + // lock (_lock) + { + _homeMenuForegroundLocked = true; + } + + _eventObserver.RequestUpdate(); + } + + internal void RequestUnlockHomeMenuFromForeground() + { + // lock (_lock) + { + _homeMenuForegroundLocked = false; + } + + _eventObserver.RequestUpdate(); + } + + internal void RequestAppletVisibilityState(RealApplet applet, bool isVisible) + { + lock (applet.Lock) + { + applet.WindowVisible = isVisible; + } + + _eventObserver.RequestUpdate(); + } + + internal void OnOperationModeChanged() + { + // lock (_lock) + { + foreach (var (aruid, applet) in _applets) + { + lock (applet.Lock) + { + applet.AppletState.OnOperationAndPerformanceModeChanged(); + } + } + } + } + + internal void OnExitRequested() + { + // lock (_lock) + { + foreach (var (aruid, applet) in _applets) + { + lock (applet.Lock) + { + applet.AppletState.OnExitRequested(); + } + } + } + } + + internal void OnSystemButtonPress(SystemButtonType type) + { + // lock (_lock) + { + switch (type) + { + case SystemButtonType.PerformHomeButtonShortPressing: + SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingHomeButton); + break; + case SystemButtonType.PerformHomeButtonLongPressing: + SendButtonAppletMessageLocked(AppletMessage.DetectLongPressingHomeButton); + break; + case SystemButtonType.PerformCaptureButtonShortPressing: + SendButtonAppletMessageLocked(AppletMessage.DetectShortPressingCaptureButton); + break; + } + } + } + + private void SendButtonAppletMessageLocked(AppletMessage message) + { + if (_homeMenu != null) + { + lock (_homeMenu.Lock) + { + _homeMenu.AppletState.PushUnorderedMessage(message); + } + } + + if (_overlayDisp != null) + { + lock (_overlayDisp.Lock) + { + _overlayDisp.AppletState.PushUnorderedMessage(message); + } + } + } + + private void PruneTerminatedAppletsLocked() + { + foreach (var (aruid, applet) in _applets) + { + lock (applet.Lock) + { + if (applet.ProcessHandle.State != ProcessState.Exited) + { + continue; + } + + if (applet.ChildApplets.Count != 0) + { + TerminateChildAppletsLocked(applet); + continue; + } + + if (applet.CallerApplet != null) + { + applet.CallerApplet.ChildApplets.Remove(applet); + applet.CallerApplet = null; + } + + if (applet == _foregroundRequestedApplet) + { + _foregroundRequestedApplet = null; + } + + if (applet == _homeMenu) + { + _homeMenu = null; + _foregroundRequestedApplet = null; + } + + if (applet == _application) + { + _application = null; + _foregroundRequestedApplet = null; + + if (_homeMenu != null) + { + _homeMenu.AppletState.PushUnorderedMessage(AppletMessage.ApplicationExited); + } + } + + applet.OnProcessTerminatedLocked(); + + _eventObserver.RequestUpdate(); + _applets.Remove(aruid); + _rootApplets.Remove(applet); + } + } + } + + private bool LockHomeMenuIntoForegroundLocked() + { + // If the home menu is not locked into the foreground, then there's nothing to do. + if (_homeMenu == null || !_homeMenuForegroundLocked) + { + _homeMenuForegroundLocked = false; + return false; + } + + lock (_homeMenu.Lock) + { + TerminateChildAppletsLocked(_homeMenu); + + if (_homeMenu.ChildApplets.Count == 0) + { + _homeMenu.WindowVisible = true; + _foregroundRequestedApplet = _homeMenu; + return false; + } + } + + return true; + } + + private void TerminateChildAppletsLocked(RealApplet parent) + { + foreach (var child in parent.ChildApplets) + { + if (child.ProcessHandle.State != ProcessState.Exited) + { + child.ProcessHandle.Terminate(); + child.TerminateResult = (ResultCode)Services.Am.ResultCode.LibraryAppletTerminated; + } + } + } + + private void UpdateAppletStateLocked(RealApplet applet, bool isForeground) + { + if (applet == null) + { + return; + } + + lock (applet.Lock) + { + var inheritedForeground = applet.IsProcessRunning && isForeground; + var visibleState = inheritedForeground ? ActivityState.ForegroundVisible : ActivityState.BackgroundVisible; + var obscuredState = inheritedForeground ? ActivityState.ForegroundObscured : ActivityState.BackgroundObscured; + + var hasObscuringChildApplets = applet.ChildApplets.Any(child => + { + lock (child.Lock) + { + var mode = child.LibraryAppletMode; + if (child.IsProcessRunning && child.WindowVisible && + (mode == LibraryAppletMode.AllForeground || mode == LibraryAppletMode.AllForegroundInitiallyHidden)) + { + return true; + } + } + + return false; + }); + + // TODO: Update visibility state + + applet.SetInteractibleLocked(isForeground && applet.WindowVisible); + + var isObscured = hasObscuringChildApplets || !applet.WindowVisible; + var state = applet.AppletState.ActivityState; + + if (isObscured && state != obscuredState) + { + applet.AppletState.ActivityState = obscuredState; + applet.UpdateSuspensionStateLocked(true); + } + else if (!isObscured && state != visibleState) + { + applet.AppletState.ActivityState = visibleState; + applet.UpdateSuspensionStateLocked(true); + } + + Logger.Info?.Print(LogClass.ServiceAm, $"Updating applet state for {applet.AppletId}: visible={applet.WindowVisible}, foreground={isForeground}, obscured={isObscured}, reqFState={applet.AppletState.RequestedFocusState}, ackFState={applet.AppletState.AcknowledgedFocusState}, runnable={applet.AppletState.IsRunnable()}"); + + + // Recurse into child applets + foreach (var child in applet.ChildApplets) + { + UpdateAppletStateLocked(child, isForeground); + } + } + } + } + + internal class ButtonPressTracker + { + private Horizon _system; + bool _homeButtonPressed = false; + long _homeButtonPressedTimeStart = 0; + bool _captureButtonPressed = false; + long _captureButtonPressedTime = 0; + + public ButtonPressTracker(Horizon system) + { + _system = system; + } + + public void Update() + { + // TODO: properly implement this + ref var shared = ref _system.Device.Hid.SharedMemory; + bool homeDown = shared.HomeButton.GetCurrentEntryRef().Buttons != 0; + bool captureDown = shared.CaptureButton.GetCurrentEntryRef().Buttons != 0; + + int homeButtonPressDuration = 0; + int captureButtonPressDuration = 0; + + if (_homeButtonPressed && !homeDown) + { + _homeButtonPressed = false; + homeButtonPressDuration = (int)(PerformanceCounter.ElapsedMilliseconds - _homeButtonPressedTimeStart); + } + else if (!_homeButtonPressed && homeDown) + { + _homeButtonPressed = true; + _homeButtonPressedTimeStart = PerformanceCounter.ElapsedMilliseconds; + } + + if (_captureButtonPressed && !captureDown) + { + _captureButtonPressed = false; + captureButtonPressDuration = (int)(PerformanceCounter.ElapsedMilliseconds - _captureButtonPressedTime); + } + else if (!_captureButtonPressed && captureDown) + { + _captureButtonPressed = true; + _captureButtonPressedTime = PerformanceCounter.ElapsedMilliseconds; + } + + if (homeButtonPressDuration > 500) + { + _system.WindowSystem.OnSystemButtonPress(SystemButtonType.PerformHomeButtonLongPressing); + } + else if (homeButtonPressDuration > 20) + { + _system.WindowSystem.OnSystemButtonPress(SystemButtonType.PerformHomeButtonShortPressing); + } + + if (captureButtonPressDuration > 500) + { + _system.WindowSystem.OnSystemButtonPress(SystemButtonType.PerformCaptureButtonLongPressing); + } + else if (captureButtonPressDuration > 20) + { + _system.WindowSystem.OnSystemButtonPress(SystemButtonType.PerformCaptureButtonShortPressing); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 6df4df91b..f04bf99ad 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -4,8 +4,10 @@ using LibHac.Fs; using LibHac.Fs.Shim; using LibHac.FsSystem; using LibHac.Tools.FsSystem; +using Ryujinx.Common.Logging; using Ryujinx.Cpu; using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Process; @@ -61,7 +63,37 @@ namespace Ryujinx.HLE.HOS internal PerformanceState PerformanceState { get; private set; } - internal AppletStateMgr AppletState { get; private set; } + internal AppletStateMgr IntialAppletState { get; private set; } + + internal AppletStateMgr AppletState + { + get + { + ulong processId = 0; + if (Device?.Processes?.ActiveApplication != null) + { + processId = Device.Processes.ActiveApplication.ProcessId; + } + if (WindowSystem?.GetByAruId(processId) != null) + { + Logger.Info?.Print(LogClass.Application, "Real applet instance found"); + return WindowSystem.GetByAruId(processId).AppletState; + } + + return IntialAppletState; + } + set + { + if (value != null) + { + IntialAppletState = value; + } + } + } + + internal WindowSystem WindowSystem { get; private set; } + + internal EventObserver EventObserver { get; private set; } internal List NfpDevices { get; private set; } @@ -172,8 +204,11 @@ namespace Ryujinx.HLE.HOS AppletCaptureBufferTransfer = new KTransferMemory(KernelContext, appletCaptureBufferStorage); - AppletState = new AppletStateMgr(this); + AppletState = new AppletStateMgr(this, true); + WindowSystem = new WindowSystem(this); + EventObserver = new EventObserver(this, WindowSystem); + AppletState.SetFocus(true); VsyncEvent = new KEvent(KernelContext); @@ -317,9 +352,7 @@ namespace Ryujinx.HLE.HOS State.DockedMode = newState; PerformanceState.PerformanceMode = State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default; - AppletState.Messages.Enqueue(AppletMessage.OperationModeChanged); - AppletState.Messages.Enqueue(AppletMessage.PerformanceModeChanged); - AppletState.MessageEvent.ReadableEvent.Signal(); + WindowSystem.OnOperationModeChanged(); SignalDisplayResolutionChange(); @@ -334,8 +367,8 @@ namespace Ryujinx.HLE.HOS public void SimulateWakeUpMessage() { - AppletState.Messages.Enqueue(AppletMessage.Resume); - AppletState.MessageEvent.ReadableEvent.Signal(); + // AppletState.Messages.Enqueue(AppletMessage.Resume); + // AppletState.MessageEvent.ReadableEvent.Signal(); } public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs index bf0f1542f..b74b23849 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs @@ -1175,5 +1175,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process { return Capabilities.IsSvcPermitted(svcId); } + + public bool IsRunning() + { + return State == ProcessState.Started || State == ProcessState.Attached || State == ProcessState.DebugSuspended; + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs index 64b2ba7d7..f83e362c0 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs @@ -87,7 +87,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService // GetApplicationCreator() -> object public ResultCode GetApplicationCreator(ServiceCtx context) { - MakeObject(context, new IApplicationCreator()); + MakeObject(context, new IApplicationCreator(context.Device.Processes.ActiveApplication.ProcessId)); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationAccessor.cs new file mode 100644 index 000000000..450f34334 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationAccessor.cs @@ -0,0 +1,107 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.HLE.Loaders.Processes; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IApplicationAccessor : IpcService + { + private readonly KernelContext _kernelContext; + private readonly ulong _callerPid; + private readonly ulong _applicationId; + private readonly string _contentPath; + + private readonly KEvent _stateChangedEvent; + private int _stateChangedEventHandle; + + public IApplicationAccessor(ulong pid, ulong applicationId, string contentPath, Horizon system) + { + _callerPid = pid; + _kernelContext = system.KernelContext; + _applicationId = applicationId; + _contentPath = contentPath; + + _stateChangedEvent = new KEvent(system.KernelContext); + } + + + [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) + { + _stateChangedEvent.ReadableEvent.Signal(); + + Logger.Info?.Print(LogClass.ServiceAm, $"Application 0x{_applicationId:X}:{_contentPath} start requested."); + ProcessResult processResult = null; + bool isApplet = false; + if (_contentPath.EndsWith("nsp")) + { + context.Device.Processes.LoadNsp(_contentPath,_applicationId, out processResult); + } + else if (_contentPath.EndsWith("xci")) + { + context.Device.Processes.LoadXci(_contentPath,_applicationId, out processResult); + } + else + { + context.Device.Processes.LoadNca(_contentPath, out processResult); + isApplet = true; + } + + var applet = context.Device.System.WindowSystem.TrackProcess(processResult.ProcessId, 0, !isApplet); + return ResultCode.Success; + } + + [CommandCmif(101)] + // RequestForApplicationToGetForeground() + public ResultCode RequestForApplicationToGetForeground(ServiceCtx context) + { + // _stateChangedEvent.ReadableEvent.Signal(); + Logger.Stub?.PrintStub(LogClass.ServiceAm); + context.Device.System.ReturnFocus(); + + return ResultCode.Success; + } + + [CommandCmif(121)] + // PushLaunchParameter(u32) -> IStorage + public ResultCode PushLaunchParameter(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + MakeObject(context, new IStorage(new byte[0],false)); + return ResultCode.Success; + } + + [CommandCmif(130)] + // SetUsers() + public ResultCode SetUsers(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + bool enable = context.RequestData.ReadBoolean(); + return ResultCode.Success; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs index 502324ea1..87ad9ab68 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs @@ -1,7 +1,54 @@ +using LibHac.Ncm; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS.Applets; +using System.Linq; + namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy { class IApplicationCreator : IpcService { - public IApplicationCreator() { } + private readonly ulong _pid; + + public IApplicationCreator(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // CreateApplication(nn::ncm::ApplicationId) -> object + public ResultCode CreateApplication(ServiceCtx context) + { + ulong applicationId = context.RequestData.ReadUInt64(); + Horizon system = context.Device.System; + string contentPath = system.Device.Configuration.Titles.FirstOrDefault(t => t.AppId.Value == applicationId).Path; + + MakeObject(context, new IApplicationAccessor(_pid, applicationId, contentPath, context.Device.System)); + return ResultCode.Success; + } + + [CommandCmif(10)] + // CreateSystemApplication(nn::ncm::SystemApplicationId) -> object + public ResultCode CreateSystemApplication(ServiceCtx context) + { + var applicationId = context.RequestData.ReadUInt64(); + var system = context.Device.System; + string contentPath = system.ContentManager.GetInstalledContentPath(applicationId, StorageId.BuiltInSystem, NcaContentType.Program); + + if (contentPath.Length == 0) + { + return ResultCode.TitleIdNotFound; + } + + if (contentPath.StartsWith("@SystemContent")) + { + contentPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); + } + + MakeObject(context, new IApplicationAccessor(_pid, applicationId, contentPath, context.Device.System)); + + return ResultCode.Success; + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs index 84c2f5cca..5a1ab9ef6 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -61,27 +61,12 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys // ReceiveMessage() -> nn::am::AppletMessage public ResultCode ReceiveMessage(ServiceCtx context) { - if (!context.Device.System.AppletState.Messages.TryDequeue(out AppletMessage message)) + if (!context.Device.System.AppletState.PopMessage(out AppletMessage message)) { return ResultCode.NoMessages; } - - KEvent messageEvent = context.Device.System.AppletState.MessageEvent; - - // NOTE: Service checks if current states are different than the stored ones. - // Since we don't support any states for now, it's fine to check if there is still messages available. - - if (context.Device.System.AppletState.Messages.IsEmpty) - { - messageEvent.ReadableEvent.Clear(); - } - else - { - messageEvent.ReadableEvent.Signal(); - } - + context.ResponseData.Write((int)message); - return ResultCode.Success; } @@ -120,7 +105,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys // GetCurrentFocusState() -> u8 public ResultCode GetCurrentFocusState(ServiceCtx context) { - context.ResponseData.Write((byte)context.Device.System.AppletState.FocusState); + context.ResponseData.Write((byte)context.Device.System.AppletState.AcknowledgedFocusState); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs index fdac7fca3..68bf5199a 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs @@ -22,7 +22,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys public ResultCode RequestToGetForeground(ServiceCtx context) { Logger.Stub?.PrintStub(LogClass.ServiceAm); - + context.Device.System.WindowSystem.RequestApplicationToGetForeground(); + return ResultCode.Success; } @@ -31,7 +32,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys public ResultCode LockForeground(ServiceCtx context) { Logger.Stub?.PrintStub(LogClass.ServiceAm); - + context.Device.System.WindowSystem.RequestLockHomeMenuIntoForeground(); + return ResultCode.Success; } [CommandCmif(12)] @@ -39,6 +41,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys public ResultCode UnlockForeground(ServiceCtx context) { Logger.Stub?.PrintStub(LogClass.ServiceAm); + context.Device.System.WindowSystem.RequestUnlockHomeMenuFromForeground(); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs index 46dc4916d..2bca8ba80 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys // GetAppletResourceUserId() -> nn::applet::AppletResourceUserId public ResultCode GetAppletResourceUserId(ServiceCtx context) { - long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid); + ulong appletResourceUserId = _pid; context.ResponseData.Write(appletResourceUserId); diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs index afb7d6b42..2142393c0 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs @@ -4,5 +4,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys { InFocus = 1, OutOfFocus = 2, + Background = 3, } } diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletChannel.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletChannel.cs new file mode 100644 index 000000000..09acd64fb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletChannel.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletChannel + { + private readonly IAppletFifo _data; + + public event EventHandler DataAvailable; + + public AppletChannel() + : this(new AppletFifo()) + { } + + public AppletChannel( + IAppletFifo data) + { + _data = data; + + _data.DataAvailable += OnDataAvailable; + } + + private void OnDataAvailable(object sender, EventArgs e) + { + DataAvailable?.Invoke(this, null); + } + + public void PushData(byte[] item) + { + if (!this.TryPushData(item)) + { + // TODO(jduncanator): Throw a proper exception + throw new InvalidOperationException(); + } + } + + public bool TryPushData(byte[] item) + { + return _data.TryAdd(item); + } + + public byte[] PopData() + { + if (this.TryPopData(out byte[] item)) + { + return item; + } + + throw new InvalidOperationException("Input data empty."); + } + + public bool TryPopData(out byte[] item) + { + return _data.TryTake(out item); + } + + public void InsertFrontData(byte[] item) + { + if (!this.TryInsertFrontData(item)) + { + // TODO: Throw a proper exception + throw new InvalidOperationException(); + } + } + + public bool TryInsertFrontData(byte[] item) + { + List items = new List(); + while (_data.TryTake(out byte[] i)) + { + items.Add(i); + } + + items.Insert(0, item); + + foreach (byte[] i in items) + { + _data.TryAdd(i); + } + + return true; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/RealAppletId.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/RealAppletId.cs new file mode 100644 index 000000000..ded0c314a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/RealAppletId.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Horizon.Sdk.Applet +{ + public enum RealAppletId : uint + { + None = 0x00, + Application = 0x01, + OverlayApplet = 0x02, + SystemAppletMenu = 0x03, + SystemApplication = 0x04, + LibraryAppletAuth = 0x0A, + LibraryAppletCabinet = 0x0B, + LibraryAppletController = 0x0C, + LibraryAppletDataErase = 0x0D, + LibraryAppletError = 0x0E, + LibraryAppletNetConnect = 0x0F, + LibraryAppletPlayerSelect = 0x10, + LibraryAppletSwkbd = 0x11, + LibraryAppletMiiEdit = 0x12, + LibraryAppletWeb = 0x13, + LibraryAppletShop = 0x14, + LibraryAppletPhotoViewer = 0x15, + LibraryAppletSet = 0x16, + LibraryAppletOfflineWeb = 0x17, + LibraryAppletLoginShare = 0x18, + LibraryAppletWifiWebAuth = 0x19, + LibraryAppletMyPage = 0x1A, + LibraryAppletGift = 0x1B, + LibraryAppletUserMigration = 0x1C, + LibraryAppletPreomiaSys = 0x1D, + LibraryAppletStory = 0x1E, + LibraryAppletPreomiaUsr = 0x1F, + LibraryAppletPreomiaUsrDummy = 0x20, + LibraryAppletSample = 0x21, + LibraryAppletPromoteQualification = 0x22, + LibraryAppletOfflineWebFw17 = 0x32, + LibraryAppletOfflineWeb2Fw17 = 0x33, + LibraryAppletLoginShareFw17 = 0x35, + LibraryAppletLoginShare2Fw17 = 0x36, + LibraryAppletLoginShare3Fw17 = 0x37, + Unknown38 = 0x38, + DevlopmentTool = 0x3E8, + CombinationLA = 0x3F1, + AeSystemApplet = 0x3F2, + AeOverlayApplet = 0x3F3, + AeStarter = 0x3F4, + AeLibraryAppletAlone = 0x3F5, + AeLibraryApplet1 = 0x3F6, + AeLibraryApplet2 = 0x3F7, + AeLibraryApplet3 = 0x3F8, + AeLibraryApplet4 = 0x3F9, + AppletISA = 0x3FA, + AppletIOA = 0x3FB, + AppletISTA = 0x3FC, + AppletILA1 = 0x3FD, + AppletILA2 = 0x3FE, + CombinationLAFw17 = 0x700000DC, + AeSystemAppletFw17 = 0x700000E6, + AeOverlayAppletFw17 = 0x700000E7, + AeStarterFw17 = 0x700000E8, + AeLibraryAppletAloneFw17 = 0x700000E9, + AeLibraryApplet1Fw17 = 0x700000EA, + AeLibraryApplet2Fw17 = 0x700000EB, + AeLibraryApplet3Fw17 = 0x700000EC, + AeLibraryApplet4Fw17 = 0x700000ED, + AppletISAFw17 = 0x700000F0, + AppletIOAFw17 = 0x700000F1, + AppletISTAFw17 = 0x700000F2, + AppletILA1Fw17 = 0x700000F3, + AppletILA2Fw17 = 0x700000F4, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/FocusHandlingMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/FocusHandlingMode.cs new file mode 100644 index 000000000..457f34c1f --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/FocusHandlingMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + enum FocusHandlingMode + { + AlwaysSuspend = 0, + SuspendHomeSleep = 1, + NoSuspend = 2, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/SystemButtonType.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/SystemButtonType.cs new file mode 100644 index 000000000..5d123970c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/SystemButtonType.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + public enum SystemButtonType + { + PerformHomeButtonShortPressing = 1, + PerformHomeButtonLongPressing = 2, + PerformSystemButtonPressing = 3, + PerformSystemButtonLongPressing = 4, + ShutdownSystem = 5, + PerformCaptureButtonShortPressing = 6, + PerformCaptureButtonLongPressing = 7 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs index 9142f65e0..9b251a8aa 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -9,6 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am NotAvailable = (2 << ErrorCodeShift) | ModuleId, NoMessages = (3 << ErrorCodeShift) | ModuleId, + LibraryAppletTerminated = (22 << ErrorCodeShift) | ModuleId, AppletLaunchFailed = (35 << ErrorCodeShift) | ModuleId, TitleIdNotFound = (37 << ErrorCodeShift) | ModuleId, ObjectInvalid = (500 << ErrorCodeShift) | ModuleId, diff --git a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs index d353ce64f..f0cf6571f 100644 --- a/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs +++ b/src/Ryujinx.HLE/HOS/Services/Fs/IFileSystemProxy.cs @@ -13,6 +13,7 @@ using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Common; using Ryujinx.Common.Logging; using Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy; +using Ryujinx.HLE.HOS.Services.Pcv; using Ryujinx.Memory; using System; using System.IO; @@ -676,6 +677,14 @@ namespace Ryujinx.HLE.HOS.Services.Fs return ResultCode.Success; } + [CommandCmif(83)] + // OpenSaveDataTransferProhibiter(u64) -> nn::fssrv::sf::ISaveDataTransferProhibiter + public ResultCode OpenSaveDataTransferProhibiter(ServiceCtx context) + { + MakeObject(context, new IPcvService(context)); + + return ResultCode.Success; + } [CommandCmif(84)] public ResultCode ListAccessibleSaveDataOwnerId(ServiceCtx context) diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index 93fef364d..82aa1ab64 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -58,6 +58,9 @@ namespace Ryujinx.HLE.HOS.Services public ManualResetEvent InitDone { get; } public string Name { get; } public Func SmObjectFactory { get; } + + internal KProcess SelfProcess => _selfProcess; + internal KThread SelfThread => _selfThread; public ServerBase(KernelContext context, string name, Func smObjectFactory = null) { diff --git a/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs index a2837e207..dbc1bb339 100644 --- a/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs +++ b/src/Ryujinx.HLE/HOS/SystemState/AppletStateMgr.cs @@ -1,33 +1,569 @@ +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Applets.Types; using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using System; using System.Collections.Concurrent; namespace Ryujinx.HLE.HOS.SystemState { class AppletStateMgr { - public ConcurrentQueue Messages { get; } + public ConcurrentQueue Messages; - public FocusState FocusState { get; private set; } + public bool ForcedSuspend { get; private set; } + public FocusState AcknowledgedFocusState { get; private set; } = FocusState.Background; + public FocusState RequestedFocusState { get; private set; } = FocusState.Background; + + public bool ResumeNotificationEnabled { get; set; } = false; + public SuspendMode SuspendMode { get; set; } = SuspendMode.NoOverride; + public ActivityState ActivityState { get; set; } = ActivityState.ForegroundVisible; public KEvent MessageEvent { get; } + public KEvent OperationModeChangedEvent { get; } + public KEvent LaunchableEvent { get; } public IdDictionary AppletResourceUserIds { get; } public IdDictionary IndirectLayerHandles { get; } - public AppletStateMgr(Horizon system) + internal bool IsApplication { get; } + private bool _focusStateChangedNotificationEnabled = true; + private bool _operationModeChangedNotificationEnabled = true; + private bool _performanceModeChangedNotificationEnabled = true; + private bool _hasRequestedExit = false; + private bool _hasAcknowledgedExit = false; + private bool _requestedRequestToDisplayState = false; + private bool _acknowledgedRequestToDisplayState = false; + private bool _hasRequestedRequestToPrepareSleep = false; + private bool _hasAcknowledgedRequestToPrepareSleep = false; + private bool _hasOperationModeChanged = false; + private bool _hasPerformanceModeChanged = false; + private bool _hasResume = false; + private bool _hasFocusStateChanged = false; + private bool _hasAlbumRecordingSaved = false; + private bool _hasAlbumScreenShotTaken = false; + private bool _hasAutoPowerDown = false; + private bool _hasSleepRequiredByLowBattery = false; + private bool _hasSleepRequiredByHighTemperature = false; + private bool _hasSdCardRemoved = false; + private bool _eventSignaled = false; + private FocusHandlingMode _focusHandlingMode = FocusHandlingMode.NoSuspend; + + public bool HasRequestedExit => _hasRequestedExit; + + public bool FocusStateChangedNotificationEnabled { + get => _focusStateChangedNotificationEnabled; + set + { + _focusStateChangedNotificationEnabled = value; + // SignalEventIfNeeded(); + } + } + + public bool OperationModeChangedNotificationEnabled + { + get => _operationModeChangedNotificationEnabled; + set + { + _operationModeChangedNotificationEnabled = value; + SignalEventIfNeeded(); + } + } + + public bool PerformanceModeChangedNotificationEnabled + { + get => _performanceModeChangedNotificationEnabled; + set + { + _performanceModeChangedNotificationEnabled = value; + SignalEventIfNeeded(); + } + } + + public AppletStateMgr(Horizon system, bool isApplication) + { + IsApplication = isApplication; Messages = new ConcurrentQueue(); MessageEvent = new KEvent(system.KernelContext); + OperationModeChangedEvent = new KEvent(system.KernelContext); + LaunchableEvent = new KEvent(system.KernelContext); AppletResourceUserIds = new IdDictionary(); IndirectLayerHandles = new IdDictionary(); } + public void SetFocusState(FocusState state) + { + if (RequestedFocusState != state) + { + RequestedFocusState = state; + _hasFocusStateChanged = true; + } + + SignalEventIfNeeded(); + } + + public FocusState GetAndClearFocusState() + { + AcknowledgedFocusState = RequestedFocusState; + return AcknowledgedFocusState; + } + + public void PushUnorderedMessage(AppletMessage message) + { + Messages.Enqueue(message); + + SignalEventIfNeeded(); + } + + public bool PopMessage(out AppletMessage message) + { + message = GetNextMessage(); + SignalEventIfNeeded(); + return message != AppletMessage.None; + } + + private AppletMessage GetNextMessage() + { + if (_hasResume) + { + _hasResume = false; + return AppletMessage.Resume; + } + + if (_hasRequestedExit != _hasAcknowledgedExit) + { + _hasAcknowledgedExit = _hasRequestedExit; + return AppletMessage.Exit; + } + + if (_focusStateChangedNotificationEnabled) + { + if (IsApplication) + { + if (_hasFocusStateChanged) + { + _hasFocusStateChanged = false; + return AppletMessage.FocusStateChanged; + } + } + else + { + if (RequestedFocusState != AcknowledgedFocusState) + { + AcknowledgedFocusState = RequestedFocusState; + + switch (RequestedFocusState) + { + case FocusState.InFocus: + return AppletMessage.ChangeIntoForeground; + case FocusState.OutOfFocus: + return AppletMessage.ChangeIntoBackground; + } + } + } + } + + if (_hasRequestedRequestToPrepareSleep != _hasAcknowledgedRequestToPrepareSleep) + { + _hasAcknowledgedRequestToPrepareSleep = true; + return AppletMessage.RequestToPrepareSleep; + } + + if (_requestedRequestToDisplayState != _acknowledgedRequestToDisplayState) + { + _acknowledgedRequestToDisplayState = _requestedRequestToDisplayState; + return AppletMessage.RequestToDisplay; + } + + if (_hasOperationModeChanged) + { + _hasOperationModeChanged = false; + return AppletMessage.OperationModeChanged; + } + + if (_hasPerformanceModeChanged) + { + _hasPerformanceModeChanged = false; + return AppletMessage.PerformanceModeChanged; + } + + if (_hasSdCardRemoved) + { + _hasSdCardRemoved = false; + return AppletMessage.SdCardRemoved; + } + + if (_hasSleepRequiredByHighTemperature) + { + _hasSleepRequiredByHighTemperature = false; + return AppletMessage.SleepRequiredByHighTemperature; + } + + if (_hasSleepRequiredByLowBattery) + { + _hasSleepRequiredByLowBattery = false; + return AppletMessage.SleepRequiredByLowBattery; + } + + if (_hasAutoPowerDown) + { + _hasAutoPowerDown = false; + return AppletMessage.AutoPowerDown; + } + + if (_hasAlbumScreenShotTaken) + { + _hasAlbumScreenShotTaken = false; + return AppletMessage.AlbumScreenShotTaken; + } + + if (_hasAlbumRecordingSaved) + { + _hasAlbumRecordingSaved = false; + return AppletMessage.AlbumRecordingSaved; + } + + if (Messages.TryDequeue(out AppletMessage message)) + { + return message; + } + + return AppletMessage.None; + } + + internal void SignalEventIfNeeded() + { + var shouldSignal = ShouldSignalEvent(); + + if (_eventSignaled != shouldSignal) + { + if (_eventSignaled) + { + MessageEvent.ReadableEvent.Clear(); + _eventSignaled = false; + } + else + { + MessageEvent.ReadableEvent.Signal(); + _eventSignaled = true; + } + } + } + + private bool ShouldSignalEvent() + { + bool focusStateChanged = false; + if (_focusStateChangedNotificationEnabled) + { + if (IsApplication) + { + if (_hasFocusStateChanged) + { + focusStateChanged = true; + } + } + else + { + if (RequestedFocusState != AcknowledgedFocusState) + { + focusStateChanged = true; + } + } + } + + return !Messages.IsEmpty + || focusStateChanged + || _hasResume + || _hasRequestedExit != _hasAcknowledgedExit + || _hasRequestedRequestToPrepareSleep != _hasAcknowledgedRequestToPrepareSleep + || _hasOperationModeChanged + || _hasPerformanceModeChanged + || _hasSdCardRemoved + || _hasSleepRequiredByHighTemperature + || _hasSleepRequiredByLowBattery + || _hasAutoPowerDown + || _requestedRequestToDisplayState != _acknowledgedRequestToDisplayState + || _hasAlbumScreenShotTaken + || _hasAlbumRecordingSaved; + } + + public void OnOperationAndPerformanceModeChanged() + { + if (_operationModeChangedNotificationEnabled) + { + _hasOperationModeChanged = true; + } + + if (_performanceModeChangedNotificationEnabled) + { + _hasPerformanceModeChanged = true; + } + + OperationModeChangedEvent.ReadableEvent.Signal(); + SignalEventIfNeeded(); + } + + public void OnExitRequested() + { + _hasRequestedExit = true; + SignalEventIfNeeded(); + } + + public void SetFocusHandlingMode(bool suspend) + { + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + case FocusHandlingMode.SuspendHomeSleep: + if (!suspend) + { + // Disallow suspension + _focusHandlingMode = FocusHandlingMode.NoSuspend; + } + break; + case FocusHandlingMode.NoSuspend: + if (suspend) + { + // Allow suspension temporarily. + _focusHandlingMode = FocusHandlingMode.SuspendHomeSleep; + } + break; + } + + // SignalEventIfNeeded(); + } + + public void RequestResumeNotification() + { + // NOTE: this appears to be a bug in am. + // If an applet makes a concurrent request to receive resume notifications + // while it is being suspended, the first resume notification will be lost. + // This is not the case with other notification types. + if (ResumeNotificationEnabled) + { + _hasResume = true; + } + } + + public void SetOutOfFocusSuspendingEnabled(bool enabled) + { + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + if (!enabled) + { + // Allow suspension temporarily. + _focusHandlingMode = FocusHandlingMode.SuspendHomeSleep; + } + break; + case FocusHandlingMode.SuspendHomeSleep: + case FocusHandlingMode.NoSuspend: + if (enabled) + { + // Allow suspension + _focusHandlingMode = FocusHandlingMode.AlwaysSuspend; + } + break; + } + + SignalEventIfNeeded(); + } + + public void RemoveForceResumeIfPossible() + { + // If resume is not forced, we have nothing to do. + if (SuspendMode != SuspendMode.ForceResume) + { + return; + } + + // Check activity state. + // If we are already resumed, we can remove the forced state. + switch (ActivityState) + { + case ActivityState.ForegroundVisible: + case ActivityState.ForegroundObscured: + SuspendMode = SuspendMode.NoOverride; + return; + } + + // Check focus handling mode. + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + case FocusHandlingMode.SuspendHomeSleep: + // If the applet allows suspension, we can remove the forced state. + SuspendMode = SuspendMode.NoOverride; + break; + case FocusHandlingMode.NoSuspend: + // If the applet is not an application, we can remove the forced state. + // Only applications can be forced to resume. + if (!IsApplication) + { + SuspendMode = SuspendMode.NoOverride; + } + break; + } + } + + public bool IsRunnable() + { + // If suspend is forced, return that. + if (ForcedSuspend) + { + return false; + } + + // Check suspend mode override. + switch (SuspendMode) + { + case SuspendMode.NoOverride: + // Continue processing. + break; + + case SuspendMode.ForceResume: + // The applet is runnable during forced resumption when its exit is requested. + return _hasRequestedExit; + + case SuspendMode.ForceSuspend: + // The applet is never runnable during forced suspension. + return false; + } + + // Always run if exit is requested. + if (_hasRequestedExit) + { + return true; + } + + if (ActivityState == ActivityState.ForegroundVisible) + { + // The applet is runnable now. + return true; + } + + if (ActivityState == ActivityState.ForegroundObscured) + { + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + // The applet is not runnable while running the applet. + return false; + + case FocusHandlingMode.SuspendHomeSleep: + // The applet is runnable while running the applet. + return true; + + case FocusHandlingMode.NoSuspend: + // The applet is always runnable. + return true; + } + } + + // The activity is a suspended one. + // The applet should be suspended unless it has disabled suspension. + return _focusHandlingMode == FocusHandlingMode.NoSuspend; + } + + public FocusState GetFocusStateWhileForegroundObscured() + { + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + // The applet never learns it has lost focus. + return FocusState.InFocus; + + case FocusHandlingMode.SuspendHomeSleep: + // The applet learns it has lost focus when launching a child applet. + return FocusState.OutOfFocus; + + case FocusHandlingMode.NoSuspend: + // The applet always learns it has lost focus. + return FocusState.OutOfFocus; + + default: + throw new IndexOutOfRangeException(); + } + } + + public FocusState GetFocusStateWhileBackground(bool isObscured) + { + switch (_focusHandlingMode) + { + case FocusHandlingMode.AlwaysSuspend: + // The applet never learns it has lost focus. + return FocusState.InFocus; + + case FocusHandlingMode.SuspendHomeSleep: + // The applet learns it has lost focus when launching a child applet. + return isObscured ? FocusState.OutOfFocus : FocusState.InFocus; + + case FocusHandlingMode.NoSuspend: + // The applet always learns it has lost focus. + return IsApplication ? FocusState.Background : FocusState.OutOfFocus; + + default: + throw new IndexOutOfRangeException(); + } + } + + public bool UpdateRequestedFocusState() + { + FocusState newState; + + if (SuspendMode == SuspendMode.NoOverride) + { + // With no forced suspend or resume, we take the focus state designated + // by the combination of the activity flag and the focus handling mode. + switch (ActivityState) + { + case ActivityState.ForegroundVisible: + newState = FocusState.InFocus; + break; + + case ActivityState.ForegroundObscured: + newState = GetFocusStateWhileForegroundObscured(); + break; + + case ActivityState.BackgroundVisible: + newState = GetFocusStateWhileBackground(false); + break; + + case ActivityState.BackgroundObscured: + newState = GetFocusStateWhileBackground(true); + break; + + default: + throw new IndexOutOfRangeException(); + } + } + else + { + // With forced suspend or resume, the applet is guaranteed to be background. + newState = GetFocusStateWhileBackground(false); + } + + if (newState != RequestedFocusState) + { + // Mark the focus state as ready for update. + RequestedFocusState = newState; + _hasFocusStateChanged = true; + + // We changed the focus state. + return true; + } + + // We didn't change the focus state. + return false; + } + public void SetFocus(bool isFocused) { - FocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus; + AcknowledgedFocusState = isFocused ? FocusState.InFocus : FocusState.OutOfFocus; Messages.Enqueue(AppletMessage.FocusStateChanged); @@ -38,5 +574,6 @@ namespace Ryujinx.HLE.HOS.SystemState MessageEvent.ReadableEvent.Signal(); } + } } diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs index 4c0866531..d2f022b85 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessLoader.cs @@ -31,8 +31,11 @@ namespace Ryujinx.HLE.Loaders.Processes get { if (!_processesByPid.TryGetValue(_latestPid, out ProcessResult value)) - throw new RyujinxException( - $"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?"); + { + return null; + } + // throw new RyujinxException( + // $"The HLE Process map did not have a process with ID {_latestPid}. Are you missing firmware?"); return value; } @@ -79,6 +82,42 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + public bool LoadXci(string path, ulong applicationId, out ProcessResult processResult) + { + FileStream stream = new(path, FileMode.Open, FileAccess.Read); + Xci xci = new(_device.Configuration.VirtualFileSystem.KeySet, stream.AsStorage()); + + if (!xci.HasPartition(XciPartitionType.Secure)) + { + Logger.Error?.Print(LogClass.Loader, "Unable to load XCI: Could not find XCI Secure partition"); + processResult = null; + return false; + } + + (bool success, processResult) = xci.OpenPartition(XciPartitionType.Secure).TryLoad(_device, path, applicationId, out string errorMessage); + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + + return false; + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + TitleIDs.CurrentApplication.Value = processResult.ProgramIdText; + + return true; + } + } + + return false; + } public bool LoadNsp(string path, ulong applicationId) { @@ -113,6 +152,40 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + public bool LoadNsp(string path, ulong applicationId, out ProcessResult processResult) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + PartitionFileSystem partitionFileSystem = new(); + partitionFileSystem.Initialize(file.AsStorage()).ThrowIfFailure(); + + (bool success, processResult) = partitionFileSystem.TryLoad(_device, path, applicationId, out string errorMessage); + + if (processResult.ProcessId == 0) + { + // This is not a normal NSP, it's actually a ExeFS as a NSP + processResult = partitionFileSystem.Load(_device, new BlitStruct(1), partitionFileSystem.GetNpdm(), 0, true); + } + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + _latestPid = processResult.ProcessId; + + TitleIDs.CurrentApplication.Value = processResult.ProgramIdText; + + return true; + } + } + + if (!success) + { + Logger.Error?.Print(LogClass.Loader, errorMessage, nameof(PartitionFileSystemExtensions.TryLoad)); + } + + return false; + } public bool LoadNca(string path, BlitStruct? customNacpData = null) { @@ -139,6 +212,32 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + public bool LoadNca(string path, out ProcessResult processResult) + { + FileStream file = new(path, FileMode.Open, FileAccess.Read); + Nca nca = new(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false)); + + processResult = nca.Load(_device, null, null, null); + + if (processResult.ProcessId != 0 && _processesByPid.TryAdd(processResult.ProcessId, processResult)) + { + if (processResult.Start(_device)) + { + // NOTE: Check if process is SystemApplicationId or ApplicationId + if (processResult.ProgramId > 0x01000000000007FF) + { + _latestPid = processResult.ProcessId; + + TitleIDs.CurrentApplication.Value = processResult.ProgramIdText; + } + + return true; + } + } + + return false; + } public bool LoadUnpackedNca(string exeFsDirPath, string romFsPath = null) { @@ -266,5 +365,10 @@ namespace Ryujinx.HLE.Loaders.Processes return false; } + + public void SetActivePID(ulong lastActivePid) + { + _latestPid = lastActivePid; + } } } diff --git a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs index 6fd9408ed..12d476fb6 100644 --- a/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs +++ b/src/Ryujinx.HLE/Loaders/Processes/ProcessResult.cs @@ -3,6 +3,7 @@ using LibHac.Loader; using LibHac.Ns; using Ryujinx.Common.Logging; using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Processes.Extensions; using Ryujinx.Horizon.Common; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs index 75b1cb80d..49b5a91a3 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/EventClearMode.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - enum EventClearMode + public enum EventClearMode { ManualClear, AutoClear, diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs index 573e79b61..938bfb391 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/EventType.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Ryujinx.Horizon.Sdk.OsTypes { - struct EventType + public struct EventType { public LinkedList MultiWaitHolders; public bool Signaled; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs index 2fbcfdc88..03722726a 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -6,7 +6,7 @@ using System.Threading; namespace Ryujinx.Horizon.Sdk.OsTypes.Impl { - class MultiWaitImpl + public class MultiWaitImpl { private const int WaitTimedOut = -1; private const int WaitCancelled = -2; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs index 3d5bb8108..fc326d617 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/InitializationState.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - enum InitializationState : byte + public enum InitializationState : byte { NotInitialized, Initialized, diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs index 39c93e570..6c276746d 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/InterProcessEventType.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - struct InterProcessEventType + public struct InterProcessEventType { public readonly bool AutoClear; public InitializationState State; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs index 41d17802a..8abe9a414 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWait.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Ryujinx.Horizon.Sdk.OsTypes { - class MultiWait + public class MultiWait { private readonly MultiWaitImpl _impl; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs index e0473ecaf..db73adce8 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolder.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - class MultiWaitHolder : MultiWaitHolderBase + public class MultiWaitHolder : MultiWaitHolderBase { public object UserData { get; set; } diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs index 4bccba6c2..ea9eb7321 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderBase.cs @@ -2,9 +2,9 @@ using Ryujinx.Horizon.Sdk.OsTypes.Impl; namespace Ryujinx.Horizon.Sdk.OsTypes { - class MultiWaitHolderBase + public class MultiWaitHolderBase { - protected MultiWaitImpl MultiWait; + public MultiWaitImpl MultiWait; public bool IsLinked => MultiWait != null; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs index 1f5fefde0..48fafce6c 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfHandle.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - class MultiWaitHolderOfHandle : MultiWaitHolder + public class MultiWaitHolderOfHandle : MultiWaitHolder { private readonly int _handle; diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfInterProcessEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfInterProcessEvent.cs new file mode 100644 index 000000000..23f525663 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/MultiWaitHolderOfInterProcessEvent.cs @@ -0,0 +1,26 @@ +using Ryujinx.Horizon.Sdk.OsTypes.Impl; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Sdk.OsTypes +{ + public class MultiWaitHolderOfInterProcessEvent : MultiWaitHolder + { + private readonly InterProcessEventType _event; + private LinkedListNode _node; + + public override TriBool Signaled + { + get + { + return TriBool.Undefined; + } + } + + public override int Handle => _event.ReadableHandle; + + public MultiWaitHolderOfInterProcessEvent(InterProcessEventType evnt) + { + _event = evnt; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs index 0cd147771..ef70314a4 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/OsEvent.cs @@ -3,7 +3,7 @@ using System.Threading; namespace Ryujinx.Horizon.Sdk.OsTypes { - static partial class Os + public static partial class Os { public static void InitializeEvent(out EventType evnt, bool signaled, EventClearMode clearMode) { diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs index a838f46c4..974423f82 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/SystemEventType.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - struct SystemEventType + public struct SystemEventType { public enum InitializationState : byte { diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs index b162db9b7..998ca4986 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/TriBool.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Horizon.Sdk.OsTypes { - enum TriBool + public enum TriBool { False, True,