SoftwareKeyboardApplet.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
  2. using Ryujinx.HLE.HOS.Services.Am.AppletAE;
  3. using System;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. namespace Ryujinx.HLE.HOS.Applets
  8. {
  9. internal class SoftwareKeyboardApplet : IApplet
  10. {
  11. private const string DEFAULT_NUMB = "1";
  12. private const string DEFAULT_TEXT = "Ryujinx";
  13. private const int STANDARD_BUFFER_SIZE = 0x7D8;
  14. private const int INTERACTIVE_BUFFER_SIZE = 0x7D4;
  15. private SoftwareKeyboardState _state = SoftwareKeyboardState.Uninitialized;
  16. private AppletSession _normalSession;
  17. private AppletSession _interactiveSession;
  18. private SoftwareKeyboardConfig _keyboardConfig;
  19. private string _textValue = DEFAULT_TEXT;
  20. public event EventHandler AppletStateChanged;
  21. public SoftwareKeyboardApplet(Horizon system) { }
  22. public ResultCode Start(AppletSession normalSession,
  23. AppletSession interactiveSession)
  24. {
  25. _normalSession = normalSession;
  26. _interactiveSession = interactiveSession;
  27. _interactiveSession.DataAvailable += OnInteractiveData;
  28. var launchParams = _normalSession.Pop();
  29. var keyboardConfig = _normalSession.Pop();
  30. var transferMemory = _normalSession.Pop();
  31. _keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
  32. _state = SoftwareKeyboardState.Ready;
  33. Execute();
  34. return ResultCode.Success;
  35. }
  36. public ResultCode GetResult()
  37. {
  38. return ResultCode.Success;
  39. }
  40. private void Execute()
  41. {
  42. // If the keyboard type is numbers only, we swap to a default
  43. // text that only contains numbers.
  44. if (_keyboardConfig.Type == SoftwareKeyboardType.NumbersOnly)
  45. {
  46. _textValue = DEFAULT_NUMB;
  47. }
  48. // If the max string length is 0, we set it to a large default
  49. // length.
  50. if (_keyboardConfig.StringLengthMax == 0)
  51. {
  52. _keyboardConfig.StringLengthMax = 100;
  53. }
  54. // If our default text is longer than the allowed length,
  55. // we truncate it.
  56. if (_textValue.Length > _keyboardConfig.StringLengthMax)
  57. {
  58. _textValue = _textValue.Substring(0, (int)_keyboardConfig.StringLengthMax);
  59. }
  60. if (!_keyboardConfig.CheckText)
  61. {
  62. // If the application doesn't need to validate the response,
  63. // we push the data to the non-interactive output buffer
  64. // and poll it for completion.
  65. _state = SoftwareKeyboardState.Complete;
  66. _normalSession.Push(BuildResponse(_textValue, false));
  67. AppletStateChanged?.Invoke(this, null);
  68. }
  69. else
  70. {
  71. // The application needs to validate the response, so we
  72. // submit it to the interactive output buffer, and poll it
  73. // for validation. Once validated, the application will submit
  74. // back a validation status, which is handled in OnInteractiveDataPushIn.
  75. _state = SoftwareKeyboardState.ValidationPending;
  76. _interactiveSession.Push(BuildResponse(_textValue, true));
  77. }
  78. }
  79. private void OnInteractiveData(object sender, EventArgs e)
  80. {
  81. // Obtain the validation status response,
  82. var data = _interactiveSession.Pop();
  83. if (_state == SoftwareKeyboardState.ValidationPending)
  84. {
  85. // TODO(jduncantor):
  86. // If application rejects our "attempt", submit another attempt,
  87. // and put the applet back in PendingValidation state.
  88. // For now we assume success, so we push the final result
  89. // to the standard output buffer and carry on our merry way.
  90. _normalSession.Push(BuildResponse(_textValue, false));
  91. AppletStateChanged?.Invoke(this, null);
  92. _state = SoftwareKeyboardState.Complete;
  93. }
  94. else if(_state == SoftwareKeyboardState.Complete)
  95. {
  96. // If we have already completed, we push the result text
  97. // back on the output buffer and poll the application.
  98. _normalSession.Push(BuildResponse(_textValue, false));
  99. AppletStateChanged?.Invoke(this, null);
  100. }
  101. else
  102. {
  103. // We shouldn't be able to get here through standard swkbd execution.
  104. throw new InvalidOperationException("Software Keyboard is in an invalid state.");
  105. }
  106. }
  107. private byte[] BuildResponse(string text, bool interactive)
  108. {
  109. int bufferSize = !interactive ? STANDARD_BUFFER_SIZE : INTERACTIVE_BUFFER_SIZE;
  110. using (MemoryStream stream = new MemoryStream(new byte[bufferSize]))
  111. using (BinaryWriter writer = new BinaryWriter(stream))
  112. {
  113. byte[] output = Encoding.Unicode.GetBytes(text);
  114. if (!interactive)
  115. {
  116. // Result Code
  117. writer.Write((uint)0);
  118. }
  119. else
  120. {
  121. // In interactive mode, we write the length of the text
  122. // as a long, rather than a result code.
  123. writer.Write((long)output.Length);
  124. }
  125. writer.Write(output);
  126. return stream.ToArray();
  127. }
  128. }
  129. private static T ReadStruct<T>(byte[] data)
  130. where T : struct
  131. {
  132. GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
  133. try
  134. {
  135. return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
  136. }
  137. finally
  138. {
  139. handle.Free();
  140. }
  141. }
  142. }
  143. }