diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Am | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Am')
44 files changed, 3061 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs new file mode 100644 index 00000000..bf86aaaa --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ILibraryAppletProxy.cs @@ -0,0 +1,105 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService +{ + class ILibraryAppletProxy : IpcService + { + private readonly ulong _pid; + + public ILibraryAppletProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter> + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object<nn::am::service::ISelfController> + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object<nn::am::service::IWindowController> + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object<nn::am::service::IAudioController> + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object<nn::am::service::IDisplayController> + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // GetProcessWindingController() -> object<nn::am::service::IProcessWindingController> + public ResultCode GetProcessWindingController(ServiceCtx context) + { + MakeObject(context, new IProcessWindingController()); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator> + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // OpenLibraryAppletSelfAccessor() -> object<nn::am::service::ILibraryAppletSelfAccessor> + public ResultCode OpenLibraryAppletSelfAccessor(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletSelfAccessor(context)); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetAppletCommonFunctions() -> object<nn::am::service::IAppletCommonFunctions> + public ResultCode GetAppletCommonFunctions(ServiceCtx context) + { + MakeObject(context, new IAppletCommonFunctions()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object<nn::am::service::IDebugFunctions> + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs new file mode 100644 index 00000000..dc26d80c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/ISystemAppletProxy.cs @@ -0,0 +1,104 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService +{ + class ISystemAppletProxy : IpcService + { + private readonly ulong _pid; + + public ISystemAppletProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter> + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object<nn::am::service::ISelfController> + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object<nn::am::service::IWindowController> + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object<nn::am::service::IAudioController> + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object<nn::am::service::IDisplayController> + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator> + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetHomeMenuFunctions() -> object<nn::am::service::IHomeMenuFunctions> + public ResultCode GetHomeMenuFunctions(ServiceCtx context) + { + MakeObject(context, new IHomeMenuFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetGlobalStateController() -> object<nn::am::service::IGlobalStateController> + public ResultCode GetGlobalStateController(ServiceCtx context) + { + MakeObject(context, new IGlobalStateController()); + + return ResultCode.Success; + } + + [CommandCmif(22)] + // GetApplicationCreator() -> object<nn::am::service::IApplicationCreator> + public ResultCode GetApplicationCreator(ServiceCtx context) + { + MakeObject(context, new IApplicationCreator()); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object<nn::am::service::IDebugFunctions> + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs new file mode 100644 index 00000000..0057eba3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs @@ -0,0 +1,261 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Applets; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator +{ + class ILibraryAppletAccessor : DisposableIpcService + { + private KernelContext _kernelContext; + + private IApplet _applet; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + private KEvent _stateChangedEvent; + private KEvent _normalOutDataEvent; + private KEvent _interactiveOutDataEvent; + + private int _stateChangedEventHandle; + private int _normalOutDataEventHandle; + private int _interactiveOutDataEventHandle; + + private int _indirectLayerHandle; + + public ILibraryAppletAccessor(AppletId appletId, Horizon system) + { + _kernelContext = system.KernelContext; + + _stateChangedEvent = new KEvent(system.KernelContext); + _normalOutDataEvent = new KEvent(system.KernelContext); + _interactiveOutDataEvent = new KEvent(system.KernelContext); + + _applet = AppletManager.Create(appletId, system); + + _normalSession = new AppletSession(); + _interactiveSession = new AppletSession(); + + _applet.AppletStateChanged += OnAppletStateChanged; + _normalSession.DataAvailable += OnNormalOutData; + _interactiveSession.DataAvailable += OnInteractiveOutData; + + Logger.Info?.Print(LogClass.ServiceAm, $"Applet '{appletId}' created."); + } + + private void OnAppletStateChanged(object sender, EventArgs e) + { + _stateChangedEvent.WritableEvent.Signal(); + } + + private void OnNormalOutData(object sender, EventArgs e) + { + _normalOutDataEvent.WritableEvent.Signal(); + } + + private void OnInteractiveOutData(object sender, EventArgs e) + { + _interactiveOutDataEvent.WritableEvent.Signal(); + } + + [CommandCmif(0)] + // GetAppletStateChangedEvent() -> handle<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) + { + return (ResultCode)_applet.Start(_normalSession.GetConsumer(), _interactiveSession.GetConsumer()); + } + + [CommandCmif(20)] + // RequestExit() + public ResultCode RequestExit(ServiceCtx context) + { + // TODO: Since we don't support software Applet for now, we can just signals the changed state of the applet. + _stateChangedEvent.ReadableEvent.Signal(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(30)] + // GetResult() + public ResultCode GetResult(ServiceCtx context) + { + return (ResultCode)_applet.GetResult(); + } + + [CommandCmif(60)] + // PresetLibraryAppletGpuTimeSliceZero() + public ResultCode PresetLibraryAppletGpuTimeSliceZero(ServiceCtx context) + { + // NOTE: This call reset two internal fields to 0 and one internal field to "true". + // It seems to be used only with software keyboard inline. + // Since we doesn't support applets for now, it's fine to stub it. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(100)] + // PushInData(object<nn::am::service::IStorage>) + public ResultCode PushInData(ServiceCtx context) + { + IStorage data = GetObject<IStorage>(context, 0); + + _normalSession.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(101)] + // PopOutData() -> object<nn::am::service::IStorage> + public ResultCode PopOutData(ServiceCtx context) + { + if (_normalSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _normalOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(103)] + // PushInteractiveInData(object<nn::am::service::IStorage>) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject<IStorage>(context, 0); + + _interactiveSession.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(104)] + // PopInteractiveOutData() -> object<nn::am::service::IStorage> + public ResultCode PopInteractiveOutData(ServiceCtx context) + { + if (_interactiveSession.TryPop(out byte[] data)) + { + MakeObject(context, new IStorage(data)); + + _interactiveOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + return ResultCode.NotAvailable; + } + + [CommandCmif(105)] + // GetPopOutDataEvent() -> handle<copy> + public ResultCode GetPopOutDataEvent(ServiceCtx context) + { + if (_normalOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out _normalOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_normalOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(106)] + // GetPopInteractiveOutDataEvent() -> handle<copy> + public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context) + { + if (_interactiveOutDataEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out _interactiveOutDataEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_interactiveOutDataEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(110)] + // NeedsToExitProcess() + public ResultCode NeedsToExitProcess(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(150)] + // RequestForAppletToGetForeground() + public ResultCode RequestForAppletToGetForeground(ServiceCtx context) + { + return ResultCode.Stubbed; + } + + [CommandCmif(160)] // 2.0.0+ + // GetIndirectLayerConsumerHandle() -> u64 indirect_layer_consumer_handle + public ResultCode GetIndirectLayerConsumerHandle(ServiceCtx context) + { + Horizon horizon = _kernelContext.Device.System; + + _indirectLayerHandle = horizon.AppletState.IndirectLayerHandles.Add(_applet); + + context.ResponseData.Write((ulong)_indirectLayerHandle); + + return ResultCode.Success; + } + + protected override void Dispose(bool isDisposing) + { + if (isDisposing) + { + if (_stateChangedEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_stateChangedEventHandle); + } + + if (_normalOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_normalOutDataEventHandle); + } + + if (_interactiveOutDataEventHandle != 0) + { + _kernelContext.Syscall.CloseHandle(_interactiveOutDataEventHandle); + } + } + + Horizon horizon = _kernelContext.Device.System; + + horizon.AppletState.IndirectLayerHandles.Delete(_indirectLayerHandle); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs new file mode 100644 index 00000000..69967c56 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/AppletStandalone.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class AppletStandalone + { + public AppletId AppletId; + public LibraryAppletMode LibraryAppletMode; + public Queue<byte[]> InputData; + + public AppletStandalone() + { + InputData = new Queue<byte[]>(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs new file mode 100644 index 00000000..176bd632 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/ILibraryAppletSelfAccessor.cs @@ -0,0 +1,78 @@ +using Ryujinx.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class ILibraryAppletSelfAccessor : IpcService + { + private AppletStandalone _appletStandalone = new AppletStandalone(); + + public ILibraryAppletSelfAccessor(ServiceCtx context) + { + if (context.Device.Processes.ActiveApplication.ProgramId == 0x0100000000001009) + { + // Create MiiEdit data. + _appletStandalone = new AppletStandalone() + { + AppletId = AppletId.MiiEdit, + LibraryAppletMode = LibraryAppletMode.AllForeground + }; + + byte[] miiEditInputData = new byte[0x100]; + miiEditInputData[0] = 0x03; // Hardcoded unknown value. + + _appletStandalone.InputData.Enqueue(miiEditInputData); + } + else + { + throw new NotImplementedException($"{context.Device.Processes.ActiveApplication.ProgramId} applet is not implemented."); + } + } + + [CommandCmif(0)] + // PopInData() -> object<nn::am::service::IStorage> + public ResultCode PopInData(ServiceCtx context) + { + byte[] appletData = _appletStandalone.InputData.Dequeue(); + + if (appletData.Length == 0) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new IStorage(appletData)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletInfo() -> nn::am::service::LibraryAppletInfo + public ResultCode GetLibraryAppletInfo(ServiceCtx context) + { + LibraryAppletInfo libraryAppletInfo = new LibraryAppletInfo() + { + AppletId = _appletStandalone.AppletId, + LibraryAppletMode = _appletStandalone.LibraryAppletMode + }; + + context.ResponseData.WriteStruct(libraryAppletInfo); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // GetCallerAppletIdentityInfo() -> nn::am::service::AppletIdentityInfo + public ResultCode GetCallerAppletIdentityInfo(ServiceCtx context) + { + AppletIdentifyInfo appletIdentifyInfo = new AppletIdentifyInfo() + { + AppletId = AppletId.QLaunch, + TitleId = 0x0100000000001000 + }; + + context.ResponseData.WriteStruct(appletIdentifyInfo); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs new file mode 100644 index 00000000..6acd18cd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletProxy/IProcessWindingController.cs @@ -0,0 +1,24 @@ +using Ryujinx.Common; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletProxy +{ + class IProcessWindingController : IpcService + { + public IProcessWindingController() { } + + [CommandCmif(0)] + // GetLaunchReason() -> nn::am::service::AppletProcessLaunchReason + public ResultCode GetLaunchReason(ServiceCtx context) + { + // NOTE: Flag is set by using an internal field. + AppletProcessLaunchReason appletProcessLaunchReason = new AppletProcessLaunchReason() + { + Flag = 0 + }; + + context.ResponseData.WriteStruct(appletProcessLaunchReason); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs new file mode 100644 index 00000000..c42202b8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAppletCommonFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAppletCommonFunctions : IpcService + { + public IAppletCommonFunctions() { } + } +}
\ No newline at end of file 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 new file mode 100644 index 00000000..79e5b050 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IApplicationCreator.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IApplicationCreator : IpcService + { + public IApplicationCreator() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs new file mode 100644 index 00000000..48dd42e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IAudioController.cs @@ -0,0 +1,66 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IAudioController : IpcService + { + public IAudioController() { } + + [CommandCmif(0)] + // SetExpectedMasterVolume(f32, f32) + public ResultCode SetExpectedMasterVolume(ServiceCtx context) + { + float appletVolume = context.RequestData.ReadSingle(); + float libraryAppletVolume = context.RequestData.ReadSingle(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetMainAppletExpectedMasterVolume() -> f32 + public ResultCode GetMainAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetLibraryAppletExpectedMasterVolume() -> f32 + public ResultCode GetLibraryAppletExpectedMasterVolume(ServiceCtx context) + { + context.ResponseData.Write(1f); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // ChangeMainAppletMasterVolume(f32, u64) + public ResultCode ChangeMainAppletMasterVolume(ServiceCtx context) + { + float unknown0 = context.RequestData.ReadSingle(); + long unknown1 = context.RequestData.ReadInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // SetTransparentVolumeRate(f32) + public ResultCode SetTransparentVolumeRate(ServiceCtx context) + { + float unknown0 = context.RequestData.ReadSingle(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +}
\ No newline at end of file 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 new file mode 100644 index 00000000..381267b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ICommonStateGetter.cs @@ -0,0 +1,285 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Settings.Types; +using Ryujinx.HLE.HOS.Services.Vi.RootService.ApplicationDisplayService; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ICommonStateGetter : IpcService + { + private Apm.ManagerServer _apmManagerServer; + private Apm.SystemManagerServer _apmSystemManagerServer; + private Lbl.LblControllerServer _lblControllerServer; + + private bool _vrModeEnabled; +#pragma warning disable CS0414 + private bool _lcdBacklighOffEnabled; + private bool _requestExitToLibraryAppletAtExecuteNextProgramEnabled; +#pragma warning restore CS0414 + private int _messageEventHandle; + private int _displayResolutionChangedEventHandle; + + public ICommonStateGetter(ServiceCtx context) + { + _apmManagerServer = new Apm.ManagerServer(context); + _apmSystemManagerServer = new Apm.SystemManagerServer(context); + _lblControllerServer = new Lbl.LblControllerServer(context); + } + + [CommandCmif(0)] + // GetEventHandle() -> handle<copy> + public ResultCode GetEventHandle(ServiceCtx context) + { + KEvent messageEvent = context.Device.System.AppletState.MessageEvent; + + if (_messageEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(messageEvent.ReadableEvent, out _messageEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_messageEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // ReceiveMessage() -> nn::am::AppletMessage + public ResultCode ReceiveMessage(ServiceCtx context) + { + if (!context.Device.System.AppletState.Messages.TryDequeue(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; + } + + [CommandCmif(5)] + // GetOperationMode() -> u8 + public ResultCode GetOperationMode(ServiceCtx context) + { + OperationMode mode = context.Device.System.State.DockedMode + ? OperationMode.Docked + : OperationMode.Handheld; + + context.ResponseData.Write((byte)mode); + + return ResultCode.Success; + } + + [CommandCmif(6)] + // GetPerformanceMode() -> nn::apm::PerformanceMode + public ResultCode GetPerformanceMode(ServiceCtx context) + { + return (ResultCode)_apmManagerServer.GetPerformanceMode(context); + } + + [CommandCmif(8)] + // GetBootMode() -> u8 + public ResultCode GetBootMode(ServiceCtx context) + { + context.ResponseData.Write((byte)0); //Unknown value. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(9)] + // GetCurrentFocusState() -> u8 + public ResultCode GetCurrentFocusState(ServiceCtx context) + { + context.ResponseData.Write((byte)context.Device.System.AppletState.FocusState); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 3.0.0+ + // IsVrModeEnabled() -> b8 + public ResultCode IsVrModeEnabled(ServiceCtx context) + { + context.ResponseData.Write(_vrModeEnabled); + + return ResultCode.Success; + } + + [CommandCmif(51)] // 3.0.0+ + // SetVrModeEnabled(b8) + public ResultCode SetVrModeEnabled(ServiceCtx context) + { + bool vrModeEnabled = context.RequestData.ReadBoolean(); + + UpdateVrMode(vrModeEnabled); + + return ResultCode.Success; + } + + [CommandCmif(52)] // 4.0.0+ + // SetLcdBacklighOffEnabled(b8) + public ResultCode SetLcdBacklighOffEnabled(ServiceCtx context) + { + // NOTE: Service sets a private field here, maybe this field is used somewhere else to turned off the backlight. + // Since we don't support backlight, it's fine to do nothing. + + _lcdBacklighOffEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(53)] // 7.0.0+ + // BeginVrModeEx() + public ResultCode BeginVrModeEx(ServiceCtx context) + { + UpdateVrMode(true); + + return ResultCode.Success; + } + + [CommandCmif(54)] // 7.0.0+ + // EndVrModeEx() + public ResultCode EndVrModeEx(ServiceCtx context) + { + UpdateVrMode(false); + + return ResultCode.Success; + } + + private void UpdateVrMode(bool vrModeEnabled) + { + if (_vrModeEnabled == vrModeEnabled) + { + return; + } + + _vrModeEnabled = vrModeEnabled; + + if (vrModeEnabled) + { + _lblControllerServer.EnableVrMode(); + } + else + { + _lblControllerServer.DisableVrMode(); + } + + // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. + } + + [CommandCmif(60)] // 3.0.0+ + // GetDefaultDisplayResolution() -> (u32, u32) + public ResultCode GetDefaultDisplayResolution(ServiceCtx context) + { + // NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolution of omm service. + // IOperationModeManager::GetDefaultDisplayResolution of omm service call IManagerDisplayService::GetDisplayResolution of vi service. + (ulong width, ulong height) = AndroidSurfaceComposerClient.GetDisplayInfo(context); + + context.ResponseData.Write((uint)width); + context.ResponseData.Write((uint)height); + + return ResultCode.Success; + } + + [CommandCmif(61)] // 3.0.0+ + // GetDefaultDisplayResolutionChangeEvent() -> handle<copy> + public ResultCode GetDefaultDisplayResolutionChangeEvent(ServiceCtx context) + { + // NOTE: Original service calls IOperationModeManager::GetDefaultDisplayResolutionChangeEvent of omm service. + if (_displayResolutionChangedEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(context.Device.System.DisplayResolutionChangeEvent.ReadableEvent, out _displayResolutionChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_displayResolutionChangedEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(62)] // 4.0.0+ + // GetHdcpAuthenticationState() -> s32 state + public ResultCode GetHdcpAuthenticationState(ServiceCtx context) + { + context.ResponseData.Write(0); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(66)] // 6.0.0+ + // SetCpuBoostMode(u32 cpu_boost_mode) + public ResultCode SetCpuBoostMode(ServiceCtx context) + { + uint cpuBoostMode = context.RequestData.ReadUInt32(); + + if (cpuBoostMode > 1) + { + return ResultCode.InvalidParameters; + } + + _apmSystemManagerServer.SetCpuBoostMode((Apm.CpuBoostMode)cpuBoostMode); + + // TODO: It signals an internal event of ICommonStateGetter. We have to determine where this event is used. + + return ResultCode.Success; + } + + [CommandCmif(91)] // 7.0.0+ + // GetCurrentPerformanceConfiguration() -> nn::apm::PerformanceConfiguration + public ResultCode GetCurrentPerformanceConfiguration(ServiceCtx context) + { + return (ResultCode)_apmSystemManagerServer.GetCurrentPerformanceConfiguration(context); + } + + [CommandCmif(300)] // 9.0.0+ + // GetSettingsPlatformRegion() -> u8 + public ResultCode GetSettingsPlatformRegion(ServiceCtx context) + { + PlatformRegion platformRegion = context.Device.System.State.DesiredRegionCode == (uint)RegionCode.China ? PlatformRegion.China : PlatformRegion.Global; + + // FIXME: Call set:sys GetPlatformRegion + context.ResponseData.Write((byte)platformRegion); + + return ResultCode.Success; + } + + [CommandCmif(900)] // 11.0.0+ + // SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled() + public ResultCode SetRequestExitToLibraryAppletAtExecuteNextProgramEnabled(ServiceCtx context) + { + // TODO : Find where the field is used. + _requestExitToLibraryAppletAtExecuteNextProgramEnabled = true; + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs new file mode 100644 index 00000000..51a112fd --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDebugFunctions.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDebugFunctions : IpcService + { + public IDebugFunctions() { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs new file mode 100644 index 00000000..92c97d86 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IDisplayController.cs @@ -0,0 +1,106 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IDisplayController : IpcService + { + private KTransferMemory _transferMem; + private bool _lastApplicationCaptureBufferAcquired; + private bool _callerAppletCaptureBufferAcquired; + + public IDisplayController(ServiceCtx context) + { + _transferMem = context.Device.System.AppletCaptureBufferTransfer; + } + + [CommandCmif(8)] // 2.0.0+ + // TakeScreenShotOfOwnLayer(b8, s32) + public ResultCode TakeScreenShotOfOwnLayer(ServiceCtx context) + { + bool unknown1 = context.RequestData.ReadBoolean(); + int unknown2 = context.RequestData.ReadInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknown1, unknown2 }); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // ReleaseLastApplicationCaptureBuffer() + public ResultCode ReleaseLastApplicationCaptureBuffer(ServiceCtx context) + { + if (!_lastApplicationCaptureBufferAcquired) + { + return ResultCode.BufferNotAcquired; + } + + _lastApplicationCaptureBufferAcquired = false; + + return ResultCode.Success; + } + + [CommandCmif(15)] + // ReleaseCallerAppletCaptureBuffer() + public ResultCode ReleaseCallerAppletCaptureBuffer(ServiceCtx context) + { + if (!_callerAppletCaptureBufferAcquired) + { + return ResultCode.BufferNotAcquired; + } + + _callerAppletCaptureBufferAcquired = false; + + return ResultCode.Success; + } + + [CommandCmif(16)] + // AcquireLastApplicationCaptureBufferEx() -> (b8, handle<copy>) + public ResultCode AcquireLastApplicationCaptureBufferEx(ServiceCtx context) + { + if (_lastApplicationCaptureBufferAcquired) + { + return ResultCode.BufferAlreadyAcquired; + } + + if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + _lastApplicationCaptureBufferAcquired = true; + + context.ResponseData.Write(_lastApplicationCaptureBufferAcquired); + + return ResultCode.Success; + } + + [CommandCmif(18)] + // AcquireCallerAppletCaptureBufferEx() -> (b8, handle<copy>) + public ResultCode AcquireCallerAppletCaptureBufferEx(ServiceCtx context) + { + if (_callerAppletCaptureBufferAcquired) + { + return ResultCode.BufferAlreadyAcquired; + } + + if (context.Process.HandleTable.GenerateHandle(_transferMem, out int handle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + _callerAppletCaptureBufferAcquired = true; + + context.ResponseData.Write(_callerAppletCaptureBufferAcquired); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs new file mode 100644 index 00000000..24eeefb9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IGlobalStateController.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IGlobalStateController : IpcService + { + public IGlobalStateController() { } + } +}
\ No newline at end of file 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 new file mode 100644 index 00000000..c7c073ff --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IHomeMenuFunctions.cs @@ -0,0 +1,48 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IHomeMenuFunctions : IpcService + { + private KEvent _channelEvent; + private int _channelEventHandle; + + public IHomeMenuFunctions(Horizon system) + { + // TODO: Signal this Event somewhere in future. + _channelEvent = new KEvent(system.KernelContext); + } + + [CommandCmif(10)] + // RequestToGetForeground() + public ResultCode RequestToGetForeground(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(21)] + // GetPopFromGeneralChannelEvent() -> handle<copy> + public ResultCode GetPopFromGeneralChannelEvent(ServiceCtx context) + { + if (_channelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_channelEvent.ReadableEvent, out _channelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_channelEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs new file mode 100644 index 00000000..fb870c24 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs @@ -0,0 +1,91 @@ +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.LibraryAppletCreator; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ILibraryAppletCreator : IpcService + { + public ILibraryAppletCreator() { } + + [CommandCmif(0)] + // CreateLibraryApplet(u32, u32) -> object<nn::am::service::ILibraryAppletAccessor> + public ResultCode CreateLibraryApplet(ServiceCtx context) + { + AppletId appletId = (AppletId)context.RequestData.ReadInt32(); + int libraryAppletMode = context.RequestData.ReadInt32(); + + MakeObject(context, new ILibraryAppletAccessor(appletId, context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // CreateStorage(u64) -> object<nn::am::service::IStorage> + public ResultCode CreateStorage(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + MakeObject(context, new IStorage(new byte[size])); + + // NOTE: Returns ResultCode.MemoryAllocationFailed if IStorage is null, it doesn't occur in our case. + + return ResultCode.Success; + } + + [CommandCmif(11)] + // CreateTransferMemoryStorage(b8, u64, handle<copy>) -> object<nn::am::service::IStorage> + public ResultCode CreateTransferMemoryStorage(ServiceCtx context) + { + bool isReadOnly = (context.RequestData.ReadInt64() & 1) == 0; + long size = context.RequestData.ReadInt64(); + int handle = context.Request.HandleDesc.ToCopy[0]; + + KTransferMemory transferMem = context.Process.HandleTable.GetObject<KTransferMemory>(handle); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + byte[] data = new byte[transferMem.Size]; + + transferMem.Creator.CpuMemory.Read(transferMem.Address, data); + + context.Device.System.KernelContext.Syscall.CloseHandle(handle); + + MakeObject(context, new IStorage(data, isReadOnly)); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 2.0.0+ + // CreateHandleStorage(u64, handle<copy>) -> object<nn::am::service::IStorage> + public ResultCode CreateHandleStorage(ServiceCtx context) + { + long size = context.RequestData.ReadInt64(); + int handle = context.Request.HandleDesc.ToCopy[0]; + + KTransferMemory transferMem = context.Process.HandleTable.GetObject<KTransferMemory>(handle); + + if (size <= 0) + { + return ResultCode.ObjectInvalid; + } + + byte[] data = new byte[transferMem.Size]; + + transferMem.Creator.CpuMemory.Read(transferMem.Address, data); + + context.Device.System.KernelContext.Syscall.CloseHandle(handle); + + MakeObject(context, new IStorage(data)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs new file mode 100644 index 00000000..399e778a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -0,0 +1,432 @@ +using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types; +using Ryujinx.Horizon.Common; +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class ISelfController : IpcService + { + private readonly ulong _pid; + + private KEvent _libraryAppletLaunchableEvent; + private int _libraryAppletLaunchableEventHandle; + + private KEvent _accumulatedSuspendedTickChangedEvent; + private int _accumulatedSuspendedTickChangedEventHandle; + + private object _fatalSectionLock = new object(); + private int _fatalSectionCount; + + // TODO: Set this when the game goes in suspension (go back to home menu ect), we currently don't support that so we can keep it set to 0. + private ulong _accumulatedSuspendedTickValue = 0; + + // TODO: Determine where those fields are used. + private bool _screenShotPermission = false; + private bool _operationModeChangedNotification = false; + private bool _performanceModeChangedNotification = false; + private bool _restartMessageEnabled = false; + private bool _outOfFocusSuspendingEnabled = false; + private bool _handlesRequestToDisplay = false; + private bool _autoSleepDisabled = false; + private bool _albumImageTakenNotificationEnabled = false; + private bool _recordVolumeMuted = false; + + private uint _screenShotImageOrientation = 0; + private uint _idleTimeDetectionExtension = 0; + + public ISelfController(ServiceCtx context, ulong pid) + { + _libraryAppletLaunchableEvent = new KEvent(context.Device.System.KernelContext); + _pid = pid; + } + + [CommandCmif(0)] + // Exit() + public ResultCode Exit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // LockExit() + public ResultCode LockExit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // UnlockExit() + public ResultCode UnlockExit(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(3)] // 2.0.0+ + // EnterFatalSection() + public ResultCode EnterFatalSection(ServiceCtx context) + { + lock (_fatalSectionLock) + { + _fatalSectionCount++; + } + + return ResultCode.Success; + } + + [CommandCmif(4)] // 2.0.0+ + // LeaveFatalSection() + public ResultCode LeaveFatalSection(ServiceCtx context) + { + ResultCode result = ResultCode.Success; + + lock (_fatalSectionLock) + { + if (_fatalSectionCount != 0) + { + _fatalSectionCount--; + } + else + { + result = ResultCode.UnbalancedFatalSection; + } + } + + return result; + } + + [CommandCmif(9)] + // GetLibraryAppletLaunchableEvent() -> handle<copy> + public ResultCode GetLibraryAppletLaunchableEvent(ServiceCtx context) + { + _libraryAppletLaunchableEvent.ReadableEvent.Signal(); + + if (_libraryAppletLaunchableEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_libraryAppletLaunchableEvent.ReadableEvent, out _libraryAppletLaunchableEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_libraryAppletLaunchableEventHandle); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // SetScreenShotPermission(u32) + public ResultCode SetScreenShotPermission(ServiceCtx context) + { + bool screenShotPermission = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotPermission }); + + _screenShotPermission = screenShotPermission; + + return ResultCode.Success; + } + + [CommandCmif(11)] + // SetOperationModeChangedNotification(b8) + public ResultCode SetOperationModeChangedNotification(ServiceCtx context) + { + bool operationModeChangedNotification = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { operationModeChangedNotification }); + + _operationModeChangedNotification = operationModeChangedNotification; + + return ResultCode.Success; + } + + [CommandCmif(12)] + // SetPerformanceModeChangedNotification(b8) + public ResultCode SetPerformanceModeChangedNotification(ServiceCtx context) + { + bool performanceModeChangedNotification = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { performanceModeChangedNotification }); + + _performanceModeChangedNotification = performanceModeChangedNotification; + + return ResultCode.Success; + } + + [CommandCmif(13)] + // SetFocusHandlingMode(b8, b8, b8) + public ResultCode SetFocusHandlingMode(ServiceCtx context) + { + bool unknownFlag1 = context.RequestData.ReadBoolean(); + bool unknownFlag2 = context.RequestData.ReadBoolean(); + bool unknownFlag3 = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { unknownFlag1, unknownFlag2, unknownFlag3 }); + + return ResultCode.Success; + } + + [CommandCmif(14)] + // SetRestartMessageEnabled(b8) + public ResultCode SetRestartMessageEnabled(ServiceCtx context) + { + bool restartMessageEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { restartMessageEnabled }); + + _restartMessageEnabled = restartMessageEnabled; + + return ResultCode.Success; + } + + [CommandCmif(16)] // 2.0.0+ + // SetOutOfFocusSuspendingEnabled(b8) + public ResultCode SetOutOfFocusSuspendingEnabled(ServiceCtx context) + { + bool outOfFocusSuspendingEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { outOfFocusSuspendingEnabled }); + + _outOfFocusSuspendingEnabled = outOfFocusSuspendingEnabled; + + return ResultCode.Success; + } + + [CommandCmif(19)] // 3.0.0+ + // SetScreenShotImageOrientation(u32) + public ResultCode SetScreenShotImageOrientation(ServiceCtx context) + { + uint screenShotImageOrientation = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { screenShotImageOrientation }); + + _screenShotImageOrientation = screenShotImageOrientation; + + return ResultCode.Success; + } + + [CommandCmif(40)] + // CreateManagedDisplayLayer() -> u64 + public ResultCode CreateManagedDisplayLayer(ServiceCtx context) + { + context.Device.System.SurfaceFlinger.CreateLayer(out long layerId, _pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(layerId); + + context.ResponseData.Write(layerId); + + return ResultCode.Success; + } + + [CommandCmif(41)] // 4.0.0+ + // IsSystemBufferSharingEnabled() + public ResultCode IsSystemBufferSharingEnabled(ServiceCtx context) + { + // NOTE: Service checks a private field and return an error if the SystemBufferSharing is disabled. + + return ResultCode.NotImplemented; + } + + [CommandCmif(44)] // 10.0.0+ + // CreateManagedDisplaySeparableLayer() -> (u64, u64) + public ResultCode CreateManagedDisplaySeparableLayer(ServiceCtx context) + { + context.Device.System.SurfaceFlinger.CreateLayer(out long displayLayerId, _pid); + context.Device.System.SurfaceFlinger.CreateLayer(out long recordingLayerId, _pid); + context.Device.System.SurfaceFlinger.SetRenderLayer(displayLayerId); + + context.ResponseData.Write(displayLayerId); + context.ResponseData.Write(recordingLayerId); + + return ResultCode.Success; + } + + [CommandCmif(50)] + // SetHandlesRequestToDisplay(b8) + public ResultCode SetHandlesRequestToDisplay(ServiceCtx context) + { + bool handlesRequestToDisplay = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { handlesRequestToDisplay }); + + _handlesRequestToDisplay = handlesRequestToDisplay; + + return ResultCode.Success; + } + + [CommandCmif(62)] + // SetIdleTimeDetectionExtension(u32) + public ResultCode SetIdleTimeDetectionExtension(ServiceCtx context) + { + uint idleTimeDetectionExtension = context.RequestData.ReadUInt32(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { idleTimeDetectionExtension }); + + _idleTimeDetectionExtension = idleTimeDetectionExtension; + + return ResultCode.Success; + } + + [CommandCmif(63)] + // GetIdleTimeDetectionExtension() -> u32 + public ResultCode GetIdleTimeDetectionExtension(ServiceCtx context) + { + context.ResponseData.Write(_idleTimeDetectionExtension); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _idleTimeDetectionExtension }); + + return ResultCode.Success; + } + + [CommandCmif(65)] + // ReportUserIsActive() + public ResultCode ReportUserIsActive(ServiceCtx context) + { + // TODO: Call idle:sys ReportUserIsActive when implemented. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(67)] //3.0.0+ + // IsIlluminanceAvailable() -> bool + public ResultCode IsIlluminanceAvailable(ServiceCtx context) + { + // NOTE: This should call IsAmbientLightSensorAvailable through to Lbl, but there's no situation where we'd want false. + context.ResponseData.Write(true); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(68)] + // SetAutoSleepDisabled(u8) + public ResultCode SetAutoSleepDisabled(ServiceCtx context) + { + bool autoSleepDisabled = context.RequestData.ReadBoolean(); + + _autoSleepDisabled = autoSleepDisabled; + + return ResultCode.Success; + } + + [CommandCmif(69)] + // IsAutoSleepDisabled() -> u8 + public ResultCode IsAutoSleepDisabled(ServiceCtx context) + { + context.ResponseData.Write(_autoSleepDisabled); + + return ResultCode.Success; + } + + [CommandCmif(71)] //5.0.0+ + // GetCurrentIlluminanceEx() -> (bool, f32) + public ResultCode GetCurrentIlluminanceEx(ServiceCtx context) + { + // TODO: The light value should be configurable - presumably users using software that takes advantage will want control. + context.ResponseData.Write(1); // OverLimit + context.ResponseData.Write(10000f); // Lux - 10K lux is ambient light. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(80)] // 4.0.0+ + // SetWirelessPriorityMode(s32 wireless_priority_mode) + public ResultCode SetWirelessPriorityMode(ServiceCtx context) + { + WirelessPriorityMode wirelessPriorityMode = (WirelessPriorityMode)context.RequestData.ReadInt32(); + + if (wirelessPriorityMode > WirelessPriorityMode.Unknown2) + { + return ResultCode.InvalidParameters; + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { wirelessPriorityMode }); + + return ResultCode.Success; + } + + [CommandCmif(90)] // 6.0.0+ + // GetAccumulatedSuspendedTickValue() -> u64 + public ResultCode GetAccumulatedSuspendedTickValue(ServiceCtx context) + { + context.ResponseData.Write(_accumulatedSuspendedTickValue); + + return ResultCode.Success; + } + + [CommandCmif(91)] // 6.0.0+ + // GetAccumulatedSuspendedTickChangedEvent() -> handle<copy> + public ResultCode GetAccumulatedSuspendedTickChangedEvent(ServiceCtx context) + { + if (_accumulatedSuspendedTickChangedEventHandle == 0) + { + _accumulatedSuspendedTickChangedEvent = new KEvent(context.Device.System.KernelContext); + + _accumulatedSuspendedTickChangedEvent.ReadableEvent.Signal(); + + if (context.Process.HandleTable.GenerateHandle(_accumulatedSuspendedTickChangedEvent.ReadableEvent, out _accumulatedSuspendedTickChangedEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_accumulatedSuspendedTickChangedEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 7.0.0+ + // SetAlbumImageTakenNotificationEnabled(u8) + public ResultCode SetAlbumImageTakenNotificationEnabled(ServiceCtx context) + { + bool albumImageTakenNotificationEnabled = context.RequestData.ReadBoolean(); + + _albumImageTakenNotificationEnabled = albumImageTakenNotificationEnabled; + + return ResultCode.Success; + } + + [CommandCmif(120)] // 11.0.0+ + // SaveCurrentScreenshot(s32 album_report_option) + public ResultCode SaveCurrentScreenshot(ServiceCtx context) + { + AlbumReportOption albumReportOption = (AlbumReportOption)context.RequestData.ReadInt32(); + + if (albumReportOption > AlbumReportOption.Unknown3) + { + return ResultCode.InvalidParameters; + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption }); + + return ResultCode.Success; + } + + [CommandCmif(130)] // 13.0.0+ + // SetRecordVolumeMuted(b8) + public ResultCode SetRecordVolumeMuted(ServiceCtx context) + { + bool recordVolumeMuted = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { recordVolumeMuted }); + + _recordVolumeMuted = recordVolumeMuted; + + return ResultCode.Success; + } + } +}
\ No newline at end of file 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 new file mode 100644 index 00000000..730df5d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/IWindowController.cs @@ -0,0 +1,36 @@ +using Ryujinx.Common.Logging; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + class IWindowController : IpcService + { + private readonly ulong _pid; + + public IWindowController(ulong pid) + { + _pid = pid; + } + + [CommandCmif(1)] + // GetAppletResourceUserId() -> nn::applet::AppletResourceUserId + public ResultCode GetAppletResourceUserId(ServiceCtx context) + { + long appletResourceUserId = context.Device.System.AppletState.AppletResourceUserIds.Add(_pid); + + context.ResponseData.Write(appletResourceUserId); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { appletResourceUserId }); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // AcquireForegroundRights() + public ResultCode AcquireForegroundRights(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs new file mode 100644 index 00000000..84fc5c83 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AlbumReportOption.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum AlbumReportOption + { + OverlayNotDisplayed, + OverlayDisplayed, + Unknown2, + Unknown3 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs new file mode 100644 index 00000000..2920c329 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/AppletMessage.cs @@ -0,0 +1,36 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum AppletMessage + { + None = 0, + ChangeIntoForeground = 1, + ChangeIntoBackground = 2, + Exit = 4, + ApplicationExited = 6, + FocusStateChanged = 15, + Resume = 16, + DetectShortPressingHomeButton = 20, + DetectLongPressingHomeButton = 21, + DetectShortPressingPowerButton = 22, + DetectMiddlePressingPowerButton = 23, + DetectLongPressingPowerButton = 24, + RequestToPrepareSleep = 25, + FinishedSleepSequence = 26, + SleepRequiredByHighTemperature = 27, + SleepRequiredByLowBattery = 28, + AutoPowerDown = 29, + OperationModeChanged = 30, + PerformanceModeChanged = 31, + DetectReceivingCecSystemStandby = 32, + SdCardRemoved = 33, + LaunchApplicationRequested = 50, + RequestToDisplay = 51, + ShowApplicationLogo = 55, + HideApplicationLogo = 56, + ForceHideApplicationLogo = 57, + FloatingApplicationDetected = 60, + DetectShortPressingCaptureButton = 90, + AlbumScreenShotTaken = 92, + AlbumRecordingSaved = 93 + } +}
\ No newline at end of file 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 new file mode 100644 index 00000000..dfd7d7f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/FocusState.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum FocusState + { + InFocus = 1, + OutOfFocus = 2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs new file mode 100644 index 00000000..a82ed476 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/OperationMode.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy +{ + enum OperationMode + { + Handheld = 0, + Docked = 1 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs new file mode 100644 index 00000000..e8ba9b61 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/Types/WirelessPriorityMode.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy.Types +{ + enum WirelessPriorityMode + { + Default, + OptimizedForWlan, + Unknown2 + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs new file mode 100644 index 00000000..fb16c86e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletFifo<T> : IAppletFifo<T> + { + private ConcurrentQueue<T> _dataQueue; + + public event EventHandler DataAvailable; + + public bool IsSynchronized + { + get { return ((ICollection)_dataQueue).IsSynchronized; } + } + + public object SyncRoot + { + get { return ((ICollection)_dataQueue).SyncRoot; } + } + + public int Count + { + get { return _dataQueue.Count; } + } + + public AppletFifo() + { + _dataQueue = new ConcurrentQueue<T>(); + } + + public void Push(T item) + { + _dataQueue.Enqueue(item); + + DataAvailable?.Invoke(this, null); + } + + public bool TryAdd(T item) + { + try + { + this.Push(item); + + return true; + } + catch + { + return false; + } + } + + public T Pop() + { + if (_dataQueue.TryDequeue(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPop(out T result) + { + return _dataQueue.TryDequeue(out result); + } + + public bool TryTake(out T item) + { + return this.TryPop(out item); + } + + public T Peek() + { + if (_dataQueue.TryPeek(out T result)) + { + return result; + } + + throw new InvalidOperationException("FIFO empty."); + } + + public bool TryPeek(out T result) + { + return _dataQueue.TryPeek(out result); + } + + public void Clear() + { + _dataQueue.Clear(); + } + + public T[] ToArray() + { + return _dataQueue.ToArray(); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _dataQueue.CopyTo(array, arrayIndex); + } + + public void CopyTo(Array array, int index) + { + this.CopyTo((T[])array, index); + } + + public IEnumerator<T> GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs new file mode 100644 index 00000000..6c9197b3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs @@ -0,0 +1,77 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + internal class AppletSession + { + private IAppletFifo<byte[]> _inputData; + private IAppletFifo<byte[]> _outputData; + + public event EventHandler DataAvailable; + + public int Length + { + get { return _inputData.Count; } + } + + public AppletSession() + : this(new AppletFifo<byte[]>(), + new AppletFifo<byte[]>()) + { } + + public AppletSession( + IAppletFifo<byte[]> inputData, + IAppletFifo<byte[]> outputData) + { + _inputData = inputData; + _outputData = outputData; + + _inputData.DataAvailable += OnDataAvailable; + } + + private void OnDataAvailable(object sender, EventArgs e) + { + DataAvailable?.Invoke(this, null); + } + + public void Push(byte[] item) + { + if (!this.TryPush(item)) + { + // TODO(jduncanator): Throw a proper exception + throw new InvalidOperationException(); + } + } + + public bool TryPush(byte[] item) + { + return _outputData.TryAdd(item); + } + + public byte[] Pop() + { + if (this.TryPop(out byte[] item)) + { + return item; + } + + throw new InvalidOperationException("Input data empty."); + } + + public bool TryPop(out byte[] item) + { + return _inputData.TryTake(out item); + } + + /// <summary> + /// This returns an AppletSession that can be used at the + /// other end of the pipe. Pushing data into this new session + /// will put it in the first session's input buffer, and vice + /// versa. + /// </summary> + public AppletSession GetConsumer() + { + return new AppletSession(this._outputData, this._inputData); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs new file mode 100644 index 00000000..728a1018 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAllSystemAppletProxiesService.cs @@ -0,0 +1,29 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Service("appletAE")] + class IAllSystemAppletProxiesService : IpcService + { + public IAllSystemAppletProxiesService(ServiceCtx context) { } + + [CommandCmif(100)] + // OpenSystemAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ISystemAppletProxy> + public ResultCode OpenSystemAppletProxy(ServiceCtx context) + { + MakeObject(context, new ISystemAppletProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + + [CommandCmif(200)] + [CommandCmif(201)] // 3.0.0+ + // OpenLibraryAppletProxy(u64, pid, handle<copy>) -> object<nn::am::service::ILibraryAppletProxy> + public ResultCode OpenLibraryAppletProxy(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs new file mode 100644 index 00000000..ca79bac7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + interface IAppletFifo<T> : IProducerConsumerCollection<T> + { + event EventHandler DataAvailable; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs new file mode 100644 index 00000000..190f1a51 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorage.cs @@ -0,0 +1,23 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorage : IpcService + { + public bool IsReadOnly { get; private set; } + public byte[] Data { get; private set; } + + public IStorage(byte[] data, bool isReadOnly = false) + { + IsReadOnly = isReadOnly; + Data = data; + } + + [CommandCmif(0)] + // Open() -> object<nn::am::service::IStorageAccessor> + public ResultCode Open(ServiceCtx context) + { + MakeObject(context, new IStorageAccessor(this)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs new file mode 100644 index 00000000..4c7e264d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs @@ -0,0 +1,86 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + class IStorageAccessor : IpcService + { + private IStorage _storage; + + public IStorageAccessor(IStorage storage) + { + _storage = storage; + } + + [CommandCmif(0)] + // GetSize() -> u64 + public ResultCode GetSize(ServiceCtx context) + { + context.ResponseData.Write((long)_storage.Data.Length); + + return ResultCode.Success; + } + + [CommandCmif(10)] + // Write(u64, buffer<bytes, 0x21>) + public ResultCode Write(ServiceCtx context) + { + if (_storage.IsReadOnly) + { + return ResultCode.ObjectInvalid; + } + + ulong writePosition = context.RequestData.ReadUInt64(); + + if (writePosition > (ulong)_storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (ulong position, ulong size) = context.Request.GetBufferType0x21(); + + size = Math.Min(size, (ulong)_storage.Data.Length - writePosition); + + if (size > 0) + { + ulong maxSize = (ulong)_storage.Data.Length - writePosition; + + if (size > maxSize) + { + size = maxSize; + } + + byte[] data = new byte[size]; + + context.Memory.Read(position, data); + + Buffer.BlockCopy(data, 0, _storage.Data, (int)writePosition, (int)size); + } + + return ResultCode.Success; + } + + [CommandCmif(11)] + // Read(u64) -> buffer<bytes, 0x22> + public ResultCode Read(ServiceCtx context) + { + ulong readPosition = context.RequestData.ReadUInt64(); + + if (readPosition > (ulong)_storage.Data.Length) + { + return ResultCode.OutOfBounds; + } + + (ulong position, ulong size) = context.Request.GetBufferType0x22(); + + size = Math.Min(size, (ulong)_storage.Data.Length - readPosition); + + byte[] data = new byte[size]; + + Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size); + + context.Memory.Write(position, data); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs new file mode 100644 index 00000000..49e342f2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -0,0 +1,28 @@ +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; +using System.IO; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage +{ + class StorageHelper + { + private const uint LaunchParamsMagic = 0xc79497ca; + + public static byte[] MakeLaunchParams(UserProfile userProfile) + { + // Size needs to be at least 0x88 bytes otherwise application errors. + using (MemoryStream ms = MemoryStreamManager.Shared.GetStream()) + { + BinaryWriter writer = new BinaryWriter(ms); + + ms.SetLength(0x88); + + writer.Write(LaunchParamsMagic); + writer.Write(1); // IsAccountSelected? Only lower 8 bits actually used. + userProfile.UserId.Write(writer); + + return ms.ToArray(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs new file mode 100644 index 00000000..917f6865 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletId.cs @@ -0,0 +1,27 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + enum AppletId + { + Application = 0x01, + OverlayDisplay = 0x02, + QLaunch = 0x03, + Starter = 0x04, + Auth = 0x0A, + Cabinet = 0x0B, + Controller = 0x0C, + DataErase = 0x0D, + Error = 0x0E, + NetConnect = 0x0F, + PlayerSelect = 0x10, + SoftwareKeyboard = 0x11, + MiiEdit = 0x12, + LibAppletWeb = 0x13, + LibAppletShop = 0x14, + PhotoViewer = 0x15, + Settings = 0x16, + LibAppletOff = 0x17, + LibAppletWhitelisted = 0x18, + LibAppletAuth = 0x19, + MyPage = 0x1A + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs new file mode 100644 index 00000000..17a485ab --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletIdentityInfo.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct AppletIdentifyInfo + { + public AppletId AppletId; + public uint Padding; + public ulong TitleId; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs new file mode 100644 index 00000000..6c528337 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/AppletProcessLaunchReason.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x4)] + struct AppletProcessLaunchReason + { + public byte Flag; + public ushort Unknown1; + public byte Unknown2; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs new file mode 100644 index 00000000..fc1c11e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct LibraryAppletInfo + { + public AppletId AppletId; + public LibraryAppletMode LibraryAppletMode; + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs new file mode 100644 index 00000000..6b9a2284 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/Types/LibraryAppletMode.cs @@ -0,0 +1,14 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletAE +{ + [Flags] + enum LibraryAppletMode : uint + { + AllForeground, + PartialForeground, + NoUi, + PartialForegroundWithIndirectDisplay, + AllForegroundInitiallyHidden + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs new file mode 100644 index 00000000..5ae8f459 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/IApplicationFunctions.cs @@ -0,0 +1,675 @@ +using LibHac.Account; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Ncm; +using LibHac.Ns; +using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.HLE.Exceptions; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Memory; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; +using Ryujinx.HLE.HOS.Services.Sdb.Pdm.QueryService; +using Ryujinx.HLE.HOS.SystemState; +using Ryujinx.Horizon.Common; +using System; +using System.Numerics; +using System.Threading; +using AccountUid = Ryujinx.HLE.HOS.Services.Account.Acc.UserId; +using ApplicationId = LibHac.Ncm.ApplicationId; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy +{ + class IApplicationFunctions : IpcService + { + private long _defaultSaveDataSize = 200000000; + private long _defaultJournalSaveDataSize = 200000000; + + private KEvent _gpuErrorDetectedSystemEvent; + private KEvent _friendInvitationStorageChannelEvent; + private KEvent _notificationStorageChannelEvent; + private KEvent _healthWarningDisappearedSystemEvent; + + private int _gpuErrorDetectedSystemEventHandle; + private int _friendInvitationStorageChannelEventHandle; + private int _notificationStorageChannelEventHandle; + private int _healthWarningDisappearedSystemEventHandle; + + private bool _gamePlayRecordingState; + + private int _jitLoaded; + + private LibHac.HorizonClient _horizon; + + public IApplicationFunctions(Horizon system) + { + // TODO: Find where they are signaled. + _gpuErrorDetectedSystemEvent = new KEvent(system.KernelContext); + _friendInvitationStorageChannelEvent = new KEvent(system.KernelContext); + _notificationStorageChannelEvent = new KEvent(system.KernelContext); + _healthWarningDisappearedSystemEvent = new KEvent(system.KernelContext); + + _horizon = system.LibHacHorizonManager.AmClient; + } + + [CommandCmif(1)] + // PopLaunchParameter(LaunchParameterKind kind) -> object<nn::am::service::IStorage> + public ResultCode PopLaunchParameter(ServiceCtx context) + { + LaunchParameterKind kind = (LaunchParameterKind)context.RequestData.ReadUInt32(); + + byte[] storageData; + + switch (kind) + { + case LaunchParameterKind.UserChannel: + storageData = context.Device.Configuration.UserChannelPersistence.Pop(); + break; + case LaunchParameterKind.PreselectedUser: + // Only the first 0x18 bytes of the Data seems to be actually used. + storageData = StorageHelper.MakeLaunchParams(context.Device.System.AccountManager.LastOpenedUser); + break; + case LaunchParameterKind.Unknown: + throw new NotImplementedException("Unknown LaunchParameterKind."); + default: + return ResultCode.ObjectInvalid; + } + + if (storageData == null) + { + return ResultCode.NotAvailable; + } + + MakeObject(context, new AppletAE.IStorage(storageData)); + + return ResultCode.Success; + } + + [CommandCmif(12)] // 4.0.0+ + // CreateApplicationAndRequestToStart(u64 title_id) + public ResultCode CreateApplicationAndRequestToStart(ServiceCtx context) + { + ulong titleId = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { titleId }); + + if (titleId == 0) + { + context.Device.UiHandler.ExecuteProgram(context.Device, ProgramSpecifyKind.RestartProgram, titleId); + } + else + { + throw new NotImplementedException(); + } + + return ResultCode.Success; + } + + [CommandCmif(20)] + // EnsureSaveData(nn::account::Uid) -> u64 + public ResultCode EnsureSaveData(ServiceCtx context) + { + Uid userId = context.RequestData.ReadStruct<AccountUid>().ToLibHacUid(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + LibHac.HorizonClient hos = context.Device.System.LibHacHorizonManager.AmClient; + LibHac.Result result = hos.Fs.EnsureApplicationSaveData(out long requiredSize, applicationId, in nacp, in userId); + + context.ResponseData.Write(requiredSize); + + return (ResultCode)result.Value; + } + + [CommandCmif(21)] + // GetDesiredLanguage() -> nn::settings::LanguageCode + public ResultCode GetDesiredLanguage(ServiceCtx context) + { + // This seems to be calling ns:am GetApplicationDesiredLanguage followed by ConvertApplicationLanguageToLanguageCode + // Calls are from a IReadOnlyApplicationControlDataInterface object + // ConvertApplicationLanguageToLanguageCode compares language code strings and returns the index + // TODO: When above calls are implemented, switch to using ns:am + + long desiredLanguageCode = context.Device.System.State.DesiredLanguageCode; + int supportedLanguages = (int)context.Device.Processes.ActiveApplication.ApplicationControlProperties.SupportedLanguageFlag; + int firstSupported = BitOperations.TrailingZeroCount(supportedLanguages); + + if (firstSupported > (int)TitleLanguage.BrazilianPortuguese) + { + Logger.Warning?.Print(LogClass.ServiceAm, "Application has zero supported languages"); + + context.ResponseData.Write(desiredLanguageCode); + + return ResultCode.Success; + } + + // If desired language is not supported by application, use first supported language from TitleLanguage. + // TODO: In the future, a GUI could enable user-specified search priority + if (((1 << (int)context.Device.System.State.DesiredTitleLanguage) & supportedLanguages) == 0) + { + SystemLanguage newLanguage = Enum.Parse<SystemLanguage>(Enum.GetName(typeof(TitleLanguage), firstSupported)); + desiredLanguageCode = SystemStateMgr.GetLanguageCode((int)newLanguage); + + Logger.Info?.Print(LogClass.ServiceAm, $"Application doesn't support configured language. Using {newLanguage}"); + } + + context.ResponseData.Write(desiredLanguageCode); + + return ResultCode.Success; + } + + [CommandCmif(22)] + // SetTerminateResult(u32) + public ResultCode SetTerminateResult(ServiceCtx context) + { + LibHac.Result result = new LibHac.Result(context.RequestData.ReadUInt32()); + + Logger.Info?.Print(LogClass.ServiceAm, $"Result = 0x{result.Value:x8} ({result.ToStringWithName()})."); + + return ResultCode.Success; + } + + [CommandCmif(23)] + // GetDisplayVersion() -> nn::oe::DisplayVersion + public ResultCode GetDisplayVersion(ServiceCtx context) + { + // If an NACP isn't found, the buffer will be all '\0' which seems to be the correct implementation. + context.ResponseData.Write(context.Device.Processes.ActiveApplication.ApplicationControlProperties.DisplayVersion); + + return ResultCode.Success; + } + + [CommandCmif(25)] // 3.0.0+ + // ExtendSaveData(u8 save_data_type, nn::account::Uid, s64 save_size, s64 journal_size) -> u64 result_code + public ResultCode ExtendSaveData(ServiceCtx context) + { + SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); + Uid userId = context.RequestData.ReadStruct<Uid>(); + long saveDataSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + // NOTE: Service calls nn::fs::ExtendApplicationSaveData. + // Since LibHac currently doesn't support this method, we can stub it for now. + + _defaultSaveDataSize = saveDataSize; + _defaultJournalSaveDataSize = journalSize; + + context.ResponseData.Write((uint)ResultCode.Success); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId, saveDataSize, journalSize }); + + return ResultCode.Success; + } + + [CommandCmif(26)] // 3.0.0+ + // GetSaveDataSize(u8 save_data_type, nn::account::Uid) -> (s64 save_size, s64 journal_size) + public ResultCode GetSaveDataSize(ServiceCtx context) + { + SaveDataType saveDataType = (SaveDataType)context.RequestData.ReadUInt64(); + Uid userId = context.RequestData.ReadStruct<Uid>(); + + // NOTE: Service calls nn::fs::FindSaveDataWithFilter with SaveDataType = 1 hardcoded. + // Then it calls nn::fs::GetSaveDataAvailableSize and nn::fs::GetSaveDataJournalSize to get the sizes. + // Since LibHac currently doesn't support the 2 last methods, we can hardcode the values to 200mb. + + context.ResponseData.Write(_defaultSaveDataSize); + context.ResponseData.Write(_defaultJournalSaveDataSize); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { saveDataType, userId }); + + return ResultCode.Success; + } + + [CommandCmif(27)] // 5.0.0+ + // CreateCacheStorage(u16 index, s64 save_size, s64 journal_size) -> (u32 storageTarget, u64 requiredSize) + public ResultCode CreateCacheStorage(ServiceCtx context) + { + ushort index = (ushort)context.RequestData.ReadUInt64(); + long saveSize = context.RequestData.ReadInt64(); + long journalSize = context.RequestData.ReadInt64(); + + // Mask out the low nibble of the program ID to get the application ID + ApplicationId applicationId = new ApplicationId(context.Device.Processes.ActiveApplication.ProgramId & ~0xFul); + + ApplicationControlProperty nacp = context.Device.Processes.ActiveApplication.ApplicationControlProperties; + + LibHac.Result result = _horizon.Fs.CreateApplicationCacheStorage(out long requiredSize, + out CacheStorageTargetMedia storageTarget, applicationId, in nacp, index, saveSize, journalSize); + + if (result.IsFailure()) return (ResultCode)result.Value; + + context.ResponseData.Write((ulong)storageTarget); + context.ResponseData.Write(requiredSize); + + return ResultCode.Success; + } + + [CommandCmif(28)] // 11.0.0+ + // GetSaveDataSizeMax() -> (s64 save_size_max, s64 journal_size_max) + public ResultCode GetSaveDataSizeMax(ServiceCtx context) + { + // NOTE: We are currently using a stub for GetSaveDataSize() which returns the default values. + // For this method we shouldn't return anything lower than that, but since we aren't interacting + // with fs to get the actual sizes, we return the default values here as well. + // This also helps in case ExtendSaveData() has been executed and the default values were modified. + + context.ResponseData.Write(_defaultSaveDataSize); + context.ResponseData.Write(_defaultJournalSaveDataSize); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(30)] + // BeginBlockingHomeButtonShortAndLongPressed() + public ResultCode BeginBlockingHomeButtonShortAndLongPressed(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 1 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(31)] + // EndBlockingHomeButtonShortAndLongPressed() + public ResultCode EndBlockingHomeButtonShortAndLongPressed(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x8B to value 0 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(32)] // 2.0.0+ + // BeginBlockingHomeButton(u64 nano_second) + public ResultCode BeginBlockingHomeButton(ServiceCtx context) + { + ulong nanoSeconds = context.RequestData.ReadUInt64(); + + // NOTE: This set two internal fields at offsets 0x89 to value 1 and 0x90 to value of "nanoSeconds" then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { nanoSeconds }); + + return ResultCode.Success; + } + + [CommandCmif(33)] // 2.0.0+ + // EndBlockingHomeButton() + public ResultCode EndBlockingHomeButton(ServiceCtx context) + { + // NOTE: This set two internal fields at offsets 0x89 and 0x90 to value 0 then it signals an internal event. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(40)] + // NotifyRunning() -> b8 + public ResultCode NotifyRunning(ServiceCtx context) + { + context.ResponseData.Write(true); + + return ResultCode.Success; + } + + [CommandCmif(50)] // 2.0.0+ + // GetPseudoDeviceId() -> nn::util::Uuid + public ResultCode GetPseudoDeviceId(ServiceCtx context) + { + context.ResponseData.Write(0L); + context.ResponseData.Write(0L); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(60)] // 2.0.0+ + // SetMediaPlaybackStateForApplication(bool enabled) + public ResultCode SetMediaPlaybackStateForApplication(ServiceCtx context) + { + bool enabled = context.RequestData.ReadBoolean(); + + // NOTE: Service stores the "enabled" value in a private field, when enabled is false, it stores nn::os::GetSystemTick() too. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { enabled }); + + return ResultCode.Success; + } + + [CommandCmif(65)] // 3.0.0+ + // IsGamePlayRecordingSupported() -> u8 + public ResultCode IsGamePlayRecordingSupported(ServiceCtx context) + { + context.ResponseData.Write(_gamePlayRecordingState); + + return ResultCode.Success; + } + + [CommandCmif(66)] // 3.0.0+ + // InitializeGamePlayRecording(u64, handle<copy>) + public ResultCode InitializeGamePlayRecording(ServiceCtx context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.Success; + } + + [CommandCmif(67)] // 3.0.0+ + // SetGamePlayRecordingState(u32) + public ResultCode SetGamePlayRecordingState(ServiceCtx context) + { + _gamePlayRecordingState = context.RequestData.ReadInt32() != 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { _gamePlayRecordingState }); + + return ResultCode.Success; + } + + [CommandCmif(90)] // 4.0.0+ + // EnableApplicationCrashReport(u8) + public ResultCode EnableApplicationCrashReport(ServiceCtx context) + { + bool applicationCrashReportEnabled = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { applicationCrashReportEnabled }); + + return ResultCode.Success; + } + + [CommandCmif(100)] // 5.0.0+ + // InitializeApplicationCopyrightFrameBuffer(s32 width, s32 height, handle<copy, transfer_memory> transfer_memory, u64 transfer_memory_size) + public ResultCode InitializeApplicationCopyrightFrameBuffer(ServiceCtx context) + { + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + ulong transferMemorySize = context.RequestData.ReadUInt64(); + int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0]; + ulong transferMemoryAddress = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle).Address; + + ResultCode resultCode = ResultCode.InvalidParameters; + + if (((transferMemorySize & 0x3FFFF) == 0) && width <= 1280 && height <= 720) + { + resultCode = InitializeApplicationCopyrightFrameBufferImpl(transferMemoryAddress, transferMemorySize, width, height); + } + + if (transferMemoryHandle != 0) + { + context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle); + } + + return resultCode; + } + + private ResultCode InitializeApplicationCopyrightFrameBufferImpl(ulong transferMemoryAddress, ulong transferMemorySize, int width, int height) + { + if ((transferMemorySize & 0x3FFFF) != 0) + { + return ResultCode.InvalidParameters; + } + + ResultCode resultCode; + + // if (_copyrightBuffer == null) + { + // TODO: Initialize buffer and object. + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { transferMemoryAddress, transferMemorySize, width, height }); + + resultCode = ResultCode.Success; + } + + return resultCode; + } + + [CommandCmif(101)] // 5.0.0+ + // SetApplicationCopyrightImage(buffer<bytes, 0x45> frame_buffer, s32 x, s32 y, s32 width, s32 height, s32 window_origin_mode) + public ResultCode SetApplicationCopyrightImage(ServiceCtx context) + { + ulong frameBufferPos = context.Request.SendBuff[0].Position; + ulong frameBufferSize = context.Request.SendBuff[0].Size; + int x = context.RequestData.ReadInt32(); + int y = context.RequestData.ReadInt32(); + int width = context.RequestData.ReadInt32(); + int height = context.RequestData.ReadInt32(); + uint windowOriginMode = context.RequestData.ReadUInt32(); + + ResultCode resultCode = ResultCode.InvalidParameters; + + if (((y | x) >= 0) && width >= 1 && height >= 1) + { + ResultCode result = SetApplicationCopyrightImageImpl(x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode); + + if (result != ResultCode.Success) + { + resultCode = result; + } + else + { + resultCode = ResultCode.Success; + } + } + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { frameBufferPos, frameBufferSize, x, y, width, height, windowOriginMode }); + + return resultCode; + } + + private ResultCode SetApplicationCopyrightImageImpl(int x, int y, int width, int height, ulong frameBufferPos, ulong frameBufferSize, uint windowOriginMode) + { + /* + if (_copyrightBuffer == null) + { + return ResultCode.NullCopyrightObject; + } + */ + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { x, y, width, height, frameBufferPos, frameBufferSize, windowOriginMode }); + + return ResultCode.Success; + } + + [CommandCmif(102)] // 5.0.0+ + // SetApplicationCopyrightVisibility(bool visible) + public ResultCode SetApplicationCopyrightVisibility(ServiceCtx context) + { + bool visible = context.RequestData.ReadBoolean(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { visible }); + + // NOTE: It sets an internal field and return ResultCode.Success in all case. + + return ResultCode.Success; + } + + [CommandCmif(110)] // 5.0.0+ + // QueryApplicationPlayStatistics(buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatistics(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 13 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context); + } + + [CommandCmif(111)] // 6.0.0+ + // QueryApplicationPlayStatisticsByUid(nn::account::Uid, buffer<bytes, 5> title_id_list) -> (buffer<bytes, 6> entries, s32 entries_count) + public ResultCode QueryApplicationPlayStatisticsByUid(ServiceCtx context) + { + // TODO: Call pdm:qry cmd 16 when IPC call between services will be implemented. + return (ResultCode)QueryPlayStatisticsManager.GetPlayStatistics(context, true); + } + + [CommandCmif(120)] // 5.0.0+ + // ExecuteProgram(ProgramSpecifyKind kind, u64 value) + public ResultCode ExecuteProgram(ServiceCtx context) + { + ProgramSpecifyKind kind = (ProgramSpecifyKind)context.RequestData.ReadUInt32(); + + // padding + context.RequestData.ReadUInt32(); + + ulong value = context.RequestData.ReadUInt64(); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { kind, value }); + + context.Device.UiHandler.ExecuteProgram(context.Device, kind, value); + + return ResultCode.Success; + } + + [CommandCmif(121)] // 5.0.0+ + // ClearUserChannel() + public ResultCode ClearUserChannel(ServiceCtx context) + { + context.Device.Configuration.UserChannelPersistence.Clear(); + + return ResultCode.Success; + } + + [CommandCmif(122)] // 5.0.0+ + // UnpopToUserChannel(object<nn::am::service::IStorage> input_storage) + public ResultCode UnpopToUserChannel(ServiceCtx context) + { + AppletAE.IStorage data = GetObject<AppletAE.IStorage>(context, 0); + + context.Device.Configuration.UserChannelPersistence.Push(data.Data); + + return ResultCode.Success; + } + + [CommandCmif(123)] // 5.0.0+ + // GetPreviousProgramIndex() -> s32 program_index + public ResultCode GetPreviousProgramIndex(ServiceCtx context) + { + int previousProgramIndex = context.Device.Configuration.UserChannelPersistence.PreviousIndex; + + context.ResponseData.Write(previousProgramIndex); + + Logger.Stub?.PrintStub(LogClass.ServiceAm, new { previousProgramIndex }); + + return ResultCode.Success; + } + + [CommandCmif(130)] // 8.0.0+ + // GetGpuErrorDetectedSystemEvent() -> handle<copy> + public ResultCode GetGpuErrorDetectedSystemEvent(ServiceCtx context) + { + if (_gpuErrorDetectedSystemEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_gpuErrorDetectedSystemEvent.ReadableEvent, out _gpuErrorDetectedSystemEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_gpuErrorDetectedSystemEventHandle); + + // NOTE: This is used by "sdk" NSO during applet-application initialization. + // A separate thread is setup where event-waiting is handled. + // When the Event is signaled, official sw will assert. + + return ResultCode.Success; + } + + [CommandCmif(140)] // 9.0.0+ + // GetFriendInvitationStorageChannelEvent() -> handle<copy> + public ResultCode GetFriendInvitationStorageChannelEvent(ServiceCtx context) + { + if (_friendInvitationStorageChannelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_friendInvitationStorageChannelEvent.ReadableEvent, out _friendInvitationStorageChannelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_friendInvitationStorageChannelEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(141)] // 9.0.0+ + // TryPopFromFriendInvitationStorageChannel() -> object<nn::am::service::IStorage> + public ResultCode TryPopFromFriendInvitationStorageChannel(ServiceCtx context) + { + // NOTE: IStorage are pushed in the channel with IApplicationAccessor PushToFriendInvitationStorageChannel + // If _friendInvitationStorageChannelEvent is signaled, the event is cleared. + // If an IStorage is available, returns it with ResultCode.Success. + // If not, just returns ResultCode.NotAvailable. Since we don't support friend feature for now, it's fine to do the same. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return ResultCode.NotAvailable; + } + + [CommandCmif(150)] // 9.0.0+ + // GetNotificationStorageChannelEvent() -> handle<copy> + public ResultCode GetNotificationStorageChannelEvent(ServiceCtx context) + { + if (_notificationStorageChannelEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_notificationStorageChannelEvent.ReadableEvent, out _notificationStorageChannelEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationStorageChannelEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(160)] // 9.0.0+ + // GetHealthWarningDisappearedSystemEvent() -> handle<copy> + public ResultCode GetHealthWarningDisappearedSystemEvent(ServiceCtx context) + { + if (_healthWarningDisappearedSystemEventHandle == 0) + { + if (context.Process.HandleTable.GenerateHandle(_healthWarningDisappearedSystemEvent.ReadableEvent, out _healthWarningDisappearedSystemEventHandle) != Result.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_healthWarningDisappearedSystemEventHandle); + + return ResultCode.Success; + } + + [CommandCmif(1001)] // 10.0.0+ + // PrepareForJit() + public ResultCode PrepareForJit(ServiceCtx context) + { + if (Interlocked.Exchange(ref _jitLoaded, 1) == 0) + { + string jitPath = context.Device.System.ContentManager.GetInstalledContentPath(0x010000000000003B, StorageId.BuiltInSystem, NcaContentType.Program); + string filePath = context.Device.FileSystem.SwitchPathToSystemPath(jitPath); + + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new InvalidSystemResourceException($"JIT (010000000000003B) system title not found! The JIT will not work, provide the system archive to fix this error. (See https://github.com/Ryujinx/Ryujinx#requirements for more information)"); + } + + context.Device.LoadNca(filePath); + + // FIXME: Most likely not how this should be done? + while (!context.Device.System.SmRegistry.IsServiceRegistered("jit:u")) + { + context.Device.System.SmRegistry.WaitForServiceRegistration(); + } + } + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs new file mode 100644 index 00000000..40432074 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/LaunchParameterKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum LaunchParameterKind : uint + { + UserChannel = 1, + PreselectedUser, + Unknown + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs new file mode 100644 index 00000000..efc284a5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/ApplicationProxy/Types/ProgramSpecifyKind.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types +{ + public enum ProgramSpecifyKind : uint + { + ExecuteProgram, + SubApplicationProgram, + RestartProgram + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs new file mode 100644 index 00000000..50e3be27 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/ApplicationProxyService/IApplicationProxy.cs @@ -0,0 +1,87 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy; + +namespace Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService +{ + class IApplicationProxy : IpcService + { + private readonly ulong _pid; + + public IApplicationProxy(ulong pid) + { + _pid = pid; + } + + [CommandCmif(0)] + // GetCommonStateGetter() -> object<nn::am::service::ICommonStateGetter> + public ResultCode GetCommonStateGetter(ServiceCtx context) + { + MakeObject(context, new ICommonStateGetter(context)); + + return ResultCode.Success; + } + + [CommandCmif(1)] + // GetSelfController() -> object<nn::am::service::ISelfController> + public ResultCode GetSelfController(ServiceCtx context) + { + MakeObject(context, new ISelfController(context, _pid)); + + return ResultCode.Success; + } + + [CommandCmif(2)] + // GetWindowController() -> object<nn::am::service::IWindowController> + public ResultCode GetWindowController(ServiceCtx context) + { + MakeObject(context, new IWindowController(_pid)); + + return ResultCode.Success; + } + + [CommandCmif(3)] + // GetAudioController() -> object<nn::am::service::IAudioController> + public ResultCode GetAudioController(ServiceCtx context) + { + MakeObject(context, new IAudioController()); + + return ResultCode.Success; + } + + [CommandCmif(4)] + // GetDisplayController() -> object<nn::am::service::IDisplayController> + public ResultCode GetDisplayController(ServiceCtx context) + { + MakeObject(context, new IDisplayController(context)); + + return ResultCode.Success; + } + + [CommandCmif(11)] + // GetLibraryAppletCreator() -> object<nn::am::service::ILibraryAppletCreator> + public ResultCode GetLibraryAppletCreator(ServiceCtx context) + { + MakeObject(context, new ILibraryAppletCreator()); + + return ResultCode.Success; + } + + [CommandCmif(20)] + // GetApplicationFunctions() -> object<nn::am::service::IApplicationFunctions> + public ResultCode GetApplicationFunctions(ServiceCtx context) + { + MakeObject(context, new IApplicationFunctions(context.Device.System)); + + return ResultCode.Success; + } + + [CommandCmif(1000)] + // GetDebugFunctions() -> object<nn::am::service::IDebugFunctions> + public ResultCode GetDebugFunctions(ServiceCtx context) + { + MakeObject(context, new IDebugFunctions()); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs new file mode 100644 index 00000000..3a4c71e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletOE/IApplicationProxyService.cs @@ -0,0 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService; + +namespace Ryujinx.HLE.HOS.Services.Am +{ + [Service("appletOE")] + class IApplicationProxyService : IpcService + { + public IApplicationProxyService(ServiceCtx context) { } + + [CommandCmif(0)] + // OpenApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy> + public ResultCode OpenApplicationProxy(ServiceCtx context) + { + MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId)); + + return ResultCode.Success; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs new file mode 100644 index 00000000..8c72319c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Idle/IPolicyManagerSystem.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Idle +{ + [Service("idle:sys")] + class IPolicyManagerSystem : IpcService + { + public IPolicyManagerSystem(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs new file mode 100644 index 00000000..2856e6d7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Omm/IOperationModeManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Omm +{ + [Service("omm")] + class IOperationModeManager : IpcService + { + public IOperationModeManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs new file mode 100644 index 00000000..5cafff67 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -0,0 +1,30 @@ +namespace Ryujinx.HLE.HOS.Services.Am +{ + enum ResultCode + { + ModuleId = 128, + ErrorCodeShift = 9, + + Success = 0, + + NotAvailable = (2 << ErrorCodeShift) | ModuleId, + NoMessages = (3 << ErrorCodeShift) | ModuleId, + AppletLaunchFailed = (35 << ErrorCodeShift) | ModuleId, + TitleIdNotFound = (37 << ErrorCodeShift) | ModuleId, + ObjectInvalid = (500 << ErrorCodeShift) | ModuleId, + IStorageInUse = (502 << ErrorCodeShift) | ModuleId, + OutOfBounds = (503 << ErrorCodeShift) | ModuleId, + BufferNotAcquired = (504 << ErrorCodeShift) | ModuleId, + BufferAlreadyAcquired = (505 << ErrorCodeShift) | ModuleId, + InvalidParameters = (506 << ErrorCodeShift) | ModuleId, + OpenedAsWrongType = (511 << ErrorCodeShift) | ModuleId, + UnbalancedFatalSection = (512 << ErrorCodeShift) | ModuleId, + NullObject = (518 << ErrorCodeShift) | ModuleId, + MemoryAllocationFailed = (600 << ErrorCodeShift) | ModuleId, + StackPoolExhausted = (712 << ErrorCodeShift) | ModuleId, + DebugModeNotEnabled = (974 << ErrorCodeShift) | ModuleId, + DevFunctionNotEnabled = (980 << ErrorCodeShift) | ModuleId, + NotImplemented = (998 << ErrorCodeShift) | ModuleId, + Stubbed = (999 << ErrorCodeShift) | ModuleId + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs new file mode 100644 index 00000000..a393f76b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Spsm/IPowerStateInterface.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Spsm +{ + [Service("spsm")] + class IPowerStateInterface : IpcService + { + public IPowerStateInterface(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs new file mode 100644 index 00000000..b31ccf8a --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Am/Tcap/IManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Am.Tcap +{ + [Service("tcap")] + class IManager : IpcService + { + public IManager(ServiceCtx context) { } + } +}
\ No newline at end of file |
