/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using HarmonyLib; using PeterHan.PLib.Core; using System; using System.Reflection; namespace PeterHan.PLib.PatchManager { /// /// Represents a method that will be run by PLib at a specific time to reduce the number /// of patches required and allow conditional integration with other mods. /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class PLibMethodAttribute : Attribute, IPLibAnnotation { /// /// Requires the specified assembly to be loaded for this method to run. If RequireType /// is null or empty, no particular types need to be defined in the assembly. The /// assembly name is required, but the version is optional (strong named assemblies /// can never load in ONI, since neither Unity nor Klei types are strong named...) /// public string RequireAssembly { get; set; } /// /// Requires the specified type full name (not assembly qualified name) to exist for /// this method to run. If RequireAssembly is null or empty, a type in any assembly /// will satisfy the requirement. /// public string RequireType { get; set; } /// /// When this method is run. /// public uint Runtime { get; } public PLibMethodAttribute(uint runtime) { Runtime = runtime; } /// /// Creates a new patch method instance. /// /// The method that was attributed. /// An instance that can execute this patch. public IPatchMethodInstance CreateInstance(MethodInfo method) { return new PLibMethodInstance(this, method); } public override string ToString() { return "PLibMethod[RunAt={0}]".F(RunAt.ToString(Runtime)); } } /// /// Refers to a single instance of the annotation, with its annotated method. /// internal sealed class PLibMethodInstance : IPatchMethodInstance { /// /// The attribute describing the method. /// public PLibMethodAttribute Descriptor { get; } /// /// The method to run. /// public MethodInfo Method { get; } public PLibMethodInstance(PLibMethodAttribute attribute, MethodInfo method) { Descriptor = attribute ?? throw new ArgumentNullException(nameof(attribute)); Method = method ?? throw new ArgumentNullException(nameof(method)); } /// /// Runs the method, passing the required parameters if any. /// /// The Harmony instance to use if the method wants to /// perform a patch. public void Run(Harmony instance) { if (PPatchManager.CheckConditions(Descriptor.RequireAssembly, Descriptor. RequireType, out Type requiredType)) { // Only runs once, no meaningful savings with a delegate var paramTypes = Method.GetParameterTypes(); int len = paramTypes.Length; if (len <= 0) // No parameters, static method only Method.Invoke(null, null); else if (paramTypes[0] == typeof(Harmony)) { if (len == 1) // Harmony instance parameter Method.Invoke(null, new object[] { instance }); else if (len == 2 && paramTypes[1] == typeof(Type)) // Type parameter Method.Invoke(null, new object[] { instance, requiredType }); } else PUtil.LogWarning("Invalid signature for PLibMethod - must have (), " + "(HarmonyInstance), or (HarmonyInstance, Type)"); } } } }