Skip to content

Commit

Permalink
refactor: improve reflection & lazy references
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine committed Mar 23, 2024
1 parent 88dedc1 commit 602f37c
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 51 deletions.
15 changes: 5 additions & 10 deletions Osu.Patcher.Hook/Patches/PatchFixDoubleSkipping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,18 @@ internal class PatchFixDoubleSkipping : BasePatch
{
[UsedImplicitly]
[HarmonyTargetMethod]
private static MethodBase Target() => Player.AllowDoubleSkip_get.Reference;
private static MethodBase Target() => Player.GetAllowDoubleSkip.Reference;

/// <summary>
/// Prefix patch to check if storyboard is enabled.
/// Prefix patch to disable double skipping if the storyboard is disabled.
/// </summary>
[UsedImplicitly]
[HarmonyPrefix]
[UsedImplicitly]
[SuppressMessage("ReSharper", "InconsistentNaming")]
private static bool Before(ref bool __result)
{
if (!(bool)EventManager.ShowStoryboard_backing.Reference.GetValue(null))
{
__result = false;

// Prevent original function from executing
return false;
}
if (!EventManager.ShowStoryboard.Get())
return __result = false;

return true;
}
Expand Down
8 changes: 4 additions & 4 deletions Osu.Patcher.Hook/Patches/PatchFixNoGroupSorting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ public static int GetSortMethodForGrouping(
int currentSortMode,
int newSortMode)
{
var beatmapTreeManager = SongSelection.beatmapTreeManager.Reference.GetValue(@this);
var currentGroupModeBindable = BeatmapTreeManager.CurrentGroupMode.Reference.GetValue(beatmapTreeManager);
var currentGroupMode = (int)Bindable.GetValue.Reference.Invoke(currentGroupModeBindable, new object[0]);
var beatmapTreeManager = SongSelection.BeatmapTreeManager.Get(@this);
var currentGroupModeBindable = BeatmapTreeManager.CurrentGroupMode.Get(beatmapTreeManager);
var currentGroupMode = Bindable.GetValue.Invoke<int>(currentGroupModeBindable);

// We only care if the grouping method changes
if (currentGroupMode == newGroupMode)
Expand All @@ -53,7 +53,7 @@ public static int GetSortMethodForGrouping(

[UsedImplicitly]
[HarmonyTargetMethod]
private static MethodBase Target() => SongSelection.choseBestSortMode.Reference;
private static MethodBase Target() => SongSelection.ChoseBestSortMode.Reference;

[UsedImplicitly]
[HarmonyTranspiler]
Expand Down
2 changes: 1 addition & 1 deletion Osu.Stubs/BeatmapTreeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static class BeatmapTreeManager
/// Original: <c>CurrentGroupMode</c> of type <c><![CDATA[ Bindable<TreeGroupMode> ]]></c>
/// b20240102.2: <c>#=zbF4rnAiPRGJl</c>
/// </summary>
public static readonly LazyField CurrentGroupMode = new(
public static readonly LazyField<object> CurrentGroupMode = new(
"BeatmapTreeManager#CurrentGroupMode",
() => SongSelection.FindReferences().CurrentGroupMode
);
Expand Down
4 changes: 2 additions & 2 deletions Osu.Stubs/Bindable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public static class Bindable
/// b20240102.2: <c>#=zHO4Uaog=</c>
/// </summary>
[UsedImplicitly]
public static readonly LazyMethod GetValue = new("Bindable#Value.get", () =>
public static readonly LazyMethod<object> GetValue = new("Bindable#Value.get", () =>
{
var instructions = MethodReader.GetInstructions(SongSelection.beatmapTreeManager_OnRightClicked.Reference);
var instructions = MethodReader.GetInstructions(SongSelection.BeatmapTreeManagerOnRightClicked.Reference);

// Get reference to Bindable:Value.get (property getter)
return (MethodBase)instructions
Expand Down
6 changes: 3 additions & 3 deletions Osu.Stubs/Color.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace Osu.Stubs;
/// </summary>
public static class Color
{
public static XnaColor Red => GetColor("Red");
public static XnaColor Orange => GetColor("Orange");
public static XnaColor GhostWhite => GetColor("GhostWhite");
public static readonly XnaColor Red = GetColor("Red");
public static readonly XnaColor Orange = GetColor("Orange");
public static readonly XnaColor GhostWhite = GetColor("GhostWhite");

private static XnaColor GetColor(string name)
{
Expand Down
10 changes: 4 additions & 6 deletions Osu.Stubs/EventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
using Osu.Stubs.Opcode;
using static System.Reflection.Emit.OpCodes;

// ReSharper disable InconsistentNaming UnusedType.Global UnusedType.Local UnusedMember.Local

namespace Osu.Stubs;

/// <summary>
Expand All @@ -21,7 +19,7 @@ public static class EventManager
/// </summary>
/// <returns></returns>
[UsedImplicitly]
public static readonly LazyMethod set_ShowStoryboard = new(
public static readonly LazyMethod SetShowStoryboard = new(
"EventManager#ShowStoryboard.set",
new[]
{
Expand All @@ -41,16 +39,16 @@ public static class EventManager

/// <summary>
/// The compiler generated backing field for the <c>ShowStoryboard</c> property.
/// See: <see cref="set_ShowStoryboard" />
/// See: <see cref="SetShowStoryboard" />
/// </summary>
[UsedImplicitly]
public static LazyField ShowStoryboard_backing = new(
public static LazyField<bool> ShowStoryboard = new(
"EventManager#ShowStoryboard.backing",
() =>
{
// Find a single StoreField instruction in the setter for this property
var instruction = MethodReader
.GetInstructions(set_ShowStoryboard.Reference)
.GetInstructions(SetShowStoryboard.Reference)
.Single(inst => inst.Opcode == Stsfld);

return (FieldInfo)instruction.Operand;
Expand Down
18 changes: 16 additions & 2 deletions Osu.Stubs/Opcode/LazyField.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
using System;
using System.Reflection;
using JetBrains.Annotations;

namespace Osu.Stubs.Opcode;

public class LazyField
/// <summary>
/// A reference to a field that gets located at runtime and interacted with reflectively.
/// </summary>
/// <typeparam name="T">The type this field should be treated as.</typeparam>
public class LazyField<T>
{
private readonly Lazy<FieldInfo?> _lazy;
private readonly string _name;

/// <summary>
/// A wrapper around Lazy for fields. <see cref="LazyMethod" />
/// A wrapper around Lazy for fields.
/// </summary>
/// <param name="name"><c>Class#Field</c> name of what this signature is matching.</param>
/// <param name="action">The lazy action to run when the value is needed.</param>
Expand All @@ -19,6 +24,7 @@ internal LazyField(string name, Func<FieldInfo?> action)
_lazy = new Lazy<FieldInfo?>(action);
}

[UsedImplicitly]
public FieldInfo Reference
{
get
Expand All @@ -31,4 +37,12 @@ public FieldInfo Reference
return value;
}
}

/// <summary>
/// Gets the current value of this field.
/// </summary>
/// <param name="instance">An instance of the field's enclosing class, if not static.</param>
/// <returns>The current field value casted to the type defined by this LazyField.</returns>
public T Get(object? instance = null) =>
(T)Reference.GetValue(instance);
}
45 changes: 45 additions & 0 deletions Osu.Stubs/Opcode/LazyMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

namespace Osu.Stubs.Opcode;

/// <summary>
/// A reference to a method that gets located at runtime and invoked reflectively.
/// </summary>
public class LazyMethod
{
private readonly Lazy<MethodBase?> _lazy;
Expand Down Expand Up @@ -44,4 +47,46 @@ public MethodBase Reference
return value;
}
}

/// <summary>
/// Find if not already cached and reflectively invoke this method. Does not return any value.
/// </summary>
/// <param name="instance">Instance of the enclosing class if this method is not static.</param>
/// <param name="parameters">Parameters to pass to the method, if any.</param>
public void Invoke(object? instance = null, object?[]? parameters = null) =>
Reference.Invoke(instance, parameters);
}

// ReSharper disable once InconsistentNaming
/// <summary>
/// A reference to a method that gets located at runtime and invoked reflectively.
/// </summary>
/// <typeparam name="R">A type that the return value of this method can be casted to.</typeparam>
public class LazyMethod<R> : LazyMethod
{
/// <inheritdoc />
internal LazyMethod(string name, IReadOnlyList<OpCode> signature)
: base(name, signature)
{
}

/// <inheritdoc />
internal LazyMethod(string name, Func<MethodBase?> action)
: base(name, action)
{
}

/// <summary>
/// Find if not already cached and reflectively invoke this method.
/// </summary>
/// <param name="instance">Instance of the enclosing class if this method is not static.</param>
/// <param name="parameters">Parameters to pass to the method, if any.</param>
/// <returns>The return value casted to the type defined by this LazyMethod.</returns>
public new R Invoke(object? instance = null, object?[]? parameters = null) =>
(R)Reference.Invoke(instance, parameters);

/// <inheritdoc cref="Invoke" />
/// <typeparam name="T">The return type of this method to cast to.</typeparam>
public T Invoke<T>(object? instance = null, object?[]? parameters = null) =>
(T)Reference.Invoke(instance, parameters);
}
5 changes: 3 additions & 2 deletions Osu.Stubs/Opcode/MethodReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using Osu.Stubs.Utils;

namespace Osu.Stubs.Opcode;

Expand All @@ -20,15 +21,15 @@ public static class MethodReader
public static IEnumerable<IlInstruction> GetInstructions(MethodBase method)
{
// List<HarmonyLib.ILInstruction>, HarmonyLib.ILInstruction is internal
var instructions = (IEnumerable<object>)MethodGetInstructions.Invoke(null, new object?[]
var instructions = MethodGetInstructions.Invoke<IEnumerable<object>>(null, new object?[]
{
/* ILGenerator generator = */ null,
/* MethodBase method = */ method,
});

return instructions.Select(inst => new IlInstruction
{
Opcode = (OpCode)FieldOpcode.GetValue(inst),
Opcode = FieldOpcode.GetValue<OpCode>(inst),
Operand = FieldOperand.GetValue(inst),
});
}
Expand Down
3 changes: 2 additions & 1 deletion Osu.Stubs/Opcode/OpCodeReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Osu.Stubs.Utils;

namespace Osu.Stubs.Opcode;

Expand All @@ -24,7 +25,7 @@ static OpCodeReader()

foreach (var field in fields)
{
var opcode = (OpCode)field.GetValue(null);
var opcode = field.GetValue<OpCode>(null);
if (opcode.OpCodeType == OpCodeType.Nternal)
continue;

Expand Down
10 changes: 4 additions & 6 deletions Osu.Stubs/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,21 @@
using Osu.Stubs.Opcode;
using static System.Reflection.Emit.OpCodes;

// ReSharper disable InconsistentNaming

namespace Osu.Stubs;

/// <summary>
/// Original: <c>osu.GameModes.Play.Player</c>
/// b20240102.2: <c>#=znsd474wIu4GAJ8swgEdrqaxwLN4O</c>
/// b20240124: <c>#=zOTWUr4vq60U15SRmD_JItyatbhdR</c>
/// </summary>
[UsedImplicitly]
public static class Player
{
/// <summary>
/// Original: <c>AllowDoubleSkip</c> (property getter)
/// b20240102.2: <c>#=zq3ahHKSwJLrHTBaQyw==</c>
/// Original: <c>AllowDoubleSkip.get</c> (property getter)
/// b20240124: <c>#=zp29IlAJ43g4WRArPQA==</c>
/// </summary>
[UsedImplicitly]
public static readonly LazyMethod AllowDoubleSkip_get = new(
public static readonly LazyMethod GetAllowDoubleSkip = new(
"Player#AllowDoubleSkip.get",
new[]
{
Expand Down
22 changes: 10 additions & 12 deletions Osu.Stubs/SongSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
using Osu.Stubs.Opcode;
using static System.Reflection.Emit.OpCodes;

// ReSharper disable InconsistentNaming UnusedType.Local UnusedType.Global UnusedMember.Local

namespace Osu.Stubs;

/// <summary>
Expand All @@ -20,7 +18,7 @@ public static class SongSelection
/// Original: <c>chooseBestSortMode(TreeGroupMode mode)</c>
/// b20240102.2: <c>#=zWDJY2KbbLKhn7OSo1w==</c>
/// </summary>
public static readonly LazyMethod choseBestSortMode = new(
public static readonly LazyMethod ChoseBestSortMode = new(
"SongSelection#chooseBestSortMode",
new[]
{
Expand All @@ -42,7 +40,7 @@ public static class SongSelection
/// b20240102.2: <c>#=zAmaE6G1Q0ysoWbGTpb40gD4dZN45</c>
/// </summary>
[UsedImplicitly]
public static readonly LazyMethod beatmapTreeManager_OnRightClicked = new(
public static readonly LazyMethod BeatmapTreeManagerOnRightClicked = new(
"SongSelection#beatmapTreeManager_OnRightClicked",
new[]
{
Expand All @@ -59,19 +57,19 @@ public static class SongSelection
);

/// <summary>
/// Original: <c>beatmapTreeManager</c> of type <see cref="BeatmapTreeManager" />
/// Original: <c>beatmapTreeManager</c> of type <see cref="Stubs.BeatmapTreeManager" />
/// b20240102.2: <c>#=zj0IgvXxTqseooUEFmQ==</c>
/// </summary>
[UsedImplicitly]
public static readonly LazyField beatmapTreeManager = new(
public static readonly LazyField<object> BeatmapTreeManager = new(
"SongSelection#beatmapTreeManager",
() => FindReferences().BeatmapTreeManager
);

internal static FoundReferences FindReferences()
{
var instructions = MethodReader
.GetInstructions(beatmapTreeManager_OnRightClicked.Reference)
.GetInstructions(BeatmapTreeManagerOnRightClicked.Reference)
.ToArray();

using var loadFieldInstructions = instructions
Expand All @@ -92,10 +90,10 @@ internal static FoundReferences FindReferences()
CurrentGroupMode = currentGroupModeField,
};
}
}

internal struct FoundReferences
{
public FieldInfo BeatmapTreeManager;
public FieldInfo CurrentGroupMode;
}
internal struct FoundReferences
{
public FieldInfo BeatmapTreeManager;
public FieldInfo CurrentGroupMode;
}
25 changes: 25 additions & 0 deletions Osu.Stubs/Utils/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Reflection;

namespace Osu.Stubs.Utils;

public static class ReflectionExtensions
{
/// <summary>
/// Reflectively get a field's value and cast it to a specific type.
/// </summary>
/// <param name="field">The target field.</param>
/// <param name="instance">Instance of the enclosing class, if any.</param>
/// <typeparam name="T">Field type to cast to.</typeparam>
public static T GetValue<T>(this FieldInfo field, object? instance)
=> (T)field.GetValue(instance);

/// <summary>
/// Reflectively invoke a method and cast the return value to a specific type.
/// </summary>
/// <param name="method">The target method.</param>
/// <param name="instance">Instance of the enclosing class, if any.</param>
/// <param name="parameters">Parameters to pass to the method, if any.</param>
/// <typeparam name="T">Return type to cast to.</typeparam>
public static T Invoke<T>(this MethodBase method, object? instance, object?[]? parameters)
=> (T)method.Invoke(instance, parameters);
}
Loading

0 comments on commit 602f37c

Please sign in to comment.