From ee81ab547ee35c4f5f3342a6f1e0d6bbc60500e8 Mon Sep 17 00:00:00 2001 From: jduncanator <1518948+jduncanator@users.noreply.github.com> Date: Mon, 18 Nov 2019 22:16:26 +1100 Subject: [PATCH] Initial swkbd implementation (#826) * am: Initial swkbd implementation Currently only implements the full screen keyboard, inline keyboard will come later. * Remove unnecessary logging * Miscellaneous tidy up * am: Always pop incoming interactive session data * am: Add a reminder to implement the full config struct * am: Check for a max length of zero We should only limit/truncate text when the max length is set to a non-zero value. * Add documentation * am: Return IStorage not available when queue is empty We should be returning the appropriate error code when the FIFO is empty, rather than just throwing an exception and killing the emulator. * Fix typo * Code style changes --- Ryujinx.HLE/HOS/Applets/AppletManager.cs | 3 +- Ryujinx.HLE/HOS/Applets/IApplet.cs | 4 +- .../PlayerSelect/PlayerSelectApplet.cs | 13 +- .../SoftwareKeyboardApplet.cs | 179 ++++++++++++++++++ .../SoftwareKeyboardConfig.cs | 33 ++++ .../SoftwareKeyboard/SoftwareKeyboardState.cs | 25 +++ .../SoftwareKeyboard/SoftwareKeyboardType.cs | 20 ++ .../ILibraryAppletAccessor.cs | 108 +++++++++-- .../ILibraryAppletCreator.cs | 14 ++ .../HOS/Services/Am/AppletAE/AppletFifo.cs | 45 ++++- .../HOS/Services/Am/AppletAE/AppletSession.cs | 77 ++++++++ .../HOS/Services/Am/AppletAE/IAppletFifo.cs | 10 + .../Services/Am/AppletAE/IStorageAccessor.cs | 27 +-- Ryujinx.HLE/HOS/Services/Am/ResultCode.cs | 2 + 14 files changed, 522 insertions(+), 38 deletions(-) create mode 100644 Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs create mode 100644 Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs create mode 100644 Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs create mode 100644 Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs create mode 100644 Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletSession.cs create mode 100644 Ryujinx.HLE/HOS/Services/Am/AppletAE/IAppletFifo.cs 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.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 inData, AppletFifo 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 _inputData; - private AppletFifo _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 inData, AppletFifo 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(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(byte[] data) + where T : struct + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + return Marshal.PtrToStructure(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 + { + /// + /// Type of keyboard. + /// + [FieldOffset(0x0)] + public SoftwareKeyboardType Type; + + /// + /// 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). + /// + [FieldOffset(0x3AC)] + public uint StringLengthMax; + + /// + /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button. + /// + [FieldOffset(0x3B0)] + public uint StringLengthMaxExtended; + + /// + /// When set, the application will validate the entered text whilst the swkbd is still on screen. + /// + [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 + { + /// + /// swkbd is uninitialized. + /// + Uninitialized, + + /// + /// swkbd is ready to process data. + /// + Ready, + + /// + /// swkbd is awaiting an interactive reply with a validation status. + /// + ValidationPending, + + /// + /// swkbd has completed. + /// + 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 + { + /// + /// Normal keyboard. + /// + Default = 0, + + /// + /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText. + /// + NumbersOnly = 1, + + /// + /// QWERTY (and variants) keyboard only. + /// + 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 _inData; - private AppletFifo _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(); - _outData = new AppletFifo(); - - _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 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(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 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) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _interactiveSession.Push(data.Data); + + return ResultCode.Success; + } + + [Command(104)] + // PopInteractiveOutData() -> object + 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 + 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 + 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) -> object + 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 : IEnumerable + internal class AppletFifo : IAppletFifo { private ConcurrentQueue _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 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 _inputData; + private IAppletFifo _outputData; + + public event EventHandler DataAvailable; + + public int Length + { + get { return _inputData.Count; } + } + + public AppletSession() + : this(new AppletFifo(), + 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/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 : IProducerConsumerCollection + { + 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) 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 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