diff options
Diffstat (limited to 'Ryujinx.HLE/HOS/Applets')
7 files changed, 269 insertions, 8 deletions
diff --git a/Ryujinx.HLE/HOS/Applets/AppletManager.cs b/Ryujinx.HLE/HOS/Applets/AppletManager.cs index e5426cd7..e7314540 100644 --- a/Ryujinx.HLE/HOS/Applets/AppletManager.cs +++ b/Ryujinx.HLE/HOS/Applets/AppletManager.cs @@ -12,7 +12,8 @@ namespace Ryujinx.HLE.HOS.Applets { _appletMapping = new Dictionary<AppletId, Type> { - { AppletId.PlayerSelect, typeof(PlayerSelectApplet) } + { AppletId.PlayerSelect, typeof(PlayerSelectApplet) }, + { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) } }; } diff --git a/Ryujinx.HLE/HOS/Applets/IApplet.cs b/Ryujinx.HLE/HOS/Applets/IApplet.cs index aa248bf5..c2d4aada 100644 --- a/Ryujinx.HLE/HOS/Applets/IApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/IApplet.cs @@ -7,7 +7,9 @@ namespace Ryujinx.HLE.HOS.Applets { event EventHandler AppletStateChanged; - ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData); + ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession); + ResultCode GetResult(); } } diff --git a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index 7658c6db..418f5c10 100644 --- a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -9,8 +9,8 @@ namespace Ryujinx.HLE.HOS.Applets { private Horizon _system; - private AppletFifo<byte[]> _inputData; - private AppletFifo<byte[]> _outputData; + private AppletSession _normalSession; + private AppletSession _interactiveSession; public event EventHandler AppletStateChanged; @@ -19,13 +19,14 @@ namespace Ryujinx.HLE.HOS.Applets _system = system; } - public ResultCode Start(AppletFifo<byte[]> inData, AppletFifo<byte[]> outData) + public ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession) { - _inputData = inData; - _outputData = outData; + _normalSession = normalSession; + _interactiveSession = interactiveSession; // TODO(jduncanator): Parse PlayerSelectConfig from input data - _outputData.Push(BuildResponse()); + _normalSession.Push(BuildResponse()); AppletStateChanged?.Invoke(this, null); diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs new file mode 100644 index 00000000..22fbe8d0 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardApplet.cs @@ -0,0 +1,179 @@ +using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard; +using Ryujinx.HLE.HOS.Services.Am.AppletAE; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.HLE.HOS.Applets +{ + internal class SoftwareKeyboardApplet : IApplet + { + private const string DEFAULT_NUMB = "1"; + private const string DEFAULT_TEXT = "Ryujinx"; + + private const int STANDARD_BUFFER_SIZE = 0x7D8; + private const int INTERACTIVE_BUFFER_SIZE = 0x7D4; + + private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized; + + private AppletSession _normalSession; + private AppletSession _interactiveSession; + + private SoftwareKeyboardConfig _keyboardConfig; + + private string _textValue = DEFAULT_TEXT; + + public event EventHandler AppletStateChanged; + + public SoftwareKeyboardApplet(Horizon system) { } + + public ResultCode Start(AppletSession normalSession, + AppletSession interactiveSession) + { + _normalSession = normalSession; + _interactiveSession = interactiveSession; + + _interactiveSession.DataAvailable += OnInteractiveData; + + var launchParams = _normalSession.Pop(); + var keyboardConfig = _normalSession.Pop(); + var transferMemory = _normalSession.Pop(); + + _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig); + + _state = SoftwareKeyboardState.Ready; + + Execute(); + + return ResultCode.Success; + } + + public ResultCode GetResult() + { + return ResultCode.Success; + } + + private void Execute() + { + // If the keyboard type is numbers only, we swap to a default + // text that only contains numbers. + if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly) + { + _textValue = DEFAULT_NUMB; + } + + // If the max string length is 0, we set it to a large default + // length. + if (_keyboardConfig.StringLengthMax == 0) + { + _keyboardConfig.StringLengthMax = 100; + } + + // If our default text is longer than the allowed length, + // we truncate it. + if (_textValue.Length > _keyboardConfig.StringLengthMax) + { + _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax); + } + + if (!_keyboardConfig.CheckText) + { + // If the application doesn't need to validate the response, + // we push the data to the non-interactive output buffer + // and poll it for completion. + _state = SoftwareKeyboardState.Complete; + + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + } + else + { + // The application needs to validate the response, so we + // submit it to the interactive output buffer, and poll it + // for validation. Once validated, the application will submit + // back a validation status, which is handled in OnInteractiveDataPushIn. + _state = SoftwareKeyboardState.ValidationPending; + + _interactiveSession.Push(BuildResponse(_textValue, true)); + } + } + + private void OnInteractiveData(object sender, EventArgs e) + { + // Obtain the validation status response, + var data = _interactiveSession.Pop(); + + if (_state == SoftwareKeyboardState.ValidationPending) + { + // TODO(jduncantor): + // If application rejects our "attempt", submit another attempt, + // and put the applet back in PendingValidation state. + + // For now we assume success, so we push the final result + // to the standard output buffer and carry on our merry way. + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + + _state = SoftwareKeyboardState.Complete; + } + else if(_state == SoftwareKeyboardState.Complete) + { + // If we have already completed, we push the result text + // back on the output buffer and poll the application. + _normalSession.Push(BuildResponse(_textValue, false)); + + AppletStateChanged?.Invoke(this, null); + } + else + { + // We shouldn't be able to get here through standard swkbd execution. + throw new InvalidOperationException("Software Keyboard is in an invalid state."); + } + } + + private byte[] BuildResponse(string text, bool interactive) + { + int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE; + + using (MemoryStream stream = new MemoryStream(new byte[bufferSize])) + using (BinaryWriter writer = new BinaryWriter(stream)) + { + byte[] output = Encoding.Unicode.GetBytes(text); + + if (!interactive) + { + // Result Code + writer.Write((uint)0); + } + else + { + // In interactive mode, we write the length of the text + // as a long, rather than a result code. + writer.Write((long)output.Length); + } + + writer.Write(output); + + return stream.ToArray(); + } + } + + private static T ReadStruct<T>(byte[] data) + where T : struct + { + GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); + + try + { + return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject()); + } + finally + { + handle.Free(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs new file mode 100644 index 00000000..183da774 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardConfig.cs @@ -0,0 +1,33 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + // TODO(jduncanator): Define all fields + [StructLayout(LayoutKind.Explicit)] + struct SoftwareKeyboardConfig + { + /// <summary> + /// Type of keyboard. + /// </summary> + [FieldOffset(0x0)] + public SoftwareKeyboardType Type; + + /// <summary> + /// When non-zero, specifies the max string length. When the input is too long, swkbd will stop accepting more input until text is deleted via the B button (Backspace). + /// </summary> + [FieldOffset(0x3AC)] + public uint StringLengthMax; + + /// <summary> + /// When non-zero, specifies the max string length. When the input is too long, swkbd will display an icon and disable the ok-button. + /// </summary> + [FieldOffset(0x3B0)] + public uint StringLengthMaxExtended; + + /// <summary> + /// When set, the application will validate the entered text whilst the swkbd is still on screen. + /// </summary> + [FieldOffset(0x3D0), MarshalAs(UnmanagedType.I1)] + public bool CheckText; + } +} diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs new file mode 100644 index 00000000..42a2831e --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardState.cs @@ -0,0 +1,25 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + internal enum SoftwareKeyboardState + { + /// <summary> + /// swkbd is uninitialized. + /// </summary> + Uninitialized, + + /// <summary> + /// swkbd is ready to process data. + /// </summary> + Ready, + + /// <summary> + /// swkbd is awaiting an interactive reply with a validation status. + /// </summary> + ValidationPending, + + /// <summary> + /// swkbd has completed. + /// </summary> + Complete + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs new file mode 100644 index 00000000..4875da80 --- /dev/null +++ b/Ryujinx.HLE/HOS/Applets/SoftwareKeyboard/SoftwareKeyboardType.cs @@ -0,0 +1,20 @@ +namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard +{ + internal enum SoftwareKeyboardType : uint + { + /// <summary> + /// Normal keyboard. + /// </summary> + Default = 0, + + /// <summary> + /// Number pad. The buttons at the bottom left/right are only available when they're set in the config by leftButtonText / rightButtonText. + /// </summary> + NumbersOnly = 1, + + /// <summary> + /// QWERTY (and variants) keyboard only. + /// </summary> + LettersOnly = 2 + } +}
\ No newline at end of file |
