public static void ProfilePatch() { var patches = Harmony.GetAllPatchedMethods().ToList(); var filteredTranspilers = patches .Where(m => Harmony.GetPatchInfo(m).Transpilers.Any(p => Utility.IsNotAnalyzerPatch(p.owner) && !TranspilerMethodUtility.PatchedMeths.Contains(m))) .ToList(); TranspilerMethodUtility.PatchedMeths.AddRange(filteredTranspilers); foreach (var meth in filteredTranspilers) { try { Modbase.Harmony.Patch(meth, transpiler: TranspilerMethodUtility.TranspilerProfiler); } catch (Exception e) { #if DEBUG ThreadSafeLogger.Error($"[Analyzer] Failed to patch transpiler, failed with the message {e.Message}"); #endif #if NDEBUG if (Settings.verboseLogging) { ThreadSafeLogger.Error($"[Analyzer] Failed to patch transpiler {meth.DeclaringType.FullName + ":" + meth.Name}, failed with the message {e.Message}"); } #endif } } }
public static void RegisterHarmonyId(string harmonyid, Assembly assembly) { if (harmonyIds.TryGetValue(harmonyid, out var asm)) { mods.TryGetValue(asm, out var asmMod); mods.TryGetValue(assembly, out var assemblyMod); // Doesn't matter a whole lot if (asmMod == assemblyMod) { return; } var blameString = $"This is a Harmony ID configuration issue with {asmMod} and {assemblyMod}. Please report that they are instantiating harmony with the same ID."; ThreadSafeLogger.Error($"The HarmonyID {harmonyid} has been loaded twice. It is associated with both: \n{asm.FullName} and {assembly.FullName}\n{blameString}\n\nEnable verbose logging and restart to view the associated callstack(s)"); if (Settings.verboseLogging) { ThreadSafeLogger.Error($"Initial Trace: {GetStackTraceString(harmonyIds_d[harmonyid], out _)}"); ThreadSafeLogger.Error($"Current Trace: {GetStackTraceString(new StackTrace(1, false), out _)}"); } } else { harmonyIds.Add(harmonyid, assembly); if (Settings.verboseLogging) { harmonyIds_d.Add(harmonyid, new StackTrace(1, false)); } } }
public static void AddEntry(string name, Category category) { Type myType = null; if (types.ContainsKey(name)) { myType = types[name]; } else { myType = DynamicTypeBuilder.CreateType(name, null); types.Add(name, myType); } #if DEBUG ThreadSafeLogger.Message($"Adding entry {name} into the category {category}"); #endif var entry = Entry.Create(name, category, myType, true, true); if (Tab(category).entries.ContainsKey(entry)) { ThreadSafeLogger.Error($"Attempting to re-add entry {name} into the category {category}"); } else { Tab(category).entries.Add(entry, myType); } }
public static FileHeader ReadHeader(BinaryReader reader) { var fileHeader = new FileHeader() { MAGIC = reader.ReadInt32(), scribingVer = reader.ReadInt32(), methodName = reader.ReadString(), name = reader.ReadString(), entryPerCall = reader.ReadBoolean(), onlyEntriesWithValues = reader.ReadBoolean(), entries = reader.ReadInt32(), targetEntries = reader.ReadInt32() }; if (fileHeader.MAGIC == ENTRY_FILE_MAGIC) { return(fileHeader); } ThreadSafeLogger.Error($"Loaded header has an invalid MAGIC number, this indicates disk corruption"); return(new FileHeader() { MAGIC = -1 }); // magic = -1 is an error value. }
private static IEnumerable <CodeInstruction> Transpiler(MethodBase __originalMethod, IEnumerable <CodeInstruction> codeInstructions) { var enumerable = codeInstructions.ToList(); try { List <CodeInstruction> instructions = enumerable; for (int i = 0; i < instructions.Count; i++) { if (!IsFunctionCall(instructions[i].opcode)) { continue; } if (!(instructions[i].operand is MethodInfo meth)) { continue; } // Check for constrained if (i != 0 && instructions[i - 1].opcode == OpCodes.Constrained) { continue; } // Make sure it is not an analyzer profiling method if (meth.DeclaringType.FullName.Contains("Analyzer.Profiling")) { continue; } var key = Utility.GetMethodKey(meth); var index = MethodInfoCache.AddMethod(key, meth); var inst = MethodTransplanting.ReplaceMethodInstruction( instructions[i], key, GUIController.types[__originalMethod.DeclaringType + ":" + __originalMethod.Name + "-int"], index); instructions[i] = inst; } return(instructions); } catch (Exception e) { #if DEBUG ThreadSafeLogger.Error($"Failed to patch the internal method {__originalMethod.DeclaringType.FullName}:{__originalMethod.Name}, failed with the error " + e.Message); #else if (Settings.verboseLogging) { ThreadSafeLogger.Warning($"Failed to patch the internal method {__originalMethod.DeclaringType.FullName}:{__originalMethod.Name}, failed with the error " + e.Message); } #endif return(enumerable); } }
private static void ExecuteWorker(LogStats logic, int[] LocalCalls, double[] LocalTimes, int currentLogCount, uint currentIndex) { try { // todo // implement a custom sorting which also keeps track of the sum. // this will take this from // o(2*nlogn + n) to o(2*nlogn) Array.Sort(LocalCalls); Array.Sort(LocalTimes); for (var i = 0; i < Profiler.RECORDS_HELD; i++) { logic.TotalCalls += LocalCalls[i]; logic.TotalTime += LocalTimes[i]; } // Mean logic.MeanTimePerCall = logic.TotalTime / logic.TotalCalls; logic.MeanTimePerUpdateCycle = logic.TotalTime / currentLogCount; logic.MeanCallsPerUpdateCycle = logic.TotalCalls / (float)currentLogCount; var medianOffset = Profiler.RECORDS_HELD - currentLogCount; var middle = currentLogCount / 2; // Medians logic.MedianTime = LocalTimes[medianOffset + middle]; logic.MedianCalls = LocalCalls[medianOffset + middle]; // Max logic.HighestTime = LocalTimes[Profiler.RECORDS_HELD - 1]; logic.HighestCalls = LocalCalls[Profiler.RECORDS_HELD - 1]; // general logic.Entries = currentLogCount; lock (CurrentLogStats.sync ) // Dump our current statistics into our static class which our drawing class uses { CurrentLogStats.stats = logic; } } catch (Exception e) { #if DEBUG ThreadSafeLogger.Error( $"[Analyzer] Failed while calculating stats for profiler, errored with the message {e.Message}"); #else if (Settings.verboseLogging) { ThreadSafeLogger.Error($"[Analyzer] Failed while calculating stats for profiler, errored with the message {e.Message}"); } #endif } }
public static void ProfilePatch() { MethodInfo jiff = AccessTools.Method(typeof(StatExtension), nameof(StatExtension.GetStatValue)); HarmonyMethod pre = new HarmonyMethod(typeof(H_GetStatValue), nameof(Prefix)); HarmonyMethod post = new HarmonyMethod(typeof(H_GetStatValue), nameof(Postfix)); Modbase.Harmony.Patch(jiff, pre, post); jiff = AccessTools.Method(typeof(StatExtension), nameof(StatExtension.GetStatValueAbstract), new[] { typeof(BuildableDef), typeof(StatDef), typeof(ThingDef) }); pre = new HarmonyMethod(typeof(H_GetStatValue), nameof(PrefixAb)); Modbase.Harmony.Patch(jiff, pre, post); jiff = AccessTools.Method(typeof(StatExtension), nameof(StatExtension.GetStatValueAbstract), new[] { typeof(AbilityDef), typeof(StatDef) }); pre = new HarmonyMethod(typeof(H_GetStatValue), nameof(PrefixAbility)); Modbase.Harmony.Patch(jiff, pre, post); jiff = AccessTools.Method(typeof(StatWorker), nameof(StatWorker.GetValue), new[] { typeof(StatRequest), typeof(bool) }); pre = new HarmonyMethod(typeof(H_GetStatValue), nameof(GetValueDetour)); Modbase.Harmony.Patch(jiff, pre); HarmonyMethod go = new HarmonyMethod(typeof(H_GetStatValue), nameof(PartPrefix)); HarmonyMethod biff = new HarmonyMethod(typeof(H_GetStatValue), nameof(PartPostfix)); foreach (Type allLeafSubclass in typeof(StatPart).AllSubclassesNonAbstract()) { try { MethodInfo mef = AccessTools.Method(allLeafSubclass, nameof(StatPart.TransformValue), new Type[] { typeof(StatRequest), typeof(float).MakeByRefType() }); if (mef.DeclaringType == allLeafSubclass) { Patches info = Harmony.GetPatchInfo(mef); bool F = true; if (info != null) { foreach (Patch infoPrefix in info.Prefixes) { if (infoPrefix.PatchMethod == go.method) { F = false; } } } if (F) { Modbase.Harmony.Patch(mef, go, biff); } } } catch (Exception) { ThreadSafeLogger.Error($"Failed to patch {allLeafSubclass} from {allLeafSubclass.Assembly.FullName} for profiling"); } } }
public static void ProfilePatch() { try { MethodTransplanting.PatchMethods(typeof(H_HarmonyPatches)); } catch (Exception e) { ThreadSafeLogger.Error("Patching HarmonyPatches failed, errored with the message" + e.Message); } }
private static void Error(string message) { #if DEBUG ThreadSafeLogger.Error($"[Analyzer] Patching error: {message}"); #endif #if NDEBUG if (!displayMessages) { return; } ThreadSafeLogger.Error($"[Analyzer] Patching error: {message}"); #endif }
public static void BeginUpdate() { #if DEBUG if (Analyzer.CurrentlyPaused) { return; } if (midUpdate) { ThreadSafeLogger.Error("[Analyzer] Attempting to begin new update cycle when the previous update has not ended"); } midUpdate = true; #endif }
// Mostly here for book keeping, optimised out of a release build. public static void BeginUpdate() { if (Analyzer.CurrentlyPaused) { return; } rootProf.Start(); if (midUpdate) { ThreadSafeLogger.Error("[CRITICAL] Caught analyzer trying to begin a new update cycle before finishing the previous one."); } midUpdate = true; }
public void RecordMeasurement() { #if DEBUG if (stopwatch.IsRunning) { ThreadSafeLogger.Error($"[Analyzer] Profile {key} was still running when recorded"); } #endif times[currentIndex] = stopwatch.Elapsed.TotalMilliseconds; hits[currentIndex] = hitCounter; currentIndex = (currentIndex + 1) % RECORDS_HELD; // ring buffer stopwatch.Reset(); hitCounter = 0; }
// Iterates through each child element in the document and attempts to extract method(s) from the strings inside the children private static void Parse(XmlDocument doc) { foreach (XmlNode node in doc.DocumentElement.ChildNodes) // entries should be { var meths = new HashSet <MethodInfo>(); foreach (XmlNode child in node.ChildNodes) { switch (child.Name.ToLower()) { case "methods": case "method": foreach (XmlNode method in child.ChildNodes) { meths.Add(ParseMethod(method.InnerText)); } break; case "types": case "type": foreach (XmlNode type in child.ChildNodes) { meths.AddRange(ParseTypeMethods(type.InnerText)); } break; case "nestedtype": case "nestedtypes": foreach (XmlNode type in child.ChildNodes) { meths.AddRange(ParseSubTypeTypeMethods(type.InnerText)); } break; default: ThreadSafeLogger.Error($"[Analyzer] Attempting to read unknown value from an Analyzer.xml, the given input was {child.Name}, it should have been either '(M/m)ethods', '(T/t)ypes' '(N/n)estedTypes"); break; } } Type myType = DynamicTypeBuilder.CreateType(node.Name, meths); GUIController.Tab(Category.Modder).entries.Add(Entry.Create(myType.Name, Category.Modder, myType, false, true), myType); } }
internal static void UpdateMethod(Type type, MethodInfo meth) { if (patchedMeths.Contains(meth)) { #if DEBUG ThreadSafeLogger.Warning($"[Analyzer] Already patched method {meth.DeclaringType.FullName + ":" + meth.Name}"); #else if (Settings.verboseLogging) { ThreadSafeLogger.Warning($"[Analyzer] Already patched method {meth.DeclaringType.FullName + ":" + meth.Name}"); } #endif return; } patchedMeths.Add(meth); typeInfo.TryAdd(meth, type); Task.Factory.StartNew(delegate { try { Modbase.Harmony.Patch(meth, transpiler: transpiler); } catch (Exception e) { #if DEBUG ThreadSafeLogger.Error($"[Analyzer] Failed to patch method {meth.DeclaringType.FullName + ":" + meth.Name} failed with the error {e.Message}"); #else if (Settings.verboseLogging) { ThreadSafeLogger.Warning($"[Analyzer] Failed to patch method {meth.DeclaringType.FullName}:{meth.Name} failed with the error {e.Message}"); } #endif } }); }
public static void ExecutePatch() { try { if (patchType == Category.Tick) { switch (input) { case CurrentInput.Method: MethodTransplanting.UpdateMethods(typeof(CustomProfilersTick), Utility.GetMethods(currentInput)); break; case CurrentInput.Type: MethodTransplanting.UpdateMethods(typeof(CustomProfilersTick), Utility.GetTypeMethods(AccessTools.TypeByName(currentInput))); break; case CurrentInput.MethodHarmony: MethodTransplanting.UpdateMethods(typeof(CustomProfilersTick), Utility.GetMethodsPatching(currentInput)); break; case CurrentInput.SubClasses: MethodTransplanting.UpdateMethods(typeof(CustomProfilersTick), Utility.SubClassImplementationsOf(AccessTools.TypeByName(currentInput), m => true)); break; case CurrentInput.TypeHarmony: MethodTransplanting.UpdateMethods(typeof(CustomProfilersTick), Utility.GetMethodsPatchingType(AccessTools.TypeByName(currentInput))); break; case CurrentInput.InternalMethod: Utility.PatchInternalMethod(currentInput, Category.Tick); return; case CurrentInput.Assembly: Utility.PatchAssembly(currentInput, Category.Tick); return; } GUIController.SwapToEntry("Custom Tick"); } else { switch (input) { case CurrentInput.Method: MethodTransplanting.UpdateMethods(typeof(CustomProfilersUpdate), Utility.GetMethods(currentInput)); break; case CurrentInput.Type: MethodTransplanting.UpdateMethods(typeof(CustomProfilersUpdate), Utility.GetTypeMethods(AccessTools.TypeByName(currentInput))); break; case CurrentInput.MethodHarmony: MethodTransplanting.UpdateMethods(typeof(CustomProfilersUpdate), Utility.GetMethodsPatching(currentInput)); break; case CurrentInput.SubClasses: MethodTransplanting.UpdateMethods(typeof(CustomProfilersUpdate), Utility.SubClassImplementationsOf(AccessTools.TypeByName(currentInput), m => true)); break; case CurrentInput.TypeHarmony: MethodTransplanting.UpdateMethods(typeof(CustomProfilersUpdate), Utility.GetMethodsPatchingType(AccessTools.TypeByName(currentInput))); break; case CurrentInput.InternalMethod: Utility.PatchInternalMethod(currentInput, Category.Update); return; case CurrentInput.Assembly: Utility.PatchAssembly(currentInput, Category.Update); return; } GUIController.SwapToEntry("Custom Update"); } } catch (Exception e) { ThreadSafeLogger.Error($"Failed to process input, failed with the error {e.Message}"); } }