diff --git a/src/Ryujinx.Horizon/Am/Ipc/AppletFifo.cs b/src/Ryujinx.Horizon/Am/Ipc/AppletFifo.cs new file mode 100644 index 000000000..57388c68f --- /dev/null +++ b/src/Ryujinx.Horizon/Am/Ipc/AppletFifo.cs @@ -0,0 +1,121 @@ +using Ryujinx.Horizon.Sdk.Am; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.Horizon.Am.Ipc +{ + internal class AppletFifo : IAppletFifo + { + private readonly ConcurrentQueue _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(); + } + + 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 GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _dataQueue.GetEnumerator(); + } + } +} diff --git a/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletAccessor.cs b/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletAccessor.cs new file mode 100644 index 000000000..989f6eca0 --- /dev/null +++ b/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletAccessor.cs @@ -0,0 +1,204 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Am; +using Ryujinx.Horizon.Sdk.Am.Controllers; +using Ryujinx.Horizon.Sdk.Am.Storage; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Am.Ipc.Controllers +{ + partial class LibraryAppletAccessor : ILibraryAppletAccessor + { + private readonly AppletSession _normalSession; + private readonly AppletSession _interactiveSession; + + private SystemEventType _stateChangedEvent; + private SystemEventType _normalOutDataEvent; + private SystemEventType _interactiveOutDataEvent; + + public LibraryAppletAccessor(AppletId appletId) + { + Os.CreateSystemEvent(out _stateChangedEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure(); + Os.CreateSystemEvent(out _normalOutDataEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure(); + Os.CreateSystemEvent(out _interactiveOutDataEvent, EventClearMode.ManualClear, interProcess: true).AbortOnFailure(); + + _normalSession = new AppletSession(); + _interactiveSession = new AppletSession(); + + _normalSession.DataAvailable += OnNormalOutData; + _interactiveSession.DataAvailable += OnInteractiveOutData; + } + + private void OnAppletStateChanged(object sender, EventArgs e) + { + Os.SignalSystemEvent(ref _stateChangedEvent); + } + + private void OnNormalOutData(object sender, EventArgs e) + { + Os.SignalSystemEvent(ref _normalOutDataEvent); + } + + private void OnInteractiveOutData(object sender, EventArgs e) + { + Os.SignalSystemEvent(ref _interactiveOutDataEvent); + } + + [CmifCommand(0)] + public Result GetAppletStateChangedEvent([CopyHandle] out int arg0) + { + arg0 = Os.GetReadableHandleOfSystemEvent(ref _stateChangedEvent); + + return Result.Success; + } + + [CmifCommand(1)] + public Result IsCompleted(out bool arg0) + { + arg0 = false; + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(10)] + public Result Start() + { + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(20)] + public Result RequestExit() + { + Os.SignalSystemEvent(ref _stateChangedEvent); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(25)] + public Result Terminate() + { + throw new System.NotImplementedException(); + } + + [CmifCommand(30)] + public Result GetResult() + { + throw new System.NotImplementedException(); + } + + [CmifCommand(50)] + public Result SetOutOfFocusApplicationSuspendingEnabled(bool arg0) + { + throw new System.NotImplementedException(); + } + + [CmifCommand(60)] + public Result PresetLibraryAppletGpuTimeSliceZero() + { + // 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. + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(100)] + public Result PushInData(IStorage data) + { + var storage = new Storage.Storage(data); + _normalSession.Push(storage.Data); + + return Result.Success; + } + + [CmifCommand(101)] + public Result PopOutData(out IStorage data) + { + if (_interactiveSession.TryPop(out byte[] bytes)) + { + data = new Storage.Storage(bytes); + + return Result.Success; + } + + data = new Storage.Storage([]); + return AmResult.NotAvailable; + } + + [CmifCommand(102)] + public Result PushExtraStorage(IStorage data) + { + throw new System.NotImplementedException(); + } + + [CmifCommand(103)] + public Result PushInteractiveInData(IStorage data) + { + var storage = new Storage.Storage(data); + + _interactiveSession.Push(storage.Data); + + return Result.Success; + } + + [CmifCommand(104)] + public Result PopInteractiveOutData(out IStorage arg0) + { + throw new System.NotImplementedException(); + } + + [CmifCommand(105)] + public Result GetPopOutDataEvent(out int handle) + { + handle = Os.GetReadableHandleOfSystemEvent(ref _normalOutDataEvent); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(106)] + public Result GetPopInteractiveOutDataEvent(out int handle) + { + handle = Os.GetReadableHandleOfSystemEvent(ref _interactiveOutDataEvent); + + Logger.Stub?.PrintStub(LogClass.ServiceAm); + + return Result.Success; + } + + [CmifCommand(110)] + public Result NeedsToExitProcess(out bool arg0) + { + arg0 = false; + + return AmResult.Stubbed; + } + + [CmifCommand(120)] + public Result GetLibraryAppletInfo(out LibraryAppletInfo arg0) + { + throw new System.NotImplementedException(); + } + + [CmifCommand(150)] + public Result RequestForAppletToGetForeground() + { + return AmResult.Stubbed; + } + + [CmifCommand(160)] + public Result GetIndirectLayerConsumerHandle(out ulong arg0, ulong arg1, ulong pid) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletCreator.cs b/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletCreator.cs index 80e49daf3..ca66d47af 100644 --- a/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletCreator.cs +++ b/src/Ryujinx.Horizon/Am/Ipc/Controllers/LibraryAppletCreator.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Logging; using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Sdk.Am.Controllers; +using Ryujinx.Horizon.Sdk.Am.Storage; using Ryujinx.Horizon.Sdk.Sf; namespace Ryujinx.Horizon.Am.Ipc.Controllers @@ -8,7 +9,7 @@ namespace Ryujinx.Horizon.Am.Ipc.Controllers partial class LibraryAppletCreator : ILibraryAppletCreator { [CmifCommand(0)] - public Result CreateLibraryApplet() + public Result CreateLibraryApplet(out ILibraryAppletAccessor arg0, uint arg1, uint arg2) { Logger.Stub?.PrintStub(LogClass.ServiceAm); @@ -16,7 +17,7 @@ namespace Ryujinx.Horizon.Am.Ipc.Controllers } [CmifCommand(1)] - public Result TerminateAllLibraryApplets() + public Result TerminateAllLibraryApplets(out bool arg0) { Logger.Stub?.PrintStub(LogClass.ServiceAm); @@ -32,7 +33,7 @@ namespace Ryujinx.Horizon.Am.Ipc.Controllers } [CmifCommand(10)] - public Result CreateStorage() + public Result CreateStorage(out IStorage arg0, long arg1) { Logger.Stub?.PrintStub(LogClass.ServiceAm); @@ -40,7 +41,7 @@ namespace Ryujinx.Horizon.Am.Ipc.Controllers } [CmifCommand(11)] - public Result CreateTransferMemoryStorage() + public Result CreateTransferMemoryStorage(out IStorage arg0, int arg1, long arg2, bool arg3) { Logger.Stub?.PrintStub(LogClass.ServiceAm); @@ -48,7 +49,7 @@ namespace Ryujinx.Horizon.Am.Ipc.Controllers } [CmifCommand(12)] - public Result CreateHandleStorage() + public Result CreateHandleStorage(out IStorage arg0, int arg1, long arg2) { Logger.Stub?.PrintStub(LogClass.ServiceAm); diff --git a/src/Ryujinx.Horizon/Am/Ipc/Storage/Storage.cs b/src/Ryujinx.Horizon/Am/Ipc/Storage/Storage.cs index c0fc66e7f..c69fa1e48 100644 --- a/src/Ryujinx.Horizon/Am/Ipc/Storage/Storage.cs +++ b/src/Ryujinx.Horizon/Am/Ipc/Storage/Storage.cs @@ -15,6 +15,12 @@ namespace Ryujinx.Horizon.Am.Ipc.Storage Data = data; } + // TODO: Something for this... + public Storage(IStorage handle) + { + + } + [CmifCommand(0)] public Result Open(out IStorageAccessor storageAccessor) { diff --git a/src/Ryujinx.Horizon/Sdk/Am/AppletProcessLaunchReason.cs b/src/Ryujinx.Horizon/Sdk/Am/AppletProcessLaunchReason.cs index 24e7ac180..b2d8c6654 100644 --- a/src/Ryujinx.Horizon/Sdk/Am/AppletProcessLaunchReason.cs +++ b/src/Ryujinx.Horizon/Sdk/Am/AppletProcessLaunchReason.cs @@ -5,7 +5,8 @@ namespace Ryujinx.Horizon.Sdk.Am [StructLayout(LayoutKind.Sequential, Size = 0x4)] public struct AppletProcessLaunchReason { - // TODO: Better way to rep single bit flag - public bool flag; + public byte Flag; + public ushort Unknown1; + public byte Unknown2; } } diff --git a/src/Ryujinx.Horizon/Sdk/Am/AppletSession.cs b/src/Ryujinx.Horizon/Sdk/Am/AppletSession.cs new file mode 100644 index 000000000..2981ac8dc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Am/AppletSession.cs @@ -0,0 +1,77 @@ +using Ryujinx.Horizon.Am.Ipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Am +{ + internal class AppletSession + { + private readonly IAppletFifo _inputData; + private readonly IAppletFifo _outputData; + + public event EventHandler DataAvailable; + + public int Length + { + get { return _inputData.Count; } + } + + public AppletSession() + { + _inputData = new AppletFifo(); + _outputData = new AppletFifo(); + } + + public AppletSession(IAppletFifo inputData, IAppletFifo 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); + } + + /// + /// 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. + /// + public AppletSession GetConsumer() + { + return new AppletSession(this._outputData, this._inputData); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletAccessor.cs b/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletAccessor.cs new file mode 100644 index 000000000..8650629db --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletAccessor.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Am.Storage; + +namespace Ryujinx.Horizon.Sdk.Am.Controllers +{ + interface ILibraryAppletAccessor : IAppletAccessor + { + Result SetOutOfFocusApplicationSuspendingEnabled(bool arg0); + Result PresetLibraryAppletGpuTimeSliceZero(); + Result PushInData(IStorage arg0); + Result PopOutData(out IStorage data); + Result PushExtraStorage(IStorage data); + Result PushInteractiveInData(IStorage data); + Result PopInteractiveOutData(out IStorage arg0); + Result GetPopOutDataEvent(out int handle); + Result GetPopInteractiveOutDataEvent(out int handle); + Result NeedsToExitProcess(out bool arg0); + Result GetLibraryAppletInfo(out LibraryAppletInfo arg0); + Result RequestForAppletToGetForeground(); + Result GetIndirectLayerConsumerHandle(out ulong arg0, ulong arg1, ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletCreator.cs b/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletCreator.cs index 457ea7a3d..abe0f5ce6 100644 --- a/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletCreator.cs +++ b/src/Ryujinx.Horizon/Sdk/Am/Controllers/ILibraryAppletCreator.cs @@ -1,15 +1,16 @@ using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Am.Storage; using Ryujinx.Horizon.Sdk.Sf; namespace Ryujinx.Horizon.Sdk.Am.Controllers { interface ILibraryAppletCreator : IServiceObject { - Result CreateLibraryApplet(); + Result CreateLibraryApplet(out ILibraryAppletAccessor arg0, uint arg1, uint arg2); Result TerminateAllLibraryApplets(); - Result AreAnyLibraryAppletsLeft(); - Result CreateStorage(); - Result CreateTransferMemoryStorage(); - Result CreateHandleStorage(); + Result AreAnyLibraryAppletsLeft(out bool arg0); + Result CreateStorage(out IStorage arg0, long arg1); + Result CreateTransferMemoryStorage(out IStorage arg0, int arg1, long arg2, bool arg3); + Result CreateHandleStorage(out IStorage arg0, int arg1, long arg2); } } diff --git a/src/Ryujinx.Horizon/Sdk/Am/IAppletFifo.cs b/src/Ryujinx.Horizon/Sdk/Am/IAppletFifo.cs new file mode 100644 index 000000000..8232d7bb5 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Am/IAppletFifo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Concurrent; + +namespace Ryujinx.Horizon.Sdk.Am +{ + interface IAppletFifo : IProducerConsumerCollection + { + event EventHandler DataAvailable; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Am/LibraryAppletInfo.cs b/src/Ryujinx.Horizon/Sdk/Am/LibraryAppletInfo.cs new file mode 100644 index 000000000..cf4b1ba5c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Am/LibraryAppletInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Am +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8)] + struct LibraryAppletInfo + { + public AppletId AppletId; + public LibraryAppletMode LibraryAppletMode; + } +}