/// <summary> /// Checks the specified type and all of its nested types for issues. /// </summary> /// <param name="type">The type to check.</param> internal static void CheckType(Type type) { if (type == null) { throw new ArgumentNullException("type"); } bool isAnnotated = false, hasMethods = false; foreach (var annotation in type.GetCustomAttributes(true)) { if (annotation is HarmonyPatch patch) { isAnnotated = true; break; } } // Patchy method names? foreach (var method in type.GetMethods(ALL)) { if (HARMONY_NAMES.Contains(method.Name)) { hasMethods = true; break; } } if (hasMethods && !isAnnotated) { DebugLogger.LogWarning("Type " + type.FullName + " looks like a Harmony patch but does not have the annotation!"); } }
/// <summary> /// Opens the output log file. /// </summary> internal static void OpenOutputLog() { // Ugly but true! var platform = Environment.OSVersion.Platform; string path = ""; switch (platform) { case PlatformID.MacOSX: // https://answers.unity.com/questions/1484445/how-do-i-find-the-player-log-file-from-code.html path = "~/Library/Logs/Unity/Player.log"; break; case PlatformID.Unix: path = Path.Combine("~/.config/unity3d", Application.companyName, Application. productName, "Player.log"); break; case PlatformID.Win32NT: case PlatformID.Win32S: case PlatformID.Win32Windows: path = Path.Combine(Environment.GetEnvironmentVariable("AppData"), "..", "LocalLow", Application.companyName, Application.productName, "output_log.txt"); break; default: DebugLogger.LogWarning("Unable to open the output log on: {0}", platform); break; } if (!string.IsNullOrEmpty(path)) { Application.OpenURL(Path.GetFullPath(path)); } }
/// <summary> /// Moves this mod to the first position and prompts to restart if necssary. /// </summary> /// <param name="parent">The parent of the dialog.</param> private static void MoveToFirst(GameObject parent) { var manager = Global.Instance.modManager; var mods = manager?.mods; var target = DebugNotIncludedPatches.ThisMod; if (mods != null && target != null) { int n = mods.Count, oldIndex = -1; // Search for this mod in the list for (int i = 0; i < n && oldIndex < 0; i++) { if (mods[i].label.Match(target.label)) { oldIndex = i; } } if (oldIndex > 0) { manager.Reinsert(oldIndex, 0, manager); manager.Report(parent); } else { DebugLogger.LogWarning("Unable to move Debug Not Included to top - uninstalled?"); } } }
internal static TranspiledMethod Transpiler(TranspiledMethod method) { var targetMethod = typeof(HoverTextDrawer).GetMethodSafe(nameof(HoverTextDrawer. EndDrawing), false); var addition = typeof(UpdateHoverElements_Patch).GetMethodSafe(nameof( DrawCoordinates), true, typeof(HoverTextDrawer), typeof( HoverTextConfiguration)); if (targetMethod != null && addition != null) { foreach (var instr in method) { if (instr.Is(OpCodes.Callvirt, targetMethod)) { yield return(new CodeInstruction(OpCodes.Ldarg_0)); yield return(new CodeInstruction(OpCodes.Call, addition)); } yield return(instr); } } else { DebugLogger.LogWarning("Unable to patch UpdateHoverElements"); foreach (var instr in method) { yield return(instr); } } }
/// <summary> /// Transpiles LoadDLLs to grab the exception information when a mod fails to load. /// </summary> private static IEnumerable <CodeInstruction> Transpiler( IEnumerable <CodeInstruction> method) { var instructions = new List <CodeInstruction>(method); // HarmonyInstance.Create and Assembly.LoadFrom will be wrapped var harmonyCreate = typeof(HarmonyInstance).GetMethodSafe(nameof( HarmonyInstance.Create), true, typeof(string)); var loadFrom = typeof(Assembly).GetMethodSafe(nameof(Assembly.LoadFrom), true, typeof(string)); bool patchException = PUtil.GameVersion >= 397125u, patchAssembly = false, patchCreate = false; // Add call to our handler in exception block, and wrap harmony instances to // have more information on each mod for (int i = instructions.Count - 1; i > 0; i--) { var instr = instructions[i]; if (instr.opcode == OpCodes.Pop && !patchException) { instr.opcode = OpCodes.Call; // Call our method instead instr.operand = typeof(ModLoadHandler).GetMethodSafe(nameof( ModLoadHandler.HandleModException), true, typeof(object)); patchException = true; } else if (instr.opcode == OpCodes.Call) { var target = instr.operand as MethodInfo; if (target == harmonyCreate && harmonyCreate != null) { // Reroute HarmonyInstance.Create instr.operand = typeof(ModLoadHandler).GetMethodSafe(nameof( ModLoadHandler.CreateHarmonyInstance), true, typeof(string)); patchCreate = true; } else if (target == loadFrom && loadFrom != null) { // Reroute Assembly.LoadFrom instr.operand = typeof(ModLoadHandler).GetMethodSafe(nameof( ModLoadHandler.LoadAssembly), true, typeof(string)); patchAssembly = true; } } } if (!patchException) { DebugLogger.LogError("Unable to transpile LoadDLLs: Could not find exception handler"); } if (!patchAssembly) { DebugLogger.LogWarning("Unable to transpile LoadDLLs: No calls to Assembly.LoadFrom found"); } if (!patchCreate) { DebugLogger.LogWarning("Unable to transpile LoadDLLs: No calls to HarmonyInstance.Create found"); } return(instructions); }
/// <summary> /// Applied before DetourMethod runs. /// </summary> internal static void Prefix(MethodBase original, MethodBase replacement) { var body = original.GetMethodBody(); if (body.GetILAsByteArray().Length < MIN_METHOD_SIZE) { DebugLogger.LogWarning("Patch {0} targets empty method {1}.{2}".F( replacement.Name, original.DeclaringType, original.Name)); } }
/// <summary> /// Runs the required postload patches after all other mods load. /// </summary> /// <param name="instance">The Harmony instance to execute patches.</param> public override void OnAllModsLoaded(Harmony harmony, IReadOnlyList <Mod> mods) { var options = DebugNotIncludedOptions.Instance; if (options?.PowerUserMode == true) { harmony.Patch(typeof(ModsScreen), "BuildDisplay", new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(HidePopups)), new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(BuildDisplay))); } if (mods != null) { ModDebugRegistry.Instance.Populate(mods); } else { DebugLogger.LogWarning("Mods list is empty! Attribution will not work"); } var runningCore = PRegistry.Instance.GetLatestVersion( "PeterHan.PLib.Core.PLibCorePatches")?.GetOwningAssembly(); if (runningCore != null) { RunningPLibAssembly = runningCore; } // Log which mod is running PLib var latest = ModDebugRegistry.Instance.OwnerOfAssembly(RunningPLibAssembly); if (latest != null) { DebugLogger.LogDebug("Executing version of PLib is from: " + latest.ModName); } HarmonyPatchInspector.Check(); #if DEBUG harmony.ProfileMethod(typeof(SaveLoader).GetMethodSafe("Load", false, typeof( IReader))); harmony.ProfileMethod(typeof(SaveLoader).GetMethodSafe("Save", false, typeof( BinaryWriter))); harmony.ProfileMethod(typeof(SaveManager).GetMethodSafe("Load", false, PPatchTools.AnyArguments)); harmony.ProfileMethod(typeof(SaveManager).GetMethodSafe("Save", false, PPatchTools.AnyArguments)); #endif if (options?.LocalizeMods == true) { typeof(PLocalization).GetMethodSafe("DumpAll", false)?.Invoke(loc, null); } }
/// <summary> /// Checks a declared Harmony annotation and warns if an inherited method is being /// patched, which "works" on Windows but fails on Mac OS X / Linux. /// </summary> /// <param name="target">The annotation to check.</param> /// <param name="patcher">The type which created the patch</param> internal static void CheckHarmonyMethod(HarmonyMethod target, Type patcher) { var targetType = target.declaringType; string name = target.methodName; const BindingFlags ONLY_DEC = ALL | BindingFlags.DeclaredOnly; if (targetType != null && !string.IsNullOrEmpty(name)) { try { PropertyInfo info; string targetName = targetType.FullName + "." + name; switch (target.methodType) { case MethodType.Normal: var argTypes = target.argumentTypes; // If no argument types, provide what we can if (((argTypes == null) ? targetType.GetMethod(name, ONLY_DEC) : targetType.GetMethod(name, ONLY_DEC, null, argTypes, null)) == null) { DebugLogger.LogWarning("Patch {0} targets inherited method {1}", patcher.FullName, targetName); } break; case MethodType.Setter: info = targetType.GetProperty(name, ONLY_DEC); if (info?.GetSetMethod(true) == null) { DebugLogger.LogWarning("Patch {0} targets inherited property {1}", patcher.FullName, targetName); } break; case MethodType.Getter: info = targetType.GetProperty(name, ONLY_DEC); if (info?.GetGetMethod(true) == null) { DebugLogger.LogWarning("Patch {0} targets inherited property {1}", patcher.FullName, targetName); } break; default: break; } } catch (AmbiguousMatchException) { } } }
public static void OnLoad(string path) { var inst = ModDebugRegistry.Instance; RunningPLibAssembly = typeof(PUtil).Assembly; PUtil.InitLibrary(); if (DebugNotIncludedOptions.Instance?.DetailedBacktrace ?? true) { DebugLogger.InstallExceptionLogger(); } POptions.RegisterOptions(typeof(DebugNotIncludedOptions)); if (DebugNotIncludedOptions.Instance?.LogAsserts ?? true) { LogAllFailedAsserts(); } // Patch the exception logger for state machines var logException = typeof(DebugUtil).GetMethodSafe("LogException", true, PPatchTools.AnyArguments); if (logException != null) { inst.DebugInstance.Patch(logException, prefix: new HarmonyMethod(typeof( DebugLogger), nameof(DebugLogger.LogException))); } foreach (var mod in Global.Instance.modManager?.mods) { if (mod.label.install_path == path) { ThisMod = mod; break; } } if (ThisMod == null) { DebugLogger.LogWarning("Unable to determine KMod instance!"); } else { inst.RegisterModAssembly(Assembly.GetExecutingAssembly(), inst.GetDebugInfo( ThisMod)); } // Default UI debug key is ALT+U UIDebugAction = PAction.Register("DebugNotIncluded.UIDebugAction", DebugNotIncludedStrings.KEY_SNAPSHOT, new PKeyBinding(KKeyCode.U, Modifier.Alt)); // Must postload the mods dialog to come out after aki's mods, ony's mods, PLib // options, and so forth PUtil.RegisterPostload(PostloadHandler); }
/// <summary> /// Profiles a method, outputting how many milliseconds it took to run on each use. /// </summary> /// <param name="instance">The Harmony instance to use for the patch.</param> /// <param name="target">The method to profile.</param> internal static void ProfileMethod(this HarmonyInstance instance, MethodBase target) { if (target == null) { DebugLogger.LogWarning("No method specified to profile!"); } else { instance.Patch(target, new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(ProfilerPrefix)), new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(ProfilerPostfix))); DebugLogger.LogDebug("Profiling method {0}.{1}".F(target.DeclaringType, target. Name)); } }
/// <summary> /// Opens the output log file. /// </summary> internal static void OpenOutputLog() { // Ugly but true! var platform = Application.platform; string verString = Application.unityVersion; // Get the major Unity version if (verString != null && verString.Length > 4) { verString = verString.Substring(0, 4); } if (!uint.TryParse(verString, out uint unityYear)) { unityYear = 2000u; } string path = ""; switch (platform) { case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: // https://answers.unity.com/questions/1484445/how-do-i-find-the-player-log-file-from-code.html path = "~/Library/Logs/Unity/Player.log"; break; case RuntimePlatform.LinuxEditor: case RuntimePlatform.LinuxPlayer: path = Path.Combine("~/.config/unity3d", Application.companyName, Application. productName, "Player.log"); break; case RuntimePlatform.WindowsEditor: case RuntimePlatform.WindowsPlayer: path = Path.Combine(Environment.GetEnvironmentVariable("AppData"), "..", "LocalLow", Application.companyName, Application.productName, unityYear < 2019 ? "output_log.txt" : "Player.log"); break; default: DebugLogger.LogWarning("Unable to open the output log on: {0}", platform); break; } if (!string.IsNullOrEmpty(path)) { Application.OpenURL(Path.GetFullPath(path)); } }
public override void OnLoad(Harmony harmony) { var inst = ModDebugRegistry.Instance; var method = typeof(Mod).GetMethodSafe("Crash", false); if (method == null) { method = typeof(Mod).GetMethodSafe("SetCrashed", false); } if (method != null) { harmony.Patch(method, prefix: new HarmonyMethod(typeof( DebugNotIncludedPatches), nameof(OnModCrash))); } base.OnLoad(harmony); RunningPLibAssembly = typeof(PUtil).Assembly; PUtil.InitLibrary(); if (DebugNotIncludedOptions.Instance?.DetailedBacktrace ?? true) { DebugLogger.InstallExceptionLogger(); } new POptions().RegisterOptions(this, typeof(DebugNotIncludedOptions)); LocString.CreateLocStringKeys(typeof(DebugNotIncludedStrings.UI)); LocString.CreateLocStringKeys(typeof(DebugNotIncludedStrings.INPUT_BINDINGS)); loc.Register(); if (DebugNotIncludedOptions.Instance?.LogAsserts ?? true) { LogAllFailedAsserts(harmony); } ThisMod = mod; if (ThisMod == null) { DebugLogger.LogWarning("Unable to determine KMod instance!"); } new PPatchManager(harmony).RegisterPatchClass(typeof(DebugNotIncludedPatches)); // Force class initialization to avoid crashes on the background thread if // an Assert fails later DebugUtils.RegisterUIDebug(); new PLib.AVC.PVersionCheck().Register(this, new PLib.AVC.SteamVersionChecker()); }
public static void OnLoad(string path) { var inst = ModDebugRegistry.Instance; RunningPLibAssembly = typeof(PUtil).Assembly; PUtil.InitLibrary(); if (DebugNotIncludedOptions.Instance?.DetailedBacktrace ?? true) { DebugLogger.InstallExceptionLogger(); } POptions.RegisterOptions(typeof(DebugNotIncludedOptions)); // Set up strings LocString.CreateLocStringKeys(typeof(DebugNotIncludedStrings.UI)); LocString.CreateLocStringKeys(typeof(DebugNotIncludedStrings.INPUT_BINDINGS)); PLocalization.Register(); if (DebugNotIncludedOptions.Instance?.LogAsserts ?? true) { LogAllFailedAsserts(); } foreach (var mod in Global.Instance.modManager?.mods) { if (mod.GetModBasePath() == path) { ThisMod = mod; break; } } if (ThisMod == null) { DebugLogger.LogWarning("Unable to determine KMod instance!"); } else { inst.RegisterModAssembly(Assembly.GetExecutingAssembly(), inst.GetDebugInfo( ThisMod)); } // Default UI debug key is ALT+U UIDebugAction = PAction.Register("DebugNotIncluded.UIDebugAction", DebugNotIncludedStrings.INPUT_BINDINGS.DEBUG.SNAPSHOT, new PKeyBinding( KKeyCode.U, Modifier.Alt)); // Must postload the mods dialog to come out after aki's mods, ony's mods, PLib // options, and so forth PUtil.RegisterPatchClass(typeof(DebugNotIncludedPatches)); }