using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Scripting; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MartinChavez")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Instrumentacion y optimizacion de tiempos de carga para modpacks de Lethal Company v81.")] [assembly: AssemblyFileVersion("0.7.1.0")] [assembly: AssemblyInformationalVersion("0.7.1")] [assembly: AssemblyProduct("MechFix")] [assembly: AssemblyTitle("MechFix")] [assembly: AssemblyVersion("0.7.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace MechFix { internal static class DungeonSizeCap { private static bool _resolveFailed; internal static void Apply() { float value = Plugin.MaxDungeonSize.Value; if (value <= 0f || _resolveFailed) { return; } try { object obj = ResolveCurrentLevel(); if (obj == null) { return; } FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), "factorySizeMultiplier"); if (fieldInfo == null) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "tamaño: la luna no tiene factorySizeMultiplier; no topeo nada.")); _resolveFailed = true; return; } float num = Convert.ToSingle(fieldInfo.GetValue(obj)); if (num <= value) { Plugin.Log.LogInfo((object)(Plugin.Stamp() + " tamaño de dungeon: " + num.ToString("F2") + " (ya por debajo del tope " + value.ToString("F2") + ", sin cambios)")); } else { fieldInfo.SetValue(obj, value); Plugin.Log.LogInfo((object)(Plugin.Stamp() + " tamaño de dungeon: " + num.ToString("F2") + " -> " + value.ToString("F2") + " (recorte del " + (100f * (1f - value / num)).ToString("F0") + "%)")); } } catch (Exception ex) { _resolveFailed = true; Plugin.Log.LogWarning((object)(Plugin.Stamp() + "tamaño: no pude aplicar el tope: " + ex.GetType().Name + ": " + ex.Message)); } } private static object ResolveCurrentLevel() { string[] array = new string[2] { "RoundManager", "StartOfRound" }; for (int i = 0; i < array.Length; i++) { Type type = AccessTools.TypeByName(array[i]); if (type == null) { continue; } object obj = AccessTools.Field(type, "Instance")?.GetValue(null) ?? AccessTools.Property(type, "Instance")?.GetValue(null); if (obj != null) { object obj2 = AccessTools.Field(obj.GetType(), "currentLevel")?.GetValue(obj); if (obj2 != null) { return obj2; } } } return null; } } internal static class FrameProfiler { private static int _lastGcGen0; private static int _lastGcGen2; private static long _lastHeap; private static int _slowFrames; private static int _gcDuringGeneration; private static bool _gcPaused; private static bool _gcModeAvailable = true; internal static void BeginGeneration() { _slowFrames = 0; _gcDuringGeneration = 0; Snapshot(); } private static void Snapshot() { try { _lastGcGen0 = GC.CollectionCount(0); _lastGcGen2 = GC.CollectionCount(GC.MaxGeneration); _lastHeap = GC.GetTotalMemory(forceFullCollection: false); } catch { } } internal static void Tick(float frameMs) { if (!(frameMs < (float)Plugin.SlowFrameThresholdMs.Value)) { _slowFrames++; int num = 0; int num2 = 0; long num3 = 0L; try { num = GC.CollectionCount(0); num2 = GC.CollectionCount(GC.MaxGeneration); num3 = GC.GetTotalMemory(forceFullCollection: false); } catch { } int num4 = num - _lastGcGen0; int num5 = num2 - _lastGcGen2; double num6 = (double)(num3 - _lastHeap) / 1048576.0; _gcDuringGeneration += num4; Plugin.Log.LogInfo((object)(Plugin.Stamp() + " frame lento " + frameMs.ToString("F0") + "ms GC gen0 +" + num4 + " gen2 +" + num5 + " heap " + num3 / 1048576 + "MB (" + ((num6 >= 0.0) ? "+" : "") + num6.ToString("F1") + ")")); _lastGcGen0 = num; _lastGcGen2 = num2; _lastHeap = num3; } } internal static string Report() { if (_slowFrames == 0) { return string.Empty; } return " [lentos=" + _slowFrames + " GC=" + _gcDuringGeneration + "]"; } internal static void PauseGC() { if (!Plugin.PauseGCDuringLoad.Value || _gcPaused || !_gcModeAvailable) { return; } try { GarbageCollector.GCMode = (Mode)0; _gcPaused = true; Plugin.Log.LogInfo((object)(Plugin.Stamp() + " GC pausado durante la generacion")); } catch (Exception ex) { _gcModeAvailable = false; Plugin.Log.LogWarning((object)(Plugin.Stamp() + "no pude pausar el GC: " + ex.Message)); } } internal static void ResumeGC() { if (!_gcPaused) { return; } _gcPaused = false; try { GarbageCollector.GCMode = (Mode)1; long totalMemory = GC.GetTotalMemory(forceFullCollection: false); GC.Collect(); long totalMemory2 = GC.GetTotalMemory(forceFullCollection: false); Plugin.Log.LogInfo((object)(Plugin.Stamp() + " GC reanudado, liberados " + (totalMemory - totalMemory2) / 1048576 + "MB")); } catch (Exception ex) { Plugin.Log.LogError((object)(Plugin.Stamp() + "FALLO al reanudar el GC: " + ex.Message)); } } } internal static class GameGraphics { private sealed class Knob { public readonly string[] FieldNames; public readonly string Label; public readonly Func TargetValue; public readonly string[] RefreshMethods; public FieldInfo Field; public object Saved; public bool Touched; public Knob(string[] fieldNames, string label, Func target, params string[] refreshMethods) { FieldNames = fieldNames; Label = label; TargetValue = target; RefreshMethods = refreshMethods; } } private static readonly Knob[] Knobs = new Knob[4] { new Knob(new string[1] { "terrainGrassDistance" }, "Terrain / Grass Detail", () => Plugin.GfxTerrainDetail.Value), new Knob(new string[1] { "motionBlur" }, "Motion Blur", () => Plugin.GfxMotionBlur.Value, "SetMotionBlur", "ResetMotionBlur"), new Knob(new string[1] { "pixelRes" }, "Pixel Resolution", () => Plugin.GfxPixelRes.Value, "SetPixelResolution"), new Knob(new string[1] { "advancedLightMode" }, "Indirect lighting", () => Plugin.GfxIndirectLight.Value, "UpdateIndirectLightMode") }; private static bool _resolved; private static bool _usable; private static object _settingsObject; private static object _settingsOwner; private static Type _ownerType; private static bool _applied; private static bool Resolve() { if (_resolved && _settingsObject != null) { return _usable; } try { _ownerType = AccessTools.TypeByName("IngamePlayerSettings"); if (_ownerType == null) { Report("no existe el tipo IngamePlayerSettings"); return Fail(); } _settingsOwner = AccessTools.Field(_ownerType, "Instance")?.GetValue(null) ?? AccessTools.Property(_ownerType, "Instance")?.GetValue(null); if (_settingsOwner == null) { return false; } FieldInfo fieldInfo = AccessTools.Field(_settingsOwner.GetType(), "settings"); if (fieldInfo == null) { Report("IngamePlayerSettings no tiene un campo 'settings'"); return Fail(); } _settingsObject = fieldInfo.GetValue(_settingsOwner); if (_settingsObject == null) { return false; } Type type = _settingsObject.GetType(); int num = 0; List list = new List(); Knob[] knobs = Knobs; foreach (Knob knob in knobs) { string[] fieldNames = knob.FieldNames; foreach (string text in fieldNames) { knob.Field = AccessTools.Field(type, text); if (knob.Field != null) { break; } } if (knob.Field == null) { list.Add(knob.Label + "=AUSENTE"); continue; } num++; list.Add(knob.Field.Name + " (" + knob.Field.FieldType.Name + ") = " + SafeRead(knob.Field)); } Plugin.Log.LogInfo((object)(Plugin.Stamp() + "graficos del juego en " + type.FullName + " -> " + string.Join(" | ", list.ToArray()))); DumpAllFields(type); _resolved = true; _usable = num > 0; if (!_usable) { Report("ninguno de los campos de graficos conocidos existe en esta version"); } return _usable; } catch (Exception ex) { Report("fallo al resolver: " + ex.GetType().Name + ": " + ex.Message); return Fail(); } } private static bool Fail() { _resolved = true; _usable = false; return false; } private static void Report(string msg) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "graficos del juego: " + msg)); } private static string SafeRead(FieldInfo f) { try { return Convert.ToString(f.GetValue(_settingsObject)); } catch { return "?"; } } private static void DumpAllFields(Type dataType) { try { List list = new List(); FieldInfo[] fields = dataType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { Type fieldType = fieldInfo.FieldType; if (fieldType.IsEnum || fieldType == typeof(bool) || fieldType == typeof(int) || fieldType == typeof(float) || fieldType == typeof(string)) { list.Add(fieldInfo.Name + "(" + fieldType.Name + ")=" + SafeRead(fieldInfo)); } } Plugin.Log.LogInfo((object)(Plugin.Stamp() + "campos de ajustes (" + list.Count + "): " + string.Join(" | ", list.ToArray()))); } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "no pude volcar los campos: " + ex.Message)); } } internal static void Apply() { if (_applied || !Plugin.LowerGameGraphicsDuringLoad.Value || !Resolve()) { return; } _applied = true; List list = new List(); Knob[] knobs = Knobs; foreach (Knob knob in knobs) { if (knob.Field == null) { continue; } try { object obj = knob.TargetValue(); if (!(obj is int num) || num >= 0) { knob.Saved = knob.Field.GetValue(_settingsObject); object obj2 = Coerce(obj, knob.Field.FieldType); if (obj2 != null && !object.Equals(obj2, knob.Saved)) { knob.Field.SetValue(_settingsObject, obj2); knob.Touched = true; InvokeRefresh(knob, obj2); list.Add(knob.Label + ": " + knob.Saved?.ToString() + "->" + obj2); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "graficos: no pude bajar " + knob.Label + ": " + ex.Message)); } } Plugin.Log.LogInfo((object)(Plugin.Stamp() + " graficos BAJADOS " + ((list.Count == 0) ? "(ya estaban al minimo)" : string.Join(", ", list.ToArray())))); } internal static void Restore() { if (!_applied) { return; } _applied = false; Knob[] knobs = Knobs; foreach (Knob knob in knobs) { if (!knob.Touched || knob.Field == null) { continue; } try { knob.Field.SetValue(_settingsObject, knob.Saved); InvokeRefresh(knob, knob.Saved); } catch (Exception ex) { Plugin.Log.LogError((object)(Plugin.Stamp() + "graficos: FALLO al restaurar " + knob.Label + ": " + ex.Message)); } finally { knob.Touched = false; } } List list = new List(); knobs = Knobs; foreach (Knob knob2 in knobs) { if (!(knob2.Field == null) && knob2.Saved != null) { object obj = null; try { obj = knob2.Field.GetValue(_settingsObject); } catch { } bool flag = object.Equals(obj, knob2.Saved); list.Add(knob2.Label + "=" + obj?.ToString() + (flag ? "" : (" ¡ESPERABA " + knob2.Saved?.ToString() + "!"))); } } Plugin.Log.LogInfo((object)(Plugin.Stamp() + " graficos RESTAURADOS " + string.Join(", ", list.ToArray()))); } private static void InvokeRefresh(Knob k, object value) { if (k.RefreshMethods == null || _settingsOwner == null) { return; } string[] refreshMethods = k.RefreshMethods; foreach (string text in refreshMethods) { MethodInfo methodInfo = AccessTools.Method(_ownerType, text, (Type[])null, (Type[])null); if (methodInfo == null) { continue; } try { ParameterInfo[] parameters = methodInfo.GetParameters(); if (parameters.Length == 0) { methodInfo.Invoke(_settingsOwner, null); break; } if (parameters.Length == 1) { object obj = Coerce(value, parameters[0].ParameterType); if (obj != null) { methodInfo.Invoke(_settingsOwner, new object[1] { obj }); break; } } } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "graficos: " + text + " fallo: " + ex.Message)); } } } private static object Coerce(object value, Type target) { try { if (value == null) { return null; } if (target.IsEnum) { return Enum.ToObject(target, Convert.ToInt32(value)); } if (target == typeof(bool)) { return (value is bool flag) ? flag : (Convert.ToInt32(value) != 0); } if (target == typeof(int) || target == typeof(float) || target == typeof(double) || target == typeof(long)) { if (value is bool flag2) { value = (flag2 ? 1 : 0); } return Convert.ChangeType(value, target); } return target.IsInstanceOfType(value) ? value : null; } catch { return null; } } } internal static class GenerationTimer { private enum Kind { GenStart, GenEnd, Bake, Control } private sealed class Target { public readonly string TypeName; public readonly string MethodName; public readonly Kind Which; public Target(string typeName, string methodName, Kind which) { TypeName = typeName; MethodName = methodName; Which = which; } public override string ToString() { return TypeName + "." + MethodName; } } private static readonly Target[] Targets = new Target[4] { new Target("RoundManager", "GenerateNewFloor", Kind.GenStart), new Target("RoundManager", "FinishGeneratingNewLevelClientRpc", Kind.GenEnd), new Target("RoundManager", "BakeDungenNavMeshOnDelay", Kind.Bake), new Target("StartOfRound", "Awake", Kind.Control) }; private static double _genStartedAt = -1.0; private static int _genCount; private static readonly List _fired = new List(); private static readonly Dictionary _kindByMethod = new Dictionary(); internal static string StatusSuffix() { if (_genStartedAt < 0.0) { return " gen=idle"; } return " gen=EN CURSO " + (Plugin.T - _genStartedAt).ToString("F1") + "s"; } internal static void Install(Harmony harmony) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(GenerationTimer), "Before", (Type[])null, (Type[])null)); HarmonyMethod val2 = new HarmonyMethod(AccessTools.Method(typeof(GenerationTimer), "After", (Type[])null, (Type[])null)); int num = 0; Target[] targets = Targets; foreach (Target target in targets) { try { Type type = AccessTools.TypeByName(target.TypeName); if (type == null) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + " no existe el tipo " + target.TypeName)); continue; } MethodInfo methodInfo = AccessTools.Method(type, target.MethodName, (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + " no existe el metodo " + target)); continue; } harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _kindByMethod[methodInfo] = target; num++; } catch (Exception ex) { Plugin.Log.LogError((object)(Plugin.Stamp() + " fallo al enganchar " + target?.ToString() + ": " + ex.GetType().Name + ": " + ex.Message)); } } if (num == 0) { Plugin.Log.LogError((object)(Plugin.Stamp() + "cronometro: CERO metodos enganchados. No va a haber mediciones.")); return; } Plugin.Log.LogInfo((object)(Plugin.Stamp() + "cronometro instalado, " + num + "/" + Targets.Length + " metodos enganchados.")); } private static Target Resolve(MethodBase original) { if (!_kindByMethod.TryGetValue(original, out var value)) { return null; } return value; } private static void Before(MethodBase __originalMethod) { Target target = Resolve(__originalMethod); if (target != null) { Announce(target); switch (target.Which) { case Kind.GenStart: _genCount++; _genStartedAt = Plugin.T; Plugin.Log.LogInfo((object)(Plugin.Stamp() + ">>> GENERACION #" + _genCount + " EMPIEZA")); DungeonSizeCap.Apply(); RuntimeHost.MarkGenerationStart(); FrameProfiler.BeginGeneration(); LoadBoost.Begin(); break; case Kind.Bake: Plugin.Log.LogInfo((object)(Plugin.Stamp() + " horneado de navmesh: solicitado" + Delta())); break; } } } private static void After(MethodBase __originalMethod) { Target target = Resolve(__originalMethod); if (target == null) { return; } switch (target.Which) { case Kind.GenEnd: if (!(_genStartedAt < 0.0)) { LoadBoost.End(); Plugin.Log.LogInfo((object)(Plugin.Stamp() + "<<< GENERACION #" + _genCount + " TERMINA" + Delta() + RuntimeHost.GenerationFrameReport())); _genStartedAt = -1.0; } break; case Kind.Control: RuntimeHost.Ensure(); LogOptimizer.ApplyStackTraceSettings(); break; } } private static string Delta() { if (_genStartedAt < 0.0) { return string.Empty; } return " (+" + (Plugin.T - _genStartedAt).ToString("F2") + "s desde el inicio)"; } private static void Announce(Target t) { string text = t.ToString(); if (!_fired.Contains(text)) { _fired.Add(text); Plugin.Log.LogInfo((object)(Plugin.Stamp() + "hook VIVO: primer disparo desde " + text)); } } } internal static class LoadBoost { private static bool _active; private static int _savedVSync; private static int _savedTargetFrameRate; private static float _savedShadowDistance; private static bool _savedAutoSyncTransforms; private static bool _shadowsTouched; private static bool _physicsTouched; internal static bool IsActive => _active; internal static void Begin() { if (_active || !Plugin.BoostDuringLoad.Value) { return; } _active = true; _shadowsTouched = false; _physicsTouched = false; try { _savedVSync = QualitySettings.vSyncCount; _savedTargetFrameRate = Application.targetFrameRate; QualitySettings.vSyncCount = 0; Application.targetFrameRate = -1; GameGraphics.Apply(); FrameProfiler.PauseGC(); if (Plugin.DropShadowsDuringLoad.Value) { _savedShadowDistance = QualitySettings.shadowDistance; QualitySettings.shadowDistance = 0f; _shadowsTouched = true; } if (Plugin.DeferPhysicsSync.Value) { _savedAutoSyncTransforms = Physics.autoSyncTransforms; Physics.autoSyncTransforms = false; _physicsTouched = true; } Plugin.Log.LogInfo((object)(Plugin.Stamp() + " boost ON (vSync " + _savedVSync + "->0, fps cap " + _savedTargetFrameRate + "->libre" + (_shadowsTouched ? ", sombras off" : "") + (_physicsTouched ? ", physics sync diferido" : "") + ")")); } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "boost fallo al activarse: " + ex.Message)); End(); } } internal static void End() { if (!_active) { return; } _active = false; try { FrameProfiler.ResumeGC(); GameGraphics.Restore(); QualitySettings.vSyncCount = _savedVSync; Application.targetFrameRate = _savedTargetFrameRate; if (_shadowsTouched) { QualitySettings.shadowDistance = _savedShadowDistance; _shadowsTouched = false; } if (_physicsTouched) { Physics.autoSyncTransforms = _savedAutoSyncTransforms; Physics.SyncTransforms(); _physicsTouched = false; } Plugin.Log.LogInfo((object)(Plugin.Stamp() + " boost OFF (ajustes restaurados)")); } catch (Exception ex) { Plugin.Log.LogError((object)(Plugin.Stamp() + "boost fallo al restaurar: " + ex.Message)); } } } internal static class LogOptimizer { private static readonly string[] DefaultPatterns = new string[12] { "samples in the preprocessor", "Insufficient buffer space", "puma tree mode", "end target tree", "Got EndTargetTree", "PUMA AI", "Tree state:", "Setting prob to", "Got target player?", "Skipping player #", "DISABLING/ENABLING SKINNEDMESH", "Is jetpack audio playing?" }; private static string[] _patterns = DefaultPatterns; private static int _blocked; private static bool _reportedFirstBlock; internal static int Blocked => Volatile.Read(in _blocked); internal static void ApplyStackTraceSettings() { if (!Plugin.SuppressStackTraces.Value) { return; } try { Application.SetStackTraceLogType((LogType)3, (StackTraceLogType)0); Application.SetStackTraceLogType((LogType)2, (StackTraceLogType)0); } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "no pude ajustar los stack traces: " + ex.Message)); } } internal static void Install(Harmony harmony) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown BuildPatternList(); if (!Plugin.SuppressSpamLogs.Value) { Plugin.Log.LogInfo((object)(Plugin.Stamp() + "bloqueo de spam desactivado por config; solo actuan los stack traces.")); return; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(LogOptimizer), "BlockIfSpam", (Type[])null, (Type[])null)); MethodInfo[] obj = new MethodInfo[4] { AccessTools.Method(typeof(Debug), "Log", new Type[1] { typeof(object) }, (Type[])null), AccessTools.Method(typeof(Debug), "Log", new Type[2] { typeof(object), typeof(Object) }, (Type[])null), AccessTools.Method(typeof(Debug), "LogWarning", new Type[1] { typeof(object) }, (Type[])null), AccessTools.Method(typeof(Debug), "LogWarning", new Type[2] { typeof(object), typeof(Object) }, (Type[])null) }; int num = 0; MethodInfo[] array = obj; foreach (MethodInfo methodInfo in array) { if (!(methodInfo == null)) { try { harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "no pude parchear " + methodInfo.Name + ": " + ex.Message)); } } } Plugin.Log.LogInfo((object)(Plugin.Stamp() + "bloqueo de spam activo sobre " + num + " sobrecargas de Debug, con " + _patterns.Length + " patrones.")); } private static void BuildPatternList() { string value = Plugin.ExtraPatterns.Value; if (string.IsNullOrEmpty(value)) { _patterns = DefaultPatterns; return; } List list = new List(DefaultPatterns); string[] array = value.Split('|'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } _patterns = list.ToArray(); } private static bool BlockIfSpam(object message) { if (message == null) { return true; } if (!(message is string text)) { return true; } string[] patterns = _patterns; for (int i = 0; i < patterns.Length; i++) { if (text.IndexOf(patterns[i], StringComparison.OrdinalIgnoreCase) >= 0) { Interlocked.Increment(ref _blocked); if (!_reportedFirstBlock) { _reportedFirstBlock = true; Plugin.Log.LogInfo((object)(Plugin.Stamp() + "primer mensaje bloqueado (coincidio con '" + patterns[i] + "'). El filtro esta vivo.")); } return false; } } return true; } } [BepInPlugin("martinchavez.mechfix", "MechFix", "0.7.1")] [BepInProcess("Lethal Company.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "martinchavez.mechfix"; public const string PluginName = "MechFix"; public const string PluginVersion = "0.7.1"; internal static readonly Stopwatch Clock = new Stopwatch(); internal static ManualLogSource Log { get; private set; } internal static ConfigEntry ModEnabled { get; private set; } internal static ConfigEntry SuppressStackTraces { get; private set; } internal static ConfigEntry SuppressSpamLogs { get; private set; } internal static ConfigEntry ExtraPatterns { get; private set; } internal static ConfigEntry HeartbeatSeconds { get; private set; } internal static ConfigEntry BoostDuringLoad { get; private set; } internal static ConfigEntry MaxDungeonSize { get; private set; } internal static ConfigEntry LowerGameGraphicsDuringLoad { get; private set; } internal static ConfigEntry GfxTerrainDetail { get; private set; } internal static ConfigEntry GfxMotionBlur { get; private set; } internal static ConfigEntry GfxPixelRes { get; private set; } internal static ConfigEntry GfxIndirectLight { get; private set; } internal static ConfigEntry DropShadowsDuringLoad { get; private set; } internal static ConfigEntry DeferPhysicsSync { get; private set; } internal static ConfigEntry PauseGCDuringLoad { get; private set; } internal static ConfigEntry SlowFrameThresholdMs { get; private set; } internal static double T => Clock.Elapsed.TotalSeconds; internal static string Stamp() { return "[t=" + T.ToString("F3") + "s] "; } private void Awake() { //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Expected O, but got Unknown //IL_02a9: Expected O, but got Unknown Clock.Start(); Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "enabled", true, "Interruptor maestro. En false MechFix no aplica ninguna optimizacion."); SuppressStackTraces = ((BaseUnityPlugin)this).Config.Bind("Optimizacion", "suppressStackTraces", true, "Deja de recolectar stack traces para Debug.Log y Debug.LogWarning. Es la medida de mayor alcance: afecta a TODAS las lineas de log, incluidas las que emite el motor en codigo nativo y que no se pueden bloquear de otra forma. Los errores y excepciones CONSERVAN su traza, que es la que sirve para diagnosticar."); SuppressSpamLogs = ((BaseUnityPlugin)this).Config.Bind("Optimizacion", "suppressSpamLogs", true, "Bloquea en origen las lineas de Debug.Log por frame que nadie lee (IA del Bracken, pipeline de microfono de Dissonance). Se cortan antes de que Unity las procese, asi que no pagan ni traza ni escritura a disco."); ExtraPatterns = ((BaseUnityPlugin)this).Config.Bind("Optimizacion", "extraPatterns", string.Empty, "Patrones extra a bloquear, separados por | (coincidencia por subcadena, sin distinguir mayusculas). Ejemplo: Puma|mi otro spam. Dejalo vacio salvo que identifiques spam nuevo en tu log."); BoostDuringLoad = ((BaseUnityPlugin)this).Config.Bind("Carga", "boostDuringLoad", true, "Sube los FPS mientras se genera el dungeon y los restaura al terminar. Es la medida de mayor impacto sobre el tiempo de carga: DunGen genera por frames, asi que cada frame ganado durante la carga es tiempo de espera que se pierde. Medido: 2.5 fps -> 85 s de carga; 22 fps -> 17 s."); MaxDungeonSize = ((BaseUnityPlugin)this).Config.Bind("Carga", "maxDungeonSizeMultiplier", 1.8f, "Tope al multiplicador de tamaño de la luna (factorySizeMultiplier). Es la unica opcion de MechFix que SI cambia la jugabilidad: mapas mas chicos. Tambien es la de mayor efecto sobre el tiempo de carga, porque menos tiles es menos trabajo. Medido en este modpack: las lunas van de 1.25 a 2.2. Usa -1 para desactivarlo. *** EN COOPERATIVO, LOS 4 JUGADORES DEBEN TENER EL MISMO VALOR. *** DunGen arma el mapa a partir de la semilla Y del tamaño; si un jugador usa 1.8 y otro 2.2, generan mapas DISTINTOS con la misma semilla y el juego se desincroniza. Tambien: NO usar a la vez la restriccion de tamaño de LethalLevelLoader, que probada hace lo contrario de lo que dice y agranda el dungeon (2.2 -> 3.67)."); LowerGameGraphicsDuringLoad = ((BaseUnityPlugin)this).Config.Bind("Graficos", "lowerGameGraphicsDuringLoad", true, "Baja los ajustes del menu GRAPHICS del juego mientras se genera el dungeon y los restaura tal cual estaban al terminar. Son ajustes LOCALES de cada jugador: para que le sirva a todo el grupo, cada uno tiene que tener MechFix instalado."); GfxTerrainDetail = ((BaseUnityPlugin)this).Config.Bind("Graficos", "terrainGrassDistanceDuringLoad", 3, "Valor de 'Terrain / Grass Detail' durante la carga. Ultra=0, High=1, Medium=2, Low=3. Usa -1 para no tocarlo."); GfxMotionBlur = ((BaseUnityPlugin)this).Config.Bind("Graficos", "motionBlurLoad", 2, "Valor de 'Motion Blur' durante la carga. Moderate=0, Subtle=1, Off=2. Usa -1 para no tocarlo."); GfxPixelRes = ((BaseUnityPlugin)this).Config.Bind("Graficos", "pixelResolutionDuringLoad", 3, "Valor de 'Pixel Resolution' durante la carga. Default=0, Performance=1, Ultra performance=2, Retro=3. Usa -1 para no tocarlo."); GfxIndirectLight = ((BaseUnityPlugin)this).Config.Bind("Graficos", "advancedLightModeDuringLoad", 0, "Valor de 'Indirect lighting' durante la carga. 0 = desmarcado. Usa -1 para no tocarlo."); DropShadowsDuringLoad = ((BaseUnityPlugin)this).Config.Bind("Carga", "dropShadowsDuringLoad", true, "Apaga las sombras SOLO mientras se genera el dungeon. Es el costo de render mas grande y mas facil de quitar. Se nota durante el descenso de la nave; si te molesta visualmente, apagalo y perderas algo de velocidad de carga."); DeferPhysicsSync = ((BaseUnityPlugin)this).Config.Bind("Carga", "deferPhysicsSync", false, "Difiere la sincronizacion de transforms con el mundo de fisicas durante la generacion. Es la medida mas potente y la unica con riesgo real: cambia la semantica de las consultas de fisica, y si el juego hace un raycast justo despues de mover algo el resultado puede diferir. APAGADO por defecto a proposito. Si lo activas, probalo antes de jugar en serio."); PauseGCDuringLoad = ((BaseUnityPlugin)this).Config.Bind("Carga", "pauseGCDuringLoad", false, "Pausa el recolector de basura durante la generacion. El trato es explicito: la memoria crece sin recogerse durante ~20s a cambio de que no haya pausas de recoleccion en el medio; al terminar se reanuda y recolecta fuera del camino critico. APAGADO hasta que el log confirme que el GC es el culpable de los frames de 3-4 segundos. Mira las lineas 'frame lento' antes de encenderlo."); SlowFrameThresholdMs = ((BaseUnityPlugin)this).Config.Bind("Diagnostico", "slowFrameThresholdMs", 1000, "A partir de cuantos milisegundos un frame se considera lento y se registra con sus contadores de GC. Bajalo para ver mas detalle, subilo para menos ruido."); HeartbeatSeconds = ((BaseUnityPlugin)this).Config.Bind("Diagnostico", "heartbeatSeconds", 5f, "Cada cuantos segundos MechFix imprime su cronometro y cuantas lineas lleva bloqueadas. Ese latido es el unico reloj fiable del log. 0 lo apaga."); Log.LogInfo((object)(Stamp() + "===== MechFix 0.7.1 =====")); Log.LogInfo((object)(Stamp() + "Arranque: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))); if (!ModEnabled.Value) { Log.LogWarning((object)(Stamp() + "Desactivado por config; no se aplica ninguna optimizacion.")); return; } LogOptimizer.ApplyStackTraceSettings(); try { Harmony val = new Harmony("martinchavez.mechfix"); LogOptimizer.Install(val); GenerationTimer.Install(val); } catch (Exception ex) { Log.LogError((object)(Stamp() + "Fallo al instalar los parches: " + ex)); } Log.LogInfo((object)(Stamp() + "Awake terminado.")); } private void OnDestroy() { Log.LogInfo((object)(Stamp() + "El objeto del plugin fue destruido (esperado). Los parches siguen activos: son estaticos.")); } } internal sealed class RuntimeHost : MonoBehaviour { private const double BoostSafetyTimeoutSeconds = 300.0; private static RuntimeHost _instance; private float _nextHeartbeat; private int _frames; private int _lastBlocked; private static bool _measuring; private static double _genStart; private static int _genFrames; private static float _worstFrameMs; internal static void Ensure() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if ((Object)(object)_instance != (Object)null) { return; } try { GameObject val = new GameObject("MechFix.RuntimeHost"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent(); Plugin.Log.LogInfo((object)(Plugin.Stamp() + "host de runtime creado. A partir de aca hay reloj en el log.")); } catch (Exception ex) { Plugin.Log.LogError((object)(Plugin.Stamp() + "no pude crear el host de runtime: " + ex.Message)); } } internal static void MarkGenerationStart() { _measuring = true; _genStart = Plugin.T; _genFrames = 0; _worstFrameMs = 0f; } internal static string GenerationFrameReport() { if (!_measuring) { return string.Empty; } _measuring = false; double num = Plugin.T - _genStart; double num2 = ((num > 0.0) ? ((double)_genFrames / num) : 0.0); return " [frames=" + _genFrames + " fps=" + num2.ToString("F1") + " peor frame=" + _worstFrameMs.ToString("F0") + "ms]" + FrameProfiler.Report(); } private void Update() { _frames++; float num = Time.unscaledDeltaTime * 1000f; if (_measuring) { _genFrames++; FrameProfiler.Tick(num); if (num > _worstFrameMs) { _worstFrameMs = num; } } if (LoadBoost.IsActive && _measuring && Plugin.T - _genStart > 300.0) { Plugin.Log.LogWarning((object)(Plugin.Stamp() + "la generacion lleva mas de " + 300.0 + "s sin cerrar; revierto el boost por las dudas.")); LoadBoost.End(); _measuring = false; } float value = Plugin.HeartbeatSeconds.Value; if (!(value <= 0f) && !(Time.unscaledTime < _nextHeartbeat)) { _nextHeartbeat = Time.unscaledTime + value; int blocked = LogOptimizer.Blocked; int num2 = blocked - _lastBlocked; _lastBlocked = blocked; Plugin.Log.LogInfo((object)(Plugin.Stamp() + "latido frames=" + _frames + " bloqueadas=" + blocked + " (+" + num2 + ")" + GenerationTimer.StatusSuffix())); } } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } LoadBoost.End(); } } }