diff --git a/Osu.Patcher.Hook/Patches/CustomStrings/AddEnumValueNames.cs b/Osu.Patcher.Hook/Patches/CustomStrings/AddEnumValueNames.cs
new file mode 100644
index 0000000..2146dc1
--- /dev/null
+++ b/Osu.Patcher.Hook/Patches/CustomStrings/AddEnumValueNames.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using HarmonyLib;
+using JetBrains.Annotations;
+using Osu.Stubs.Helpers;
+
+namespace Osu.Patcher.Hook.Patches.CustomStrings;
+
+///
+/// Patch Enum::ToString() to add additional enum values that can be stringified.
+///
+[OsuPatch]
+[HarmonyPatch]
+[UsedImplicitly]
+internal static class AddEnumValueNames
+{
+ [UsedImplicitly]
+ [HarmonyTargetMethod]
+ private static MethodBase Target() => typeof(Enum).Method(nameof(Enum.GetName));
+
+ [HarmonyPrefix]
+ [UsedImplicitly]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ private static bool Before(
+ ref string __result,
+ [HarmonyArgument(0)] Type enumType,
+ [HarmonyArgument(1)] object? value)
+ {
+ if (value == null) return true;
+
+ var valueIdx = Convert.ToUInt32(value);
+
+ if (enumType == OsuString.Class.Reference)
+ return !CustomStrings.OsuStringNames.TryGetValue(valueIdx, out __result);
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/Osu.Patcher.Hook/Patches/CustomStrings/AddLocalizedStrings.cs b/Osu.Patcher.Hook/Patches/CustomStrings/AddLocalizedStrings.cs
new file mode 100644
index 0000000..2349211
--- /dev/null
+++ b/Osu.Patcher.Hook/Patches/CustomStrings/AddLocalizedStrings.cs
@@ -0,0 +1,28 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using HarmonyLib;
+using JetBrains.Annotations;
+using Osu.Stubs.Helpers;
+
+namespace Osu.Patcher.Hook.Patches.CustomStrings;
+
+///
+/// Adds the new hardcoded OsuStrings into the LocalisationManager.
+///
+[OsuPatch]
+[HarmonyPatch]
+[UsedImplicitly]
+internal static class AddLocalizedStrings
+{
+ // Perhaps add localization support in the future?
+
+ [UsedImplicitly]
+ [HarmonyTargetMethod]
+ private static MethodBase Target() => LocalisationManager.GetString.Reference;
+
+ [HarmonyPrefix]
+ [UsedImplicitly]
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ private static bool Before(ref string __result, [HarmonyArgument(0)] int osuString) =>
+ !CustomStrings.OsuStrings.TryGetValue((uint)osuString, out __result);
+}
\ No newline at end of file
diff --git a/Osu.Patcher.Hook/Patches/CustomStrings/CustomStrings.cs b/Osu.Patcher.Hook/Patches/CustomStrings/CustomStrings.cs
new file mode 100644
index 0000000..463cce7
--- /dev/null
+++ b/Osu.Patcher.Hook/Patches/CustomStrings/CustomStrings.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using Osu.Utils.Extensions;
+
+namespace Osu.Patcher.Hook.Patches.CustomStrings;
+
+///
+/// Store custom string values to be injected into the game later.
+///
+internal static class CustomStrings
+{
+ ///
+ /// New OsuString value index -> new enum value name
+ ///
+ internal static readonly Dictionary OsuStringNames = new();
+
+ ///
+ /// New OsuString value index -> the localized value.
+ ///
+ internal static readonly Dictionary OsuStrings = new();
+
+ private static uint _lastOsuString = 0x00010000;
+
+ ///
+ /// Add a new value to be stringified if it is not already added.
+ ///
+ /// The name of the enum value. (So ToString() works properly)
+ /// The hardcoded localized string value (no multi-language support)
+ /// The value to use to reference this entry as enums are valuetypes.
+ internal static int AddOsuString(string name, string value)
+ {
+ if (OsuStringNames.TryGetKey(name, out var key))
+ return (int)key;
+
+ var idx = ++_lastOsuString;
+ OsuStringNames.Add(idx, name);
+ OsuStrings.Add(idx, value);
+ return (int)idx;
+ }
+}
\ No newline at end of file
diff --git a/Osu.Stubs/Helpers/LocalisationManager.cs b/Osu.Stubs/Helpers/LocalisationManager.cs
new file mode 100644
index 0000000..1b8fb8b
--- /dev/null
+++ b/Osu.Stubs/Helpers/LocalisationManager.cs
@@ -0,0 +1,35 @@
+using JetBrains.Annotations;
+using Osu.Utils.Lazy;
+using static System.Reflection.Emit.OpCodes;
+
+namespace Osu.Stubs.Helpers;
+
+///
+/// Original: osu_common.Helpers.LocalisationManager
+/// b20240123:
+///
+[PublicAPI]
+public class LocalisationManager
+{
+ ///
+ /// Original: GetString(OsuString stringType)
+ /// b20240123:
+ ///
+ [Stub]
+ public static readonly LazyMethod GetString = LazyMethod.ByPartialSignature(
+ "osu_common.Helpers.LocalisationManager::GetString(OsuString)",
+ [
+ Ldsfld,
+ Ldarg_0,
+ Callvirt,
+ Stloc_0,
+ Leave_S,
+ Pop,
+ Ldsfld,
+ Stloc_0,
+ Leave_S,
+ Ldloc_0,
+ Ret,
+ ]
+ );
+}
\ No newline at end of file
diff --git a/Osu.Stubs/Helpers/OsuString.cs b/Osu.Stubs/Helpers/OsuString.cs
new file mode 100644
index 0000000..26ec098
--- /dev/null
+++ b/Osu.Stubs/Helpers/OsuString.cs
@@ -0,0 +1,15 @@
+using JetBrains.Annotations;
+using Osu.Utils.Lazy;
+
+namespace Osu.Stubs.Helpers;
+
+[PublicAPI]
+public class OsuString
+{
+ ///
+ /// Original: osu_common.Helpers.OsuString
+ /// b20240123:
+ ///
+ [Stub]
+ public static readonly LazyType Class = LazyType.ByName("osu_common.Helpers.OsuString");
+}
\ No newline at end of file
diff --git a/Osu.Utils/Extensions/Extensions.cs b/Osu.Utils/Extensions/Extensions.cs
index 12626eb..c892e1d 100644
--- a/Osu.Utils/Extensions/Extensions.cs
+++ b/Osu.Utils/Extensions/Extensions.cs
@@ -5,6 +5,28 @@ namespace Osu.Utils.Extensions;
public static class Extensions
{
+ ///
+ /// Find the key for a value. This is inefficient.
+ ///
+ /// The dictionary to search in.
+ /// Value to search for.
+ /// Out value for the found key if returning true, otherwise default.
+ /// True if found.
+ public static bool TryGetKey(this Dictionary dict, TValue value, out TKey key)
+ {
+ foreach (var pair in dict)
+ {
+ if (!EqualityComparer.Default.Equals(pair.Value, value))
+ continue;
+
+ key = pair.Key;
+ return true;
+ }
+
+ key = default!;
+ return false;
+ }
+
///
/// Filters out null values in this sequence.
///