Initial commit
This commit is contained in:
38
mod/PLibCore/PatchManager/IPLibAnnotation.cs
Normal file
38
mod/PLibCore/PatchManager/IPLibAnnotation.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 System.Reflection;
|
||||
|
||||
namespace PeterHan.PLib.PatchManager {
|
||||
/// <summary>
|
||||
/// The commmon parent of [PLibPatch] and [PLibMethod].
|
||||
/// </summary>
|
||||
internal interface IPLibAnnotation {
|
||||
/// <summary>
|
||||
/// When this method is run.
|
||||
/// </summary>
|
||||
uint Runtime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new patch method instance.
|
||||
/// </summary>
|
||||
/// <param name="method">The method that was attributed.</param>
|
||||
/// <returns>An instance that can execute this patch.</returns>
|
||||
IPatchMethodInstance CreateInstance(MethodInfo method);
|
||||
}
|
||||
}
|
||||
33
mod/PLibCore/PatchManager/IPatchMethodInstance.cs
Normal file
33
mod/PLibCore/PatchManager/IPatchMethodInstance.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
namespace PeterHan.PLib.PatchManager {
|
||||
/// <summary>
|
||||
/// Refers to a single instance of the annotation, with its annotated method.
|
||||
/// </summary>
|
||||
public interface IPatchMethodInstance {
|
||||
/// <summary>
|
||||
/// Runs the patch or method if the conditions are met. This method should check its
|
||||
/// preconditions before executing the target.
|
||||
/// </summary>
|
||||
/// <param name="instance">The Harmony instance to use.</param>
|
||||
void Run(Harmony instance);
|
||||
}
|
||||
}
|
||||
115
mod/PLibCore/PatchManager/PLibMethodAttribute.cs
Normal file
115
mod/PLibCore/PatchManager/PLibMethodAttribute.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 {
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class PLibMethodAttribute : Attribute, IPLibAnnotation {
|
||||
/// <summary>
|
||||
/// 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...)
|
||||
/// </summary>
|
||||
public string RequireAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string RequireType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this method is run.
|
||||
/// </summary>
|
||||
public uint Runtime { get; }
|
||||
|
||||
public PLibMethodAttribute(uint runtime) {
|
||||
Runtime = runtime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new patch method instance.
|
||||
/// </summary>
|
||||
/// <param name="method">The method that was attributed.</param>
|
||||
/// <returns>An instance that can execute this patch.</returns>
|
||||
public IPatchMethodInstance CreateInstance(MethodInfo method) {
|
||||
return new PLibMethodInstance(this, method);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "PLibMethod[RunAt={0}]".F(RunAt.ToString(Runtime));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refers to a single instance of the annotation, with its annotated method.
|
||||
/// </summary>
|
||||
internal sealed class PLibMethodInstance : IPatchMethodInstance {
|
||||
/// <summary>
|
||||
/// The attribute describing the method.
|
||||
/// </summary>
|
||||
public PLibMethodAttribute Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method to run.
|
||||
/// </summary>
|
||||
public MethodInfo Method { get; }
|
||||
|
||||
public PLibMethodInstance(PLibMethodAttribute attribute, MethodInfo method) {
|
||||
Descriptor = attribute ?? throw new ArgumentNullException(nameof(attribute));
|
||||
Method = method ?? throw new ArgumentNullException(nameof(method));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the method, passing the required parameters if any.
|
||||
/// </summary>
|
||||
/// <param name="instance">The Harmony instance to use if the method wants to
|
||||
/// perform a patch.</param>
|
||||
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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
312
mod/PLibCore/PatchManager/PLibPatchAttribute.cs
Normal file
312
mod/PLibCore/PatchManager/PLibPatchAttribute.cs
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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 {
|
||||
/// <summary>
|
||||
/// Represents a method that will be patched by PLib at a specific time to allow
|
||||
/// conditional integration with other mods.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
|
||||
public sealed class PLibPatchAttribute : Attribute, IPLibAnnotation {
|
||||
/// <summary>
|
||||
/// The required argument types. If null, any matching method name is patched, or an
|
||||
/// exception thrown if more than one matches.
|
||||
/// </summary>
|
||||
public Type[] ArgumentTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this flag is set, the patch will emit only at DEBUG level if the target method
|
||||
/// is not found or matches ambiguously.
|
||||
/// </summary>
|
||||
public bool IgnoreOnFail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the method to patch.
|
||||
/// </summary>
|
||||
public string MethodName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of patch to apply through Harmony.
|
||||
/// </summary>
|
||||
public HarmonyPatchType PatchType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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...)
|
||||
/// </summary>
|
||||
public string RequireAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string RequireType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When this method is run.
|
||||
/// </summary>
|
||||
public uint Runtime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The type to patch. If null, the patcher will try to use the required type from the
|
||||
/// RequireType parameter.
|
||||
/// </summary>
|
||||
public Type TargetType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Patches a concrete type and method.
|
||||
///
|
||||
/// Passing null as the method name will attempt to patch a constructor. Only one
|
||||
/// declared constructor may be present, or the call will fail at patch time.
|
||||
/// </summary>
|
||||
/// <param name="runtime">When to apply the patch.</param>
|
||||
/// <param name="target">The type to patch.</param>
|
||||
/// <param name="method">The method name to patch.</param>
|
||||
public PLibPatchAttribute(uint runtime, Type target, string method) {
|
||||
ArgumentTypes = null;
|
||||
IgnoreOnFail = false;
|
||||
MethodName = method;
|
||||
PatchType = HarmonyPatchType.All;
|
||||
Runtime = runtime;
|
||||
TargetType = target ?? throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches a concrete type and overloaded method.
|
||||
///
|
||||
/// Passing null as the method name will attempt to patch a constructor.
|
||||
/// </summary>
|
||||
/// <param name="runtime">When to apply the patch.</param>
|
||||
/// <param name="target">The type to patch.</param>
|
||||
/// <param name="method">The method name to patch.</param>
|
||||
/// <param name="argTypes">The types of the overload to patch.</param>
|
||||
public PLibPatchAttribute(uint runtime, Type target, string method,
|
||||
params Type[] argTypes) {
|
||||
ArgumentTypes = argTypes;
|
||||
IgnoreOnFail = false;
|
||||
MethodName = method;
|
||||
PatchType = HarmonyPatchType.All;
|
||||
Runtime = runtime;
|
||||
TargetType = target ?? throw new ArgumentNullException(nameof(target));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches a method only if a specified type is available. Use optional parameters to
|
||||
/// specify the type to patch using RequireType / RequireAssembly.
|
||||
///
|
||||
/// Passing null as the method name will attempt to patch a constructor. Only one
|
||||
/// declared constructor may be present, or the call will fail at patch time.
|
||||
/// </summary>
|
||||
/// <param name="runtime">When to apply the patch.</param>
|
||||
/// <param name="method">The method name to patch.</param>
|
||||
public PLibPatchAttribute(uint runtime, string method) {
|
||||
ArgumentTypes = null;
|
||||
IgnoreOnFail = false;
|
||||
MethodName = method;
|
||||
PatchType = HarmonyPatchType.All;
|
||||
Runtime = runtime;
|
||||
TargetType = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patches an overloaded method only if a specified type is available. Use optional
|
||||
/// parameters to specify the type to patch using RequireType / RequireAssembly.
|
||||
///
|
||||
/// Passing null as the method name will attempt to patch a constructor.
|
||||
/// </summary>
|
||||
/// <param name="runtime">When to apply the patch.</param>
|
||||
/// <param name="method">The method name to patch.</param>
|
||||
/// <param name="argTypes">The types of the overload to patch.</param>
|
||||
public PLibPatchAttribute(uint runtime, string method, params Type[] argTypes) {
|
||||
ArgumentTypes = argTypes;
|
||||
IgnoreOnFail = false;
|
||||
MethodName = method;
|
||||
PatchType = HarmonyPatchType.All;
|
||||
Runtime = runtime;
|
||||
TargetType = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new patch method instance.
|
||||
/// </summary>
|
||||
/// <param name="method">The method that was attributed.</param>
|
||||
/// <returns>An instance that can execute this patch.</returns>
|
||||
public IPatchMethodInstance CreateInstance(MethodInfo method) {
|
||||
return new PLibPatchInstance(this, method);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "PLibPatch[RunAt={0},PatchType={1},MethodName={2}]".F(RunAt.ToString(
|
||||
Runtime), PatchType, MethodName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refers to a single instance of the annotation, with its annotated method.
|
||||
/// </summary>
|
||||
internal sealed class PLibPatchInstance : IPatchMethodInstance {
|
||||
/// <summary>
|
||||
/// The attribute describing the method.
|
||||
/// </summary>
|
||||
public PLibPatchAttribute Descriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The method to run.
|
||||
/// </summary>
|
||||
public MethodInfo Method { get; }
|
||||
|
||||
public PLibPatchInstance(PLibPatchAttribute attribute, MethodInfo method) {
|
||||
Descriptor = attribute ?? throw new ArgumentNullException(nameof(attribute));
|
||||
Method = method ?? throw new ArgumentNullException(nameof(method));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the patch type to perform.
|
||||
/// </summary>
|
||||
/// <returns>The type of Harmony patch to use for this method.</returns>
|
||||
private HarmonyPatchType GetPatchType() {
|
||||
var patchType = Descriptor.PatchType;
|
||||
if (patchType == HarmonyPatchType.All) {
|
||||
// Auto-determine the patch type based on name, if possible
|
||||
string patchName = Method.Name;
|
||||
foreach (var value in Enum.GetValues(typeof(HarmonyPatchType)))
|
||||
if (value is HarmonyPatchType eval && eval != patchType && patchName.
|
||||
EndsWith(eval.ToString(), StringComparison.Ordinal)) {
|
||||
patchType = eval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return patchType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified instance constructor.
|
||||
/// </summary>
|
||||
/// <param name="targetType">The type to be constructed.</param>
|
||||
/// <returns>The target constructor.</returns>
|
||||
/// <exception cref="AmbiguousMatchException">If no parameter types were specified,
|
||||
/// and multiple declared constructors exist.</exception>
|
||||
private MethodBase GetTargetConstructor(Type targetType, Type[] argumentTypes) {
|
||||
MethodBase constructor;
|
||||
if (argumentTypes == null) {
|
||||
var cons = targetType.GetConstructors(PPatchManager.FLAGS | BindingFlags.
|
||||
Instance);
|
||||
if (cons == null || cons.Length != 1)
|
||||
throw new InvalidOperationException("No constructor for {0} found".F(
|
||||
targetType.FullName));
|
||||
constructor = cons[0];
|
||||
} else
|
||||
constructor = targetType.GetConstructor(PPatchManager.FLAGS | BindingFlags.
|
||||
Instance, null, argumentTypes, null);
|
||||
return constructor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the target method to patch.
|
||||
/// </summary>
|
||||
/// <param name="requiredType">The type to use if no type was specified.</param>
|
||||
/// <returns>The method to patch.</returns>
|
||||
/// <exception cref="AmbiguousMatchException">If no parameter types were specified,
|
||||
/// and multiple options match the method name.</exception>
|
||||
/// <exception cref="InvalidOperationException">If the target method was not found.</exception>
|
||||
private MethodBase GetTargetMethod(Type requiredType) {
|
||||
var targetType = Descriptor.TargetType;
|
||||
var argumentTypes = Descriptor.ArgumentTypes;
|
||||
string name = Descriptor.MethodName;
|
||||
MethodBase method;
|
||||
if (targetType == null)
|
||||
targetType = requiredType;
|
||||
// Only allow non-inherited members, patching inherited members gets spooky on
|
||||
// Mac OS and Linux
|
||||
if (targetType == null)
|
||||
throw new InvalidOperationException("No type specified to patch");
|
||||
if (string.IsNullOrEmpty(name) || name == ".ctor")
|
||||
// Constructor
|
||||
method = GetTargetConstructor(targetType, argumentTypes);
|
||||
else
|
||||
// Method
|
||||
method = (argumentTypes == null) ? targetType.GetMethod(name, PPatchManager.
|
||||
FLAGS_EITHER) : targetType.GetMethod(name, PPatchManager.FLAGS_EITHER,
|
||||
null, argumentTypes, null);
|
||||
if (method == null)
|
||||
throw new InvalidOperationException("Method {0}.{1} not found".F(targetType.
|
||||
FullName, name));
|
||||
return method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message at debug level if Ignore On Patch Fail is enabled.
|
||||
/// </summary>
|
||||
/// <param name="e">The exception thrown during patching.</param>
|
||||
/// <returns>true to suppress the exception, or false to rethrow it.</returns>
|
||||
private bool LogIgnoreOnFail(Exception e) {
|
||||
bool ignore = Descriptor.IgnoreOnFail;
|
||||
if (ignore)
|
||||
PUtil.LogDebug("Patch for {0} not applied: {1}".F(Descriptor.
|
||||
MethodName, e.Message));
|
||||
return ignore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the patch.
|
||||
/// </summary>
|
||||
/// <param name="instance">The Harmony instance to use.</param>
|
||||
/// <exception cref="InvalidOperationException">If the </exception>
|
||||
/// <exception cref="AmbiguousMatchException">If no parameter types were specified,
|
||||
/// and multiple options match the method name.</exception>
|
||||
public void Run(Harmony instance) {
|
||||
if (PPatchManager.CheckConditions(Descriptor.RequireAssembly, Descriptor.
|
||||
RequireType, out Type requiredType)) {
|
||||
var dest = new HarmonyMethod(Method);
|
||||
if (instance == null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
try {
|
||||
var method = GetTargetMethod(requiredType);
|
||||
switch (GetPatchType()) {
|
||||
case HarmonyPatchType.Postfix:
|
||||
instance.Patch(method, postfix: dest);
|
||||
break;
|
||||
case HarmonyPatchType.Prefix:
|
||||
instance.Patch(method, prefix: dest);
|
||||
break;
|
||||
case HarmonyPatchType.Transpiler:
|
||||
instance.Patch(method, transpiler: dest);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(HarmonyPatchType));
|
||||
}
|
||||
} catch (AmbiguousMatchException e) {
|
||||
// Multi catch or filtering is not available in this version of C#
|
||||
if (!LogIgnoreOnFail(e))
|
||||
throw;
|
||||
} catch (InvalidOperationException e) {
|
||||
if (!LogIgnoreOnFail(e))
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
268
mod/PLibCore/PatchManager/PPatchManager.cs
Normal file
268
mod/PLibCore/PatchManager/PPatchManager.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
using PrivateRunList = System.Collections.Generic.ICollection<PeterHan.PLib.PatchManager.
|
||||
IPatchMethodInstance>;
|
||||
|
||||
namespace PeterHan.PLib.PatchManager {
|
||||
/// <summary>
|
||||
/// Manages patches that PLib will conditionally apply.
|
||||
/// </summary>
|
||||
public sealed class PPatchManager : PForwardedComponent {
|
||||
/// <summary>
|
||||
/// The base flags to use when matching instance or static methods.
|
||||
/// </summary>
|
||||
internal const BindingFlags FLAGS = PPatchTools.BASE_FLAGS | BindingFlags.DeclaredOnly;
|
||||
|
||||
/// <summary>
|
||||
/// The flags to use when matching instance and static methods.
|
||||
/// </summary>
|
||||
internal const BindingFlags FLAGS_EITHER = FLAGS | BindingFlags.Static | BindingFlags.
|
||||
Instance;
|
||||
|
||||
/// <summary>
|
||||
/// The version of this component. Uses the running PLib version.
|
||||
/// </summary>
|
||||
internal static readonly Version VERSION = new Version(PVersion.VERSION);
|
||||
|
||||
/// <summary>
|
||||
/// The instantiated copy of this class.
|
||||
/// </summary>
|
||||
internal static PPatchManager Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// true if the AfterModsLoad patches have been run, or false otherwise.
|
||||
/// </summary>
|
||||
private static volatile bool afterModsLoaded = false;
|
||||
|
||||
private static void Game_DestroyInstances_Postfix() {
|
||||
Instance?.InvokeAllProcess(RunAt.OnEndGame, null);
|
||||
}
|
||||
|
||||
private static void Game_OnPrefabInit_Postfix() {
|
||||
Instance?.InvokeAllProcess(RunAt.OnStartGame, null);
|
||||
}
|
||||
|
||||
private static void Initialize_Prefix() {
|
||||
Instance?.InvokeAllProcess(RunAt.BeforeDbInit, null);
|
||||
}
|
||||
|
||||
private static void Initialize_Postfix() {
|
||||
Instance?.InvokeAllProcess(RunAt.AfterDbInit, null);
|
||||
}
|
||||
|
||||
private static void Instance_Postfix() {
|
||||
bool load = false;
|
||||
if (Instance != null)
|
||||
lock (VERSION) {
|
||||
if (!afterModsLoaded)
|
||||
load = afterModsLoaded = true;
|
||||
}
|
||||
if (load)
|
||||
Instance.InvokeAllProcess(RunAt.AfterLayerableLoad, null);
|
||||
}
|
||||
|
||||
private static void MainMenu_OnSpawn_Postfix() {
|
||||
Instance?.InvokeAllProcess(RunAt.InMainMenu, null);
|
||||
}
|
||||
|
||||
public override Version Version => VERSION;
|
||||
|
||||
/// <summary>
|
||||
/// The Harmony instance to use for patching.
|
||||
/// </summary>
|
||||
private readonly Harmony harmony;
|
||||
|
||||
/// <summary>
|
||||
/// Patches and delegates to be run at specific points in the runtime. Put the kibosh
|
||||
/// on patching Db.Initialize()!
|
||||
/// </summary>
|
||||
private readonly IDictionary<uint, PrivateRunList> patches;
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the conditions for a method running are met.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly name that must be present, or null if none is required.</param>
|
||||
/// <param name="typeName">The type full name that must be present, or null if none is required.</param>
|
||||
/// <param name="requiredType">The type that was required, if typeName was not null or empty.</param>
|
||||
/// <returns>true if the requirements are met, or false otherwise.</returns>
|
||||
internal static bool CheckConditions(string assemblyName, string typeName,
|
||||
out Type requiredType) {
|
||||
bool ok = false, emptyType = string.IsNullOrEmpty(typeName);
|
||||
if (string.IsNullOrEmpty(assemblyName)) {
|
||||
if (emptyType) {
|
||||
requiredType = null;
|
||||
ok = true;
|
||||
} else {
|
||||
requiredType = PPatchTools.GetTypeSafe(typeName);
|
||||
ok = requiredType != null;
|
||||
}
|
||||
} else if (emptyType) {
|
||||
requiredType = null;
|
||||
// Search for assembly only, by name
|
||||
foreach (var candidate in AppDomain.CurrentDomain.GetAssemblies())
|
||||
if (candidate.GetName().Name == assemblyName) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
requiredType = PPatchTools.GetTypeSafe(typeName, assemblyName);
|
||||
ok = requiredType != null;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a patch manager to execute patches at specific times.
|
||||
///
|
||||
/// Create this instance in OnLoad() and use RegisterPatchClass to register a
|
||||
/// patch class.
|
||||
/// </summary>
|
||||
/// <param name="harmony">The Harmony instance to use for patching.</param>
|
||||
public PPatchManager(Harmony harmony) {
|
||||
if (harmony == null) {
|
||||
PUtil.LogWarning("Use the Harmony instance from OnLoad to create PPatchManager");
|
||||
harmony = new Harmony("PLib.PostLoad." + Assembly.GetExecutingAssembly().
|
||||
GetNameSafe());
|
||||
}
|
||||
this.harmony = harmony;
|
||||
patches = new Dictionary<uint, PrivateRunList>(8);
|
||||
InstanceData = patches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedules a patch method instance to be run.
|
||||
/// </summary>
|
||||
/// <param name="when">When to run the patch.</param>
|
||||
/// <param name="instance">The patch method instance to run.</param>
|
||||
/// <param name="harmony">The Harmony instance to use for patching.</param>
|
||||
private void AddHandler(uint when, IPatchMethodInstance instance) {
|
||||
if (!patches.TryGetValue(when, out PrivateRunList atTime))
|
||||
patches.Add(when, atTime = new List<IPatchMethodInstance>(16));
|
||||
atTime.Add(instance);
|
||||
}
|
||||
|
||||
public override void Initialize(Harmony plibInstance) {
|
||||
Instance = this;
|
||||
|
||||
// Db
|
||||
plibInstance.Patch(typeof(Db), nameof(Db.Initialize), prefix: PatchMethod(nameof(
|
||||
Initialize_Prefix)), postfix: PatchMethod(nameof(Initialize_Postfix)));
|
||||
|
||||
// Game
|
||||
plibInstance.Patch(typeof(Game), "DestroyInstances", postfix: PatchMethod(nameof(
|
||||
Game_DestroyInstances_Postfix)));
|
||||
plibInstance.Patch(typeof(Game), "OnPrefabInit", postfix: PatchMethod(nameof(
|
||||
Game_OnPrefabInit_Postfix)));
|
||||
|
||||
// GlobalResources
|
||||
plibInstance.Patch(typeof(GlobalResources), "Instance", postfix:
|
||||
PatchMethod(nameof(Instance_Postfix)));
|
||||
|
||||
// MainMenu
|
||||
plibInstance.Patch(typeof(MainMenu), "OnSpawn", postfix: PatchMethod(
|
||||
nameof(MainMenu_OnSpawn_Postfix)));
|
||||
}
|
||||
|
||||
public override void PostInitialize(Harmony plibInstance) {
|
||||
InvokeAllProcess(RunAt.AfterModsLoad, null);
|
||||
}
|
||||
|
||||
public override void Process(uint when, object _) {
|
||||
if (patches.TryGetValue(when, out PrivateRunList atTime) && atTime != null &&
|
||||
atTime.Count > 0) {
|
||||
string stage = RunAt.ToString(when);
|
||||
#if DEBUG
|
||||
PRegistry.LogPatchDebug("Executing {0:D} handler(s) from {1} for stage {2}".F(
|
||||
atTime.Count, Assembly.GetExecutingAssembly().GetNameSafe() ?? "?", stage));
|
||||
#endif
|
||||
foreach (var patch in atTime)
|
||||
try {
|
||||
patch.Run(harmony);
|
||||
} catch (TargetInvocationException e) {
|
||||
// Use the inner exception
|
||||
PUtil.LogError("Error running patches for stage " + stage + ":");
|
||||
PUtil.LogException(e.GetBaseException());
|
||||
} catch (Exception e) {
|
||||
// Say which mod's postload crashed
|
||||
PUtil.LogError("Error running patches for stage " + stage + ":");
|
||||
PUtil.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a single patch to be run by Patch Manager. Obviously, the patch must be
|
||||
/// registered before the time that it is used.
|
||||
/// </summary>
|
||||
/// <param name="when">The time when the method should be run.</param>
|
||||
/// <param name="patch">The patch to execute.</param>
|
||||
public void RegisterPatch(uint when, IPatchMethodInstance patch) {
|
||||
RegisterForForwarding();
|
||||
if (patch == null)
|
||||
throw new ArgumentNullException(nameof(patch));
|
||||
if (when == RunAt.Immediately)
|
||||
// Now now now!
|
||||
patch.Run(harmony);
|
||||
else
|
||||
AddHandler(when, patch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a class containing methods for [PLibPatch] and [PLibMethod] handlers.
|
||||
/// All methods, public and private, of the type will be searched for annotations.
|
||||
/// However, nested and derived types will not be searched, nor will inherited methods.
|
||||
///
|
||||
/// This method cannot be used to register a class from another mod, as the annotations
|
||||
/// on those methods would have a different assembly qualified name and would thus
|
||||
/// not be recognized.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to register.</param>
|
||||
/// <param name="harmony">The Harmony instance to use for immediate patches. Use
|
||||
/// the instance provided from UserMod2.OnLoad().</param>
|
||||
public void RegisterPatchClass(Type type) {
|
||||
int count = 0;
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
RegisterForForwarding();
|
||||
foreach (var method in type.GetMethods(FLAGS | BindingFlags.Static))
|
||||
foreach (var attrib in method.GetCustomAttributes(true))
|
||||
if (attrib is IPLibAnnotation pm) {
|
||||
var when = pm.Runtime;
|
||||
var instance = pm.CreateInstance(method);
|
||||
if (when == RunAt.Immediately)
|
||||
// Now now now!
|
||||
instance.Run(harmony);
|
||||
else
|
||||
AddHandler(pm.Runtime, instance);
|
||||
count++;
|
||||
}
|
||||
if (count > 0)
|
||||
PRegistry.LogPatchDebug("Registered {0:D} handler(s) for {1}".F(count,
|
||||
Assembly.GetCallingAssembly().GetNameSafe() ?? "?"));
|
||||
else
|
||||
PRegistry.LogPatchWarning("RegisterPatchClass could not find any handlers!");
|
||||
}
|
||||
}
|
||||
}
|
||||
93
mod/PLibCore/PatchManager/RunAt.cs
Normal file
93
mod/PLibCore/PatchManager/RunAt.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
namespace PeterHan.PLib.PatchManager {
|
||||
/// <summary>
|
||||
/// Describes when a PLibPatch or PLibMethod should be invoked.
|
||||
///
|
||||
/// Due to a bug in ILRepack an enum type in PLib cannot be used as a parameter for a
|
||||
/// custom attribute. ILmerge does not have this bug.
|
||||
/// </summary>
|
||||
public static class RunAt {
|
||||
/// <summary>
|
||||
/// Runs the method/patch now.
|
||||
///
|
||||
/// Note that mods may load in any order and thus not all mods may be initialized at
|
||||
/// this time.
|
||||
/// </summary>
|
||||
public const uint Immediately = 0U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs after all mods load, but before most other aspects of the game (including
|
||||
/// Assets, Db, and so forth) are initialized. This will run before any other mod
|
||||
/// has their UserMod2.AfterModsLoad executed. All PLib components will be initialized
|
||||
/// by this point.
|
||||
/// </summary>
|
||||
public const uint AfterModsLoad = 1U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs immediately before Db.Initialize.
|
||||
/// </summary>
|
||||
public const uint BeforeDbInit = 2U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs immediately after Db.Initialize.
|
||||
/// </summary>
|
||||
public const uint AfterDbInit = 3U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs when the main menu has loaded.
|
||||
/// </summary>
|
||||
public const uint InMainMenu = 4U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs when Game.OnPrefabInit has completed.
|
||||
/// </summary>
|
||||
public const uint OnStartGame = 5U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs when Game.DestroyInstances is executed.
|
||||
/// </summary>
|
||||
public const uint OnEndGame = 6U;
|
||||
|
||||
/// <summary>
|
||||
/// Runs after all mod data (including layerable files like world gen and codex/
|
||||
/// elements) are loaded. This comes after all UserMod2.AfterModsLoad handlers execute.
|
||||
/// All PLib components will be initialized by this point.
|
||||
/// </summary>
|
||||
public const uint AfterLayerableLoad = 7U;
|
||||
|
||||
/// <summary>
|
||||
/// The string equivalents of each constant for debugging.
|
||||
/// </summary>
|
||||
private static readonly string[] STRING_VALUES = new string[] {
|
||||
nameof(Immediately), nameof(AfterModsLoad), nameof(BeforeDbInit),
|
||||
nameof(AfterDbInit), nameof(InMainMenu), nameof(OnStartGame), nameof(OnEndGame),
|
||||
nameof(AfterLayerableLoad)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human readable representation of a run time constant.
|
||||
/// </summary>
|
||||
/// <param name="runtime">The time when the patch should be run.</param>
|
||||
public static string ToString(uint runtime) {
|
||||
return (runtime < STRING_VALUES.Length) ? STRING_VALUES[runtime] : runtime.
|
||||
ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user