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.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 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 7658c6db13..a40d923bcd 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,14 +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); return ResultCode.Success; diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs new file mode 100644 index 0000000000..7554c8636d --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -0,0 +1,164 @@ +using Ryujinx.Common.Logging; +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 (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly) + { + _textValue = DEFAULT_NUMB; + } + + 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) + { + if(_state == SoftwareKeyboardState.ValidationPending) + { + // TODO(jduncantor): + // If application rejects our "attempt", submit another attempt, + // and put the applet back in PendingValidation state. + + // Obtain the validation status response, for now we assume + // success and carry on our merry way. + _interactiveSession.Pop(); + + _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 + { + 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 0000000000..3cc3e03d75 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + [StructLayout(LayoutKind.Explicit)] + struct SoftwareKeyboardConfig + { + [FieldOffset(0)] + public SoftwareKeyboardType Type; + + [FieldOffset(0x3AC)] + public uint StringLengthMax; + + [FieldOffset(0x3B0)] + public uint StringLengthMaxExtended; + + [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..a8a71aa36f --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + internal enum SoftwareKeyboardState + { + Uninitialized, + Ready, + ValidationPending, + 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..7e7f3a758a --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + internal enum SoftwareKeyboardType : uint + { + Default = 0, + NumbersOnly = 1, + 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..9400117471 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); + _normalOutDataEvent = new KEvent(system); + _interactiveOutDataEvent = new KEvent(system); _applet = AppletManager.Create(appletId, system); - _inData = new AppletFifo(); - _outData = new AppletFifo(); - + + _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!"); @@ -47,6 +62,8 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + Logger.PrintInfo(LogClass.ServiceAm, $"GetAppletStateChangedEvent called."); + return ResultCode.Success; } @@ -54,13 +71,18 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib // Start() public ResultCode Start(ServiceCtx context) { - return (ResultCode)_applet.Start(_inData, _outData); + Logger.PrintInfo(LogClass.ServiceAm, $"Start called."); + + return (ResultCode)_applet.Start(_normalSession.GetConsumer(), + _interactiveSession.GetConsumer()); } [Command(30)] // GetResult() public ResultCode GetResult(ServiceCtx context) { + Logger.PrintInfo(LogClass.ServiceAm, $"GetResult called."); + return (ResultCode)_applet.GetResult(); } @@ -70,7 +92,9 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib { IStorage data = GetObject(context, 0); - _inData.Push(data.Data); + _normalSession.Push(data.Data); + + Logger.PrintInfo(LogClass.ServiceAm, $"PushInData called."); return ResultCode.Success; } @@ -79,10 +103,74 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Lib // PopOutData() -> object public ResultCode PopOutData(ServiceCtx context) { - byte[] data = _outData.Pop(); + byte[] data = _normalSession.Pop(); MakeObject(context, new IStorage(data)); - + + _normalOutDataEvent.WritableEvent.Clear(); + + Logger.PrintInfo(LogClass.ServiceAm, $"PopOutData called."); + + return ResultCode.Success; + } + + [Command(103)] + // PushInteractiveInData(object) + public ResultCode PushInteractiveInData(ServiceCtx context) + { + IStorage data = GetObject(context, 0); + + _interactiveSession.Push(data.Data); + + Logger.PrintInfo(LogClass.ServiceAm, $"PushInteractiveInData called."); + + return ResultCode.Success; + } + + [Command(104)] + // PopInteractiveOutData() -> object + public ResultCode PopInteractiveOutData(ServiceCtx context) + { + byte[] data = _interactiveSession.Pop(); + + MakeObject(context, new IStorage(data)); + + Logger.PrintInfo(LogClass.ServiceAm, $"PopInteractiveOutData called."); + + _interactiveOutDataEvent.WritableEvent.Clear(); + + return ResultCode.Success; + } + + [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); + + Logger.PrintInfo(LogClass.ServiceAm, $"GetPopOutDataEvent called."); + + 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); + + Logger.PrintInfo(LogClass.ServiceAm, $"GetPopInteractiveOutDataEvent called."); + 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) -> 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 2391ba5e2a..80d3f73cc7 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/AppletFifo.cs @@ -2,14 +2,30 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; 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 +35,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 +68,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 +103,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 0000000000..fbf1303351 --- /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 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 : 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 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) 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 a5eb42f3bf..de83ab3995 100644 --- a/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs +++ b/Ryujinx.HLE/HOS/Services/Am/ResultCode.cs @@ -9,6 +9,7 @@ namespace Ryujinx.HLE.HOS.Services.Am NoMessages = (3 << ErrorCodeShift) | ModuleId, ObjectInvalid = (500 << ErrorCodeShift) | ModuleId, + OutOfBounds = (503 << ErrorCodeShift) | ModuleId, CpuBoostModeInvalid = (506 << ErrorCodeShift) | ModuleId } } \ No newline at end of file