summaryrefslogtreecommitdiff
path: root/src/SMAPI.Internal.Patching/PatchHelper.cs
blob: edd8ef57f08f1da1b999fdf277f577d31978b108 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;

namespace StardewModdingAPI.Internal.Patching
{
    /// <summary>Provides utility methods for patching game code with Harmony.</summary>
    internal static class PatchHelper
    {
        /*********
        ** Public methods
        *********/
        /// <summary>Get a constructor and assert that it was found.</summary>
        /// <typeparam name="TTarget">The type containing the method.</typeparam>
        /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
        /// <exception cref="InvalidOperationException">The type has no matching constructor.</exception>
        public static ConstructorInfo RequireConstructor<TTarget>(Type[]? parameters = null)
        {
            return
                AccessTools.Constructor(typeof(TTarget), parameters)
                ?? throw new InvalidOperationException($"Can't find constructor {PatchHelper.GetMethodString(typeof(TTarget), null, parameters)} to patch.");
        }

        /// <summary>Get a method and assert that it was found.</summary>
        /// <typeparam name="TTarget">The type containing the method.</typeparam>
        /// <param name="name">The method name.</param>
        /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
        /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
        /// <exception cref="InvalidOperationException">The type has no matching method.</exception>
        public static MethodInfo RequireMethod<TTarget>(string name, Type[]? parameters = null, Type[]? generics = null)
        {
            return
                AccessTools.Method(typeof(TTarget), name, parameters, generics)
                ?? throw new InvalidOperationException($"Can't find method {PatchHelper.GetMethodString(typeof(TTarget), name, parameters, generics)} to patch.");
        }

        /// <summary>Get a human-readable representation of a method target.</summary>
        /// <param name="type">The type containing the method.</param>
        /// <param name="name">The method name, or <c>null</c> for a constructor.</param>
        /// <param name="parameters">The method parameter types, or <c>null</c> if it's not overloaded.</param>
        /// <param name="generics">The method generic types, or <c>null</c> if it's not generic.</param>
        public static string GetMethodString(Type type, string? name, Type[]? parameters = null, Type[]? generics = null)
        {
            StringBuilder str = new();

            // type
            str.Append(type.FullName);

            // method name (if not constructor)
            if (name != null)
            {
                str.Append('.');
                str.Append(name);
            }

            // generics
            if (generics?.Any() == true)
            {
                str.Append('<');
                str.Append(string.Join(", ", generics.Select(p => p.FullName)));
                str.Append('>');
            }

            // parameters
            if (parameters?.Any() == true)
            {
                str.Append('(');
                str.Append(string.Join(", ", parameters.Select(p => p.FullName)));
                str.Append(')');
            }

            return str.ToString();
        }
    }
}