/// <summary> /// Transpiles UpdateMods to postpone the report. /// </summary> internal static IEnumerable <CodeInstruction> Transpiler( IEnumerable <CodeInstruction> method) { #if DEBUG DebugLogger.LogDebug("Transpiling Steam.UpdateMods()"); #endif var report = typeof(Manager).GetMethodSafe(nameof(Manager.Report), false, typeof(GameObject)); var sanitize = typeof(Manager).GetMethodSafe(nameof(Manager.Sanitize), false, typeof(GameObject)); var newReport = typeof(QueuedReportManager).GetMethodSafe(nameof( QueuedReportManager.QueueDelayedReport), true, typeof(Manager), typeof(GameObject)); var newSanitize = typeof(QueuedReportManager).GetMethodSafe(nameof( QueuedReportManager.QueueDelayedSanitize), true, typeof(Manager), typeof(GameObject)); foreach (var instruction in method) { if (instruction.opcode == OpCodes.Callvirt) { var callee = instruction.operand as MethodInfo; if (callee == report && newReport != null) { instruction.opcode = OpCodes.Call; instruction.operand = newReport; } else if (callee == sanitize && newSanitize != null) { instruction.opcode = OpCodes.Call; instruction.operand = newSanitize; } } yield return(instruction); } }
private static void PostloadHandler(HarmonyInstance instance) { if (DebugNotIncludedOptions.Instance?.PowerUserMode ?? false) { instance.Patch(typeof(ModsScreen), "BuildDisplay", new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(HidePopups)), new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(BuildDisplay))); } KInputHandler.Add(Global.Instance.GetInputManager().GetDefaultController(), new UISnapshotHandler(), 1024); // New postload architecture requires going back a little ways var st = new System.Diagnostics.StackTrace(6); Assembly assembly = null; if (st.FrameCount > 0) { assembly = st.GetFrame(0).GetMethod()?.DeclaringType?.Assembly; } PUtil.LogDebug(assembly?.FullName ?? "none"); RunningPLibAssembly = assembly ?? Assembly.GetCallingAssembly(); // 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(); }
/// <summary> /// Registers an assembly as loaded by a particular mod. /// </summary> /// <param name="assembly">The assembly to register.</param> /// <param name="owner">The owning mod.</param> internal void RegisterAssembly(Assembly assembly, ModDebugInfo owner) { if (assembly == null) { throw new ArgumentNullException(nameof(assembly)); } if (owner == null) { throw new ArgumentNullException(nameof(owner)); } string fullName = assembly.FullName; var oldMod = modAssemblies.GetOrAdd(fullName, owner); if (oldMod != owner) { // Possible if multiple mods include the same dependency DLL DebugLogger.LogDebug("Assembly \"{0}\" is used by multiple mods:", fullName); DebugLogger.LogDebug("First loaded by {0} (used), also loaded by {1} (ignored)", oldMod.ModName, owner.ModName); } else { owner.ModAssemblies.Add(assembly); } }
/// <summary> /// When creating a Harmony instance, ignores the silly constant OxygenNotIncluded_v0.1 /// and instead fills in a name sensible to the current mod. /// </summary> internal static HarmonyInstance CreateHarmonyInstance(string name) { HarmonyInstance instance; if (CurrentMod != null) { // This mod's changes to names were integrated into the game! if (PUtil.GameVersion >= 397125u) { CurrentMod.HarmonyIdentifier = name; } else { name = CurrentMod.HarmonyIdentifier; } } instance = HarmonyInstance.Create(name); if (CurrentMod != null) { CurrentMod.HarmonyInstance = instance; } #if DEBUG DebugLogger.LogDebug("Created Harmony instance with ID {0} for mod {1}", name, CurrentModTitle); #endif return(instance); }
/// <summary> /// Transpiles Spawn to add more error logging. /// </summary> internal static IEnumerable <CodeInstruction> Transpiler( IEnumerable <CodeInstruction> method) { #if DEBUG DebugLogger.LogDebug("Transpiling Spawn()"); #endif return(TranspileSpawn(method)); }
/// <summary> /// Transpiles RegisterBuilding to catch exceptions and log them. /// </summary> internal static IEnumerable <CodeInstruction> Transpiler(ILGenerator generator, IEnumerable <CodeInstruction> method) { #if DEBUG DebugLogger.LogDebug("Transpiling BuildingConfigManager.RegisterBuilding()"); #endif var logger = typeof(DebugLogger).GetMethodSafe(nameof(DebugLogger. LogBuildingException), true, typeof(Exception), typeof(IBuildingConfig)); var ee = method.GetEnumerator(); // Emit all but the last instruction if (ee.MoveNext()) { CodeInstruction last; bool hasNext, isFirst = true; var endMethod = generator.DefineLabel(); do { last = ee.Current; if (isFirst) { last.blocks.Add(new ExceptionBlock(ExceptionBlockType. BeginExceptionBlock, null)); } hasNext = ee.MoveNext(); isFirst = false; if (hasNext) { yield return(last); } } while (hasNext); // Preserves the labels "ret" might have had last.opcode = OpCodes.Nop; last.operand = null; yield return(last); // Add a "leave" yield return(new CodeInstruction(OpCodes.Leave, endMethod)); // The exception is already on the stack var startHandler = new CodeInstruction(OpCodes.Ldarg_1); startHandler.blocks.Add(new ExceptionBlock(ExceptionBlockType. BeginCatchBlock, typeof(Exception))); yield return(startHandler); yield return(new CodeInstruction(OpCodes.Call, logger)); // End catch block, quash the exception var endCatch = new CodeInstruction(OpCodes.Leave, endMethod); endCatch.blocks.Add(new ExceptionBlock(ExceptionBlockType. EndExceptionBlock, null)); yield return(endCatch); // Actual new ret var ret = new CodeInstruction(OpCodes.Ret); ret.labels.Add(endMethod); yield return(ret); } }
/// <summary> /// Checks all types currently loaded for issues. /// </summary> public static void Check() { var types = GetAllTypes(); DebugLogger.LogDebug("Inspecting {0:D} types".F(types.Count)); foreach (var type in types) { CheckType(type); } }
/// <summary> /// Logs mod load information to the debug log. /// </summary> /// <param name="mod">The mod that was loaded.</param> private static void LogModScanned(Mod mod) { if (mod != null) { string path = mod.relative_root; DebugLogger.LogDebug("{3} ({0}): Successfully loaded {2} from '{1}'".F( mod.label.title, string.IsNullOrEmpty(path) ? "root" : path, mod.available_content.ToString(), mod.staticID)); } }
/// <summary> /// Handles an exception thrown when loading a mod. /// </summary> /// <param name="ex">The exception thrown.</param> internal static void HandleModException(object ex) { if (ex is Exception e) { string title = CurrentModTitle; if (!string.IsNullOrEmpty(title)) { DebugLogger.LogDebug("When loading mod {0}:", title); } DebugLogger.LogException(e); } }
/// <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> /// Loads the assembly from the path just like Assembly.LoadFrom, but saves the mod /// associated with that assembly. /// </summary> /// <param name="path">The path to load.</param> /// <returns>The assembly loaded.</returns> internal static Assembly LoadAssembly(string path) { var assembly = string.IsNullOrEmpty(path) ? null : Assembly.LoadFrom(path); if (assembly != null && CurrentMod != null) { #if DEBUG DebugLogger.LogDebug("Loaded assembly {0} for mod {1}", assembly.FullName, CurrentModTitle); #endif ModDebugRegistry.Instance.RegisterModAssembly(assembly, CurrentMod); } return(assembly); }
/// <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> /// Adds the assemblies in the loaded mod data to the active mod. /// </summary> /// <param name="data">The assemblies loaded.</param> internal static void LoadAssemblies(object data) { var dllList = data.GetType().GetFieldSafe("dlls", false); if (dllList != null && dllList.GetValue(data) is ICollection <Assembly> dlls) { foreach (var assembly in dlls) { #if DEBUG DebugLogger.LogDebug("Attributed assembly {0} to mod {1}", assembly. FullName, CurrentModTitle); #endif ModDebugRegistry.Instance.RegisterModAssembly(assembly, CurrentMod); } } }
/// <summary> /// Runs the required postload patches after all other mods load. /// </summary> /// <param name="instance">The Harmony instance to execute patches.</param> private static void PostloadHandler(HarmonyInstance instance) { if (DebugNotIncludedOptions.Instance?.PowerUserMode ?? false) { instance.Patch(typeof(ModsScreen), "BuildDisplay", postfix: new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(BuildDisplay))); } KInputHandler.Add(Global.Instance.GetInputManager().GetDefaultController(), new UISnapshotHandler(), 1024); // Log which mod is running PLib RunningPLibAssembly = Assembly.GetCallingAssembly(); var latest = ModDebugRegistry.Instance.OwnerOfAssembly(RunningPLibAssembly); if (latest != null) { DebugLogger.LogDebug("Executing version of PLib is from: " + latest.ModName); } }
/// <summary> /// Transpiles UpdateMods to postpone the report. /// </summary> internal static IEnumerable <CodeInstruction> Transpiler( IEnumerable <CodeInstruction> method) { #if DEBUG DebugLogger.LogDebug("Transpiling Steam.UpdateMods()"); #endif return(PPatchTools.ReplaceMethodCall(method, new Dictionary <MethodInfo, MethodInfo>() { { typeof(Manager).GetMethodSafe(nameof(Manager.Report), false, typeof(GameObject)), typeof(QueuedReportManager).GetMethodSafe(nameof( QueuedReportManager.QueueDelayedReport), true, typeof(Manager), typeof(GameObject)) }, { typeof(Manager).GetMethodSafe(nameof(Manager.Sanitize), false, typeof(GameObject)), typeof(QueuedReportManager).GetMethodSafe(nameof( QueuedReportManager.QueueDelayedSanitize), true, typeof(Manager), typeof(GameObject)) } })); }
/// <summary> /// Logs all failed asserts to the error log. /// </summary> private static void LogAllFailedAsserts() { var handler = new HarmonyMethod(typeof(DebugLogger), nameof(DebugLogger. OnAssertFailed)); var inst = ModDebugRegistry.Instance.DebugInstance; MethodInfo assert; 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); } // Assert(bool, string) assert = typeof(KCrashReporter).GetMethodSafe("Assert", true, typeof(bool), typeof(string)); if (assert != null) { inst.Patch(assert, handler); } #if DEBUG DebugLogger.LogDebug("Logging all failed asserts"); #endif } catch (Exception e) { DebugLogger.BaseLogException(e, null); } }
/// <summary> /// Applied before OnPrefabInit runs. /// </summary> internal static MethodBase TargetMethod() { MethodBase target = null; #if DEBUG DebugLogger.LogDebug("Transpiling LoadDLLs()"); #endif try { target = typeof(Mod).Assembly.GetType("KMod.DLLLoader", false)?. GetMethodSafe("LoadDLLs", true, PPatchTools.AnyArguments); if (target == null) { DebugLogger.LogError("Unable to transpile LoadDLLs: Method not found"); } } catch (IOException e) { // This should theoretically be impossible since the type is loaded DebugLogger.BaseLogException(e, null); } return(target); }
private static void PostloadHandler(HarmonyInstance instance) { if (DebugNotIncludedOptions.Instance?.PowerUserMode ?? false) { instance.Patch(typeof(ModsScreen), "BuildDisplay", new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(HidePopups)), new HarmonyMethod(typeof(DebugNotIncludedPatches), nameof(BuildDisplay))); } KInputHandler.Add(Global.Instance.GetInputManager().GetDefaultController(), new UISnapshotHandler(), 1024); // New postload architecture requires going back a little ways var st = new StackTrace(6); Assembly assembly = null; if (st.FrameCount > 0) { assembly = st.GetFrame(0).GetMethod()?.DeclaringType?.Assembly; } RunningPLibAssembly = assembly ?? Assembly.GetCallingAssembly(); // 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 // SaveManager.Load:: 13831 ms instance.ProfileMethod(typeof(SaveLoader).GetMethodSafe("Load", false, typeof( IReader))); instance.ProfileMethod(typeof(SaveLoader).GetMethodSafe("Save", false, typeof( BinaryWriter))); instance.ProfileMethod(typeof(SaveManager).GetMethodSafe("Load", false, PPatchTools.AnyArguments)); instance.ProfileMethod(typeof(SaveManager).GetMethodSafe("Save", false, PPatchTools.AnyArguments)); #endif }
/// <summary> /// A postfix method for instrumenting methods in the code base. Logs the total time /// taken in milliseconds. /// </summary> private static void ProfilerPostfix(MethodBase __originalMethod, Stopwatch __state) { DebugLogger.LogDebug("{1}.{2}:: {0:D} ms".F(__state.ElapsedMilliseconds, __originalMethod.DeclaringType, __originalMethod.Name)); }
/// <summary> /// Applied after OnSpawn runs. /// </summary> internal static void Postfix(ScheduleManager __instance) { DebugLogger.LogDebug("Sorting schedules"); __instance.GetSchedules().Sort((a, b) => a.name.CompareTo(b.name)); }
/// <summary> /// Applied after CreateSound runs. /// </summary> internal static void Postfix(string file_name, string anim_name, string sound_name) { // Add sound "GasPump_intake" to anim pumpgas_kanim.working_loop DebugLogger.LogDebug("Add sound \"{0}\" to anim {1}.{2}".F(sound_name, file_name, anim_name)); }
/// <summary> /// Applied after AddKAnimMod runs. /// </summary> internal static void Postfix(string name) { DebugLogger.LogDebug("Adding anim \"{0}\"", name); }