Exemple #1
0
        /// <summary>
        /// Applies a bootstrapper patch which will patch in the appropriate version of
        /// PLibPatches when Global.Awake() completes.
        /// </summary>
        private void ApplyBootstrapper()
        {
            try {
                // Gets called in Global.Awake() after mods load, and a few other places, but
                // we have a flag to avoid initializing more than once
                PLibInstance.Patch(typeof(GlobalResources), "Instance", null,
                                   new HarmonyMethod(typeof(PRegistry), nameof(ApplyLatest)));
            } catch (AmbiguousMatchException e) {
                PUtil.LogException(e);
            } catch (ArgumentException e) {
                PUtil.LogException(e);
            } catch (TypeLoadException e) {
                PUtil.LogException(e);
            }
            try {
                // This method is on the call stack, but the next invocation is fine to patch
                PLibInstance.Patch(typeof(KMod.Manager), "Load", null, new HarmonyMethod(
                                       typeof(PRegistry), nameof(DropFence)));
                dllsLoaded = false;
            } catch (Exception e) {
                // If this patch fails for any reason, disable the fence so that PLib loads
                // at the right time
#if DEBUG
                PUtil.LogException(e);
#endif
                DropFence();
            }
        }
 /// <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 HarmonyInstance instance, Type type,
                                   string methodName, HarmonyMethod transpiler)
 {
     if (type == null)
     {
         throw new ArgumentNullException("type");
     }
     if (string.IsNullOrEmpty(methodName))
     {
         throw new ArgumentNullException("method");
     }
     // Fetch the method
     try {
         var method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.
                                     Public | 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));
     }
 }
Exemple #3
0
        /// <summary>
        /// Adds a candidate patch.
        /// </summary>
        /// <param name="patch">The patch from PLib to add.</param>
        public void AddPatch(object patch)
        {
            if (patch == null)
            {
                throw new ArgumentNullException("patch");
            }
            // Verify the class name
            if (patch.GetType().FullName == typeof(PLibPatches).FullName)
            {
                string ver = null;
                try {
                    ver = Traverse.Create(patch).GetProperty <string>(nameof(PLibPatches.
                                                                             MyVersion));
                } catch (Exception e) {
                    PUtil.LogException(e);
                }
                if (ver == null)
                {
#if DEBUG
                    LogPatchWarning("Invalid patch provided to AddPatch!");
#endif
                }
                else if (!Patches.ContainsKey(ver))
                {
                    LogPatchDebug("Candidate version {0} from {1}".F(ver, patch.GetType().
                                                                     Assembly.GetName()?.Name));
                    Patches.Add(ver, patch);
                }
            }
            else
            {
                LogPatchWarning("Invalid patch provided to AddPatch!");
            }
        }
Exemple #4
0
#pragma warning restore IDE0051 // Remove unused private members

        /// <summary>
        /// Initializes the patch bootstrapper, creating a PLibPatchRegistry if not yet
        /// present and offering our library as a candidate for shared patches.
        /// </summary>
        public static void Init()
        {
            var obj = Global.Instance.gameObject;

            if (obj != null)
            {
                // The hack is sick but we have few choices
                object reg = obj.GetComponent(typeof(PRegistry).Name);
                if (reg == null)
                {
                    var plr = obj.AddComponent <PRegistry>();
#if DEBUG
                    LogPatchDebug("Creating PLibRegistry from " + Assembly.
                                  GetExecutingAssembly().FullName);
#endif
                    // Patch in the bootstrap method
                    plr.ApplyBootstrapper();
                    reg = plr;
                }
                // Use reflection to execute the actual AddPatch method
                try {
                    Traverse.Create(reg).CallMethod("AddPatch", (object)new PLibPatches());
                } catch (ArgumentException e) {
                    PUtil.LogException(e);
                }
            }
            else
            {
#if DEBUG
                LogPatchWarning("Attempted to Init before Global created!");
#endif
            }
        }
        /// <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 = HarmonyInstance.Create("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);
            }
        }
Exemple #6
0
        /// <summary>
        /// Adds a logger to all failed assertions. The assertions will still fail, but a stack
        /// trace will be printed for each failed assertion.
        /// </summary>
        public static void LogAllFailedAsserts()
        {
            var        inst = HarmonyInstance.Create("PeterHan.PLib.LogFailedAsserts");
            MethodBase assert;
            var        handler = new HarmonyMethod(typeof(PPatchTools), nameof(OnAssertFailed));

            // This is not for production use
            PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name +
                             " is logging ALL failed assertions!");
            try {
                // Assert(bool)
                assert = GetMethodSafe(typeof(Debug), "Assert", true, typeof(bool));
                if (assert != null)
                {
                    inst.Patch(assert, handler);
                }
                // Assert(bool, object)
                assert = GetMethodSafe(typeof(Debug), "Assert", true, typeof(bool), typeof(
                                           object));
                if (assert != null)
                {
                    inst.Patch(assert, handler);
                }
                // Assert(bool, object, UnityEngine.Object)
                assert = GetMethodSafe(typeof(Debug), "Assert", true, typeof(bool), typeof(
                                           object), typeof(UnityEngine.Object));
                if (assert != null)
                {
                    inst.Patch(assert, handler);
                }
            } catch (Exception e) {
                PUtil.LogException(e);
            }
        }
Exemple #7
0
        /// <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);
        }
Exemple #8
0
#pragma warning disable IDE0051 // Remove unused private members
        /// <summary>
        /// Finds the latest patch and applies only it.
        /// </summary>
        private static void ApplyLatest()
        {
            if (instance != null)
            {
                object  latest    = null;
                Version latestVer = null;
                try {
                    foreach (var pair in instance.Patches)
                    {
                        var patch    = pair.Value;
                        var patchVer = new Version(pair.Key);
                        if (latestVer == null || latestVer.CompareTo(patchVer) < 0)
                        {
                            // First element or newer version
                            latest    = patch;
                            latestVer = patchVer;
                        }
                    }
                } catch (ArgumentOutOfRangeException e) {
                    // .NET 3.5 please
                    PUtil.LogException(e);
                } catch (FormatException e) {
                    PUtil.LogException(e);
                } catch (OverflowException e) {
                    PUtil.LogException(e);
                }
                if (latest != null)
                {
                    // Store the winning version
                    PSharedData.PutData(KEY_VERSION, latestVer.ToString());
                    try {
                        Traverse.Create(latest).CallMethod("Apply", instance.PLibInstance);
                    } catch (ArgumentException e) {
                        PUtil.LogException(e);
                    } catch (Exception e) {
                        if (e.Message?.ToLower() == "cannot get method value without method")
                        {
                            // Cannot get method value without method
                            PUtil.LogError("The first PLib mod in the load order did not " +
                                           "use PUtil.InitLibrary(). PLib cannot patch correctly!");
                        }
                        else
                        {
                            PUtil.LogException(e);
                        }
                    }
                }
                // Reduce memory usage by cleaning up the patch list
                instance.Patches.Clear();
            }
            else
            {
#if DEBUG
                LogPatchWarning("ApplyLatest invoked with no Instance!");
#endif
            }
        }
Exemple #9
0
 /// <summary>
 /// Applies the patches for this version of PLib.
 /// </summary>
 /// <param name="instance">The Harmony instance to use for patching.</param>
 public void Apply(HarmonyInstance instance)
 {
     PRegistry.LogPatchDebug("Using version " + MyVersion);
     try {
         PatchAll(instance);
     } catch (TypeLoadException e) {
         PUtil.LogException(e);
     } catch (TargetInvocationException e) {
         PUtil.LogException(e);
     }
     PActionManager.Instance.Init();
     PRegistry.LogPatchDebug("PLib patches applied");
 }
Exemple #10
0
        /// <summary>
        /// Applies the latest patch version.
        /// </summary>
        private static void DoApplyLatest()
        {
            object  latest    = null;
            Version latestVer = null;

            try {
                foreach (var pair in instance.Patches)
                {
                    var patch    = pair.Value;
                    var patchVer = new Version(pair.Key);
                    if (latestVer == null || latestVer.CompareTo(patchVer) < 0)
                    {
                        // First element or newer version
                        latest    = patch;
                        latestVer = patchVer;
                    }
                }
            } catch (ArgumentOutOfRangeException e) {
                // .NET 3.5 please
                PUtil.LogException(e);
            } catch (FormatException e) {
                PUtil.LogException(e);
            } catch (OverflowException e) {
                PUtil.LogException(e);
            }
            if (latest != null)
            {
                // Store the winning version
                PSharedData.PutData(KEY_VERSION, latestVer.ToString());
                try {
                    var applyMethod = Traverse.Create(latest).Method(nameof(PLibPatches.Apply),
                                                                     new Type[] { typeof(HarmonyInstance) });
                    // Raise warning if a bad patch made it in somehow
                    if (applyMethod.MethodExists())
                    {
                        applyMethod.GetValue(instance.PLibInstance);
                    }
                    else
                    {
                        LogPatchWarning("The first PLib mod in the load order did not use " +
                                        "PUtil.InitLibrary()!");
                    }
                } catch (Exception e) {
                    PUtil.LogException(e);
                }
            }
            // Reduce memory usage by cleaning up the patch list
            instance.Patches.Clear();
        }
Exemple #11
0
 /// <summary>
 /// Runs all patches for the specified time.
 /// </summary>
 /// <param name="when">The time to run the patch.</param>
 private static void RunThisMod(uint when)
 {
     if (patches.TryGetValue(when, out PrivateRunList toRun))
     {
         // Create Harmony instance for the patches
         var instance = HarmonyInstance.Create("PLib.PostLoad." + RunAt.ToString(when));
         foreach (var method in toRun)
         {
             try {
                 method.Run(instance);
             } catch (TargetInvocationException e) {
                 // Use the inner exception
                 PUtil.LogException(e.GetBaseException());
             }
         }
Exemple #12
0
 /// <summary>
 /// Applies a bootstrapper patch which will patch in the appropriate version of
 /// PLibPatches when Global.Awake() completes.
 /// </summary>
 private void ApplyBootstrapper()
 {
     try {
         // Gets called in Global.Awake() after mods load
         PLibInstance.Patch(typeof(Global), "RestoreLegacyMetricsSetting", null,
                            new HarmonyMethod(typeof(PRegistry).GetMethod("ApplyLatest",
                                                                          BindingFlags.NonPublic | BindingFlags.Static)));
     } catch (AmbiguousMatchException e) {
         PUtil.LogException(e);
     } catch (ArgumentException e) {
         PUtil.LogException(e);
     } catch (TypeLoadException e) {
         PUtil.LogException(e);
     }
 }
Exemple #13
0
 /// <summary>
 /// Applies a bootstrapper patch which will patch in the appropriate version of
 /// PLibPatches when Global.Awake() completes.
 /// </summary>
 private void ApplyBootstrapper()
 {
     try {
         // Gets called in Global.Awake() after mods load, and a few other places, but
         // we have a flag to avoid initializing more than once
         PLibInstance.Patch(typeof(GlobalResources), "Instance", null,
                            new HarmonyMethod(typeof(PRegistry), "ApplyLatest"));
     } catch (AmbiguousMatchException e) {
         PUtil.LogException(e);
     } catch (ArgumentException e) {
         PUtil.LogException(e);
     } catch (TypeLoadException e) {
         PUtil.LogException(e);
     }
 }
Exemple #14
0
        /// <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);
        }
Exemple #15
0
        /// <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);
        }
Exemple #16
0
 /// <summary>
 /// Runs all patches for the specified time.
 /// </summary>
 /// <param name="when">The time to run the patch.</param>
 private static void RunThisMod(uint when)
 {
     if (patches.TryGetValue(when, out PrivateRunList toRun))
     {
         // Create Harmony instance for the patches
         var instance = HarmonyInstance.Create("PLib.PostLoad." + RunAt.ToString(when));
         foreach (var method in toRun)
         {
             try {
                 method.Run(instance);
             } catch (Exception e) {
                 // Say which mod's postload crashed
                 PUtil.LogException(e);
             }
         }
     }
 }
Exemple #17
0
#pragma warning disable IDE0051 // Remove unused private members
        /// <summary>
        /// Finds the latest patch and applies only it.
        /// </summary>
        private static void ApplyLatest()
        {
            if (instance != null)
            {
                object  latest    = null;
                Version latestVer = null;
                try {
                    foreach (var pair in instance.Patches)
                    {
                        var patch    = pair.Value;
                        var patchVer = new Version(pair.Key);
                        if (latestVer == null || latestVer.CompareTo(patchVer) < 0)
                        {
                            // First element or newer version
                            latest    = patch;
                            latestVer = patchVer;
                        }
                    }
                } catch (ArgumentOutOfRangeException e) {
                    // .NET 3.5 please
                    PUtil.LogException(e);
                } catch (FormatException e) {
                    PUtil.LogException(e);
                } catch (OverflowException e) {
                    PUtil.LogException(e);
                }
                if (latest != null)
                {
                    // Store the winning version
                    PSharedData.PutData(KEY_VERSION, latestVer.ToString());
                    try {
                        Traverse.Create(latest).CallMethod("Apply", instance.PLibInstance);
                    } catch (ArgumentException e) {
                        PUtil.LogException(e);
                    }
                }
                // Reduce memory usage by cleaning up the patch list
                instance.Patches.Clear();
            }
            else
            {
#if DEBUG
                LogPatchWarning("ApplyLatest invoked with no Instance!");
#endif
            }
        }
Exemple #18
0
        /// <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);
        }
Exemple #19
0
        /// <summary>
        /// Initializes the patch bootstrapper, creating a PLibPatchRegistry if not yet
        /// present and offering our library as a candidate for shared patches.
        /// </summary>
        public static void Init()
        {
            var obj = Global.Instance.gameObject;

            if (obj != null)
            {
                // The hack is sick but we have few choices
                object reg = obj.GetComponent(typeof(PRegistry).Name);
                if (reg == null)
                {
                    var plr = obj.AddComponent <PRegistry>();
                    // If PLib is ILMerged more than once, PRegistry gets added with a weird
                    // type name including a GUID which does not match GetComponent.Name!
                    string typeName = plr.GetType().Name;
                    if (typeName != "PRegistry")
                    {
                        Debug.LogErrorFormat("PRegistry has the type name {0}; this may be " +
                                             "the result of ILMerging PLib more than once!", typeName);
                    }
#if DEBUG
                    LogPatchDebug("Creating PLibRegistry from " + Assembly.
                                  GetExecutingAssembly().FullName);
#endif
                    // Patch in the bootstrap method
                    plr.ApplyBootstrapper();
                    reg = plr;
                }
                // Use reflection to execute the actual AddPatch method
                try {
                    Traverse.Create(reg).CallMethod(nameof(PRegistry.AddPatch),
                                                    (object)new PLibPatches());
                } catch (Exception e) {
                    PUtil.LogException(e);
                }
            }
            else
            {
#if DEBUG
                LogPatchWarning("Attempted to Init before Global created!");
#endif
            }
        }
Exemple #20
0
        /// <summary>
        /// Executes all legacy post-load handlers.
        /// </summary>
        internal static void ExecuteLegacyPostload()
        {
            IList <PostLoadHandler> postload = null;

            lock (PSharedData.GetLock(PRegistry.KEY_POSTLOAD_LOCK)) {
                // Get list holding postload information
                var list = PSharedData.GetData <IList <PostLoadHandler> >(PRegistry.
                                                                          KEY_POSTLOAD_TABLE);
                if (list != null)
                {
                    postload = new List <PostLoadHandler>(list);
                }
            }
            // If there were any, run them
            if (postload != null)
            {
                var hInst = HarmonyInstance.Create("PLib.PostLoad");
                PRegistry.LogPatchDebug("Executing {0:D} legacy post-load handler(s)".F(
                                            postload.Count));
                foreach (var handler in postload)
                {
                    try {
                        handler?.Invoke(hInst);
                    } catch (Exception e) {
                        var method = handler.Method;
                        // Say which mod's postload crashed
                        if (method != null)
                        {
                            PRegistry.LogPatchWarning("Postload handler for mod {0} failed:".F(
                                                          method.DeclaringType.Assembly?.GetName()?.Name ?? "?"));
                        }
                        PUtil.LogException(e);
                    }
                }
            }
        }
Exemple #21
0
 /// <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 HarmonyInstance instance, Type type,
                                     Type[] arguments, HarmonyMethod prefix = null, HarmonyMethod postfix = null)
 {
     if (type == null)
     {
         throw new ArgumentNullException("type");
     }
     // Fetch the constructor
     try {
         var cons = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public |
                                        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);
     }
 }