Skip to content

Commit

Permalink
feat(Patch): add custom OsuStrings to game
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine committed Apr 15, 2024
1 parent 67455fe commit 9573b9e
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 0 deletions.
39 changes: 39 additions & 0 deletions Osu.Patcher.Hook/Patches/CustomStrings/AddEnumValueNames.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Patch <c>Enum::ToString()</c> to add additional enum values that can be stringified.
/// </summary>
[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;
}
}
28 changes: 28 additions & 0 deletions Osu.Patcher.Hook/Patches/CustomStrings/AddLocalizedStrings.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Adds the new hardcoded <c>OsuString</c>s into the LocalisationManager.
/// </summary>
[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);
}
39 changes: 39 additions & 0 deletions Osu.Patcher.Hook/Patches/CustomStrings/CustomStrings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using Osu.Utils.Extensions;

namespace Osu.Patcher.Hook.Patches.CustomStrings;

/// <summary>
/// Store custom string values to be injected into the game later.
/// </summary>
internal static class CustomStrings
{
/// <summary>
/// New OsuString value index -> new enum value name
/// </summary>
internal static readonly Dictionary<uint, string> OsuStringNames = new();

/// <summary>
/// New OsuString value index -> the localized value.
/// </summary>
internal static readonly Dictionary<uint, string> OsuStrings = new();

private static uint _lastOsuString = 0x00010000;

/// <summary>
/// Add a new value to be stringified if it is not already added.
/// </summary>
/// <param name="name">The name of the enum value. (So <c>ToString()</c> works properly)</param>
/// <param name="value">The hardcoded localized string value (no multi-language support)</param>
/// <returns>The value to use to reference this entry as enums are valuetypes.</returns>
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;
}
}
35 changes: 35 additions & 0 deletions Osu.Stubs/Helpers/LocalisationManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using JetBrains.Annotations;
using Osu.Utils.Lazy;
using static System.Reflection.Emit.OpCodes;

namespace Osu.Stubs.Helpers;

/// <summary>
/// Original: <c>osu_common.Helpers.LocalisationManager</c>
/// b20240123: <c></c>
/// </summary>
[PublicAPI]
public class LocalisationManager
{
/// <summary>
/// Original: <c>GetString(OsuString stringType)</c>
/// b20240123: <c></c>
/// </summary>
[Stub]
public static readonly LazyMethod<string> GetString = LazyMethod<string>.ByPartialSignature(
"osu_common.Helpers.LocalisationManager::GetString(OsuString)",
[
Ldsfld,
Ldarg_0,
Callvirt,
Stloc_0,
Leave_S,
Pop,
Ldsfld,
Stloc_0,
Leave_S,
Ldloc_0,
Ret,
]
);
}
15 changes: 15 additions & 0 deletions Osu.Stubs/Helpers/OsuString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using JetBrains.Annotations;
using Osu.Utils.Lazy;

namespace Osu.Stubs.Helpers;

[PublicAPI]
public class OsuString
{
/// <summary>
/// Original: <c>osu_common.Helpers.OsuString</c>
/// b20240123: <c></c>
/// </summary>
[Stub]
public static readonly LazyType Class = LazyType.ByName("osu_common.Helpers.OsuString");
}
22 changes: 22 additions & 0 deletions Osu.Utils/Extensions/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ namespace Osu.Utils.Extensions;

public static class Extensions
{
/// <summary>
/// Find the key for a value. This is inefficient.
/// </summary>
/// <param name="dict">The dictionary to search in.</param>
/// <param name="value">Value to search for.</param>
/// <param name="key">Out value for the found key if returning true, otherwise <c>default</c>.</param>
/// <returns>True if found.</returns>
public static bool TryGetKey<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue value, out TKey key)
{
foreach (var pair in dict)
{
if (!EqualityComparer<TValue>.Default.Equals(pair.Value, value))
continue;

key = pair.Key;
return true;
}

key = default!;
return false;
}

/// <summary>
/// Filters out null values in this sequence.
/// </summary>
Expand Down

0 comments on commit 9573b9e

Please sign in to comment.