/// <summary> /// Retrieves a method using reflection, or returns null if it does not exist. /// </summary> /// <param name="type">The base type.</param> /// <param name="methodName">The method name.</param> /// <param name="isStatic">true to find static methods, or false to find instance /// methods.</param> /// <param name="arguments">The method argument types. If null is provided, any /// argument types are matched, whereas no arguments match only void methods.</param> /// <returns>The method, or null if no such method could be found.</returns> public static MethodInfo GetMethodSafe(this Type type, string methodName, bool isStatic, params Type[] arguments) { MethodInfo method = null; if (type != null && arguments != null) { try { var flag = isStatic ? BindingFlags.Static : BindingFlags.Instance; if (arguments.Length == 1 && arguments[0] == null) { // AnyArguments method = type.GetMethod(methodName, BASE_FLAGS | flag); } else { method = type.GetMethod(methodName, BASE_FLAGS | flag, null, arguments, new ParameterModifier[arguments.Length]); } } catch (AmbiguousMatchException e) { PUtil.LogException(e); } } return(method); }
/// <summary> /// Adds a logger to all failed assertions. The assertions will still fail, but a stack /// trace will be printed for each failed assertion. /// /// Not for production use. /// </summary> internal static void LogAllFailedAsserts() { var inst = new Harmony("PeterHan.PLib.LogFailedAsserts"); MethodBase assert; var handler = new HarmonyMethod(typeof(PTranspilerTools), nameof(OnAssertFailed)); try { // Assert(bool) assert = typeof(Debug).GetMethodSafe("Assert", true, typeof(bool)); if (assert != null) { inst.Patch(assert, handler); } // Assert(bool, object) assert = typeof(Debug).GetMethodSafe("Assert", true, typeof(bool), typeof( object)); if (assert != null) { inst.Patch(assert, handler); } // Assert(bool, object, UnityEngine.Object) assert = typeof(Debug).GetMethodSafe("Assert", true, typeof(bool), typeof( object), typeof(UnityEngine.Object)); if (assert != null) { inst.Patch(assert, handler); } } catch (Exception e) { PUtil.LogException(e); } }
/// <summary> /// Gets the data from this component, serialized to the specified type. The data is /// retrieved from the base component, serialized with JSON, and reconstituted as type /// T in the memory space of the caller. /// /// The target type must exist and be a [JsonObject] in both this assembly and the /// target component's assembly. /// /// This method is somewhat slow and memory intensive, and should be used sparingly. /// </summary> /// <typeparam name="T">The data type to retrieve and into which to convert.</typeparam> /// <param name="defValue">The default value if the instance data is unset.</param> /// <returns>The data, or defValue if the instance data has not been set or cannot be serialized.</returns> public T GetInstanceDataSerialized <T>(T defValue = default) { var remoteData = InstanceData; T result = defValue; using (var buffer = new MemoryStream(1024)) { try { var writer = new StreamWriter(buffer, Encoding.UTF8); SerializationSettings.Serialize(writer, remoteData); writer.Flush(); buffer.Position = 0L; var reader = new StreamReader(buffer, Encoding.UTF8); if (SerializationSettings.Deserialize(reader, typeof(T)) is T decoded) { result = decoded; } } catch (JsonException e) { PUtil.LogError("Unable to serialize instance data for component " + ID + ":"); PUtil.LogException(e); result = defValue; } } return(result); }
/// <summary> /// Patches a method manually with a transpiler. /// </summary> /// <param name="instance">The Harmony instance.</param> /// <param name="type">The class to modify.</param> /// <param name="methodName">The method to patch.</param> /// <param name="transpiler">The transpiler to apply.</param> public static void PatchTranspile(this Harmony instance, Type type, string methodName, HarmonyMethod transpiler) { if (type == null) { throw new ArgumentNullException(nameof(type)); } if (string.IsNullOrEmpty(methodName)) { throw new ArgumentNullException(nameof(methodName)); } // Fetch the method try { var method = type.GetMethod(methodName, PPatchTools.BASE_FLAGS | BindingFlags.Static | BindingFlags.Instance); if (method != null) { instance.Patch(method, null, null, transpiler); } else { PUtil.LogWarning("Unable to find method {0} on type {1}".F(methodName, type.FullName)); } } catch (AmbiguousMatchException e) { PUtil.LogException(e); } catch (FormatException e) { PUtil.LogWarning("Unable to transpile method {0}: {1}".F(methodName, e.Message)); } }
/// <summary> /// Applies a bootstrapper patch which will complete forwarded component initialization /// before mods are post-loaded. /// </summary> internal void ApplyBootstrapper() { try { PLibInstance.Patch(typeof(KMod.Mod), nameof(KMod.Mod.PostLoad), prefix: new HarmonyMethod(typeof(PRegistryComponent), nameof(ApplyLatest))); } catch (AmbiguousMatchException e) { PUtil.LogException(e); } catch (ArgumentException e) { PUtil.LogException(e); } catch (TypeLoadException e) { PUtil.LogException(e); } }
/// <summary> /// Retrieves a field using reflection, or returns null if it does not exist. /// </summary> /// <param name="type">The base type.</param> /// <param name="fieldName">The field name.</param> /// <param name="isStatic">true to find static fields, or false to find instance /// fields.</param> /// <returns>The field, or null if no such field could be found.</returns> public static FieldInfo GetFieldSafe(this Type type, string fieldName, bool isStatic) { FieldInfo field = null; if (type != null) { try { var flag = isStatic ? BindingFlags.Static : BindingFlags.Instance; field = type.GetField(fieldName, BASE_FLAGS | flag); } catch (AmbiguousMatchException e) { PUtil.LogException(e); } } return(field); }
/// <summary> /// Retrieves an indexed property using reflection, or returns null if it does not /// exist. /// </summary> /// <param name="type">The base type.</param> /// <param name="propName">The property name.</param> /// <param name="isStatic">true to find static properties, or false to find instance /// properties.</param> /// <param name="arguments">The property indexer's arguments.</param> /// <typeparam name="T">The property field type.</typeparam> /// <returns>The property, or null if no such property could be found.</returns> public static PropertyInfo GetPropertyIndexedSafe <T>(this Type type, string propName, bool isStatic, params Type[] arguments) { PropertyInfo field = null; if (type != null && arguments != null) { try { var flag = isStatic ? BindingFlags.Static : BindingFlags.Instance; field = type.GetProperty(propName, BASE_FLAGS | flag, null, typeof(T), arguments, new ParameterModifier[arguments.Length]); } catch (AmbiguousMatchException e) { PUtil.LogException(e); } } return(field); }
/// <summary> /// Retrieves a property using reflection, or returns null if it does not exist. /// </summary> /// <param name="type">The base type.</param> /// <param name="propName">The property name.</param> /// <param name="isStatic">true to find static properties, or false to find instance /// properties.</param> /// <typeparam name="T">The property field type.</typeparam> /// <returns>The property, or null if no such property could be found.</returns> public static PropertyInfo GetPropertySafe <T>(this Type type, string propName, bool isStatic) { PropertyInfo field = null; if (type != null) { try { var flag = isStatic ? BindingFlags.Static : BindingFlags.Instance; field = type.GetProperty(propName, BASE_FLAGS | flag, null, typeof(T), Type.EmptyTypes, null); } catch (AmbiguousMatchException e) { PUtil.LogException(e); } } return(field); }
/// <summary> /// Goes through the forwarded components, and picks the latest version of each to /// instantiate. /// </summary> public void Instantiate() { foreach (var pair in forwardedComponents) { // Sort value by version var versions = pair.Value.Components; int n = versions.Count; if (n > 0) { string id = pair.Key; versions.Sort(); var component = versions[n - 1]; latestComponents.GetOrAdd(id, component); #if DEBUG PRegistry.LogPatchDebug("Instantiating component {0} using version {1} from assembly {2}".F( id, component.Version, component.GetOwningAssembly().FullName)); #endif try { instantiatedComponents.GetOrAdd(id, component?.DoInitialize( PLibInstance)); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + id + ":"); PUtil.LogException(e); } } } // Post initialize for component compatibility foreach (var pair in latestComponents) { try { pair.Value.PostInitialize(PLibInstance); } catch (Exception e) { PRegistry.LogPatchWarning("Error when instantiating component " + pair.Key + ":"); PUtil.LogException(e); } } }
/// <summary> /// Gets the shared data between components with this ID, serialized to the specified /// type. The shared data is retrieved, serialized with JSON, and reconstituted as type /// T in the memory space of the caller. /// /// The target type must exist and be a [JsonObject] in both this assembly and the /// target component's assembly. /// /// This method is somewhat slow and memory intensive, and should be used sparingly. /// </summary> /// <typeparam name="T">The data type to retrieve and into which to convert.</typeparam> /// <param name="defValue">The default value if the shared data is unset.</param> /// <returns>The data, or defValue if the shared data has not been set or cannot be serialized.</returns> public T GetSharedDataSerialized <T>(T defValue = default) { var remoteData = PRegistry.Instance.GetSharedData(ID); T result = defValue; using (var buffer = new MemoryStream(1024)) { try { SerializationSettings.Serialize(new StreamWriter(buffer, Encoding.UTF8), remoteData); buffer.Position = 0L; if (SerializationSettings.Deserialize(new StreamReader(buffer, Encoding.UTF8), typeof(T)) is T decoded) { result = decoded; } } catch (JsonException e) { PUtil.LogError("Unable to serialize shared data for component " + ID + ":"); PUtil.LogException(e); result = defValue; } } return(result); }
/// <summary> /// Patches a constructor manually. /// </summary> /// <param name="instance">The Harmony instance.</param> /// <param name="type">The class to modify.</param> /// <param name="arguments">The constructor's argument types.</param> /// <param name="prefix">The prefix to apply, or null if none.</param> /// <param name="postfix">The postfix to apply, or null if none.</param> public static void PatchConstructor(this Harmony instance, Type type, Type[] arguments, HarmonyMethod prefix = null, HarmonyMethod postfix = null) { if (type == null) { throw new ArgumentNullException(nameof(type)); } // Fetch the constructor try { var cons = type.GetConstructor(PPatchTools.BASE_FLAGS | BindingFlags.Static | BindingFlags.Instance, null, arguments, null); if (cons != null) { instance.Patch(cons, prefix, postfix); } else { PUtil.LogWarning("Unable to find constructor on type {0}".F(type. FullName)); } } catch (ArgumentException e) { PUtil.LogException(e); } }