diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs
index e5426cd758..e731454048 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 aa248bf599..c2d4aada1f 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 7658c6db13..418f5c1004 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 0000000000..22fbe8d0a8
--- /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 0000000000..183da774ce
--- /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 0000000000..42a2831ec9
--- /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 0000000000..4875da8098
--- /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 8c4d10084e..9ebb0b99bd 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 094ed30508..564bde0971 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 2391ba5e2a..fb16c86e76 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 0000000000..6c9197b3ed
--- /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 0000000000..ca79bac7af
--- /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 90eb13ce00..5013e2e3d0 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 a5eb42f3bf..d8979f4af9 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