Implement application launching

Co-authored-by: Alula <6276139+alula@users.noreply.github.com>
This commit is contained in:
Jacobwasbeast 2025-02-09 08:22:59 -06:00
parent 50926d6a2f
commit ffcc13e1cc
40 changed files with 2170 additions and 49 deletions

View File

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

View File

@ -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<T>(ReadOnlySpan<byte> data) where T : unmanaged
{
return MemoryMarshal.Cast<byte, T>(data)[0];
}
}
}

View File

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

View File

@ -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<RealAppletId, ulong> _appletTitles = new Dictionary<RealAppletId, ulong>
{
{ 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<RealApplet> 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();
}
}
}

View File

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Applets.Types
{
public enum ActivityState
{
ForegroundVisible = 0,
ForegroundObscured = 1,
BackgroundVisible = 2,
BackgroundObscured = 3,
}
}

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Applets.Types
{
public enum SuspendMode
{
NoOverride = 0,
ForceResume = 1,
ForceSuspend = 2,
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Applets.Types
{
enum UserDataTag : uint
{
WakeupEvent,
AppletProcess,
}
}

View File

@ -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<ulong, RealApplet> _applets = new();
List<RealApplet> _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);
}
}
}
}

View File

@ -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<NfpDevice> NfpDevices { get; private set; }
@ -172,7 +204,10 @@ 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);
@ -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)

View File

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

View File

@ -87,7 +87,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService
// GetApplicationCreator() -> object<nn::am::service::IApplicationCreator>
public ResultCode GetApplicationCreator(ServiceCtx context)
{
MakeObject(context, new IApplicationCreator());
MakeObject(context, new IApplicationCreator(context.Device.Processes.ActiveApplication.ProcessId));
return ResultCode.Success;
}

View File

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

View File

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

View File

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

View File

@ -22,6 +22,7 @@ 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,6 +32,7 @@ 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;
}
@ -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;
}

View File

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

View File

@ -4,5 +4,6 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
{
InFocus = 1,
OutOfFocus = 2,
Background = 3,
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
internal class AppletChannel
{
private readonly IAppletFifo<byte[]> _data;
public event EventHandler DataAvailable;
public AppletChannel()
: this(new AppletFifo<byte[]>())
{ }
public AppletChannel(
IAppletFifo<byte[]> 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<byte[]> items = new List<byte[]>();
while (_data.TryTake(out byte[] i))
{
items.Add(i);
}
items.Insert(0, item);
foreach (byte[] i in items)
{
_data.TryAdd(i);
}
return true;
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
enum FocusHandlingMode
{
AlwaysSuspend = 0,
SuspendHomeSleep = 1,
NoSuspend = 2,
}
}

View File

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

View File

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

View File

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

View File

@ -59,6 +59,9 @@ namespace Ryujinx.HLE.HOS.Services
public string Name { get; }
public Func<IpcService> SmObjectFactory { get; }
internal KProcess SelfProcess => _selfProcess;
internal KThread SelfThread => _selfThread;
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
{
_context = context;

View File

@ -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<AppletMessage> Messages { get; }
public ConcurrentQueue<AppletMessage> 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<AppletMessage>();
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();
}
}
}

View File

@ -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;
}
@ -80,6 +83,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)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
@ -114,6 +153,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<ApplicationControlProperty>(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<ApplicationControlProperty>? customNacpData = null)
{
FileStream file = new(path, FileMode.Open, FileAccess.Read);
@ -140,6 +213,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)
{
ProcessResult processResult = new LocalFileSystem(exeFsDirPath).Load(_device, romFsPath);
@ -266,5 +365,10 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
public void SetActivePID(ulong lastActivePid)
{
_latestPid = lastActivePid;
}
}
}

View File

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

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum EventClearMode
public enum EventClearMode
{
ManualClear,
AutoClear,

View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.OsTypes
{
struct EventType
public struct EventType
{
public LinkedList<MultiWaitHolderBase> MultiWaitHolders;
public bool Signaled;

View File

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

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum InitializationState : byte
public enum InitializationState : byte
{
NotInitialized,
Initialized,

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
struct InterProcessEventType
public struct InterProcessEventType
{
public readonly bool AutoClear;
public InitializationState State;

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
namespace Ryujinx.Horizon.Sdk.OsTypes
{
class MultiWait
public class MultiWait
{
private readonly MultiWaitImpl _impl;

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
class MultiWaitHolder : MultiWaitHolderBase
public class MultiWaitHolder : MultiWaitHolderBase
{
public object UserData { get; set; }

View File

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

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
class MultiWaitHolderOfHandle : MultiWaitHolder
public class MultiWaitHolderOfHandle : MultiWaitHolder
{
private readonly int _handle;

View File

@ -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<MultiWaitHolderBase> _node;
public override TriBool Signaled
{
get
{
return TriBool.Undefined;
}
}
public override int Handle => _event.ReadableHandle;
public MultiWaitHolderOfInterProcessEvent(InterProcessEventType evnt)
{
_event = evnt;
}
}
}

View File

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

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
struct SystemEventType
public struct SystemEventType
{
public enum InitializationState : byte
{

View File

@ -1,6 +1,6 @@
namespace Ryujinx.Horizon.Sdk.OsTypes
{
enum TriBool
public enum TriBool
{
False,
True,