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