Prevent raw Unicode control codes from showing on software keyboard applet. (#3845)

* Revert "Add support for releasing a semaphore to DmaClass (#2926)"

This reverts commit 521a07e612.

* Revert "Revert "Add support for releasing a semaphore to DmaClass (#2926)""

This reverts commit ec8a5fd053.

* Strip non-visible control codes from strings before they are sent to the software keyboard to prevent ugly unicode blocks from being shown on the UI.

* remove debugging junk

* Initialize stringbuilder capacity at the start to prevent resizing (a tiny tiny microoptimization)

* Update remarks documentation. Remove unneeded imports.

* Removing a test that's actually just redundant

Co-authored-by: Logan Stromberg <lostromb@microsoft.com>
This commit is contained in:
Logan Stromberg 2022-11-16 14:53:17 -08:00 committed by GitHub
parent d536cc8ae6
commit 2c9ab5e45f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 4 deletions

View file

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Ryujinx.Tests")]

View file

@ -204,12 +204,11 @@ namespace Ryujinx.HLE.HOS.Applets
else else
{ {
// Call the configured GUI handler to get user's input. // Call the configured GUI handler to get user's input.
var args = new SoftwareKeyboardUiArgs var args = new SoftwareKeyboardUiArgs
{ {
HeaderText = _keyboardForegroundConfig.HeaderText, HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
SubtitleText = _keyboardForegroundConfig.SubtitleText, SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
GuideText = _keyboardForegroundConfig.GuideText, GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),
SubmitText = (!string.IsNullOrWhiteSpace(_keyboardForegroundConfig.SubmitText) ? SubmitText = (!string.IsNullOrWhiteSpace(_keyboardForegroundConfig.SubmitText) ?
_keyboardForegroundConfig.SubmitText : "OK"), _keyboardForegroundConfig.SubmitText : "OK"),
StringLengthMin = _keyboardForegroundConfig.StringLengthMin, StringLengthMin = _keyboardForegroundConfig.StringLengthMin,
@ -764,6 +763,41 @@ namespace Ryujinx.HLE.HOS.Applets
} }
} }
/// <summary>
/// Removes all Unicode control code characters from the input string.
/// This includes CR/LF, tabs, null characters, escape characters,
/// and special control codes which are used for formatting by the real keyboard applet.
/// </summary>
/// <remarks>
/// Some games send special control codes (such as 0x13 "Device Control 3") as part of the string.
/// Future implementations of the emulated keyboard applet will need to handle these as well.
/// </remarks>
/// <param name="input">The input string to sanitize (may be null).</param>
/// <returns>The sanitized string.</returns>
internal static string StripUnicodeControlCodes(string input)
{
if (input is null)
{
return null;
}
if (input.Length == 0)
{
return string.Empty;
}
StringBuilder sb = new StringBuilder(capacity: input.Length);
foreach (char c in input)
{
if (!char.IsControl(c))
{
sb.Append(c);
}
}
return sb.ToString();
}
private static T ReadStruct<T>(byte[] data) private static T ReadStruct<T>(byte[] data)
where T : struct where T : struct
{ {

View file

@ -0,0 +1,71 @@
using NUnit.Framework;
using Ryujinx.HLE.HOS.Applets;
using System.Text;
namespace Ryujinx.Tests.HLE
{
public class SoftwareKeyboardTests
{
[Test]
public void StripUnicodeControlCodes_NullInput()
{
Assert.IsNull(SoftwareKeyboardApplet.StripUnicodeControlCodes(null));
}
[Test]
public void StripUnicodeControlCodes_EmptyInput()
{
Assert.AreEqual(string.Empty, SoftwareKeyboardApplet.StripUnicodeControlCodes(string.Empty));
}
[Test]
public void StripUnicodeControlCodes_Passthrough()
{
string[] prompts = new string[]
{
"Please name him.",
"Name her, too.",
"Name your friend.",
"Name another friend.",
"Name your pet.",
"Favorite homemade food?",
"Whats your favorite thing?",
"Are you sure?",
};
foreach (string prompt in prompts)
{
Assert.AreEqual(prompt, SoftwareKeyboardApplet.StripUnicodeControlCodes(prompt));
}
}
[Test]
public void StripUnicodeControlCodes_StripsNewlines()
{
Assert.AreEqual("I am very tall", SoftwareKeyboardApplet.StripUnicodeControlCodes("I \r\nam \r\nvery \r\ntall"));
}
[Test]
public void StripUnicodeControlCodes_StripsDeviceControls()
{
// 0x13 is control code DC3 used by some games
string specialInput = Encoding.UTF8.GetString(new byte[] { 0x13, 0x53, 0x68, 0x69, 0x6E, 0x65, 0x13 });
Assert.AreEqual("Shine", SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput));
}
[Test]
public void StripUnicodeControlCodes_StripsToEmptyString()
{
string specialInput = Encoding.UTF8.GetString(new byte[] { 17, 18, 19, 20 }); // DC1 - DC4 special codes
Assert.AreEqual(string.Empty, SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput));
}
[Test]
public void StripUnicodeControlCodes_PreservesMultiCodePoints()
{
// Turtles are a good example of multi-codepoint Unicode chars
string specialInput = "♀ 🐢 🐢 ♂ ";
Assert.AreEqual(specialInput, SoftwareKeyboardApplet.StripUnicodeControlCodes(specialInput));
}
}
}