From 65e73e931fcd05f6c0cd87d9bc069f6386267104 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 26 Jul 2026 23:09:01 +0900 Subject: [PATCH 1/5] fix: Reject non-name key inputs in simulate-keyboard Enum.TryParse accepted far more than key names: ordinals ("3" resolved to Key.Tab), signed ordinals ("+3"), whitespace-padded input, comma-separated names OR-ed into one value ("Space,Enter"), and undefined ordinals ("300") that passed the Key.None guard and then threw ArgumentOutOfRangeException from the keyboard indexer. Runs that pressed an unrelated key still reported Success: true, so verification results silently described the wrong input. Resolve a key only when the normalized name matches a name defined on the Key enum, and route everything else to the existing suggester error path. KeyboardKeyNameSuggester is unchanged; its digit suggestions were already implemented but unreachable because TryParse succeeded first. - Add an immutable OrdinalIgnoreCase whitelist of Enum.GetNames(typeof(Key)) - Explain the ordinal history when rejecting numeric input, so earlier results and scripts that passed digits get re-checked - Add "Digit3" to the example key names in both validation messages --- .../Tests/PlayMode/SimulateKeyboardTests.cs | 89 +++++++++++++++++++ .../SimulateKeyboardUseCase.cs | 55 +++++++++++- 2 files changed, 141 insertions(+), 3 deletions(-) diff --git a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs index 38fafa755..6472a8ec1 100644 --- a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs +++ b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs @@ -634,6 +634,95 @@ public IEnumerator Press_WithInvalidKey_Should_ReturnFailure() StringAssert.Contains("Invalid key name", lastResponse.Message); } + /// + /// Verifies that a bare digit key is rejected instead of being parsed as an enum ordinal, + /// and that the failure message offers both the Digit and Numpad candidates. + /// + [UnityTest] + public IEnumerator Press_WithBareDigitKey_Should_ReturnFailureSuggestingDigitAndNumpad() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "3" + }); + + Assert.IsFalse(lastResponse.Success, "A bare digit must not be accepted as a Key enum ordinal"); + StringAssert.Contains("Digit3", lastResponse.Message); + StringAssert.Contains("Numpad3", lastResponse.Message); + } + + /// + /// Verifies that a signed numeric key is rejected, closing the Enum.TryParse signed-ordinal path. + /// + [UnityTest] + public IEnumerator Press_WithSignedNumericKey_Should_ReturnFailure() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "+3" + }); + + Assert.IsFalse(lastResponse.Success, "A signed numeric key must not be accepted as a Key enum ordinal"); + } + + /// + /// Verifies that a comma-separated key list is rejected, closing the Enum.TryParse flag-OR path. + /// + [UnityTest] + public IEnumerator Press_WithCommaSeparatedKeyNames_Should_ReturnFailure() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "Space,Enter" + }); + + Assert.IsFalse(lastResponse.Success, "A comma-separated key list must not be OR-ed into a single key"); + } + + /// + /// Verifies that an out-of-range numeric key fails as a validation error instead of throwing + /// ArgumentOutOfRangeException from the keyboard indexer. + /// + [UnityTest] + public IEnumerator Press_WithUndefinedNumericKey_Should_ReturnFailure() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "300" + }); + + Assert.IsFalse(lastResponse.Success, "An undefined numeric key must fail validation rather than throw"); + } + + /// + /// Verifies that the existing Return-to-Enter alias still resolves after key names are whitelisted. + /// + [UnityTest] + public IEnumerator Press_WithReturnAlias_Should_Succeed() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "Return" + }); + + Assert.IsTrue(lastResponse.Success, lastResponse.Message); + } + [UnityTest] public IEnumerator Press_WithEmptyKey_Should_ReturnFailure() { diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs index 780e512a8..e6e490e0e 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs @@ -72,23 +72,39 @@ public async Task ExecuteAsync( return new SimulateKeyboardResponse { Success = false, - Message = "Key parameter is required. Examples: \"W\", \"Space\", \"LeftShift\", \"A\", \"Enter\".", + Message = "Key parameter is required. Examples: \"W\", \"Space\", \"LeftShift\", \"A\", \"Enter\", \"Digit3\".", Action = parameters.Action.ToString() }; } + // Why not Enum.TryParse alone: it also accepts ordinals ("3"), signed ordinals ("+3"), + // whitespace-padded input, comma-separated names OR-ed together ("Space,Enter"), and + // undefined ordinals ("300") that later throw from the keyboard indexer. Only a name + // that is defined on the Key enum may resolve to a key. string normalizedKey = NormalizeKeyName(parameters.Key); - if (!Enum.TryParse(normalizedKey, ignoreCase: true, out Key key) || key == Key.None) + bool isKnownKeyName = DefinedKeyNames.Contains(normalizedKey); + Key key = Key.None; + if (isKnownKeyName) + { + Enum.TryParse(normalizedKey, ignoreCase: true, out key); + } + + if (!isKnownKeyName || key == Key.None) { IReadOnlyList suggestions = KeyboardKeyNameSuggester.Suggest(parameters.Key); string suggestionText = suggestions.Count == 0 ? string.Empty : $" Did you mean: {string.Join(", ", suggestions)}?"; + // Why: digits used to resolve silently to unrelated keys, so earlier runs that + // reported success may have pressed something else and need to be re-checked. + string ordinalHistoryText = LooksLikeNumericKeyInput(parameters.Key) + ? " Digits are not key names: bare digits were previously parsed as enum ordinals (e.g. \"3\" pressed Tab), so re-check any earlier results or scripts that passed digits." + : string.Empty; return new SimulateKeyboardResponse { Success = false, Message = - $"Invalid key name: \"{parameters.Key}\". Use Input System Key enum names (e.g. \"W\", \"Space\", \"LeftShift\", \"A\", \"Enter\").{suggestionText}", + $"Invalid key name: \"{parameters.Key}\". Use Input System Key enum names (e.g. \"W\", \"Space\", \"LeftShift\", \"A\", \"Enter\", \"Digit3\").{suggestionText}{ordinalHistoryText}", Action = parameters.Action.ToString() }; } @@ -230,6 +246,39 @@ private static void EnsureOverlayExists() OverlayCanvasFactory.EnsureExists(); } + // Immutable whitelist of the names defined on the Input System Key enum, so key resolution + // never falls back to Enum.TryParse's ordinal and flag-combination behavior. + private static readonly HashSet DefinedKeyNames = + new(Enum.GetNames(typeof(Key)), StringComparer.OrdinalIgnoreCase); + + /// + /// Reports whether the raw key input is the numeric form that Enum.TryParse used to accept + /// as an enum ordinal, so the rejection can explain what earlier runs actually pressed. + /// + private static bool LooksLikeNumericKeyInput(string keyName) + { + string trimmed = keyName.Trim(); + if (trimmed.Length > 0 && (trimmed[0] == '+' || trimmed[0] == '-')) + { + trimmed = trimmed.Substring(1); + } + + if (trimmed.Length == 0) + { + return false; + } + + for (int index = 0; index < trimmed.Length; index++) + { + if (!char.IsDigit(trimmed[index])) + { + return false; + } + } + + return true; + } + private static string NormalizeKeyName(string keyName) { if (string.Equals(keyName, "Return", StringComparison.OrdinalIgnoreCase)) From cc6fa41d1594d36e4b3cf598ee0dbae00360b4cf Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 26 Jul 2026 23:15:52 +0900 Subject: [PATCH 2/5] test: Assert the numeric rejection guidance in simulate-keyboard tests Rejecting a digit is only half the contract: the message must also explain that bare digits used to resolve to unrelated keys, so earlier results get re-checked. Pin that text so it cannot regress while rejection still works. Applies to the numeric cases only. "Space,Enter" is not numeric input, so it is rejected without the digit guidance and its test must not assert it. --- Assets/Tests/PlayMode/SimulateKeyboardTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs index 6472a8ec1..0bc422858 100644 --- a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs +++ b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs @@ -652,6 +652,8 @@ public IEnumerator Press_WithBareDigitKey_Should_ReturnFailureSuggestingDigitAnd Assert.IsFalse(lastResponse.Success, "A bare digit must not be accepted as a Key enum ordinal"); StringAssert.Contains("Digit3", lastResponse.Message); StringAssert.Contains("Numpad3", lastResponse.Message); + StringAssert.Contains("Digits are not key names", lastResponse.Message); + StringAssert.Contains("re-check any earlier results", lastResponse.Message); } /// @@ -669,6 +671,8 @@ public IEnumerator Press_WithSignedNumericKey_Should_ReturnFailure() }); Assert.IsFalse(lastResponse.Success, "A signed numeric key must not be accepted as a Key enum ordinal"); + StringAssert.Contains("Digits are not key names", lastResponse.Message); + StringAssert.Contains("re-check any earlier results", lastResponse.Message); } /// @@ -704,6 +708,8 @@ public IEnumerator Press_WithUndefinedNumericKey_Should_ReturnFailure() }); Assert.IsFalse(lastResponse.Success, "An undefined numeric key must fail validation rather than throw"); + StringAssert.Contains("Digits are not key names", lastResponse.Message); + StringAssert.Contains("re-check any earlier results", lastResponse.Message); } /// From a51e1c2fe8088c9876781b3a488f952ef74d4961 Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 26 Jul 2026 23:17:58 +0900 Subject: [PATCH 3/5] fix: Keep the digit guidance accurate and pin its scope Self-review findings on the whitelist change. - Match ASCII digits explicitly. char.IsDigit is also true for non-ASCII digits, which Enum.TryParse never accepted as ordinals, so the message would have claimed an earlier run pressed another key when it did not. - Assert that a whitelisted name parses, stating the invariant the two-step resolution relies on. - Cover whitespace-padded digits (" 3 "), another input Enum.TryParse used to read as an ordinal. - Pin the comma-separated case: it must report an invalid key name and must not carry the digit guidance, which is scoped to numeric input. --- .../Tests/PlayMode/SimulateKeyboardTests.cs | 22 +++++++++++++++++++ .../SimulateKeyboardUseCase.cs | 7 ++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs index 0bc422858..a4f7a2fe2 100644 --- a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs +++ b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs @@ -690,6 +690,28 @@ public IEnumerator Press_WithCommaSeparatedKeyNames_Should_ReturnFailure() }); Assert.IsFalse(lastResponse.Success, "A comma-separated key list must not be OR-ed into a single key"); + StringAssert.Contains("Invalid key name", lastResponse.Message); + // The digit guidance is scoped to numeric input, so it must not appear here. + StringAssert.DoesNotContain("Digits are not key names", lastResponse.Message); + } + + /// + /// Verifies that a whitespace-padded digit is rejected, closing the Enum.TryParse path that + /// trimmed the input before reading it as an enum ordinal. + /// + [UnityTest] + public IEnumerator Press_WithPaddedDigitKey_Should_ReturnFailure() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = " 3 " + }); + + Assert.IsFalse(lastResponse.Success, "A whitespace-padded digit must not be accepted as a Key enum ordinal"); + StringAssert.Contains("Digits are not key names", lastResponse.Message); } /// diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs index e6e490e0e..3a67c7000 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs @@ -86,7 +86,8 @@ public async Task ExecuteAsync( Key key = Key.None; if (isKnownKeyName) { - Enum.TryParse(normalizedKey, ignoreCase: true, out key); + bool parsed = Enum.TryParse(normalizedKey, ignoreCase: true, out key); + UnityEngine.Debug.Assert(parsed, $"A whitelisted Key name must parse: {normalizedKey}"); } if (!isKnownKeyName || key == Key.None) @@ -270,7 +271,9 @@ private static bool LooksLikeNumericKeyInput(string keyName) for (int index = 0; index < trimmed.Length; index++) { - if (!char.IsDigit(trimmed[index])) + // Why not char.IsDigit: it is true for non-ASCII digits, which Enum.TryParse never + // accepted as ordinals. Claiming they used to press another key would be false. + if (trimmed[index] < '0' || trimmed[index] > '9') { return false; } From 37897808ef7cbd9a4756f74b7cc6ffee671b93bc Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 26 Jul 2026 23:22:14 +0900 Subject: [PATCH 4/5] fix: Keep accepting whitespace-padded key names The whitelist made padded correct input fail. Enum.TryParse used to trim, so "--key ' Space '" already worked and rejecting it would break valid scripts. Blocking ambiguous input must not narrow correct input. Trim once at the input entry point so the Return-to-Enter alias also sees the padded form. " 3 " still fails: after trimming it is "3", which is not a name defined on the Key enum, so the numeric block is unaffected. - Suggest candidates from the normalized name, while the error message keeps reporting the raw input verbatim - Cover padded valid names and the padded alias --- .../Tests/PlayMode/SimulateKeyboardTests.cs | 35 +++++++++++++++++++ .../SimulateKeyboardUseCase.cs | 12 +++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs index a4f7a2fe2..5ef089ec4 100644 --- a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs +++ b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs @@ -751,6 +751,41 @@ public IEnumerator Press_WithReturnAlias_Should_Succeed() Assert.IsTrue(lastResponse.Success, lastResponse.Message); } + /// + /// Verifies that a whitespace-padded valid key name keeps resolving, since padded correct + /// input was already accepted before key names were whitelisted. + /// + [UnityTest] + public IEnumerator Press_WithPaddedKeyName_Should_Succeed() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = " Space " + }); + + Assert.IsTrue(lastResponse.Success, lastResponse.Message); + } + + /// + /// Verifies that the Return-to-Enter alias still applies when the name is whitespace-padded. + /// + [UnityTest] + public IEnumerator Press_WithPaddedReturnAlias_Should_Succeed() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = " Return " + }); + + Assert.IsTrue(lastResponse.Success, lastResponse.Message); + } + [UnityTest] public IEnumerator Press_WithEmptyKey_Should_ReturnFailure() { diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs index 3a67c7000..dcf0ca4c0 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs @@ -92,7 +92,9 @@ public async Task ExecuteAsync( if (!isKnownKeyName || key == Key.None) { - IReadOnlyList suggestions = KeyboardKeyNameSuggester.Suggest(parameters.Key); + // Suggest from the normalized form so padding does not degrade the candidates, + // while the message below still reports the raw input verbatim. + IReadOnlyList suggestions = KeyboardKeyNameSuggester.Suggest(normalizedKey); string suggestionText = suggestions.Count == 0 ? string.Empty : $" Did you mean: {string.Join(", ", suggestions)}?"; @@ -284,11 +286,15 @@ private static bool LooksLikeNumericKeyInput(string keyName) private static string NormalizeKeyName(string keyName) { - if (string.Equals(keyName, "Return", StringComparison.OrdinalIgnoreCase)) + // Why trim here rather than at the whitelist comparison: Enum.TryParse used to accept + // whitespace-padded names, so padded correct input already worked. Blocking ambiguous + // input must not narrow correct input, and the alias has to see the padded form too. + string trimmed = keyName.Trim(); + if (string.Equals(trimmed, "Return", StringComparison.OrdinalIgnoreCase)) { return Key.Enter.ToString(); } - return keyName; + return trimmed; } #endif From 5ffb98e60f89f0e8f1f0ee2e2afde5faa84995bc Mon Sep 17 00:00:00 2001 From: hatayama Date: Sun, 26 Jul 2026 23:29:40 +0900 Subject: [PATCH 5/5] fix: Resolve key names through one lookup and keep digit hints ASCII-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-up on the key name whitelist. - Replace the name set plus Enum.TryParse round-trip with a single immutable name-to-value map, which drops the second lookup, the invariant assert, and the provisional key value. The Key.None guard stays: "None" is a defined name and still must not resolve. - Cover case-insensitive resolution, which no test pinned before: swapping the comparer for Ordinal left every test green while breaking "--key space". - Cover Digit3 itself, the name the rejection message advertises. - Match ASCII digits in KeyboardKeyNameSuggester as well. char.IsDigit is true for non-ASCII digits such as "3", so it suggested a Digit3 name that no Key value has. --- .../Editor/KeyboardKeyNameSuggesterTests.cs | 11 ++++++ .../Tests/PlayMode/SimulateKeyboardTests.cs | 36 +++++++++++++++++++ .../KeyboardKeyNameSuggester.cs | 4 ++- .../SimulateKeyboardUseCase.cs | 30 +++++++++------- 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/Assets/Tests/Editor/KeyboardKeyNameSuggesterTests.cs b/Assets/Tests/Editor/KeyboardKeyNameSuggesterTests.cs index 3e3fe00ac..d5ff020b0 100644 --- a/Assets/Tests/Editor/KeyboardKeyNameSuggesterTests.cs +++ b/Assets/Tests/Editor/KeyboardKeyNameSuggesterTests.cs @@ -19,6 +19,17 @@ public void Suggest_ForBareDigit_IncludesDigitAndNumpadNames() Assert.That(suggestions, Does.Contain("Numpad3")); } + // Verifies non-ASCII digits do not produce Digit/Numpad names that no Key enum value has. + [Test] + public void Suggest_ForFullWidthDigit_DoesNotSuggestDigitNames() + { + IReadOnlyList suggestions = KeyboardKeyNameSuggester.Suggest("3"); + + Assert.That(suggestions, Does.Not.Contain("Digit3")); + Assert.That(suggestions, Does.Not.Contain("Numpad3")); + Assert.That(suggestions.Any(name => name.StartsWith("Digit")), Is.False); + } + // Verifies partial key names still return close enum matches. [Test] public void Suggest_ForPartialName_ReturnsPrefixMatches() diff --git a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs index 5ef089ec4..a63ba4fd9 100644 --- a/Assets/Tests/PlayMode/SimulateKeyboardTests.cs +++ b/Assets/Tests/PlayMode/SimulateKeyboardTests.cs @@ -751,6 +751,42 @@ public IEnumerator Press_WithReturnAlias_Should_Succeed() Assert.IsTrue(lastResponse.Success, lastResponse.Message); } + /// + /// Verifies that key names resolve case-insensitively, which the whitelist must preserve + /// because Enum.TryParse was previously called with ignoreCase. + /// + [UnityTest] + public IEnumerator Press_WithLowercaseKeyName_Should_Succeed() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "space" + }); + + Assert.IsTrue(lastResponse.Success, lastResponse.Message); + } + + /// + /// Verifies that Digit3, the name the rejection message advertises for bare digits, is + /// actually accepted. + /// + [UnityTest] + public IEnumerator Press_WithDigitKeyName_Should_Succeed() + { + yield return null; + + yield return RunTool(new JObject + { + ["action"] = KeyboardAction.Press.ToString(), + ["key"] = "Digit3" + }); + + Assert.IsTrue(lastResponse.Success, lastResponse.Message); + } + /// /// Verifies that a whitespace-padded valid key name keeps resolving, since padded correct /// input was already accepted before key names were whitelisted. diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardKeyNameSuggester.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardKeyNameSuggester.cs index fdbfa27bf..9c4ec3dbf 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardKeyNameSuggester.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/KeyboardKeyNameSuggester.cs @@ -26,7 +26,9 @@ public static IReadOnlyList Suggest(string invalidKeyName) string trimmed = invalidKeyName.Trim(); List suggestions = new(); - if (trimmed.Length == 1 && char.IsDigit(trimmed[0])) + // Why not char.IsDigit: it is true for non-ASCII digits such as "3", which have no + // Digit/Numpad enum name, so suggesting them would point at keys that do not exist. + if (trimmed.Length == 1 && trimmed[0] >= '0' && trimmed[0] <= '9') { string digit = trimmed; AddUnique(suggestions, $"Digit{digit}"); diff --git a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs index dcf0ca4c0..6b7601e98 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateKeyboard/SimulateKeyboardUseCase.cs @@ -82,15 +82,7 @@ public async Task ExecuteAsync( // undefined ordinals ("300") that later throw from the keyboard indexer. Only a name // that is defined on the Key enum may resolve to a key. string normalizedKey = NormalizeKeyName(parameters.Key); - bool isKnownKeyName = DefinedKeyNames.Contains(normalizedKey); - Key key = Key.None; - if (isKnownKeyName) - { - bool parsed = Enum.TryParse(normalizedKey, ignoreCase: true, out key); - UnityEngine.Debug.Assert(parsed, $"A whitelisted Key name must parse: {normalizedKey}"); - } - - if (!isKnownKeyName || key == Key.None) + if (!DefinedKeysByName.TryGetValue(normalizedKey, out Key key) || key == Key.None) { // Suggest from the normalized form so padding does not degrade the candidates, // while the message below still reports the raw input verbatim. @@ -249,10 +241,22 @@ private static void EnsureOverlayExists() OverlayCanvasFactory.EnsureExists(); } - // Immutable whitelist of the names defined on the Input System Key enum, so key resolution - // never falls back to Enum.TryParse's ordinal and flag-combination behavior. - private static readonly HashSet DefinedKeyNames = - new(Enum.GetNames(typeof(Key)), StringComparer.OrdinalIgnoreCase); + // Immutable name-to-value map of the Input System Key enum, so key resolution never falls + // back to Enum.TryParse's ordinal and flag-combination behavior. + private static readonly IReadOnlyDictionary DefinedKeysByName = BuildDefinedKeysByName(); + + private static IReadOnlyDictionary BuildDefinedKeysByName() + { + string[] names = Enum.GetNames(typeof(Key)); + Array values = Enum.GetValues(typeof(Key)); + Dictionary keysByName = new(names.Length, StringComparer.OrdinalIgnoreCase); + for (int index = 0; index < names.Length; index++) + { + keysByName[names[index]] = (Key)values.GetValue(index); + } + + return keysByName; + } /// /// Reports whether the raw key input is the numeric form that Enum.TryParse used to accept