コード例 #1
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);
            }
        }
コード例 #2
0
 /// <summary>
 /// Adds a logger to all unhandled exceptions.
 /// </summary>
 public static void LogAllExceptions()
 {
     // This is not for production use
     PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name +
                      " is logging ALL unhandled exceptions!");
     AppDomain.CurrentDomain.UnhandledException += OnThrown;
 }
コード例 #3
0
 /// <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));
     }
 }
コード例 #4
0
        /// <summary>
        /// Registers a PAction with the action manager. There is no corresponding Unregister
        /// call, so avoid spamming PActions.
        ///
        /// This call should occur after PUtil.InitLibrary() during the mod OnLoad(). If called
        /// earlier, it may fail with InvalidOperationException, and if called later, the
        /// user's custom key bind (if applicable) will be discarded.
        /// </summary>
        /// <param name="identifier">The identifier for this action.</param>
        /// <param name="title">The action's title. To localize the action's title, use the
        /// UpdateLocalizedTitle method on the PAction instance in a postload patch (which
        /// runs after localization mods have been loaded)</param>
        /// <param name="binding">The default key binding for this action.</param>
        /// <returns>The action thus registered.</returns>
        /// <exception cref="InvalidOperationException">If PLib is not yet initialized.</exception>
        public static PAction Register(string identifier, LocString title,
                                       PKeyBinding binding = null)
        {
            // In case this call is used before the library was initialized
            if (!PUtil.PLibInit)
            {
                PUtil.InitLibrary(false);
                PUtil.LogWarning("PUtil.InitLibrary was not called before using " +
                                 "PAction.Register!");
            }
            int     actionID;
            PAction action;

            // Avoid any future threading issues
            lock (PSharedData.GetLock(PRegistry.KEY_ACTION_LOCK)) {
                actionID = PSharedData.GetData <int>(PRegistry.KEY_ACTION_ID);
                if (actionID <= 0)
                {
                    throw new InvalidOperationException("PAction action ID is not set!");
                }
                PSharedData.PutData(PRegistry.KEY_ACTION_ID, actionID + 1);
            }
            action = new PAction(actionID, identifier, title);
            if (!string.IsNullOrEmpty(title))
            {
                PActionManager.ConfigureTitle(action);
            }
            action.AddKeyBinding(binding ?? new PKeyBinding());
            return(action);
        }
コード例 #5
0
ファイル: PPatchTools.cs プロジェクト: pether-pg/ONIMods
        /// <summary>
        /// Retrieves a type using its full name (including namespace). However, the assembly
        /// name is optional, as this method searches all assemblies in the current
        /// AppDomain if it is null or empty.
        /// </summary>
        /// <param name="name">The type name to retrieve.</param>
        /// <param name="assemblyName">If specified, the name of the assembly that contains
        /// the type. No other assembly name will be searched if this parameter is not null
        /// or empty. The assembly name might not match the DLL name, use a decompiler to
        /// make sure.</param>
        /// <returns>The type, or null if the type is not found or cannot be loaded.</returns>
        public static Type GetTypeSafe(string name, string assemblyName = null)
        {
            Type type = null;

            if (string.IsNullOrEmpty(assemblyName))
            {
                foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try {
                        type = assembly.GetType(name, false);
                    } catch (System.IO.IOException) {
                        // The common parent of exceptions when the type requires another type
                        // that cannot be loaded
                    } catch (BadImageFormatException) { }
                    if (type != null)
                    {
                        break;
                    }
                }
            }
            else
            {
                try {
                    type = Type.GetType(name + ", " + assemblyName, false);
                } catch (TargetInvocationException e) {
                    PUtil.LogWarning("Unable to load type {0} from assembly {1}:".F(name,
                                                                                    assemblyName));
                    PUtil.LogExcWarn(e);
                } catch (ArgumentException e) {
                    // A generic type is loaded with bad arguments
                    PUtil.LogWarning("Unable to load type {0} from assembly {1}:".F(name,
                                                                                    assemblyName));
                    PUtil.LogExcWarn(e);
                } catch (System.IO.IOException) {
                    // The common parent of exceptions when the type requires another type that
                    // cannot be loaded
                } catch (BadImageFormatException) { }
            }
            return(type);
        }
コード例 #6
0
ファイル: ExtensionMethods.cs プロジェクト: pether-pg/ONIMods
 /// <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);
     }
 }
コード例 #7
0
        /// <summary>
        /// Applies all patches.
        /// </summary>
        /// <param name="instance">The Harmony instance to use when patching.</param>
        private static void PatchAll(HarmonyInstance instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            // ColonyAchievementStatus
            instance.Patch(typeof(ColonyAchievementStatus), "Serialize",
                           PatchMethod(nameof(Serialize_Prefix)), null);

            // Db
            instance.Patch(typeof(Db), "Initialize", PatchMethod(nameof(Initialize_Prefix)),
                           PatchMethod(nameof(Initialize_Postfix)));

            // Game
            instance.Patch(typeof(Game), "DestroyInstances", null, PatchMethod(nameof(
                                                                                   Game_DestroyInstances_Postfix)));
            instance.Patch(typeof(Game), "OnPrefabInit", null, PatchMethod(nameof(
                                                                               Game_OnPrefabInit_Postfix)));

            // GameInputMapping
            instance.Patch(typeof(GameInputMapping), "SetDefaultKeyBindings", null,
                           PatchMethod(nameof(SetDefaultKeyBindings_Postfix)));

            // GameUtil
            instance.Patch(typeof(GameUtil), "GetKeycodeLocalized",
                           PatchMethod(nameof(GetKeycodeLocalized_Prefix)), null);

            // KInputController
            instance.PatchConstructor(typeof(KInputController.KeyDef), new Type[] {
                typeof(KKeyCode), typeof(Modifier)
            }, null, PatchMethod(nameof(CKeyDef_Postfix)));
            instance.Patch(typeof(KInputController), "IsActive",
                           PatchMethod(nameof(IsActive_Prefix)), null);
            instance.Patch(typeof(KInputController), "QueueButtonEvent",
                           PatchMethod(nameof(QueueButtonEvent_Prefix)), null);

            if (PLightManager.InitInstance())
            {
                // DiscreteShadowCaster
                instance.Patch(typeof(DiscreteShadowCaster), "GetVisibleCells",
                               PatchMethod(nameof(GetVisibleCells_Prefix)), null);

                // Light2D
                instance.Patch(typeof(Light2D), "AddToScenePartitioner",
                               PatchMethod(nameof(AddToScenePartitioner_Prefix)), null);
                instance.Patch(typeof(Light2D), "RefreshShapeAndPosition", null,
                               PatchMethod(nameof(RefreshShapeAndPosition_Postfix)));

                // LightGridEmitter
                instance.Patch(typeof(LightGridEmitter), "AddToGrid", null,
                               PatchMethod(nameof(AddToGrid_Postfix)));
                instance.Patch(typeof(LightGridEmitter), "ComputeLux",
                               PatchMethod(nameof(ComputeLux_Prefix)), null);
                instance.Patch(typeof(LightGridEmitter), "RemoveFromGrid",
                               null, PatchMethod(nameof(RemoveFromGrid_Postfix)));
                instance.Patch(typeof(LightGridEmitter), "UpdateLitCells",
                               PatchMethod(nameof(UpdateLitCells_Prefix)), null);

                // LightGridManager
                instance.Patch(typeof(LightGridManager), "CreatePreview",
                               PatchMethod(nameof(CreatePreview_Prefix)), null);

                // LightShapePreview
                instance.Patch(typeof(LightShapePreview), "Update",
                               PatchMethod(nameof(LightShapePreview_Update_Prefix)), null);

                // Rotatable
                instance.Patch(typeof(Rotatable), "OrientVisualizer", null,
                               PatchMethod(nameof(OrientVisualizer_Postfix)));
            }

            // MainMenu
            instance.Patch(typeof(MainMenu), "OnSpawn", null, PatchMethod(
                               nameof(MainMenu_OnSpawn_Postfix)));

            // PBuilding
            instance.Patch(typeof(BuildingTemplates), "CreateBuildingDef", null,
                           PatchMethod(nameof(CreateBuildingDef_Postfix)));
            instance.Patch(typeof(EquipmentTemplates), "CreateEquipmentDef", null,
                           PatchMethod(nameof(CreateEquipmentDef_Postfix)));
            if (PBuilding.CheckBuildings())
            {
                instance.Patch(typeof(GeneratedBuildings), "LoadGeneratedBuildings",
                               PatchMethod(nameof(LoadGeneratedBuildings_Prefix)), null);
            }

            // PCodex
            instance.Patch(typeof(CodexCache), "CollectEntries", null,
                           PatchMethod(nameof(CollectEntries_Postfix)));
            instance.Patch(typeof(CodexCache), "CollectSubEntries", null,
                           PatchMethod(nameof(CollectSubEntries_Postfix)));

            // PLocalization
            var locale = Localization.GetLocale();

            if (locale != null)
            {
                PLocalization.LocalizeAll(locale);
            }

            // ModsScreen
            POptions.Init();
            instance.Patch(typeof(ModsScreen), "BuildDisplay", null,
                           PatchMethod(nameof(BuildDisplay_Postfix)));

            // SteamUGCService
            var ugc = PPatchTools.GetTypeSafe("SteamUGCService", "Assembly-CSharp");

            if (ugc != null)
            {
                try {
                    instance.PatchTranspile(ugc, "LoadPreviewImage", PatchMethod(nameof(
                                                                                     LoadPreviewImage_Transpile)));
                } catch (Exception e) {
                    PUtil.LogExcWarn(e);
                }
            }

            // TMPro.TMP_InputField
            try {
                instance.Patch(typeof(TMPro.TMP_InputField), "OnEnable", null,
                               PatchMethod(nameof(OnEnable_Postfix)));
            } catch (Exception) {
                PUtil.LogWarning("Unable to patch TextMeshPro bug, text fields may display " +
                                 "improperly inside scroll areas");
            }

            // Postload, legacy and normal
            PPatchManager.ExecuteLegacyPostload();
            PPatchManager.RunAll(RunAt.AfterModsLoad);
        }
コード例 #8
0
ファイル: PPatchTools.cs プロジェクト: pether-pg/ONIMods
        /// <summary>
        /// Transpiles a method to replace calls to the specified victim methods with
        /// replacement methods, altering the call type if necessary.
        ///
        /// Each key to value pair must meet the criteria defined in
        /// ReplaceMethodCall(TranspiledMethod, MethodInfo, MethodInfo).
        /// </summary>
        /// <param name="method">The method to patch.</param>
        /// <param name="translation">A mapping from the old method calls to replace, to the
        /// new method calls to use instead.</param>
        /// <returns>A transpiled version of that method that replaces or removes all calls
        /// to the specified methods.</returns>
        /// <exception cref="ArgumentException">If any of the new methods' argument types do
        /// not exactly match the old methods' argument types.</exception>
        public static TranspiledMethod ReplaceMethodCall(TranspiledMethod method,
                                                         IDictionary <MethodInfo, MethodInfo> translation)
        {
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            if (translation == null)
            {
                throw new ArgumentNullException("translation");
            }
            // Sanity check arguments
            int replaced = 0;

            foreach (var pair in translation)
            {
                var victim    = pair.Key;
                var newMethod = pair.Value;
                if (victim == null)
                {
                    throw new ArgumentNullException("victim");
                }
                if (newMethod != null)
                {
                    PTranspilerTools.CompareMethodParams(victim, victim.GetParameterTypes(),
                                                         newMethod);
                }
                else if (victim.ReturnType != typeof(void))
                {
                    throw new ArgumentException("Cannot remove method {0} with a return value".
                                                F(victim.Name));
                }
            }
            foreach (var instruction in method)
            {
                var        opcode = instruction.opcode;
                MethodInfo target;
                if ((opcode == OpCodes.Call || opcode == OpCodes.Calli || opcode == OpCodes.
                     Callvirt) && translation.TryGetValue(target = instruction.operand as
                                                                   MethodInfo, out MethodInfo newMethod))
                {
                    if (newMethod != null)
                    {
                        // Replace with new method
                        instruction.opcode = newMethod.IsStatic ? OpCodes.Call :
                                             OpCodes.Callvirt;
                        instruction.operand = newMethod;
                        yield return(instruction);
                    }
                    else
                    {
                        // Pop "this" if needed
                        int n = target.GetParameters().Length;
                        if (!target.IsStatic)
                        {
                            n++;
                        }
                        // Pop the arguments off the stack
                        instruction.opcode  = (n == 0) ? OpCodes.Nop : OpCodes.Pop;
                        instruction.operand = null;
                        yield return(instruction);

                        for (int i = 0; i < n - 1; i++)
                        {
                            yield return(new CodeInstruction(OpCodes.Pop));
                        }
                    }
                    replaced++;
                }
                else
                {
                    yield return(instruction);
                }
            }
#if DEBUG
            if (replaced == 0)
            {
                if (translation.Count == 1)
                {
                    // Diagnose the method that could not be replaced
                    var items = new KeyValuePair <MethodInfo, MethodInfo> [1];
                    translation.CopyTo(items, 0);
                    MethodInfo from = items[0].Key, to = items[0].Value;
                    PUtil.LogWarning("No method calls replaced: {0}.{1} to {2}.{3}".F(
                                         from.DeclaringType.FullName, from.Name, to.DeclaringType.FullName,
                                         to.Name));
                }
                else
                {
                    PUtil.LogWarning("No method calls replaced (multiple replacements)");
                }
            }
#endif
        }
コード例 #9
0
ファイル: PPatchTools.cs プロジェクト: pether-pg/ONIMods
 public static void LogAllFailedAsserts()
 {
     PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name +
                      " is logging ALL failed assertions!");
     PTranspilerTools.LogAllFailedAsserts();
 }
コード例 #10
0
ファイル: PPatchTools.cs プロジェクト: pether-pg/ONIMods
 public static void LogAllExceptions()
 {
     PUtil.LogWarning("PLib in mod " + Assembly.GetCallingAssembly().GetName()?.Name +
                      " is logging ALL unhandled exceptions!");
     PTranspilerTools.LogAllExceptions();
 }
コード例 #11
0
        /// <summary>
        /// Transpiles a method to replace all calls to the specified victim method with
        /// another method, altering the call type if necessary. The argument types and return
        /// type must match exactly, including in/out/ref parameters.
        ///
        /// If replacing an instance method call with a static method, the first argument
        /// will receive the "this" which the old method would have received.
        ///
        /// If newMethod is null, the calls will all be removed silently instead. This will
        /// fail if the method call being removed had a return type (what would it be replaced
        /// with?); in those cases, declare an empty method with the same signature and
        /// replace it instead.
        /// </summary>
        /// <param name="method">The method to patch.</param>
        /// <param name="victim">The old method calls to remove.</param>
        /// <param name="newMethod">The new method to replace, or null to delete the calls.</param>
        /// <returns>A transpiled version of that method that replaces or removes all calls
        /// to method.</returns>
        /// <exception cref="ArgumentException">If the new method's argument types do not
        /// exactly match the old method's argument types.</exception>
        public static TranspiledMethod ReplaceMethodCall(TranspiledMethod method,
                                                         MethodInfo victim, MethodInfo newMethod = null)
        {
            if (method == null)
            {
                throw new ArgumentNullException("method");
            }
            if (victim == null)
            {
                throw new ArgumentNullException("victim");
            }
            // Sanity check arguments
            var types = victim.GetParameterTypes();
            int n = types.Length, replaced = 0;

            if (newMethod != null)
            {
                CompareMethodParams(victim, types, newMethod);
            }
            else if (victim.ReturnType != typeof(void))
            {
                throw new ArgumentException("Cannot remove method {0} with a return value".F(
                                                victim.Name));
            }
            // Pop "this" in removal cases
            if (!victim.IsStatic)
            {
                n++;
            }
            foreach (var instruction in method)
            {
                var opcode = instruction.opcode;
                if ((opcode == OpCodes.Call || opcode == OpCodes.Calli || opcode == OpCodes.
                     Callvirt) && instruction.operand == victim)
                {
                    if (newMethod != null)
                    {
                        // Replace with new method
                        instruction.opcode = newMethod.IsStatic ? OpCodes.Call :
                                             OpCodes.Callvirt;
                        instruction.operand = newMethod;
                        yield return(instruction);
                    }
                    else
                    {
                        // Pop the arguments off the stack
                        instruction.opcode  = (n == 0) ? OpCodes.Nop : OpCodes.Pop;
                        instruction.operand = null;
                        yield return(instruction);

                        for (int i = 0; i < n - 1; i++)
                        {
                            yield return(new CodeInstruction(OpCodes.Pop));
                        }
                    }
                    replaced++;
                }
                else
                {
                    yield return(instruction);
                }
            }
#if DEBUG
            if (replaced == 0)
            {
                PUtil.LogWarning("No method calls replaced: {0}.{1} to {2}.{3}".F(victim.
                                                                                  DeclaringType.Name, victim.Name, newMethod?.DeclaringType?.Name ?? "None",
                                                                                  newMethod?.Name));
            }
#endif
        }