aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Ryujinx.HLE/HOS/Applets/AppletManager.cs3
-rw-r--r--Ryujinx.HLE/HOS/Applets/IApplet.cs4
-rw-r--r--Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs13
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs179
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs33
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs25
-rw-r--r--Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs20
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs108
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs14
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs45
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs77
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs10
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs27
-rw-r--r--Ryujinx.HLE/HOS/Services/Am/ResultCode.cs2
14 files changed, 522 insertions, 38 deletions
diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
index e5426cd7..e7314540 100644
--- a/Ryujinx.HLE/HOS/Applets/AppletManager.cs
+++ b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
@@ -12,7 +12,8 @@ namespace Ryujinx.HLE.HOS.Applets
{
_appletMapping = new Dictionary<AppletId, Type>
{
- { AppletId.PlayerSelect, typeof(PlayerSelectApplet) }
+ { AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
+ { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) }
};
}
diff --git a/Ryujinx.HLE/HOS/Applets/IApplet.cs b/Ryujinx.HLE/HOS/Applets/IApplet.cs
index aa248bf5..c2d4aada 100644
--- a/Ryujinx.HLE/HOS/Applets/IApplet.cs
+++ b/Ryujinx.HLE/HOS/Applets/IApplet.cs
@@ -7,7 +7,9 @@ namespace Ryujinx.HLE.HOS.Applets
{
event EventHandler AppletStateChanged;
- ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData);
+ ResultCode Start(AppletSession normalSession,
+ AppletSession interactiveSession);
+
ResultCode GetResult();
}
}
diff --git a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
index 7658c6db..418f5c10 100644
--- a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
+++ b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs
@@ -9,8 +9,8 @@ namespace Ryujinx.HLE.HOS.Applets
{
private Horizon _system;
- private AppletFifo<byte[]> _inputData;
- private AppletFifo<byte[]> _outputData;
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
public event EventHandler AppletStateChanged;
@@ -19,13 +19,14 @@ namespace Ryujinx.HLE.HOS.Applets
_system = system;
}
- public ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData)
+ public ResultCode Start(AppletSession normalSession,
+ AppletSession interactiveSession)
{
- _inputData = inData;
- _outputData = outData;
+ _normalSession = normalSession;
+ _interactiveSession = interactiveSession;
// TODO(jduncanator): Parse PlayerSelectConfig from input data
- _outputData.Push(BuildResponse());
+ _normalSession.Push(BuildResponse());
AppletStateChanged?.Invoke(this, null);
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
new file mode 100644
index 00000000..22fbe8d0
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs
@@ -0,0 +1,179 @@
+using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
+using Ryujinx.HLE.HOS.Services.Am.AppletAE;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Applets
+{
+ internal class SoftwareKeyboardApplet : IApplet
+ {
+ private const string DEFAULT_NUMB = "1";
+ private const string DEFAULT_TEXT = "Ryujinx";
+
+ private const int STANDARD_BUFFER_SIZE = 0x7D8;
+ private const int INTERACTIVE_BUFFER_SIZE = 0x7D4;
+
+ private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized;
+
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
+
+ private SoftwareKeyboardConfig _keyboardConfig;
+
+ private string _textValue = DEFAULT_TEXT;
+
+ public event EventHandler AppletStateChanged;
+
+ public SoftwareKeyboardApplet(Horizon system) { }
+
+ public ResultCode Start(AppletSession normalSession,
+ AppletSession interactiveSession)
+ {
+ _normalSession = normalSession;
+ _interactiveSession = interactiveSession;
+
+ _interactiveSession.DataAvailable += OnInteractiveData;
+
+ var launchParams = _normalSession.Pop();
+ var keyboardConfig = _normalSession.Pop();
+ var transferMemory = _normalSession.Pop();
+
+ _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
+
+ _state = SoftwareKeyboardState.Ready;
+
+ Execute();
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode GetResult()
+ {
+ return ResultCode.Success;
+ }
+
+ private void Execute()
+ {
+ // If the keyboard type is numbers only, we swap to a default
+ // text that only contains numbers.
+ if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly)
+ {
+ _textValue = DEFAULT_NUMB;
+ }
+
+ // If the max string length is 0, we set it to a large default
+ // length.
+ if (_keyboardConfig.StringLengthMax == 0)
+ {
+ _keyboardConfig.StringLengthMax = 100;
+ }
+
+ // If our default text is longer than the allowed length,
+ // we truncate it.
+ if (_textValue.Length > _keyboardConfig.StringLengthMax)
+ {
+ _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax);
+ }
+
+ if (!_keyboardConfig.CheckText)
+ {
+ // If the application doesn't need to validate the response,
+ // we push the data to the non-interactive output buffer
+ // and poll it for completion.
+ _state = SoftwareKeyboardState.Complete;
+
+ _normalSession.Push(BuildResponse(_textValue, false));
+
+ AppletStateChanged?.Invoke(this, null);
+ }
+ else
+ {
+ // The application needs to validate the response, so we
+ // submit it to the interactive output buffer, and poll it
+ // for validation. Once validated, the application will submit
+ // back a validation status, which is handled in OnInteractiveDataPushIn.
+ _state = SoftwareKeyboardState.ValidationPending;
+
+ _interactiveSession.Push(BuildResponse(_textValue, true));
+ }
+ }
+
+ private void OnInteractiveData(object sender, EventArgs e)
+ {
+ // Obtain the validation status response,
+ var data = _interactiveSession.Pop();
+
+ if (_state == SoftwareKeyboardState.ValidationPending)
+ {
+ // TODO(jduncantor):
+ // If application rejects our "attempt", submit another attempt,
+ // and put the applet back in PendingValidation state.
+
+ // For now we assume success, so we push the final result
+ // to the standard output buffer and carry on our merry way.
+ _normalSession.Push(BuildResponse(_textValue, false));
+
+ AppletStateChanged?.Invoke(this, null);
+
+ _state = SoftwareKeyboardState.Complete;
+ }
+ else if(_state == SoftwareKeyboardState.Complete)
+ {
+ // If we have already completed, we push the result text
+ // back on the output buffer and poll the application.
+ _normalSession.Push(BuildResponse(_textValue, false));
+
+ AppletStateChanged?.Invoke(this, null);
+ }
+ else
+ {
+ // We shouldn't be able to get here through standard swkbd execution.
+ throw new InvalidOperationException("Software Keyboard is in an invalid state.");
+ }
+ }
+
+ private byte[] BuildResponse(string text, bool interactive)
+ {
+ int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE;
+
+ using (MemoryStream stream = new MemoryStream(new byte[bufferSize]))
+ using (BinaryWriter writer = new BinaryWriter(stream))
+ {
+ byte[] output = Encoding.Unicode.GetBytes(text);
+
+ if (!interactive)
+ {
+ // Result Code
+ writer.Write((uint)0);
+ }
+ else
+ {
+ // In interactive mode, we write the length of the text
+ // as a long, rather than a result code.
+ writer.Write((long)output.Length);
+ }
+
+ writer.Write(output);
+
+ return stream.ToArray();
+ }
+ }
+
+ private static T ReadStruct<T>(byte[] data)
+ where T : struct
+ {
+ GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
+
+ try
+ {
+ return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
+ }
+ finally
+ {
+ handle.Free();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs
new file mode 100644
index 00000000..183da774
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs
@@ -0,0 +1,33 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ // TODO(jduncanator): Define all fields
+ [StructLayout(LayoutKind.Explicit)]
+ struct SoftwareKeyboardConfig
+ {
+ /// <summary>
+ /// Type of keyboard.
+ /// </summary>
+ [FieldOffset(0x0)]
+ public SoftwareKeyboardType Type;
+
+ /// <summary>
+ /// When non-zero, specifies the max string length. When the input is too long, swkbd will stop accepting more input until text is deleted via the B button (Backspace).
+ /// </summary>
+ [FieldOffset(0x3AC)]
+ public uint StringLengthMax;
+
+ /// <summary>
+ /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button.
+ /// </summary>
+ [FieldOffset(0x3B0)]
+ public uint StringLengthMaxExtended;
+
+ /// <summary>
+ /// When set, the application will validate the entered text whilst the swkbd is still on screen.
+ /// </summary>
+ [FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)]
+ public bool CheckText;
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs
new file mode 100644
index 00000000..42a2831e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ internal enum SoftwareKeyboardState
+ {
+ /// <summary>
+ /// swkbd is uninitialized.
+ /// </summary>
+ Uninitialized,
+
+ /// <summary>
+ /// swkbd is ready to process data.
+ /// </summary>
+ Ready,
+
+ /// <summary>
+ /// swkbd is awaiting an interactive reply with a validation status.
+ /// </summary>
+ ValidationPending,
+
+ /// <summary>
+ /// swkbd has completed.
+ /// </summary>
+ Complete
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs
new file mode 100644
index 00000000..4875da80
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
+{
+ internal enum SoftwareKeyboardType : uint
+ {
+ /// <summary>
+ /// Normal keyboard.
+ /// </summary>
+ Default = 0,
+
+ /// <summary>
+ /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText.
+ /// </summary>
+ NumbersOnly = 1,
+
+ /// <summary>
+ /// QWERTY (and variants) keyboard only.
+ /// </summary>
+ LettersOnly = 2
+ }
+} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
index 8c4d1008..9ebb0b99 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/LibraryAppletCreator/ILibraryAppletAccessor.cs
@@ -11,35 +11,50 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
{
private IApplet _applet;
- private AppletFifo<byte[]> _inData;
- private AppletFifo<byte[]> _outData;
+ private AppletSession _normalSession;
+ private AppletSession _interactiveSession;
private KEvent _stateChangedEvent;
+ private KEvent _normalOutDataEvent;
+ private KEvent _interactiveOutDataEvent;
public ILibraryAppletAccessor(AppletId appletId, Horizon system)
{
- _stateChangedEvent = new KEvent(system);
+ _stateChangedEvent = new KEvent(system);
+ _normalOutDataEvent = new KEvent(system);
+ _interactiveOutDataEvent = new KEvent(system);
- _applet = AppletManager.Create(appletId, system);
- _inData = new AppletFifo<byte[]>();
- _outData = new AppletFifo<byte[]>();
-
- _applet.AppletStateChanged += OnAppletStateChanged;
+ _applet = AppletManager.Create(appletId, system);
+
+ _normalSession = new AppletSession();
+ _interactiveSession = new AppletSession();
+
+ _applet.AppletStateChanged += OnAppletStateChanged;
+ _normalSession.DataAvailable += OnNormalOutData;
+ _interactiveSession.DataAvailable += OnInteractiveOutData;
Logger.PrintInfo(LogClass.ServiceAm, $"Applet '{appletId}' created.");
}
private void OnAppletStateChanged(object sender, EventArgs e)
{
- _stateChangedEvent.ReadableEvent.Signal();
+ _stateChangedEvent.WritableEvent.Signal();
+ }
+
+ private void OnNormalOutData(object sender, EventArgs e)
+ {
+ _normalOutDataEvent.WritableEvent.Signal();
+ }
+
+ private void OnInteractiveOutData(object sender, EventArgs e)
+ {
+ _interactiveOutDataEvent.WritableEvent.Signal();
}
[Command(0)]
// GetAppletStateChangedEvent() -> handle<copy>
public ResultCode GetAppletStateChangedEvent(ServiceCtx context)
{
- _stateChangedEvent.ReadableEvent.Signal();
-
if (context.Process.HandleTable.GenerateHandle(_stateChangedEvent.ReadableEvent, out int handle) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
@@ -54,7 +69,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
// Start()
public ResultCode Start(ServiceCtx context)
{
- return (ResultCode)_applet.Start(_inData, _outData);
+ return (ResultCode)_applet.Start(_normalSession.GetConsumer(),
+ _interactiveSession.GetConsumer());
}
[Command(30)]
@@ -70,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
{
IStorage data = GetObject<IStorage>(context, 0);
- _inData.Push(data.Data);
+ _normalSession.Push(data.Data);
return ResultCode.Success;
}
@@ -79,10 +95,70 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib
// PopOutData() -> object<nn::am::service::IStorage>
public ResultCode PopOutData(ServiceCtx context)
{
- byte[] data = _outData.Pop();
+ if(_normalSession.TryPop(out byte[] data))
+ {
+ MakeObject(context, new IStorage(data));
+
+ _normalOutDataEvent.WritableEvent.Clear();
+
+ return ResultCode.Success;
+ }
+
+ return ResultCode.NotAvailable;
+ }
+
+ [Command(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;
+ }
+
+ [Command(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;
+ }
+
+ [Command(105)]
+ // GetPopOutDataEvent() -> handle<copy>
+ public ResultCode GetPopOutDataEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_normalOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ return ResultCode.Success;
+ }
+
+ [Command(106)]
+ // GetPopInteractiveOutDataEvent() -> handle<copy>
+ public ResultCode GetPopInteractiveOutDataEvent(ServiceCtx context)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_interactiveOutDataEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
- MakeObject(context, new IStorage(data));
-
return ResultCode.Success;
}
}
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
index 094ed305..564bde09 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ILibraryAppletCreator.cs
@@ -29,5 +29,19 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys
return ResultCode.Success;
}
+
+ [Command(11)]
+ // CreateTransferMemoryStorage(b8, u64, handle<copy>) -> object<nn::am::service::IStorage>
+ public ResultCode CreateTransferMemoryStorage(ServiceCtx context)
+ {
+ bool unknown = context.RequestData.ReadBoolean();
+ long size = context.RequestData.ReadInt64();
+
+ // NOTE: We don't support TransferMemory for now.
+
+ MakeObject(context, new IStorage(new byte[size]));
+
+ return ResultCode.Success;
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs
index 2391ba5e..fb16c86e 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs
@@ -5,11 +5,26 @@ using System.Collections.Generic;
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
{
- internal class AppletFifo<T> : IEnumerable<T>
+ internal class AppletFifo<T> : IAppletFifo<T>
{
private ConcurrentQueue<T> _dataQueue;
- public int Count => _dataQueue.Count;
+ 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()
{
@@ -19,6 +34,22 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
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()
@@ -36,6 +67,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
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))
@@ -66,6 +102,11 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
_dataQueue.CopyTo(array, arrayIndex);
}
+ public void CopyTo(Array array, int index)
+ {
+ this.CopyTo((T[])array, index);
+ }
+
public IEnumerator<T> GetEnumerator()
{
return _dataQueue.GetEnumerator();
diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs
new file mode 100644
index 00000000..6c9197b3
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs
new file mode 100644
index 00000000..ca79bac7
--- /dev/null
+++ b/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/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs
index 90eb13ce..5013e2e3 100644
--- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/IStorageAccessor.cs
@@ -24,11 +24,17 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
// Write(u64, buffer<bytes, 0x21>)
public ResultCode Write(ServiceCtx context)
{
- // TODO: Error conditions.
long writePosition = context.RequestData.ReadInt64();
+ if (writePosition > _storage.Data.Length)
+ {
+ return ResultCode.OutOfBounds;
+ }
+
(long position, long size) = context.Request.GetBufferType0x21();
+ size = Math.Min(size, _storage.Data.Length - writePosition);
+
if (size > 0)
{
long maxSize = _storage.Data.Length - writePosition;
@@ -50,23 +56,20 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
// Read(u64) -> buffer<bytes, 0x22>
public ResultCode Read(ServiceCtx context)
{
- // TODO: Error conditions.
long readPosition = context.RequestData.ReadInt64();
+ if (readPosition > _storage.Data.Length)
+ {
+ return ResultCode.OutOfBounds;
+ }
+
(long position, long size) = context.Request.GetBufferType0x22();
- byte[] data;
+ size = Math.Min(size, _storage.Data.Length - readPosition);
- if (_storage.Data.Length > size)
- {
- data = new byte[size];
+ byte[] data = new byte[size];
- Buffer.BlockCopy(_storage.Data, 0, data, 0, (int)size);
- }
- else
- {
- data = _storage.Data;
- }
+ Buffer.BlockCopy(_storage.Data, (int)readPosition, data, 0, (int)size);
context.Memory.WriteBytes(position, data);
diff --git a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
index a5eb42f3..d8979f4a 100644
--- a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs
@@ -7,8 +7,10 @@ namespace Ryujinx.HLE.HOS.Services.Am
Success = 0,
+ NotAvailable = (2 << ErrorCodeShift) | ModuleId,
NoMessages = (3 << ErrorCodeShift) | ModuleId,
ObjectInvalid = (500 << ErrorCodeShift) | ModuleId,
+ OutOfBounds = (503 << ErrorCodeShift) | ModuleId,
CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId
}
} \ No newline at end of file