1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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();
}
}
}
}
|